上下文

默认情况下,所有Hibernate测试都在 H2 上运行。然而,我们还有很多数据库特定的测试,因此我们应该在Oracle、PostgreSQL、MySQL以及可能还有SQL Server上进行测试。

当我们尝试设置一个使用PostgreSQL的Jenkins作业时,我们意识到作业失败是因为我们用完了连接。知道PostgreSQL服务器的max_connections设置为30,我们意识到连接泄漏问题非常严重。

大海捞针

hibernate-core模块就有超过5000个测试,而hibernate-envers也有大约2500个测试。但是还有很多其他模块:hibernate-c3p0hibernate-ehcachehibernate-jcache等等。总的来说,我们无法仅仅通过浏览代码就发现问题。我们需要一个自动化的连接泄漏检测器。

话虽如此,我想出了一个适用于H2、Oracle、PostgreSQL和MySQL的解决方案。幸运的是,实际框架代码库中没有发现任何问题。所有问题都是由没有正确处理数据库资源的单元测试引起的。

最常见的问题

最常见的问题之一是由不正确的引导逻辑引起的

@Test
public void testInvalidMapping() {
    try {
        new MetadataSources( )
                .addAnnotatedClass( TheEntity.class )
                .buildMetadata();
        fail( "Was expecting failure" );
    }
    catch (AnnotationException ignore) {
    }
}

这里的问题是,MetadataSources在幕后创建了一个BootstrapServiceRegistry,它反过来又触发了底层ConnectionProvider的初始化。如果不显式关闭BootstrapServiceRegistry,则ConnectionProvider将不会有机会关闭所有当前池化的JDBCConnection(s)

修复方法很简单

@Test
public void testInvalidMapping() {
    MetadataSources metadataSources = new MetadataSources( )
        .addAnnotatedClass( TheEntity.class );
    try {
        metadataSources.buildMetadata();
        fail( "Was expecting failure" );
    }
    catch (AnnotationException ignore) {
    }
    finally {
        ServiceRegistry metaServiceRegistry = metadataSources.getServiceRegistry();
        if(metaServiceRegistry instanceof BootstrapServiceRegistry ) {
            BootstrapServiceRegistryBuilder.destroy( metaServiceRegistry );
        }
    }
}

另一个经常出现的问题是处理事务不正确,例如以下示例

protected void cleanup() {
    Session s = getFactory().openSession();
    s.beginTransaction();

    TestEntity testEntity = s.get( TestEntity.class, "foo" );
    Assert.assertTrue( testEntity.getParams().isEmpty() );

    TestOtherEntity testOtherEntity = s.get( TestOtherEntity.class, "foo" );
    Assert.assertTrue( testOtherEntity.getParams().isEmpty() );

    s.getTransaction().commit();
    s.clear();
    s.close();
}

首先要注意的是缺少try/finally块,即使在抛出异常的情况下也应关闭会话。但这还不是全部。

不久前,我修复了HHH-7412,这意味着对于RESOURCE_LOCAL(例如,JDBC Connection绑定的交易),只有当逻辑或物理 Connection事务结束时(无论是提交还是回滚)才关闭。

在修复HHH-7412之前,当Hibernate Session关闭时,Connection会自动关闭,但这种行为已经不再支持。如今,除了关闭底层Session外,您还需要提交/回滚当前正在运行的Transaction

protected void cleanup() {
    Session s = getFactory().openSession();
    s.beginTransaction();

    try {
        TestEntity testEntity = s.get( TestEntity.class, "foo" );
        Assert.assertTrue( testEntity.getParams().isEmpty() );

        TestOtherEntity testOtherEntity = s.get( TestOtherEntity.class, "foo" );
        Assert.assertTrue( testOtherEntity.getParams().isEmpty() );

        s.getTransaction().commit();
    }
    catch ( RuntimeException e ) {
        s.getTransaction().rollback();
        throw e;
    }
    finally {
        s.close();
    }
}

如果您想了解所有需要的更改,可以查看以下两个提交:da9c6e1f5e10c2。好消息是PostgreSQL工作正在顺利进行,不久我们将添加Oracle和MySQL的工作。


回到顶部