问题

  • 报错日志:详见文章结尾附:报错日志

程序ORM框架使用的SpringData JPA,程序中未配置@Version或者@OptimisticLocking注解,但是报了一个乐观锁异常。Cause By中可以看到数据是被其它线程更改了。

  • 程序逻辑:
@Transaction
void method(String name) {
    // 略

    // @Lock(value = LockModeType.PESSIMISTIC_WRITE)
    // User findPessimisticWriteByUsername(String id);
    User user = findPessimisticWriteByUsername(name);

    if (user == null) {
        user = new User();
        user.setUsername(name);
    }

    userDao.save(user);
}
  • 数据库:MySQL

原因

悲观锁 for update 是当前读,普通查询是快照读。

  • 程序事务开始后,在悲观锁读之前,其它逻辑查询过一次数据库,所以数据库生成了一次快照
  • 在悲观锁读之前,数据被删除了
  • 然后程序悲观锁读数据不存在,所以新建了一条记录想插入到数据库中
  • 但是save之前会select一次,这次是快照读,所以jpa判断数据存在,然后想要update,但是实际数据库中没有这条数据。update失败

复现

@Slf4j
@Service
public class TestService {

    @Autowired
    private TestTableDao testTableDao;

    @SneakyThrows
    @Transactional
    public String testPessimisticWrite(String id) {
        log(TransactionAspectSupport.currentTransactionStatus().toString());

        testTableDao.findById("111").ifPresent(System.out::println);

        TestTable testTable1 = testTableDao.findById(id).orElseGet(() -> null);
        log("finished query1 " + testTable1);

        TestTable testTable = testTableDao.findPessimisticWriteByUsername(id).orElseGet(() -> null);
        log("finished query2 " + testTable);

        TestTable testTable2 = testTableDao.findById(id).orElseGet(() -> null);
        log("finished query3 " + testTable2);

        if (testTable == null) {
            testTable = new TestTable().setUsername(id);
        }

        log("start save");
        TestTable save = testTableDao.save(testTable);
        log("finished save " + save.getPassword());
        return save.toString();
    }

    private void log(String startQuery) {
        log.info(" ----------- " + Thread.currentThread().getName() + " --- " + startQuery);
    }
    
}

在第一次TestTable testTable1 = testTableDao.findById(id).orElseGet(() -> null);前打个断点。
step1.调用这个方法查询一条数据库已有的数据2222
step2.到断点处程序停止,然后去数据库中删除2222数据
step3.放行方法

可以看到,只有select for update查询到的2222是null,因为它是当前读。

程序最终会报一个乐观锁异常:

附:报错日志

org.springframework.orm.ObjectOptimisticLockingFailureException: Object of class [com.totainfo.entity.ppt.McsCarrier] with identifier [H66P0430]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.totainfo.entity.ppt.McsCarrier#H66P0430]
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:337)
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:255)
	at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:531)
	at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
	at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:154)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:178)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
	at com.sun.proxy.$Proxy142.save(Unknown Source)
	at com.totainfo.synchronize.RetWcsSynchronizeSecsServiceImpl.lambda$enhancedPorts$4(RetWcsSynchronizeSecsServiceImpl.java:351)
	at java.util.ArrayList$Itr.forEachRemaining(ArrayList.java:891)
	at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
	at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
	at com.totainfo.synchronize.RetWcsSynchronizeSecsServiceImpl.enhancedPorts(RetWcsSynchronizeSecsServiceImpl.java:322)
	at com.totainfo.synchronize.RetWcsSynchronizeSecsServiceImpl.subMainProc(RetWcsSynchronizeSecsServiceImpl.java:82)
	at com.totainfo.synchronize.RetWcsSynchronizeSecsServiceImpl.subMainProc(RetWcsSynchronizeSecsServiceImpl.java:34)
	at com.totainfo.synchronize.RetWcsSynchronizeSecsServiceImpl$$FastClassBySpringCGLIB$$a4ca3f32.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
	at com.totainfo.synchronize.RetWcsSynchronizeSecsServiceImpl$$EnhancerBySpringCGLIB$$b7e2b709.subMainProc(<generated>)
	at com.totainfo.send.RetSendWcsSecsServiceImpl.handleS1F4(RetSendWcsSecsServiceImpl.java:137)
	at com.totainfo.send.RetSendWcsSecsServiceImpl.S1F3(RetSendWcsSecsServiceImpl.java:87)
	at com.totainfo.send.RetSendWcsSecsServiceImpl$$FastClassBySpringCGLIB$$dc9e2ffa.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
	at com.totainfo.send.RetSendWcsSecsServiceImpl$$EnhancerBySpringCGLIB$$7cd74661.S1F3(<generated>)
	at com.totainfo.recieive.RetReceiveWcsSecsServiceImpl.scPauseCompleted(RetReceiveWcsSecsServiceImpl.java:2076)
	at com.totainfo.recieive.RetReceiveWcsSecsServiceImpl.subMainProc(RetReceiveWcsSecsServiceImpl.java:245)
	at com.totainfo.recieive.RetReceiveWcsSecsServiceImpl.subMainProc(RetReceiveWcsSecsServiceImpl.java:61)
	at com.totainfo.common.core.bean.base.serviceabstract.ICIMBaseServiceAbstract.mainProc(Unknown Source)
	at com.totainfo.common.core.bean.base.serviceabstract.ICIMBaseServiceAbstract$$FastClassBySpringCGLIB$$3038395b.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:367)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)
	at com.totainfo.recieive.RetReceiveWcsSecsServiceImpl$$EnhancerBySpringCGLIB$$49e64182.mainProc(<generated>)
	at com.totainfo.listener.RetWcsListener.rptId13(RetWcsListener.java:595)
	at com.totainfo.listener.RetWcsListener.receiveWcsS6F11(RetWcsListener.java:156)
	at com.totainfo.listener.RetWcsListener.subMainProc(RetWcsListener.java:69)
	at com.totainfo.serviceabstract.ICIMMcsAbstractService.mainProc(ICIMMcsAbstractService.java:32)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:282)
	at com.lmrj.util.spring.SpringContext.reInvoke(SpringContext.java:134)
	at com.lmrj.codec.secs.e.a.iiIiiIIIIi.???()(rb:21)
	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.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.totainfo.entity.ppt.McsCarrier#H66P0430]
	at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:2649)
	at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3492)
	at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3355)
	at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3769)
	at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:201)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604)
	at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478)
	at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475)
	at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:344)
	at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40)
	at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:99)
	at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1362)
	at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1349)
	at sun.reflect.GeneratedMethodAccessor198.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:314)
	at com.sun.proxy.$Proxy121.flush(Unknown Source)
	at org.springframework.data.jpa.repository.support.SimpleJpaRepository.flush(SimpleJpaRepository.java:601)
	at com.totainfo.common.core.dao.BaseDaoImpl.save(Unknown Source)
	at sun.reflect.GeneratedMethodAccessor197.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.data.repository.core.support.ImplementationInvocationMetadata.invoke(ImplementationInvocationMetadata.java:72)
	at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:382)
	at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:205)
	at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:550)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:155)
	at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:130)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:367)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
	... 53 common frames omitted