由于业务需要一次性连续写入超过10k条以上的新数据,当对象超过10个成员变量以后,整个写入过程居然需要长达35秒,这个速度是不能接受的,故此研究了一下怎么开启Hibernate批量写入的功能。
我这边使用的是Hibernate 5.6.15
在网上找了一些答案,都提到了Hibernate自带批量写入的功能,通过在
“hibernate.cfg.xml”的文件中增加一个特征
<property name="hibernate.jdbc.batch_size">50</property>
必须在connection.url 的后面增加一个参数?rewriteBatchedStatements=true&
我的配置:
<!--数据源配置-->
<property name="connection.username">root</property>
<property name="connection.password">12345678</property>
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/hibernate_test?rewriteBatchedStatements=true&userUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC</property>
<!--批量处理配置-->
<property name="hibernate.jdbc.batch_size">50</property>
我这边实测,发现速度并没有变快,然后使用WireShake监听了一下程序对MYSQL的网络请求,发现如果写入1W条数据,那么就会发生2W多次的TCP链接,每次链接传输的数据内容虽然加密了,但是内容并不多,明显不是批量发送的多个条目,而是一个一个发送的。
这一点让我觉得很奇怪,明明启用了Hibernate的批量写入功能,为什么还是一条一条写入的?
网上的大牛们指出,如果写入的对象的ID采用了Hibernate自增策略,那么批量写入功能就会自动关闭,因为自增的前提是获取到上次写入的对象得到的ID+1来实现的。
那么,既然知道写入慢的原因了,那么解决起来也就简单了。
首先删掉自增ID的策略,然后在代码中增加手动赋值ID的功能
String hql = "from People order by id desc";//将当前数据库的数据采用ID倒序排列
Query query = session.createQuery(hql);
query.setMaxResults(1);//读取第一个数据
List<People> list = query.list();
Integer lasstId = 0;
if(list.size() == 0){//如果读取队列长度为0.说明这是一个空表,ID赋值为1
lasstId = 1;
}else{//获取最后一个ID值
lasstId = list.get(0).getId()+1;
}
List<People> peopleList = new ArrayList<>();
for(int i=0;i<10000;i++){
People people = new People();
people.setId(lasstId++);//每次赋值结束后自增1
people.setName("测试"+i);
people.setMoney(100*i);
peopleList.add(people);
}
session.beginTransaction();
for(int i=0;i<10000;i++){
session.save(peopleList.get(i));
}
session.getTransaction().commit();
session.close();
通过这种方式,就可以使用Hibernate的批量写入功能。
通过实际监听的结果,同样写入1W条数据,实际发生了800多次TCP链接
既然如此,我干脆把批量处理的SQL条数改成了10000条
<property name="hibernate.jdbc.batch_size">10000</property>
结果速度就更快了,而且TCP只发生了105次连接
如果实际投入生产是不可以改成10000条保存一次的,因为这里使用的是Hibernate的一级缓存用于累积数据,如果你要保存的对象有很多成员变量,1W条数据很容易让缓存内存溢出,所以,针对每个人实际业务的情况酌情处理,即便每次只有50条,也相当于加速了50倍,也是很快啦。