为了简化我的场景,我将给出以下示例:
我使用的是Spring.jpa.properties.hibernate.order_inserts=true属性的Spring Boot应用程序。
在一个用@Transactional注释标记的java方法中,我想用纯hibernate执行几千条记录的批量插入。批处理插入由一个单独的方法执行,该方法由带有@Transactional的主方法调用。我是这样做的:
@Transactional
public void doSomeStuff() {
......
insertRecords(records, session);
......
}
private void insertRecords(final List<Record> records, final Session session) {
int counter = 0;
int batchSize = 50;
for(Record r: records){
session.save(r);
counter ++;
if(counter > 0 && counter % batchSize == 0){
session.flush();
session.clear();
}
}
}
问题是,我希望操作是原子操作,如果其中一个批插入操作失败,则所有操作都将回滚。我知道@Transactional anotation会保证在出现RunTimeException或Error时自动回滚。这是否意味着,如果其中一个批次失败,@Transactional将覆盖回滚,或者我应该用try/catch块包围for循环,并抛出一个已检查的异常,并使用@Transactional的rollbackFor属性?我不确定在批处理失败期间抛出的异常是选中还是未选中。
另一个重要的问题是,在这种情况下,hibernate是否会在成功插入的情况下自动提交每个批次?这意味着,如果一个批处理失败,我将无法回滚已经提交的一次,这将破坏原子性。
此外,我不想手动管理提交,而是希望将其处理到Spring提供的事务上下文中。
你说得对。默认情况下,@Transactional
只会回滚RuntimeException
和Error
,而不会回滚已检查的异常。这意味着,如果您还想回滚已检查的异常,则必须捕获所有已检查的例外并将其作为RuntimeException
重新抛出,或者只需使用@Transactional
中的rollbackFor
设置。
我不确定是否检查了批处理失败期间抛出的异常或未检查。
java编译器将帮助检查它。如果某些内部方法抛出已检查异常,则它要求您必须处理它,而您既不能捕获它,也不能在方法声明中指定抛出此已检查异常。如果你不这样做,代码就无法编译。
这意味着,如果您的代码可以编译,则不会从内部方法抛出已检查的异常,并且您可以简单地坚持当前设置。另一方面,如果从内部方法抛出一些已检查的异常,则可以选择捕获它并将其重新抛出为RuntimeException
:
@Transactional
public void doSomeStuff() {
try{
}catch(Exception ex){
throw new RuntimeException("something goes wrong.." ,ex);
}
}
或配置rollbackFor
@Transactional(rollbackFor={Exception.class})
public void doSomeStuff() throws Exception{
}
此外,请注意,session.flush()
与提交事务不同,因此您的代码不会为每个批次提交。相反,事务将在标记有@Transactional return的方法成功后提交,在您的情况下,这是插入所有记录时doSomeStuff()
的返回。
flush()
的作用是为后续的session.clear()
清除会话中的内存,这样,如果插入大量记录,就不会导致JVM内存不足。