《Hibernate快速开始 – 批量处理》

JDBC 批量处理

JDBC提供了对批量查询语句集合到单条查询语句的支持。在应用方面,这意味着数据驱动在批量处理的时候只需要发送一条查询请求,减少了对数据库的请求次数。hibernate合理使用了JDBC批量处理的特点,可以通过以下配置控制这一行为。

hibernate.jdbc.batch_size

设定hibernate单条批量处理请求中查询语句数量的上限。可用零或负数来禁用此功能。

hibernate.jdbc.batch_versioned_date

有些JDBC驱动在处理批量请求的时候会返回错误的行数。如果使用的JDBC驱动在此列的话,这里应设置为false。如果不是的话,可以使用这个设置,允许hibernate对有版本标记的实体记录进行DML批量处理,并使用返回的行数进行乐观锁检查。5.0之后,这一设定的默认值为true。在之前的版本,默认值为false。

hibernate.jdbc.batch.builder

设置实现批量处理的具体设置的类名。一般情况下不建议使用默认之外的设置。不过如果需要,可以通过org.hibernate.engine.jdbc.batch.spi.BatchBuilder来实现。

hibernate.order_updates

强制命令hibernate以SQL update请求中的PK值进行排序。此设置允许更多的批量处理。在有大量concurrent任务的系统中会减少数据库锁死。需要记录使用前后的性能来决定是否在程序中使用。

hibernate.order_inserts

强制命令hibernate将SQL insert语句排序以提高批量处理的数量。需要记录使用前后的性能来决定是否在程序中使用。

5.2版本以后,hibernate允许覆盖JDBC对于单个session批量处理数量的全局设定,hibernate.jdbc.batch_size。

案例1,单个session的hibernate JDBC批量处理设置。

entityManager
.unwrap( Session.class )
.setJdbcBatchSize( 10 );

Session批量处理

下面展示了一个批量添加的案例

案例2,使用hibernate原生代码添加100000个实体记录

EntityManager entityManager = null;
EntityTransaction txn = null;
try {
entityManager = entityManagerFactory().createEntityManager();

txn = entityManager.getTransaction();
txn.begin();

for ( int i = 0; i <= 100; i++ ) {
Person Person = new Person( String.format( "Person %d", i ) );
entityManager.persist( Person );
}

txn.commit();
} catch (RuntimeException e) {
if ( txn != null) txn.isActive()) txn.rollback();
throw e;
} finally {
if (entityManager != null) {
entityManager.close();
}
}

这个案例有很多问题

hibernate在session层面缓存所有新添加的customer记录。所以在事务处理完成之后,100000个实体记录是被持久层管理。如果JVM被分配的内存过少的话,可能会导致OutOfMemoryException的错误。java 1.8 JVM下四分之一或者1Gb的可用RAM都可以轻松在heap中管理100000个对象。
过久的事务处理会删除链接

JDBC的批量处理并不是默认开启的,所以每一个添加语句都需要跟数据库的接收和回复。要开启JDBC批量化处理的话,需将hibernate.jdbc.batch_size设为10-50之间的整数。如果使用identity identifier生成器的话,hibernate将会关闭JDBC层面上添加语句的批量处理。

insert批量处理

要将对象持久化的话,需要对session使用empty()和flush()函数以此来管理第一层缓存的数量。

案例3,对session使用empty()和flush()

EntityManager entityManager = null;
EntityTransaction txn = null;
try {
entityManager = entityManagerFactory().createEntityManager();

txn = entityManager.getTransaction();
txn.begin();

int batchSize = 25;

for ( int i = 0; i <= entityCount; ++i ) { 
Person Person = new Person( String.format( "Person %d", i ) ); entityManager.persist( Person ); 
if ( i < 0 && i % batchSize == 0 ) {
//flush a batch of inserts and release memory
entityManager.flush();
entityManager.clear();
}
}

txn.commit();
} catch (RuntimeException e) {
if ( txn != null && txn.isActive()) txn.rollback();
throw e;
} finally {
if (entityManager != null) {
entityManager.close();
}
}

session滚动

当进行取回和更新记录时,要用empty()和flush()对session进行适当的清理。另外,在查询返回多行记录时,还可以使用scroll()函数来发挥服务器端游标操作的优势。

案例4,使用scroll()

