Hibernate批处理实战

本文分享了使用Hibernate进行批处理的实践经验,包括修改代码实现批量保存,调整配置文件设置hibernate.jdbc.batch_size,更换标识符生成器为MultipleHiLoPerTableGenerator,以及解决批处理到块处理的问题,确保数据高效插入数据库。同时提到了批处理可能导致的异常及解决办法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们在用数据库时,如果可以批量插入或者更新数据,可以很大地提高使用数据库的性能。但是我在用Hibernate实现批处理的过程中,走了一些弯路。下面我就把我的整个配置过程和经验总结分享,希望可以帮助到后来人。

撰写代码

首先你必须要撰写或者修改你的批处理代码。比如你的目标是通过Hibernate一次保存50个Customer对象,那你必须每save满50个对象,就flush一次,如下:

Transaction tx = session.beginTransaction();

for(Customer customer : customers) {
  session.save(customer );

  if(++i % 50 == 0) {
    session.flush();
    session.clear();
  }
}
tx.commit();

这是因为Hibernate会把新插入的数据都保存在缓存(persistence context cache)中。因为缓存大小的限制,所以隔一定数量就要flush,否则可能会碰上OutMemorException.

修改配置文件

Hibernate官方指导文档建议,为了最好的性能,应该把hibernate.jdbc.batch_size设置成和代码中procedure batch一样的size,在hibernate.cfg.xml中加上:

<property name="hibernate.jdbc.batch_size">50</property>

这里还有一点要注意的是,记得关掉batch操作的二级缓存。否则的话,每个对象被保存时,都会同时保存到二级缓存,这是一个不必要的浪费。

修改标识符生成器(id generator)

上面两个步骤看起来应该大功告成了,其实离成功还得远。首先,Hibernate明确说明了这样一个事实:

Hibernate silently disables JDBC batch inserts if your entity is mapped with an identity identifier generator; many JDBC drivers don’t support batching in that case

也就是说,如果你用的是identity的标识符生成器,那Hibernate的批处理就不灵了,所以我们必须得换一个,下面告诉你如何用MultipleHiLoPerTableGenerator。这个标识符生成器由Hibernate按照HiLo算法来生成id,它从数据库的特定表的字段长获取high值。至于什么是Hilo算法,可以参考这篇文章(https://vladmihalcea.com/2014/06/23/the-hilo-algorithm/)。在你的Customer.hbm.xml里,加上下面的代码:

<id name="id" type="java.lang.Integer">
      <column name="id"/>
      <generator class="org.hibernate.id.MultipleHiLoPerTableGenerator">
        <param name="table">identityGenerator</param>
        <param name="primary_key_column">sequence_name</param>
        <param name="value_column">next_hi_value</param>
        <param name="primary_key_value">TableCustomer</param>
        <param name="max_lo">10000</param>
      </generator>
    </id>

hi值存放在identityGenerator表的next_hi_value字段中:

CREATE TABLE IdentityGenerator (
    id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
    sequence_name VARCHAR(36) NOT NULL,
    next_hi_value INTEGER NOT NULL
)ENGINE= InnoDB DEFAULT CHARSET=latin1;

批处理到块处理

到这一步位置,batch操作已经完成了,但是你可能发现性能没有太多的提高,如果你看一眼mysql的log,你会发现数据实际上还是一条条插入数据库的。事实上,我们之前做的事情,只是把50条insert语句打包成了一个batch,发给了数据库,而数据库并没有把这50条insert变成下面这个我们期望的样子:

INSERT INTO Customer () VALUES (), (), (), (), ()

这被称作bulk insert的操作通常依赖于数据库connector provider。Hibernate依赖的mysql-connector-java提供了rewriteBatchedStatements这个配置参数来帮助我们把batch inserts转成bulk inserts。再次修改你的hibernate.cfg.xml如下:

<property name="hibernate.connection.url">jdbc:mysql://local:3306/database?rewriteBatchedStatements=true</property>

查看数据库log

最后,你还可以查看下mysql的log,来确定这会是不是真的bulk insert了

sudo tail -f  /var/log/mysql/mysql.log

mysqllog的打开方法如下:

mysql> show global variables like '%general%';     
mysql> SET GLOBAL general_log = 'ON';   // 打开     
mysql> SET GLOBAL general_log = 'OFF'; // 关闭  

一个常见的flush时抛出的异常

如果你跟我一样不幸运,session.flush()时可能会看到以下异常:

org.hibernate.jdbc.BatchedTooManyRowsAffectedException: Batch update returned unexpected row count from update [0]; actual row count: 10; expected: 1

我在stackoverflow上找到了两种解决方法:

  • 检查你的mysql-connector-java版本,把mysql-connector-java降级到5.1.24,或者升级到更新的版本(参考:http://stackoverflow.com/questions/23663557/hibernate-mysql-rewritebatchedstatements-true)
  • 在org.hibernate.jdbc这个源码包中,拷贝Exceptations.java这个文件到你的工程里(不要改变包名和相对路径),然后在你拷贝的这个Exceptions.java中,注释掉checkBatched方法中抛出BatchedTooManyRowsAffectedException的相关代码

    我是用第二种方法解决的。

以上就是我根据自己的实际经验总结的如果用Hibernate实现一次插入多条数据到数据库。欢迎留言指正讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值