上下文
默认情况下,所有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的工作。