测试对@transactional @Async方法的调用的JUnit会导致超出锁定等待超时

时间:2011-02-25 09:26:08

标签: java mysql hibernate spring junit

我正在尝试测试异步运行的服务方法(@Async)。

这是异步方法:

@Async
@Transactional(propagation=Propagation.SUPPORTS, isolation = Isolation.READ_UNCOMMITTED)
public Future<UserPrefs> checkLanguagePreference(long id) {

    UserPrefs prefs = prefsDao.retrieveUserPreferences(id);
    if(prefs == null || !StringUtils.hasLength(prefs.getLanguage())) {
        //Save a new sms-command object
        SmsBean command = SmsHelper.buildSmsCommand();
        if(! smsDao.checkSameCommandExists(id, command)) {

            smsDao.saveSms(id, new SmsBean[] {command}); //Will wait until Lock wait timeout
        }
    }
    return new AsyncResult<UserPrefs>(prefs);
}

这是调用异步的测试方法:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(location = "...")
@TransactionConfiguration(transactionManager = "txManager", defaultRollback = false)
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@TestExecutionListeners( {  DependencyInjectionTestExecutionListener.class,
  DirtiesContextTestExecutionListener.class,
  TransactionalTestExecutionListener.class })
public class MessagingServiceTest {

   @Before
   public void setUp() {        
     //Avant tout mettre tout les sms en lu 
     smsDao.deleteAllSms(1);
     sessionFactory.getCurrentSession().flush();

     //On vérifie bien qu'il n y a plus de sms
     List<SmsBean> list = smsDao.getNewSmsList(1);
     assertEquals(0,list.size());
   }

   @Test
   public void checkLanguagePreferenceTest() throws InterruptedException, ExecutionException {
     User user = (User) sessionFactory.getCurrentSession().load(User.class, new Long(1));//idUser = 1
     // We explicitly blank the preference from db
     prefsDao.saveLanguagePref(new UserPrefs("",user));

     Future<UserPrefs> prefs =  messagingService.checkLanguagePreference(user.getId()); 
     System.out.println("wait completion of async task");           
     prefs.get();
     System.out.println("Async task has finished");
   }
}

当执行prefs.get()时,我有这个错误:

Caused by: org.springframework.orm.hibernate3.HibernateJdbcException: JDBC exception on Hibernate data access: SQLException for SQL [insert into SmsBean (destination, message, origin, sens, status, USER_ID) values (?, ?, ?, ?, ?, ?)]; SQL state [41000]; error code [1205]; could not insert:

Caused by: java.sql.SQLException: Lock wait timeout exceeded; try restarting transaction

    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1075)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3562)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3494)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1960)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2114)
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2696)
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2105)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2398)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2316)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2301)
    at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:101)
    at org.hibernate.id.IdentityGenerator$GetGeneratedKeysDelegate.executeAndExtract(IdentityGenerator.java:94)
    at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:57)
    ... 39 more

这是因为setup方法中的smsDao.deleteAllSms在sms表上持有一个锁。

如何正确避免此锁定超时并能够成功运行我的测试?

感谢您的帮助。

仅供参考,这是一些控制台输出:

