迁移到SpringBoot 05 - 事务管理

Spring项目中使用数据库的方式多种多样,注入MyBatis、JDBC、Hibernate等等都是支持的。之前的系统是基于MyBatis的。

关于如何Spring Boot和MyBatis网上有很多文章,可以参考。有时间我也会专门写一篇介绍集成的过程。本文讲的就是MyBatis移植到Spring Boot方面碰到的一些问题,主要是事务控制方面的。

1. Spring原始配置

在Spring中,之前是通过XML方式进行的事务配置。通过tx:advice定义了事务的属性;然后通过aop:config将事务织入业务代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!-- from the file 'context.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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.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">

<!-- this is the service object that we want to make transactional -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>

<!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true"/>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

<!-- ensure that the above transactional advice runs for any execution
of an operation defined by the FooService interface -->
<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>

<!-- don't forget the DataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>

<!-- similarly, don't forget the PlatformTransactionManager -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- other <bean/> definitions here -->

</beans>

2. 迁移到SpringBoot

迁移到SpringBoot还是倾向于使用基于Java的配置,因此最初的想法是完全跑起xml配置文件。但是在实践中发现了一些限制:<aop:config>标签无法完整准确的翻译成Java配置。

2.1 限制

通过上网搜索,发现解决方案并不完美:

1
2
3
4
5
The @Aspect model in AspectJ is designed for pure-Java configuration, and should be used in favor of <aop:config> when looking to create 100% java-based configuration using Spring's @Configuration model.
<aop:config> cannot properly be translated into the @Configuration model, largely because Java does not support method literals in the language. This leaves us resorting to String-based references to methods, which is not ideal.
So the approach that users should consider are:
1. Continue using <aop:config> by including the relevant XML snippet using @ImportResource
2. Convert any existing <aop:config> elmements to use @Aspect style.

上面推荐的方案有两种:

  1. 继续使用xml配置,然后通过 @ImportResource注解导入Java代码中;
  2. 转换<aop:config>配置成@Aspect注解。

2.2 最终方案

因为时间的原因,对@Aspect形式的配置没有做深入研究。另外从StackOverflow上查到的说法是,tx-advice并没有合适的Java配置方法,使用xml配置还是最方便的。只是xml配置通过@Configuration中的@ImportResource注解引入即可,这样还是比较优雅,比较符合Java配置的规范性的。因此最终方案是使用了部分xml配置,通过@ImportResource引入。

2.2.1 MasterDataSourceConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Configuration
public class MasterDataSourceConfig {
static final String PACKAGE = "com.eveus.iot.shadow.mapper";
static final String MAPPER_LOCATION = "classpath:mybatis/mapper/**/*.xml";

@Bean
@Primary
@ConfigurationProperties("spring.datasource")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}

@Bean(name = "masterDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return firstDataSourceProperties().initializeDataSourceBuilder().build();
}

@Bean(name = "masterTransactionManager")
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}


@Bean(name = "masterSqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDataSource") DataSource masterDataSource)
throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(masterDataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources(MasterDataSourceConfig.MAPPER_LOCATION));
return sessionFactory.getObject();
}
}

定义dataSource, transactionManager。

2.2.2 TransactionConfig

1
2
3
4
5
6
7
...
@Configuration
@Aspect
@ComponentScan("com.eveus.cloudauth")
@ImportResource("classpath:/spring/uid-tx.xml")
public class TransactionConfig {
}

引入xml配置。

2.2.3 uid-tx.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

<!-- AOP -->
<tx:advice id="merchantTransactionAdvice" transaction-manager="masterTransactionManager">
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="lock*" propagation="REQUIRED" />
<tx:method name="unlock*" propagation="REQUIRED" />

<tx:method name="select*" propagation="SUPPORTS" />
<tx:method name="list*" propagation="SUPPORTS" />

<tx:method name="*" propagation="SUPPORTS" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="merchantTransactionPointcut" expression="execution(* com.eveus.cloudauth.dal.mybatis.MerchantDalImpl.*(..))" />
<aop:advisor pointcut-ref="merchantTransactionPointcut" advice-ref="merchantTransactionAdvice" />
</aop:config>
...
</beans>

如上配置中:transactionManager是在Java配置中定义的。其他的和原先的xml配置没有区别。

附录A. 参考资料

热评文章