【发布时间】:2016-04-21 15:50:54
【问题描述】:
我无法让事务在此单元测试中正常工作。 TransactionTest 类包含所有必要的 Spring 配置。它启动、初始化数据库并并行执行两个 Runnable(插入器和选择器)。从记录的输出中可以看出,测试执行,记录被插入并以正确的顺序从数据库中选择,但没有事务隔离。
我希望在日志中看到的内容类似于:
2016-01-16 00:29:32,447 [main] DEBUG TransactionTest - Starting test
2016-01-16 00:29:32,619 [pool-2-thread-2] DEBUG Selector - Select 1 returned: 0
2016-01-16 00:29:33,121 [pool-2-thread-1] DEBUG Inserter - inserting record: 1
2016-01-16 00:29:33,621 [pool-2-thread-2] DEBUG Selector - Select 2 returned: 0
2016-01-16 00:29:34,151 [pool-2-thread-1] DEBUG Inserter - inserting record: 2
2016-01-16 00:29:34,624 [pool-2-thread-2] DEBUG Selector - Select 3 returned: 2
2016-01-16 00:29:34,624 [main] DEBUG TransactionTest - Terminated
但是,我看到的是:
2016-01-16 00:29:32,447 [main] DEBUG TransactionTest - Starting test
2016-01-16 00:29:32,619 [pool-2-thread-2] DEBUG Selector - Select 1 returned: 0
2016-01-16 00:29:33,121 [pool-2-thread-1] DEBUG Inserter - inserting record: 1
2016-01-16 00:29:33,621 [pool-2-thread-2] DEBUG Selector - Select 2 returned: 1
2016-01-16 00:29:34,151 [pool-2-thread-1] DEBUG Inserter - inserting record: 2
2016-01-16 00:29:34,624 [pool-2-thread-2] DEBUG Selector - Select 3 returned: 2
2016-01-16 00:29:34,624 [main] DEBUG TransactionTest - Terminated
请考虑下面的测试代码。在 TransactionTest.java 中有一些注释在类主体本身之前被注释掉。当我包含这些注释时,我可以从日志中看到 Spring 在单独的事务中执行整个测试本身。但是我的目标是让它在单独的事务中执行方法 Inserter.insertSeveralRecords() 。遗憾的是,日志中没有迹象表明 Spring 甚至在那里看到了 @Transactional 注释。
我尝试将 @EnableTransactionManagement 注释也添加到 TransactionTest 类本身,而不是 Configuration 部分,但没有区别。
TransactionTest.java
package program.test.db.transaction;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.flywaydb.core.Flyway;
import org.flywaydb.test.annotation.FlywayTest;
import org.flywaydb.test.junit.FlywayTestExecutionListener;
import org.jooq.DSLContext;
import org.jooq.SQLDialect;
import org.jooq.impl.DataSourceConnectionProvider;
import org.jooq.impl.DefaultConfiguration;
import org.jooq.impl.DefaultDSLContext;
import org.jooq.impl.DefaultExecuteListenerProvider;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;
import program.db.JooqExceptionTranslator;
import static org.junit.Assert.assertTrue;
import static program.db.Tables.SYSTEM_LOG;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, FlywayTestExecutionListener.class})//, TransactionalTestExecutionListener.class})
//@Transactional
//@TransactionConfiguration(transactionManager="transactionManager", defaultRollback=false)
public class TransactionTest {
private static Logger log = LogManager.getLogger(TransactionTest.class);
@Configuration
@PropertySource("classpath:program.properties")
@EnableTransactionManagement
static class ContextConfiguration {
@Autowired
private Environment env;
@Bean
public Flyway flyway(){
Flyway flyway = new Flyway();
flyway.setDataSource(dataSource());
flyway.setSchemas("program_x");
flyway.setLocations("db/migration");
return flyway;
}
@Bean
public BasicDataSource dataSource() {
BasicDataSource result = new BasicDataSource();
result.setDriverClassName(env.getRequiredProperty("program.database.driver"));
result.setUrl(env.getRequiredProperty("program.database.url"));
result.setUsername(env.getRequiredProperty("program.database.username"));
result.setPassword(env.getRequiredProperty("program.database.password"));
return result;
}
@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public TransactionAwareDataSourceProxy transactionAwareDataSource(){
return new TransactionAwareDataSourceProxy(dataSource());
}
@Bean
public DataSourceConnectionProvider connectionProvider(){
return new DataSourceConnectionProvider(transactionAwareDataSource());
}
@Bean
public JooqExceptionTranslator jooqExceptionTranslator(){
return new JooqExceptionTranslator();
}
@Bean
public DefaultConfiguration config(){
DefaultConfiguration result = new DefaultConfiguration();
result.set(connectionProvider());
result.set(new DefaultExecuteListenerProvider(jooqExceptionTranslator()));
result.set(SQLDialect.POSTGRES);
return result;
}
@Bean
public DefaultDSLContext db(){
return new DefaultDSLContext(config());
}
@Bean
public Inserter inserter(){
return new Inserter();
}
@Bean
public Selector selector(){
return new Selector();
}
}
@Autowired
private DSLContext db;
@Autowired
private Selector selector;
@Autowired
private Inserter inserter;
private final ThreadPoolExecutor THREAD_POOL = (ThreadPoolExecutor) Executors.newCachedThreadPool();
@Test
@FlywayTest
public void runTest() throws InterruptedException {
log.debug("Starting test");
int count0 = db.selectCount().from(SYSTEM_LOG).fetchOne(0, int.class);
assertTrue(count0 == 0);
THREAD_POOL.execute(inserter);
THREAD_POOL.execute(selector);
THREAD_POOL.shutdown();
THREAD_POOL.awaitTermination(5, TimeUnit.SECONDS);
log.debug("Terminated");
}
}
Selector.java
package program.test.db.transaction;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import static program.db.Tables.SYSTEM_LOG;
@Component
public class Selector implements Runnable {
private static Logger log = LogManager.getLogger(Selector.class);
@Autowired
private DSLContext db;
@Override
public void run() {
try {
int count1 = db.selectCount().from(SYSTEM_LOG).fetchOne(0, int.class);
log.debug("Select 1 returned: " + count1);
Thread.sleep(1000);
int count2 = db.selectCount().from(SYSTEM_LOG).fetchOne(0, int.class);
log.debug("Select 2 returned: " + count2);
Thread.sleep(1000);
int count3 = db.selectCount().from(SYSTEM_LOG).fetchOne(0, int.class);
log.debug("Select 3 returned: " + count3);
} catch (InterruptedException e) {
log.error("Selects were interrupted", e);
}
}
}
Inserter.java
package program.test.db.transaction;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joda.time.DateTime;
import org.jooq.DSLContext;
import org.jooq.InsertQuery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import program.db.tables.records.SystemLogRecord;
import static org.junit.Assert.assertTrue;
import static program.db.Tables.SYSTEM_LOG;
@Component
public class Inserter implements Runnable {
private static Logger log = LogManager.getLogger(Inserter.class);
@Autowired
private DSLContext db;
@Override
public void run() {
insertSeveralRecords();
}
@Transactional
private void insertSeveralRecords(){
try {
Thread.sleep(500);
insertRecord(1);
Thread.sleep(1000);
insertRecord(2);
} catch (InterruptedException e) {
log.error("Inserts were interrupted", e);
}
}
private void insertRecord(int i){
log.debug("inserting record: " + i);
InsertQuery<SystemLogRecord> insertQuery = db.insertQuery(SYSTEM_LOG);
insertQuery.addValue(SYSTEM_LOG.SERVICE, "Service " + i);
insertQuery.addValue(SYSTEM_LOG.MESSAGE, "Message " + i);
insertQuery.addValue(SYSTEM_LOG.SYS_INSERT_TIME, DateTime.now());
int result = insertQuery.execute();
assertTrue(result == 1);
}
}
我可能在这里遗漏了一些相当基本的东西 - 我做错了什么?
【问题讨论】:
标签: java database spring spring-transactions jooq