Spring Test Transactional @Rollback

作者:範宗雲 来源:原创 发布时间:2015-04-19 归档:spring-test

开发环境 : JDK 7 Maven 3 Junit 4.11 Spring 4.1.5 MySQL 5.5 Eclipse Luna
pom.xml
		    <project>
			  <properties>
			    <spring.version>4.1.5.RELEASE</spring.version>
			    <mysql.version>5.1.17</mysql.version>
			    <junit.version>4.11</junit.version>
			    <hamcrest.version>1.3</hamcrest.version>
			    <aspectj.version>1.6.8</aspectj.version>
			  </properties>
			  <dependencies>
			    <dependency>
			      <groupId>org.springframework</groupId>
			      <artifactId>spring-context</artifactId>
			      <version>${spring.version}</version>
			    </dependency>
			    <dependency>
			      <groupId>org.springframework</groupId>
			      <artifactId>spring-jdbc</artifactId>
			      <version>${spring.version}</version>
			    </dependency>
			    <dependency>
			      <groupId>org.springframework</groupId>
			      <artifactId>spring-test</artifactId>
			      <version>${spring.version}</version>
			      <scope>test</scope>
			    </dependency>
			    <dependency>
			      <groupId>mysql</groupId>
			      <artifactId>mysql-connector-java</artifactId>
			      <version>${mysql.version}</version>
			    </dependency>
			    <dependency>
			      <groupId>org.aspectj</groupId>
			      <artifactId>aspectjweaver</artifactId>
			      <version>${aspectj.version}</version>
			    </dependency>
			    <dependency>
			      <groupId>org.hamcrest</groupId>
			      <artifactId>hamcrest-all</artifactId>
			      <version>${hamcrest.version}</version>
			      <scope>test</scope>
			    </dependency>
			    <dependency>
			      <groupId>junit</groupId>
			      <artifactId>junit</artifactId>
			      <version>${junit.version}</version>
			      <scope>test</scope>
			      <exclusions>
			        <exclusion>
			          <groupId>org.hamcrest</groupId>
			          <artifactId>hamcrest-core</artifactId>
			        </exclusion>
			      </exclusions>
			    </dependency>
			  </dependencies>
			</project>
		    
beans.xml
		    <?xml version="1.0" encoding="UTF-8"?>
			<beans xmlns="http://www.springframework.org/schema/beans"
			  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
			  xmlns:context="http://www.springframework.org/schema/context"
			  xmlns:tx="http://www.springframework.org/schema/tx"
			  xmlns:aop="http://www.springframework.org/schema/aop"
			  xsi:schemaLocation="http://www.springframework.org/schema/beans
			  http://www.springframework.org/schema/beans/spring-beans.xsd
			  http://www.springframework.org/schema/context
			  http://www.springframework.org/schema/context/spring-context.xsd
			  http://www.springframework.org/schema/tx 
			  http://www.springframework.org/schema/tx/spring-tx.xsd
			  http://www.springframework.org/schema/aop 
			  http://www.springframework.org/schema/aop/spring-aop.xsd">
			  
			  <context:annotation-config />
			  
			  <context:component-scan base-package="org.lychie" />
			  
			  <context:property-placeholder location="classpath:jdbc.properties" />
			  <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
			    <property name="url" value="${jdbc.url}" />
			    <property name="username" value="${jdbc.username}" />
			    <property name="password" value="${jdbc.password}" />
			    <property name="driverClassName" value="${jdbc.driverClassName}" />
			  </bean>
			  
			  <bean class="org.springframework.jdbc.core.JdbcTemplate">
			    <property name="dataSource" ref="dataSource" />
			  </bean>
			  
			  <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
			    <property name="dataSource" ref="dataSource" />
			  </bean>
			 
			  <tx:annotation-driven transaction-manager="transactionManager"/>
			  
			  <tx:advice id="txAdvice" transaction-manager="transactionManager">
			    <tx:attributes>
			      <tx:method name="save*" propagation="REQUIRED" rollback-for="Throwable" />
			      <tx:method name="insert*" propagation="REQUIRED" rollback-for="Throwable" />
			      <tx:method name="update*" propagation="REQUIRED" rollback-for="Throwable" />
			      <tx:method name="delete*" propagation="REQUIRED" rollback-for="Throwable" />
			      <tx:method name="*" read-only="true" /> 
			    </tx:attributes>
			  </tx:advice>
			
			  <aop:config>
			    <aop:advisor pointcut="execution(* org.lychie.dao.*.*(..))" advice-ref="txAdvice" />
			  </aop:config>
			  
			</beans>
		    
