Hibernate中执行Transaction.commit()方法遇到错误

Hibernate事务异常解析
本文分析了一个常见的Hibernate事务异常问题:TransactionException。详细解释了错误发生的原因在于同一个session中并发开启多个事务并尝试提交,提供了代码示例并指出了解决此类问题的方法。
每次都显示org.hibernate.TransactionException: Transaction not successfully started
看了下代码,查了下资料,原因是代码结构问题:
Transaction tx1 = session.beginTransaction();
Transaction tx2 = session.beginTransaction();
tx2.commit();
tx1.commit();
因为他们是指向的一下transaction, 所以当执行tx1.commit()时就报那个错。
org.springframework.orm.ObjectOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:320) at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244) at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521) at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761) at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730) at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:487) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:291) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) at com.sun.proxy.$Proxy566.deleteLiquidReportChartByDataDtAndReportCode(Unknown Source) at com.htsc.service.DebtBusinessDayReportService.generateReport(DebtBusinessDayReportService.java:81) at com.htsc.task.DebtBusinessDayReportJob.init(DebtBusinessDayReportJob.java:56) at com.htsc.task.DebtBusinessDayReportJob$$FastClassBySpringCGLIB$$6c4100a4.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:115) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Caused by: org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 at org.hibernate.jdbc.Expectations$BasicExpectation.checkBatched(Expectations.java:67) at org.hibernate.jdbc.Expectations$BasicExpectation.verifyOutcome(Expectations.java:54) at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:46) at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3209) at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3446) at org.hibernate.action.internal.EntityDeleteAction.execute(EntityDeleteAction.java:98) at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:582) at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:456) at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337) at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1282) at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:465) at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2963) at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2339) at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485) at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147) at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38) at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231) at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65) at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:61) at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517) ... 25 common frames omitted这是什么原因导致的
最新发布
11-11
这个异常: ``` org.springframework.orm.ObjectOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 Caused by: org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 ``` 是 **乐观锁(Optimistic Locking)机制** 触发的典型错误,表示在尝试更新或删除数据库记录时,**预期影响了 1 行数据,但实际影响了 0 行**。 --- ### ✅ 异常原因分析 #### 核心问题: > **你要操作的数据在数据库中已经不存在了,或者版本不一致(version mismatch),导致 Hibernate 的乐观锁检查失败。** 具体场景如下: 1. ### 使用了 `@Version` 字段进行乐观锁控制 ```java @Entity public class LiquidReportChart { @Id private Long id; @Version private Integer version; // 乐观锁字段 // 其他字段... } ``` 2. 当你加载实体后,Hibernate 会记录其 `version` 值。 3.执行 `save()` / `delete()` 操作前,如果该实体已经被其他事务修改过(`version` 已经递增),那么当前事务持有的 `version` 就是旧的。 4. 此时再做 `update` 或 `delete`,Hibernate 会在 SQL 中加入条件: ```sql UPDATE liquid_report_chart SET ..., version = ? WHERE id = ? AND version = ? ``` 5. 如果没有匹配到任何行(比如数据已被删除,或 version 不符),数据库返回受影响行数为 0。 6. Hibernate 预期应影响 1 行,结果得到 0 → 抛出 `StaleStateException` → 转换为 `ObjectOptimisticLockingFailureException` --- ### 📌 常见触发场景 | 场景 | 描述 | |------|------| | 1. 数据已被并发修改 | 多个线程/请求同时操作同一实体,一个提交后另一个使用的是旧数据 | | 2. 实体已被删除 | 在调用 `delete()` 之前,数据已经被别的事务删掉了 | | 3. 手动设置错误的 ID 或 Version | 程序中伪造了一个不存在的 ID 或过期的 version 去执行更新 | | 4. 缓存中的脏数据 | 使用二级缓存或应用缓存,读到了过期对象 | --- ### 🔍 结合你的堆栈信息分析 从堆栈看关键方法是: ```java at com.htsc.service.DebtBusinessDayReportService.generateReport(DebtBusinessDayReportService.java:81) at com.htsc.task.DebtBusinessDayReportJob.init(DebtBusinessDayReportJob.java:56) ``` 说明你在定时任务中生成报表,并试图删除某个历史图表数据: ```java deleteLiquidReportChartByDataDtAndReportCode(...) ``` 可能流程是: 1. 查询某天某报表的数据是否存在; 2. 删除旧数据; 3. 插入新数据; 但如果多个实例运行、或上一次未完成就再次触发 → 可能出现“重复删除” → 第二次删除时数据已不存在 → 抛出此异常。 --- ### ✅ 解决方案 #### ✅ 方案一:捕获异常并优雅处理(推荐用于幂等操作) 如果你的操作是 **幂等的**(例如“确保某数据不存在”),可以直接忽略删除失败的情况。 ```java @Service public class DebtBusinessDayReportService { @Autowired private LiquidReportChartRepository repository; public void deleteSafely(String dataDt, String reportCode) { try { repository.deleteLiquidReportChartByDataDtAndReportCode(dataDt, reportCode); } catch (ObjectOptimisticLockingFailureException ex) { // 数据可能已被删除,无需处理 log.warn("Data already deleted for date={} and reportCode={}", dataDt, reportCode); } } public void generateReport(...) { deleteSafely(dataDt, reportCode); // 安全删除 // 再插入新数据... } } ``` --- #### ✅ 方案二:先查后删 + merge 判断是否存在 ```java public void deleteIfExists(String dataDt, String reportCode) { List<LiquidReportChart> charts = repository.findByDataDtAndReportCode(dataDt, reportCode); if (charts.isEmpty()) { log.info("No data found to delete for {} - {}", dataDt, reportCode); return; } repository.deleteAll(charts); } ``` 这种方式避免直接基于旧状态删除,适合无 `@Version` 控制的场景。 --- #### ✅ 方案三:关闭乐观锁(不推荐!仅特殊用途) 如果你确定不需要并发安全保护,可以移除 `@Version` 字段。但这会带来数据覆盖风险,**不建议在生产环境使用**。 --- #### ✅ 方案四:使用原生 SQL 删除,绕过实体管理 适用于只关心结果而不关心对象状态的场景: ```java @Modifying @Query(value = "DELETE FROM liquid_report_chart WHERE data_dt = :dataDt AND report_code = :reportCode", nativeQuery = true) int deleteByDataDtAndReportCode(@Param("dataDt") String dataDt, @Param("reportCode") String reportCode); ``` 注意返回值是 `int` 影响行数,你可以判断是否删除成功: ```java int rows = repo.deleteByDataDtAndReportCode(...); if (rows == 0) { log.info("No rows were deleted, possibly already gone."); } ``` 这种方式不会抛出乐观锁异常,因为没走实体版本校验逻辑。 --- ### 🧪 如何复现这个问题? - 启动两个线程: - Thread A 加载实体(version=1) - Thread B 更新实体(version→2) - Thread A 提交更改 → 失败! --- ### ✅ 最佳实践建议 | 措施 | 说明 | |------|------| | ✅ 对写操作添加异常处理 | 特别是涉及乐观锁的更新/删除 | | ✅ 使用业务主键查询代替盲目操作 | 减少对持久化上下文的依赖 | | ✅ 幂等设计 | “删除不存在的数据”不应报错 | | ✅ 日志记录 | 记录何时发生 stale state,便于排查 | | ✅ 分布式环境下加分布式锁 | 如 Redis 锁防止重复执行定时任务 | --- ###
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值