Spring Data JPA @Lock(PESSIMISTIC_WRITE)实体更新未同步到数据库问题求助
问题根因及解决方案
核心问题1:Kotlin data class 导致Hibernate脏检查失效
你用Kotlin data class定义JPA实体存在天生缺陷:
- data class默认生成的
equals/hashCode方法基于主构造参数,你贴出的实体主构造只有id、endDate、startDate三个字段,业务逻辑中修改的paymentDate、paymentErroneous等字段不在主构造中,Hibernate做脏数据检查时会认为实体没有发生变更,自然不会生成UPDATE语句同步到数据库。 - 搭配
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)二级缓存后,这个问题会被放大,缓存层的状态对比逻辑也会错误判断实体无变化。
修复方案:
将data class改为普通JPA实体,手动重写equals/hashCode只基于主键判断:
@Entity @Table(name = "transaction") @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) class PaymentTransaction( @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") @SequenceGenerator(name = "sequenceGenerator") var id: Long? = null, @Column(name = "end_date") var endDate: ZonedDateTime? = null, @Column(name = "start_date") var startDate: ZonedDateTime? = null, // 补全你业务用到的其他字段 var paymentDate: ZonedDateTime? = null, var paymentErroneous: Boolean? = null, var paymentStatusMessage: String? = null, var paymentExtId: String? = null ) { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as PaymentTransaction return id != null && id == other.id } override fun hashCode(): Int = javaClass.hashCode() }
核心问题2:事务边界错误
你把@Transactional直接加在Controller层方法上,若Service层的findCorresponding方法配置了独立的事务传播规则(比如REQUIRES_NEW),会导致查询实体的事务和保存实体的事务不是同一个,实体脱离持久化上下文,修改自然不会被同步。
修复方案:
将业务逻辑移到Service层统一管控事务,Controller只做参数校验和路由:
// Service层代码 @Service class MyPaymentTransactionService( private val transactionRepository: TransactionRepository ) { @Transactional(isolation = Isolation.REPEATABLE_READ) fun updateTransactionStatus(transRef: String, status: String, message: String) { val transaction = transactionRepository.findByPaymentExtId(transRef) ?: return transaction.apply { paymentDate = now(UTC) paymentErroneous = status in listOf("000/01", "000/200") paymentStatusMessage = (paymentStatusMessage ?: "") + message } // 无需显式调用saveAndFlush,事务提交时持久化上下文会自动flush变更 } } // Controller层代码 @RestController @RequestMapping("/api") class EndResource( private val transactionService: MyPaymentTransactionService, ) { @PostMapping("/end") fun end( principal: Principal, transactionStatusEntity: RequestEntity<TransactionStatusResponse> ): ResponseEntity<String> { val transactionStatus = transactionStatusEntity.body!! transactionService.updateTransactionStatus( transRef = transactionStatus.trans_ref, status = transactionStatus.trans_status, message = transactionStatus.message ) return ResponseEntity.noContent().build() } }
额外排查点
- 确认定时任务的事务边界:你需要保证定时任务的逻辑是「查询锁实体→写extId→提交事务释放锁→调用外部服务」,如果把外部服务调用包在事务内部,会导致锁持有时间超长,甚至外部调用超时事务回滚后extId变更失效。
- 开启SQL日志验证:在配置文件添加以下配置,确认
SELECT ... FOR UPDATE语句和UPDATE语句正常生成,事务正常提交:
spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true logging.level.org.springframework.transaction=DEBUG logging.level.org.hibernate.lock=DEBUG
- 检查是否存在乐观锁冲突:如果实体加了
@Version乐观锁字段,版本号不匹配时更新会静默失败,不会抛出异常。
内容的提问来源于stack exchange,提问作者Frischling
相关产品推荐
相关产品推荐