DEBUG - Adding transactional method 'checkLanguagePreferenceTest' with attribute: PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED; ''
DEBUG - Explicit transaction definition [PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED; ''] found for test context [[TestContext@b76fa testClass = MessagingServiceTest, locations = array['file:src/main/resources/myapp-context.xml', 'file:src/main/resources/myapp-data.xml', 'file:src/main/resources/myapp-services.xml'], testInstance = fr.myapp.service.MessagingServiceTest@b01d43, testMethod = checkLanguagePreferenceTest@MessagingServiceTest, testException = [null]]]
DEBUG - Retrieved @TransactionConfiguration [@org.springframework.test.context.transaction.Tran sactionConfiguration(defaultRollback=false, transactionManager=txManager)] for test class [class fr.myapp.service.MessagingServiceTest]
DEBUG - Retrieved TransactionConfigurationAttributes [[TransactionConfigurationAttributes@5f7d3f transactionManagerName = 'txManager', defaultRollback = false]] for class [class fr.myapp.service.MessagingServiceTest]
DEBUG - Returning cached instance of singleton bean 'txManager'
DEBUG - Creating new transaction with name [checkLanguagePreferenceTest]: PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED; ''
DEBUG - Opened new Session [org.hibernate.impl.SessionImpl@666a53] for Hibernate transaction
DEBUG - Preparing JDBC Connection of Hibernate Session [org.hibernate.impl.SessionImpl@666a53]
DEBUG - Changing isolation level of JDBC Connection [org.apache.commons.dbcp.PoolableConnection@1bde3d2] to 2
DEBUG - Exposing Hibernate transaction as JDBC transaction [org.apache.commons.dbcp.PoolableConnection@1bde3d2]
DEBUG - No method-level @Rollback override: using default rollback [false] for test context [[TestContext@b76fa testClass = MessagingServiceTest, locations = array['file:src/main/resources/myapp-context.xml', 'file:src/main/resources/myapp-data.xml', 'file:src/main/resources/myapp-services.xml'], testInstance = fr.myapp.service.MessagingServiceTest@b01d43, testMethod = checkLanguagePreferenceTest@MessagingServiceTest, testException = [null]]]
INFO - Began transaction (1): transaction manager [org.springframework.orm.hibernate3.HibernateTransa ctionManager@17753a8]; rollback [false]
Hibernate: delete from SmsBean where USER_ID=?
Hibernate: select user0_.id as id3_1_, user0_.email as email3_1_, user0_.login as login3_1_, user0_.passwd as passwd3_1_, smsbeans1_.USER_ID as USER7_3_3_, smsbeans1_.id as id3_, smsbeans1_.id as id0_0_, smsbeans1_.destination as destinat2_0_0_, smsbeans1_.message as message0_0_, smsbeans1_.origin as origin0_0_, smsbeans1_.sens as sens0_0_, smsbeans1_.status as status0_0_, smsbeans1_.USER_ID as USER7_0_0_ from User user0_ left outer join SmsBean smsbeans1_ on user0_.id=smsbeans1_.USER_ID where user0_.id=?
Hibernate: select user0_.id as id3_, user0_.email as email3_, user0_.login as login3_, user0_.passwd as passwd3_ from User user0_ where user0_.login=?
Hibernate: select userprefs0_.id as id2_, userprefs0_.language as language2_, userprefs0_.USER_ID as USER3_2_ from user_prefs userprefs0_ where userprefs0_.USER_ID=?
wait completion of async task
DEBUG - Returning cached instance of singleton bean 'txManager'
INFO - Ener dans checkLanguagePreference(1)
DEBUG - Opening Hibernate Session
DEBUG - Registering Spring transaction synchronization for new Hibernate Session
Hibernate: select userprefs0_.id as id2_, userprefs0_.language as language2_, userprefs0_.USER_ID as USER3_2_ from user_prefs userprefs0_ where userprefs0_.USER_ID=?
Hibernate: select user0_.id as id3_1_, user0_.email as email3_1_, user0_.login as login3_1_, user0_.passwd as passwd3_1_, smsbeans1_.USER_ID as USER7_3_3_, smsbeans1_.id as id3_, smsbeans1_.id as id0_0_, smsbeans1_.destination as destinat2_0_0_, smsbeans1_.message as message0_0_, smsbeans1_.origin as origin0_0_, smsbeans1_.sens as sens0_0_, smsbeans1_.status as status0_0_, smsbeans1_.USER_ID as USER7_0_0_ from User user0_ left outer join SmsBean smsbeans1_ on user0_.id=smsbeans1_.USER_ID where user0_.id=?
INFO - Checking if same sms command already exist
Hibernate: select * from smsbean S where S.USER_ID=? and S.status=? and S.message=?
DEBUG - Flushing Hibernate Session on transaction synchronization

//Deadlock here :
Hibernate: insert into SmsBean (destination, message, origin, sens, status, USER_ID) values (?, ?, ?, ?, ?, ?)
DEBUG - Closing Hibernate Session
58799 [SimpleAsyncTaskExecutor-1] WARN org.hibernate.util.JDBCExceptionReporter - SQL Error: 1205, SQLState: 41000
58799 [SimpleAsyncTaskExecutor-1] ERROR org.hibernate.util.JDBCExceptionReporter - Lock wait timeout exceeded; try restarting transaction

已经解决,但我之前已经创建了一个关于MySQL论坛的一个主题,关于为什么我从DBMS的角度来看这个死锁。这是链接(也很好解释):

http://forums.mysql.com/read.php?97,409237,409237#msg-409237

2 个答案:

答案 0 :(得分:4)

由于您的测试被声明为@Transactional,因此您有一个大型事务,可以通过执行setUp方法和您的测试方法进行传播。此事务死锁与另一个事务在异步操作中启动(异步操作等待释放主事务获取的锁,主事务等待异步操作完成)。

您可以通过将主要交易分成几个单独的交易来解决它:

@Before
@Transactional // separate transaction for setUp
public void setUp() {        
    //Avant tout mettre tout les sms en lu 
    smsDao.deleteAllSms(1);
    sessionFactory.getCurrentSession().flush();

    //On vérifie bien qu'il n y a plus de sms
    List<SmsBean> list = smsDao.getNewSmsList(1);
    assertEquals(0,list.size());
}

@Test
@Transactional(propagation = NEVER) // Disable main transaction
public void checkLanguagePreferenceTest() throws InterruptedException, ExecutionException {

    // Programmatic transaction for test preparation
    User user = tx.execute(new TransactionCallback<User>() {
        public User doInTransaction(TransactionStatus status) {
            User user = (User) sessionFactory.getCurrentSession().load(User.class, new Long(1));//idUser = 1
            // We explicitly blank the preference from db
            prefsDao.saveLanguagePref(new UserPrefs("",user));    
            return user;
        }
    });

    Future<UserPrefs> prefs = messagingService.checkLanguagePreference(user.getId()); 
    System.out.println("wait completion of async task");            
    prefs.get();
    System.out.println("Async task has finished");
}

private TransactionTemplate tx;

@Autowired
public void setPtm(PlatformTransactionManager ptm) {
    tx = new TransactionTemplate(ptm);
}

答案 1 :(得分:0)

如果您可以容忍测试的同步执行,您还可以配置使用SyncTaskExecutor而不是其线程对等的测试上下文。

换句话说:

<bean id="myExecutor" class="org.springframework.core.task.SyncTaskExecutor"/>

而不是:

<task:executor id="myExecutor" pool-size="5"/>

然后一切都在同一个线程中运行,您不必处理因竞争事务导致的数据库锁定问题。