使用Spring Data JPA时悲观锁无法工作

7

我希望通过数据库获取序列号。

这是我想要的:

1. 从数据库中读取实体并锁定它

2. 增加序列号并更新实体

我假设第一个线程可以锁定记录,而其他线程在第一个线程提交其事务之前不会起作用,然而,我得到了相反的结果。

以下是我的代码:

存储库:

public interface ActivityNoGeneratorRepository extends BaseRepository<ActivityNoGenerator, Long> {
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query(value = "select  generator from ActivityNoGenerator generator where id=:id")
    ActivityNoGenerator getGeneratorByIdForUpdate(@Param("id") Long id);
}

服务:

@Service
public class ActivityNoGeneratorServiceImpl implements IActivityNoGeneratorService {
    @Autowired
    private ActivityNoGeneratorRepository activityNoGeneratorRepository;

    @Override
    @Transactional
    public String getActivityNo() {
        ActivityNoGenerator activityNoGenerator = activityNoGeneratorRepository.getGeneratorByIdForUpdate(1L);
        System.out.println(1);
        Integer currentValue = activityNoGenerator.getCurrentValue() + 1;
        if (!StringUtils.equals(DateFormatUtils.format(new Date(), "yyyyMM"), DateFormatUtils.format(activityNoGenerator.getLastAccessTime(), "yyyyMM"))) {
            currentValue = 1;
        }
        String serialNum = String.format("%0" + activityNoGenerator.getWidth() + "d", currentValue);
        String activityNo = activityNoGenerator.getPrefix() + activityNoGenerator.getPlatformCode() + DateFormatUtils.format(new Date(), "yyyyMM") + serialNum;

        activityNoGenerator.setCurrentValue(currentValue);
        activityNoGenerator.setLastAccessTime(new Date());
        activityNoGeneratorRepository.save(activityNoGenerator);

        return activityNo;
    }
}

测试:

public class IActivityNoGeneratorServiceTest {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath*:spring/applicationContext.xml");
        final IActivityNoGeneratorService activityNoGeneratorService = applicationContext.getBean(IActivityNoGeneratorService.class);

        for (int i = 0; i < 2; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(activityNoGeneratorService.getActivityNo());
                }
            }).start();
        }
    }
}

结果:

Hibernate: 
    select
        activityno0_.id as id1_3_,
        activityno0_.current_value as current_2_3_,
        activityno0_.last_access_time as last_acc3_3_,
        activityno0_.platform_code as platform4_3_,
        activityno0_.platform_name as platform5_3_,
        activityno0_.prefix as prefix6_3_,
        activityno0_.step as step7_3_,
        activityno0_.width as width8_3_ 
    from
        activity_no_generator activityno0_ 
    where
        activityno0_.id=? for update

Hibernate: 
    select
        activityno0_.id as id1_3_,
        activityno0_.current_value as current_2_3_,
        activityno0_.last_access_time as last_acc3_3_,
        activityno0_.platform_code as platform4_3_,
        activityno0_.platform_name as platform5_3_,
        activityno0_.prefix as prefix6_3_,
        activityno0_.step as step7_3_,
        activityno0_.width as width8_3_ 
    from
        activity_no_generator activityno0_ 
    where
        activityno0_.id=? for update

1
1
Hibernate: 
    update
        activity_no_generator 
    set
        current_value=?,
        last_access_time=?,
        platform_code=?,
        platform_name=?,
        prefix=?,
        step=?,
        width=? 
    where
        id=?
Hibernate: 
    update
        activity_no_generator 
    set
        current_value=?,
        last_access_time=?,
        platform_code=?,
        platform_name=?,
        prefix=?,
        step=?,
        width=? 
    where
        id=?
Exception in thread "Thread-6" org.springframework.orm.jpa.JpaSystemException: commit failed; nested exception is org.hibernate.TransactionException: commit failed
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:333)
    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:483)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:290)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
    at com.sun.proxy.$Proxy44.getActivityNo(Unknown Source)
    at com.lemall.srd.pop.activity.oa.service.IActivityNoGeneratorServiceTest$1.run(IActivityNoGeneratorServiceTest.java:18)
    at java.lang.Thread.run(Thread.java:745)
Caused by: org.hibernate.TransactionException: commit failed
    at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:187)
    at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:77)
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517)
    ... 10 more
Caused by: org.hibernate.TransactionException: unable to commit against JDBC connection
    at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.doCommit(JdbcTransaction.java:116)
    at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:180)
    ... 12 more
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:404)
    at com.mysql.jdbc.Util.getInstance(Util.java:387)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:950)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3966)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3902)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2526)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2673)
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2545)
    at com.mysql.jdbc.ConnectionImpl.commit(ConnectionImpl.java:1614)
    at com.zaxxer.hikari.pool.ProxyConnection.commit(ProxyConnection.java:355)
    at com.zaxxer.hikari.pool.HikariProxyConnection.commit(HikariProxyConnection.java)
    at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.doCommit(JdbcTransaction.java:112)
    ... 13 more
HD1020012017050631

Process finished with exit code 0

我调试我的代码,发现第二个线程在没有等待第一个线程提交的情况下就被执行了。

有什么错误的线索吗?非常感谢!


尝试使用@Transactional(propagation = Propagation.REQUIRES_NEW),因为我怀疑它会在两个线程中重用事务。 - Leonid Dashko
我有同样的问题。你的代码正是我想写的。你可以尝试使用普通的entitymanager.createQuery()而不是repo和query.setLockMode()来验证一下是否有效吗?因为对我来说,使用entityMnager和query.setLockMode让它工作了,我相信我们错过了repo的某些模糊设置。 - user2023577
如果你正在使用MySQL InnoDB,请在死锁后检查“SHOW ENGINE INNODB STATUS;”以确定哪个语句和锁模式正在运行。在你的问题中报告(当然只需报告最新死锁的部分)。 - user2023577
你找到任何解决方案了吗? - Alam
1个回答

0
我看到的是你在存储库方法中缺少@Transactional注释,这可能是导致服务方法和存储库方法在两个不同事务中运行的原因。同时,在你的事务注释中使用Propagation.Required选项。

2
@Service中的@Transactional启动了事务并在线程本地传递,当repo尝试运行其SQL时进行重用。不需要在repo上添加事务包装器。除非指定重用它,否则如果repo自动创建新的事务,那将是出乎意料的设计,这与正常行为相悖。 - user2023577

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接