ORA-02396: 使用多个 DS 和 Hikari 时超出最大空闲

ORA-02396: exceeded maximum idle when several DS and Hikari used

我们有几个系统,使用相同的核心库和相同的 Oracle 数据库。但是每天只有一个系统出错,下面是堆栈跟踪。

错误是:ORA-04042: procedure, function, package, or package body does not exist。该系统与其他系统的区别在于,该系统使用多个数据源,在下面您可以看到 Hikari 配置、build.gradle 的一部分和堆栈跟踪。所有其他系统使用一个数据源。

这是 Oracle 版本信息:

BANNER                                                                               CON_ID
-------------------------------------------------------------------------------- ----------
Oracle Database 12c Release 12.1.0.1.0 - 64bit Production                                 0
PL/SQL Release 12.1.0.1.0 - Production                                                    0
CORE    12.1.0.1.0  Production                                                            0
TNS for 64-bit Windows: Version 12.1.0.1.0 - Production                                   0
NLSRTL Version 12.1.0.1.0 - Production                                                    0

这是用于配置其中一个数据源的代码。第二个以同样的方式生产:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
  entityManagerFactoryRef = "sourceEntityManagerFactory",
  transactionManagerRef = "sourceTransactionManager",
  basePackages = { "com.maxi.jpa.source" }
)
public class SourceConfig
{
    @Bean(name = "sourceDS")
    @Primary
    @ConfigurationProperties("spring.datasource.ds-src")
    public DataSource dataSource(){
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "sourceEntityManagerFactory")
    @DependsOn("sourceDS")
    @Primary
    public LocalContainerEntityManagerFactoryBean sourceEMF(
        EntityManagerFactoryBuilder builder,
        @Qualifier("sourceDS")
        DataSource ds
    ) {
        return builder
            .dataSource(ds)
            .packages("com.maxi.jpa.model")
            .persistenceUnit("source")
            .build();
    }

    @Bean(name = "sourceEntityManager")
    @Primary
    public EntityManager sourceEM(
        @Qualifier("sourceEntityManagerFactory")
        EntityManagerFactory factory
    ) {
        return factory.createEntityManager();
    }

    @Bean(name = "sourceTransactionManager")
    @Primary
    public PlatformTransactionManager transactionManager(
        @Qualifier("sourceEntityManagerFactory") EntityManagerFactory
        entityManagerFactory
    ) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

这是异常的代码:

private List<String> descTable(String owner, String tableName) {
    String query =
        "select column_name " +
        "from all_tab_columns " +
        "where upper(owner) = '%s' " +
        "and upper(table_name) = '%s'";

    query = String.format(query, owner, tableName);

    @SuppressWarnings("unchecked")
    List<String> result = (List<String>)sourceEntityManager
        .createNativeQuery(query)
        .getResultList();

    return result;
}

这样,我注入 sourceEntityManager:

@Service
public class DMLService
{
    private EntityManager sourceEntityManager;
    private EntityManager destEntityManager;

    public DMLService(
        @Qualifier("sourceEntityManager")
        EntityManager sourceEntityManager,
        @Qualifier("destEntityManager")
        EntityManager destEntityManager
    ) {
        this.sourceEntityManager = sourceEntityManager;
        this.destEntityManager = destEntityManager;
    }

