You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()
    }
}

额外排查点

  1. 确认定时任务的事务边界:你需要保证定时任务的逻辑是「查询锁实体→写extId→提交事务释放锁→调用外部服务」,如果把外部服务调用包在事务内部,会导致锁持有时间超长,甚至外部调用超时事务回滚后extId变更失效。
  2. 开启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
  1. 检查是否存在乐观锁冲突:如果实体加了@Version乐观锁字段,版本号不匹配时更新会静默失败,不会抛出异常。

内容的提问来源于stack exchange,提问作者Frischling

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 08:54:04