上下文
默认情况下,所有Hibernate测试都在 H2 上运行。然而,我们还有很多数据库特定的测试,因此我们应该在Oracle、PostgreSQL、MySQL以及可能还有SQL Server上进行测试。
当我们尝试设置一个使用PostgreSQL的Jenkins作业时,我们意识到作业失败是因为我们用完了连接。知道PostgreSQL服务器的max_connections设置为30,我们意识到连接泄漏问题非常严重。
大海捞针
仅hibernate-core模块就有超过5000个测试,而hibernate-envers也有大约2500个测试。但是还有很多其他模块:hibernate-c3p0、hibernate-ehcache、hibernate-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();
}
}
如果您想了解所有需要的更改,可以查看以下两个提交:da9c6e1和f5e10c2。好消息是PostgreSQL工作正在顺利进行,不久我们将添加Oracle和MySQL的工作。