通过 Hibernate 缓存进行批量插入
1 | public static void batchAddByCache() |
对于大批量的数据用 Hibernate 插入到数据库时容易发生 OutOfMemoryError(内存溢出异常)。
Hibernate 缓存分为一级缓存和二级缓存,对于二级缓存,Hibernate 可以对它的大小进行相关配置,而对于一级缓存,Hibernate 对它的容量没有限制。Hibernate 的 Session 持有一个必选的一级缓存,执行海量数据插入操作时,所有的实体类对象都会被纳入一级缓存(一级缓存是在内存中做缓存的),这样内存就会被一点一点占用,直到内存溢出。
要解决内存溢出的问题,需要定时将 Session 缓存的数据刷入数据库,而不是一直在 Session 级别缓存,具体步骤如下:
- 设置批量尺寸。
在 Hibernate 配置文件 hibernate.cfg.xml 中设置 hibernate.jdbc.batch_size 属性:1
<property name="hibernate.jdbc.batch_size">100</property>
以上的配置是等到程序积累到了 100 个SQL之后再批量提交。
关闭二级缓存。
除了 Session 级别的一级缓存,Hibernate 还有一个 SessionFactory 级别的二级缓存。如果启用了二级缓存,从机制上来说,Hibernate 为了维护二级缓存,在批量插入时,Hibernate 会将实体类对象纳入二级缓存,容易损失性能,引发异常。因此最好关闭二级缓存。1
<property name="hibernate.cache.user_second_level_cache">false</property>
清空 Session 级别的一级缓存。
在进行批量操作的方法中,定时将 Session 级别的一级缓存中的数据刷入数据库,并清空 Session 缓存:1
2
3
4
5
6
7
8
9
10
11
12
13Session session=HibernateSessionFactory.getSession();
Tranaction transaction=session.beginTransaction();
for(int i=1;i<1000000;i++)
{
User user=new User("u"+i);
session.save(user);
if(i%100 == 0)
{
session.flush();
session.clear();
}
}
transaction.commit();
绕过 Hibernate 直接使用 JDBC API 进行批量操作
通过 JDBC API 中的 PreparedStatement 接口来执行 SQL 语句,SQL 语句中涉及到的数据不会被加载到 Session 的缓存中,因此不会占用内存空间。另外,直接调用 JDBC API 批量插入的效率要高于 Hibernate 缓存的批量插入。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31public static void batchAddByJDBC()
{
Session session = HibernateSessionFactory.getSession();
Transaction transaction = null;
try
{
transaction = session.beginTransaction();
//执行 Work 对象指定的操作,即调用 Work 对象的 execute() 方法
//session 会把所有的数据库连接传给 execute() 方法
//这里使用了 Lambda表达式
session.doWork((connection) -> {
String hql = "insert into tb_test(name,info) values(?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(hql);
for (int i = 1; i < 1000000; i++)
{
preparedStatement.setString(1, "测试" + i);
preparedStatement.setString(2, "INFO" + i);
preparedStatement.addBatch();
}
preparedStatement.executeBatch();
});
transaction.commit();
} catch (Exception e)
{
if (transaction != null)
{
transaction.rollback();
}
}
//注意不要关闭连接
}