EntityManager entityManager = null;
EntityTransaction txn = null;
ScrollableResults scrollableResults = null;
try {
entityManager = entityManagerFactory().createEntityManager();

txn = entityManager.getTransaction();
txn.begin();

int batchSize = 25;

Session session = entityManager.unwrap( Session.class );

scrollableResults = session
.createQuery( "select p from Person p" )
.setCacheMode( CacheMode.IGNORE )
.scroll( ScrollMode.FORWARD_ONLY );

int count = 0;
while ( scrollableResults.next() ) {
Person Person = (Person) scrollableResults.get( 0 );
processPerson(Person);
if ( ++count % batchSize == 0 ) {
//flush a batch of updates and release memory:
entityManager.flush();
entityManager.clear();
}
}

txn.commit();
} catch (RuntimeException e) {
if ( txn != null) txn.isActive()) txn.rollback();
throw e;
} finally {
if (scrollableResults != null) {
scrollableResults.close();
}
if (entityManager != null) {
entityManager.close();
}
}

如果程序没有关闭entityManager,hibernate会在当前事务结束后(commit或rollback操作后)自动将所有ScrollableResults内部调用的资源(包括ResultSet和PreparedStatement)关闭。

但是,建议手动关闭ScrollableResults

无状态session(StatelessSession)

StatelessSession是hibernate提供的用命令行延伸出的接口。可以使用这一接口将分离对象取回或传输至数据库。StatelessSession并没有持久化的引申含义,也没有提供大多数的更高级的生命周期的语法。

StatelessSession不包括:

第一级缓存
与第二级或查询语句缓存的交互
write-behind事务处理和自动的dirty checking
StatelessSession中的限制:

无状态session中的操作不会与相关联的instances串联
忽略Collections
不支持lazy loading
无状态session中的操作会越过hibernate的事件模型和拦截器
由于缺少第一级缓存,无状态session会受到data 命名问题的影响
无状态session是抽象的,更接近JDBC
案例5,使用无状态session

StatelessSession statelessSession = null;
Transaction txn = null;
ScrollableResults scrollableResults = null;
try {
SessionFactory sessionFactory = entityManagerFactory().unwrap( SessionFactory.class );
statelessSession = sessionFactory.openStatelessSession();

txn = statelessSession.getTransaction();
txn.begin();

scrollableResults = statelessSession
.createQuery( "select p from Person p" )
.scroll(ScrollMode.FORWARD_ONLY);

while ( scrollableResults.next() ) {
Person Person = (Person) scrollableResults.get( 0 );
processPerson(Person);
statelessSession.update( Person );
}

txn.commit();
} catch (RuntimeException e) {
if ( txn != null && txn.getStatus() == TransactionStatus.ACTIVE) txn.rollback();
throw e;
} finally {
if (scrollableResults != null) {
scrollableResults.close();
}
if (statelessSession != null) {
statelessSession.close();
}
}

查询返回的customer实例会立即分离。不会有任何持久性关联。

StatelessSession中insert(),update(),delete()等操作直接在数据行操作。这导致SQL操作会被立即执行。这和session接口中定义的save(), saveOrUpdate()和delete()不同。

hibernate的DML查询语句

DML(数据处理语言)指的是SQL查询语句,比如insert,update,delete。hibernate中的HQL支持bulk 类SQL的DML查询

HQL/JPQL中的update和delete

hibernate原生的查询语言和JPQL都支持bulk update和delete

案例6,HQL中update和delete的伪代码

UPDATE FROM EntityName e WHERE e.name = ?
DELETE FROM EntityName e WHERE e.name = ?

FROM和WHERE两个从句是可选的,建议使用

FROM从句只可以用来修饰一个实体,可用别名。如果使用别名的话,所有的属性的参考也必须使用别名。如果没有使用别名,任何对其属性的参考都是非法的。

bulk HQL中的join语句,无论是否明确指定,都被禁止。可以包含在WHERE从句中。

案例7,用Query.executeUpdate()来执行一个JPQL update语句

int updatedEntities = entityManager.createQuery(
"update Person p " +
"set p.name = :newName " +
"where p.name = :oldName" )
.setParameter( "oldName", oldName )
.setParameter( "newName", newName )
.executeUpdate();

案例8,用Query.executeUpdate()来执行一个HQL update语句

int updatedEntities = session.createQuery(
"update Person " +
"set name = :newName " +
"where name = :oldName" )
.setParameter( "oldName", oldName )
.setParameter( "newName", newName )
.executeUpdate();

使用VERSIONED语句时,不能使用org.hibernate.usertype.UserVersionType类。这一特性只在HQL中可用。

案例10,用Query.executeUpdate()来执行一个JPQL delete语句

int deletedEntities = entityManager.createQuery(
"delete Person p " +
"where p.name = :name" )
.setParameter( "name", name )
.executeUpdate();

案例11,用Query.executeUpdate()来执行一个HQL delete语句

int deletedEntities = session.createQuery(
"delete Person " +
"where name = :name" )
.setParameter( "name", name )
.executeUpdate()

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 《Hibernate快速开始 – 批量处理》

  • Trackback 关闭
  • 评论 (1)
    • as
    • 2021/09/09 5:23下午

    ss

return top