【问题标题】:pessimistic lock not work using spring data jpa悲观锁不能使用spring数据jpa
【发布时间】:2017-05-18 07:40:05
【问题描述】:

我想借助数据库获取序列号。 这是我想要的:

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),因为我怀疑它会尝试在两个线程中重用事务
  • 我也有同样的问题。你的代码正是我会写的。您可以尝试使用常规 entitymanager.createQuery() 而不是 repo 和 query.setLockMode() 来证明这是否可行?因为使用 entityMnager 和 query.setLockMode 使它对我有用,而且我相信我们错过了一个模糊的 repo 设置。
  • 如果你在mysql innodb上,检查“SHOW ENGINE INNODB STATUS;”在死锁之后只是断言哪些语句和锁定模式在起作用。报告您的问题。 (当然只是关于最新死锁的部分)。
  • 你找到解决办法了吗?

标签: java multithreading jpa spring-data-jpa


【解决方案1】:

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

【讨论】:

  • 服务的 @Transactional 启动 txn 并在线程本地进行,并在 repo 尝试运行其 sql 时重用。不需要在 repo 中添加 txn 包装器。如果 erepo 会自动创建一个新的 txn,除非有人指定重用它,这将是一个令人惊讶的设计。这违背了正常行为。
猜你喜欢
  • 1970-01-01
  • 2018-07-30
  • 2017-03-25
  • 2014-08-30
  • 1970-01-01
  • 2018-10-22
  • 1970-01-01
  • 2015-09-29
  • 2013-10-16
相关资源
最近更新 更多