Hibernate 在 CICS 上的 WAS Liberty 上的应用程序启动时连接到 DB2

Hibernate connecting to DB2 at application startup on WAS Liberty on CICS

我们是 运行 WebSphere Liberty 上的一个简单网络应用程序,它使用 Hibernate 作为持久性提供程序(作为库包含在 WAR 文件中)。

当应用程序启动时,Hibernate 被初始化,它将打开一个到 DB2 的连接并发出一些 SQL 语句。但是,当在 CICS 上使用 运行 并使用 JDBC Type 2 Driver DataSource 时,这会失败。记录了以下消息(一些额外的换行符以提高可读性):

WARN  org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator -
    HHH000342: Could not obtain connection to query metadata : [jcc][50053][12310][4.19.56]
    T2zOS exception: [jcc][T2zos]T2zosCicsApi.checkApiStatus:
       Thread is not CICS-DB2 compatible: CICS_REGION_BUT_API_DISALLOWED ERRORCODE=-4228, SQLSTATE=null
...
ERROR org.hibernate.hql.spi.id.IdTableHelper - Unable obtain JDBC Connection
com.ibm.db2.jcc.am.SqlException: [jcc][50053][12310][4.19.56] T2zOS exception: [jcc][T2zos]T2zosCicsApi.checkApiStatus:
       Thread is not CICS-DB2 compatible: CICS_REGION_BUT_API_DISALLOWED ERRORCODE=-4228, SQLSTATE=null
    at com.ibm.db2.jcc.am.kd.a(Unknown Source) ~[db2jcc4.jar:?]
    ...
    at com.ibm.db2.jcc.t2zos.T2zosConnection.a(Unknown Source) ~[db2jcc4.jar:?]
    ...
    at com.ibm.db2.jcc.DB2SimpleDataSource.getConnection(Unknown Source) ~[db2jcc4.jar:?]
    at com.ibm.cics.wlp.jdbc.internal.CICSDataSource.getConnection(CICSDataSource.java:176) ~[?:?]
    at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122) ~[our-app.war:5.1.0.Final]
    at org.hibernate.internal.SessionFactoryImpl.obtainConnection(SessionFactoryImpl.java:643) ~[our-app.war:5.1.0.Final]
    at org.hibernate.hql.spi.id.IdTableHelper.executeIdTableCreationStatements(IdTableHelper.java:67) [our-app.war:5.1.0.Final]
    at org.hibernate.hql.spi.id.global.GlobalTemporaryTableBulkIdStrategy.finishPreparation(GlobalTemporaryTableBulkIdStrategy.java:125) [our-app.war:5.1.0.Final]
    at org.hibernate.hql.spi.id.global.GlobalTemporaryTableBulkIdStrategy.finishPreparation(GlobalTemporaryTableBulkIdStrategy.java:42) [our-app.war:5.1.0.Final]
    at org.hibernate.hql.spi.id.AbstractMultiTableBulkIdStrategyImpl.prepare(AbstractMultiTableBulkIdStrategyImpl.java:88) [our-app.war:5.1.0.Final]
    at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:451) [our-app.war:5.1.0.Final]

我目前的理解是,当在 CICS 上 运行 并使用 JDBC 类型 2 驱动程序时,只有一些线程能够打开 DB2 连接。那将是应用程序线程(处理 HTTP 请求的线程)以及服务于 CICSExecutorService.

的工作线程

目前的解决方案是:

  1. JdbcEnvironmentInitiator 中禁用 JDBC 元数据查找 设置 hibernate.temp.use_jdbc_metadata_defaults 属性 为 false
  2. IdTableHelper#executeIdTableCreationStatements 的执行包装在 Runnable 中并提交给 CICSExecutorService

您认为此解决方案是否足够并适合生产?或者,也许您使用了一些不同的方法?

使用的版本:


更新: 澄清一下,一旦我们的应用程序启动,它就可以毫无问题地查询 DB2(为 HTTP 请求提供服务时)。问题只与启动有关。

CICS TS v5.3 对 Liberty 中 JPA 功能的支持最近在服务更新中可用(2016 年 7 月)。在此更新之前,尝试在应用程序中 运行 JPA 会导致与您描述的问题非常相似的问题。

尽管您正在 运行 休眠并且处于启用 CICS 的线程上,但它没有 API 环境(这将允许类型 2 JDBC 调用成功)。新的检测逻辑是专门(但不完全)开发用于与 DB2 JDBC 类型 2 驱动程序和 JPA 一起使用的。此更新是在最近的服务更新中发布的,可能会解决您遇到的问题。

尝试申请: http://www-01.ibm.com/support/docview.wss?crawler=1&uid=swg1PI58375

描述说它是为了 'Standard-mode Liberty' 支持,但它包含上面概述的其他开发。

以下解决方案经测试可以正常工作。

想法是使用 CICSExecutorService#runAsCICS 执行 SQL/DDL 语句。以下扩展名是通过 hibernate.hql.bulk_id_strategy 属性.

注册的
package org.hibernate.hql.spi.id.global;

import java.util.concurrent.*;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.springframework.util.ClassUtils;
import com.ibm.cics.server.*;

public class CicsAwareGlobalTemporaryTableBulkIdStrategy extends GlobalTemporaryTableBulkIdStrategy {

    @Override
    protected void finishPreparation(JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess, MetadataImplementor metadata, PreparationContextImpl context) {
        execute(() -> super.finishPreparation(jdbcServices, connectionAccess, metadata, context));
    }

    @Override
    public void release(JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess) {
        execute(() -> super.release(jdbcServices, connectionAccess));
    }

    private void execute(Runnable runnable) {
        if (isCics() && IsCICS.getApiStatus() == IsCICS.CICS_REGION_BUT_API_DISALLOWED) {
            RunnableFuture<Void> task = new FutureTask<>(runnable, null);
            CICSExecutorService.runAsCICS(task);
            try {
                task.get();
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException("Failed to execute in a CICS API-enabled thread. " + e.getMessage(), e);
            }
        } else {
            runnable.run();
        }
    }

    private boolean isCics() {
        return ClassUtils.isPresent("com.ibm.cics.server.CICSExecutorService", null);
    }
}

请注意,较新的 JCICS API 版本对接受 CallablerunAsCics 方法有一个覆盖,这可能有助于简化 execute 的 CICS 分支像这样的方法:

CICSExecutorService.runAsCICS(() -> { runnable.run(); return null; }).get();

尝试了一些替代方案:

  1. 仅包装连接获取操作 (org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl#getConnection) 不起作用,因为连接在主线程中使用时已经关闭。
  2. 包装整个应用程序启动 (org.springframework.web.context.ContextLoaderListener#contextInitialized) 导致类加载问题。

编辑: 最终采用自定义 Hibernate 的 MultiTableBulkIdStrategy 实现,在启动时 运行 没有任何 SQL/DDL(see project page on GitHub).