    ....
}

application.yaml

spring:
  datasource:
    ds-src:
      driverClassName: oracle.jdbc.OracleDriver
      jdbcUrl: jdbc:oracle:thin:@${DB4VAL_SRC_DB}
      username: ${DB4VAL_SRC_USERNAME}
      password: ${DB4VAL_SRC_PASSWORD}
      poolName: Db4ValidateSource
      connectionTestQuery: SELECT 1 FROM DUAL
    ds-dest:
      driverClassName: oracle.jdbc.OracleDriver
      jdbcUrl: jdbc:oracle:thin:@${DB4VAL_DEST_DB}
      username: ${DB4VAL_DEST_USERNAME}
      password: ${DB4VAL_DEST_PASSWORD}
      poolName: Db4ValidateDestination
      connectionTestQuery: SELECT 1 FROM DUAL
  jpa:
    database-platform: org.hibernate.dialect.Oracle12cDialect

application-default.yaml

spring:
  datasource:
    ds-src:
      minimumIdle: 1
      maximumPoolSize: 5
      idleTimeout: 20000
      maxLifetime: 60000
      keepaliveTime: 10000
      connectionTimeout: 30000
    ds-dest:
      minimumIdle: 1
      maximumPoolSize: 5
      idleTimeout: 20000
      maxLifetime: 60000
      keepaliveTime: 10000
      connectionTimeout: 30000

build.gradle

dependencies {
    implementation 'org.apache.commons:commons-lang3'
    implementation 'commons-net:commons-net:3.6'
    implementation 'commons-io:commons-io:2.6'
    implementation 'com.zaxxer:HikariCP:4.0.2'

    implementation 'org.springframework:spring-context-support'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    ...
}

stacktrace

at oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:628)
at oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:557)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:730)
at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:291)
at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:492)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:148)
at oracle.jdbc.driver.T4CPreparedStatement.executeForDescribe(T4CPreparedStatement.java:928)
at oracle.jdbc.driver.OracleStatement.prepareDefineBufferAndExecute(OracleStatement.java:1158)
at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1093)
at oracle.jdbc.driver.OracleStatement.executeSQLSelect(OracleStatement.java:1402)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1285)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3735)
at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:3847)
at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeQuery(OraclePreparedStatementWrapper.java:1098)
at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52)
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:57)
at org.hibernate.loader.Loader.getResultSet(Loader.java:2297)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2050)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2012)
at org.hibernate.loader.Loader.doQuery(Loader.java:948)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:349)
at org.hibernate.loader.Loader.doList(Loader.java:2843)
at org.hibernate.loader.Loader.doList(Loader.java:2825)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2657)
at org.hibernate.loader.Loader.list(Loader.java:2652)
at org.hibernate.loader.custom.CustomLoader.list(CustomLoader.java:338)
at org.hibernate.internal.SessionImpl.listCustomQuery(SessionImpl.java:2141)
at org.hibernate.internal.AbstractSharedSessionContract.list(AbstractSharedSessionContract.java:1169)
at org.hibernate.query.internal.NativeQueryImpl.doList(NativeQueryImpl.java:176)
at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1604)
at org.hibernate.query.Query.getResultList(Query.java:165)
at com.maxi.services.DMLService.descTable(DMLService.java:77)
at com.maxi.services.DMLService.getTableData(DMLService.java:191)
at com.maxi.services.DMLService.processRecord(DMLService.java:203)
at com.maxi.services.ValidateService.processRecord(ValidateService.java:263)
at com.maxi.services.ValidateService.runETL(ValidateService.java:362)
at com.maxi.services.Handler.runProcess(Handler.java:54)
at com.maxi.services.Handler.lambda$handleRequest[=17=](Handler.java:35)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)

如您所见,系统捕获了所有配置

所以,问题是 EntityManager 从池中检索连接,但 returns 不检索它,直到它关闭。但是我们无法手动关闭它,因为它是一个 bean,我们可能无法重新打开它。

所以解决方案不是手动创建 EM,而是使用 @PersistenceContext(unitName = "unit_name").

从框架中获取它

有必要删除此代码(对于两个 EM):

@Bean(name = "sourceEntityManager")
@Primary
public EntityManager sourceEM(
    @Qualifier("sourceEntityManagerFactory")
    EntityManagerFactory factory
) {
    return factory.createEntityManager();
}

@Bean(name = "sourceTransactionManager")
@Primary
public PlatformTransactionManager transactionManager(
    @Qualifier("sourceEntityManagerFactory") EntityManagerFactory
    entityManagerFactory
) {
    return new JpaTransactionManager(entityManagerFactory);
}

并按以下方式 Autowire beans:

@PersistenceContext(unitName = "source")
private EntityManager sourceEntityManager;
@PersistenceContext(unitName = "destination")
private EntityManager destEntityManager;

不要忘记重写代码以使用 @Transactionsjpaspring)。将注释放在 public 方法或整个 class.