LoggerRule.java
		    public class LoggerRule implements TestRule {
			
				@Override
				public Statement apply(final Statement base, final Description description) {
					return new Statement() {
						@Override
						public void evaluate() throws Throwable {
							String method = description.getDisplayName();
							System.out.println("---> " + method + " is ready to execute");
							base.evaluate();
							System.out.println("---> " + method + " has been executed");
						}
					};
				}
				
			}
		    
EmployeeDaoImpl.java
		    @Repository
			public class EmployeeDaoImpl implements EmployeeDao {
			
				@Autowired
				private JdbcTemplate jdbcTemplate;
				
				@Override
				public List<Employee> getAll() {
					String sql = "SELECT * FROM Employee";
					return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Employee.class));
				}
			
				@Override
				public boolean insert(Employee employee) {
					String sql = "INSERT INTO Employee(id, name, age, mail) VALUES(?, ?, ?, ?)";
					int result = jdbcTemplate.update(sql, null, employee.getName(), employee.getAge(), employee.getMail());
					return result > 0;
				}
			
			}
		    
EmployeeDaoImplTest.java
		    @RunWith(SpringJUnit4ClassRunner.class)
			@ContextConfiguration("/beans.xml")
			@Transactional
			public class EmployeeDaoImplTest {
			
				@Autowired
				private EmployeeDao employeeDao;
				
				@Rule
				public TestRule loggerRule = new LoggerRule();
			
				@Test
				@Rollback
				public void testInsert() {
					Employee employee = new Employee();
					employee.setAge(20);
					employee.setName("店小四");
					employee.setMail("dianxiaosi@yeah.net");
					boolean successful = employeeDao.insert(employee);
					assertThat(successful, is(true));
				}
			
			}
		    
前面是测试环境的准备, 没有什么特别的。这里才是焦点的开始。
@Rollback 默认值是 true, 如果想设为 false, 可使用 @Rollback(false) 标注。执行单元测试的结果, 如下 :
---> testInsert(org.lychie.dao.impl.EmployeeDaoImplTest) is ready to execute
四月 20, 2015 12:01:19 上午 org.springframework.test.context.transaction.TransactionContext startTransaction
信息: Began transaction (1) for test context [DefaultTestContext@3f9b4e74 testClass = EmployeeDaoImplTest,
 testInstance = org.lychie.dao.impl.EmployeeDaoImplTest@6d3bc24b, testMethod = testInsert@EmployeeDaoImplTest,
 testException = [null], mergedContextConfiguration = [MergedContextConfiguration@6e5101f3 testClass =
 EmployeeDaoImplTest, locations = '{classpath:/beans.xml}', classes = '{}', contextInitializerClasses = '[]',
 activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextLoader =
 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]; transaction manager
 [org.springframework.jdbc.datasource.DataSourceTransactionManager@857311a]; rollback [true]
四月 20, 2015 12:01:19 上午 org.springframework.test.context.transaction.TransactionContext endTransaction
信息: Rolled back transaction for test context [DefaultTestContext@3f9b4e74 testClass = EmployeeDaoImplTest,
 testInstance = org.lychie.dao.impl.EmployeeDaoImplTest@6d3bc24b, testMethod = testInsert@EmployeeDaoImplTest,
 testException = [null], mergedContextConfiguration = [MergedContextConfiguration@6e5101f3 testClass =
 EmployeeDaoImplTest, locations = '{classpath:/beans.xml}', classes = '{}', contextInitializerClasses = '[]',
 activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextLoader =
 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]].
---> testInsert(org.lychie.dao.impl.EmployeeDaoImplTest) has been executed
测试执行结果的信息已经用不同的颜色标识了出来, 仔细阅读, 你可以获得 testInsert 在执行期间的详细信息 :方法开始 --> 事务开始 --> 事务回滚 --> 事务结束 --> 方法结束。
使用 @Rollback 的好处是, 测试数据不会对数据库造成污染, 这一点是很重要的。但 @Rollback 其实也不是真正意义上的数据零污染, 如果数据库表的主键是自增长类型, 虽然发生了事务回滚, 但是主键的索引还是会递增的。
执行这个测试, 数据库是不会插入记录的, 如果把 @Rollback 改成 @Rollback(false), 数据库就会插入一条数据。
@Rollback 需要 @Transactional 的支持 ( 我们知道, @Transactional 默认是会自动提交事务的 ), 如果没有 @Transactional 标注, 则事务就不会受 @Rollback 的控制。

示例代码下载
SpringTestTransactionalRollback.zip