如果需要操作多个Dao,需要每次操作数据库都需要在Service层的方法里写开启连接、提交事务、回滚事务的模版代码,Service转账伪代码如下
public void transfer1(int sourceid, int targetid, double money)
{
Connection conn = null;
try
{
conn = JdbcUtils.getConnection();
//设置事务不自动提交
conn.setAutoCommit(false);
//将连接传入dao层
AccountDao dao = new AccountDao(conn);
Account a = dao.find(sourceid);
Account b = dao.find(targetid);
a.setMoney(a.getMoney() - money);
b.setMoney(b.getMoney() + money);
dao.update(a);
dao.update(b);
conn.commit();
}catch(Exception e){
if(conn!=null)
conn.rollback();
} finally
{
if (conn != null)
conn.close();
}
}
为了解决上述问题,可以通过AOP方式将上面的非业务代码抽取成框架层面的公共代码。并且将连接对象放在ThreadLocal中,无需通过构造方法或者其他方式传递连接对象,而且Service和dao在同一个线程下,Service可以控制事务的提交和回滚。虽然Spring对事务做了很好的处理,简化了我们很多模版代码,但是同样带来很多坑(主要还是很多开发不熟悉或者没有深入理解),导致出现各种诡异问题,甚至重大bug
使用AOP简化代码
public void transfer1(int sourceid, int targetid, double money)
{
从ThreadLocal中获取数据库连接对象conn
AccountDao dao = new AccountDao();
Account a = dao.find(sourceid);
Account b = dao.find(targetid);
a.setMoney(a.getMoney() - money);
b.setMoney(b.getMoney() + money);
dao.update(a); // update
dao.update(b);// update
}
架构图

Spring事务管理高层抽象主要包括3个接口
PlatformTransactionManager根据 TransactionDefinition 信息来进行事务管理, 在管理事务过程中,每个时间点都可以获取事务状态 (TransactionStatus )
//返回当前活动的事务或创建一个新的事务
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
//根据给定事务的状态提交给定事务
void commit(TransactionStatus status) throws TransactionException;
//执行给定事务的回滚
void rollback(TransactionStatus status) throws TransactionException;
spring没有直接管理事务,而是将管理事务的责任委托给JTA或相应的持久性机制所提供的某个特定平台的事务实现。Spring为不同的持久化框架提供了不同PlatformTransactionManager接口实现
TransactionDefinition接口
public interface TransactionDefinition {
//事务的7个传播行为
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
//事务的5个隔离级别
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1; // same as java.sql.Connection.TRANSACTION_READ_UNCOMMITTED;
int ISOLATION_READ_COMMITTED = 2; // same as java.sql.Connection.TRANSACTION_READ_COMMITTED;
int ISOLATION_REPEATABLE_READ = 4; // same as java.sql.Connection.TRANSACTION_REPEATABLE_READ;
int ISOLATION_SERIALIZABLE = 8; // same as java.sql.Connection.TRANSACTION_SERIALIZABLE;
//事务超时时间
int TIMEOUT_DEFAULT = -1;
//返回传播行为
default int getPropagationBehavior() {
return PROPAGATION_REQUIRED;
}
//返回隔离级别
default int getIsolationLevel() {
return ISOLATION_DEFAULT;
}
//返回超时时间
default int getTimeout() {
return TIMEOUT_DEFAULT;
}
//是否为只读事务
default boolean isReadOnly() {
return false;
}
//返回事务的名称
@Nullable
default String getName() {
return null;
}
static TransactionDefinition withDefaults() {
return StaticTransactionDefinition.INSTANCE;
}
}
TransactionStatus:代表当前事务的状态,也可以对当前事务进行控制
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
//当前事务是否有保存点
boolean hasSavepoint();
//用于刷新底层会话中的修改到数据库,一般用于刷新如Hibernate/JPA的会话
@Override
void flush();
}
事务传播行为,并不是数据库内部支持特性,而是Spring 为了解决企业开发中实际事务管理问题而设计的
| 隔离级别 | 含义 |
|---|---|
| PROPAGATION_REQUIRED(默认值) | 支持当前事务,如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务 |
| PROPAGATION_SUPPORTS | 业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行 |
| PROPAGATION_MANDATORY | 业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出异常。 |
| PROPAGATION_REQUIRES_NEW | 不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行。原有事务和新事务的事务提交、回滚互不影响,两个事务是互补想干的独立事务 |
| PROPAGATION_NOT_SUPPORTED | 声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行 |
| PROPAGATION_NEVER | 业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出异常,只有业务方法没有关联到任何事务,才能正常执行 |
| PROPAGATION_NESTED | 如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。底层数据源必须基于JDBC3.0,需要支持保存点事务机制 |
归类
常用的总结
REQUIRED调用REQUIRED:不出异常,事务不一定提交。调用transferRequired方法,虽然此方法本身没有抛出异常,但是事务回滚了。调用方出现了UnexpectedRollbackException异常,因为transferRequiredThrowRuntimeException方法标记了事务需要回滚,而且是REQUIRED,所以导致主方法也提交不了
@Service("propagationAccountService")
@Slf4j
public class PropagationAccountServiceImpl implements PropagationAccountService {
@Autowired
private PropagationAccountDao accountDao;
@Autowired
private PropagationAccountService propagationAccountService;
/**
* 抛出异常
* UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transferRequired(String account, BigDecimal money) {
accountDao.in(account, money);
try {
propagationAccountService.transferRequiredThrowRuntimeException(account, money);
} catch (Exception e) {
//虽然捕获了异常,但是因为没有开启新事务,而当前事务因为异常已经被标记为rollback了
log.error(e.getMessage(), e);
}
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transferRequiredThrowRuntimeException(String account, BigDecimal money) {
accountDao.in(account, money);
int d = 1 / 0;
}
}
解决REQUIRED调用REQUIRED方法一:使用手动方式设置回滚保存点
@Service("propagationAccountService")
@Slf4j
public class PropagationAccountServiceImpl implements PropagationAccountService {
@Autowired
private PropagationAccountDao accountDao;
@Autowired
private PropagationAccountService propagationAccountService;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transferRequiredSavePoint(String account, BigDecimal money) {
accountDao.in(account, money);
//只回滚以下异常
Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
try {
propagationAccountService.transferRequiredThrowRuntimeException("tom", money);
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
log.error(e.getMessage(), e);
}
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transferRequiredThrowRuntimeException(String account, BigDecimal money) {
accountDao.in(account, money);
int d = 1 / 0;
}
}
解决REQUIRED调用REQUIRES_NEW方法二:调用此方法时开启新的事务,并挂起当前事务
@Service("propagationAccountService")
@Slf4j
public class PropagationAccountServiceImpl implements PropagationAccountService {
@Autowired
private PropagationAccountDao accountDao;
@Autowired
private PropagationAccountService propagationAccountService;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transferRequired2(String account, BigDecimal money) {
accountDao.in(account, money);
try {
propagationAccountService.transferRequiredThrowRuntimeExceptionNew(account, money);
} catch (Exception e) {
//虽然捕获了异常,但是因为没有开启新事务,而当前事务因为异常已经被标记为rollback了
log.error(e.getMessage(), e);
}
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void transferRequiredThrowRuntimeExceptionNew(String account, BigDecimal money) {
accountDao.in(account, money);
int d = 1 / 0;
}
}
数据库SQL
CREATE DATABASE IF NOT EXISTS test_tx DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_bin;
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(50) COLLATE utf8_bin NOT NULL COMMENT '账户名',
`money` decimal(20,2) NOT NULL COMMENT '金额',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
BEGIN;
INSERT INTO `account` VALUES (1, 'jannal', 2000.00);
INSERT INTO `account` VALUES (2, 'tom', 500.00);
COMMIT;
Jdbc配置
jdbc.username=root
jdbc.password=root
jdbc.url=jdbc:mysql://127.0.0.1:3306/test_tx?useUnicode=true&autoReconnect=true&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&characterEncoding=utf8&&charaterSetResults=utf8&useSSL=false&serverTimezone=GMT%2B8
jdbc.driver=com.mysql.jdbc.Driver
事务配置
@Configuration
@PropertySource(value = {"classpath:jdbc.properties"})
@ComponentScan({"cn.jannal.tx.programmatic.account"})
public class DataSourceConfiguration {
@Value("${jdbc.username}")
private String jdbcUsername;
@Value("${jdbc.password}")
private String jdbcPassword;
@Value("${jdbc.driver}")
private String jdbcDriverClass;
@Value("${jdbc.url}")
private String jdbcUrl;
@Bean
public DataSource datasource() {
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setJdbcUrl(jdbcUrl);
hikariDataSource.setUsername(jdbcUsername);
hikariDataSource.setDriverClassName(jdbcDriverClass);
hikariDataSource.setPassword(jdbcPassword);
return hikariDataSource;
}
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
return dataSourceTransactionManager;
}
/**
* 配置事务管理器模板
*/
@Bean
public TransactionTemplate transactionTemplate(DataSourceTransactionManager transactionManager) {
TransactionTemplate transactionTemplate = new TransactionTemplate();
transactionTemplate.setTransactionManager(transactionManager);
return transactionTemplate;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource datasource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(datasource);
return jdbcTemplate;
}
}
业务操作代码
public interface AccountDao {
public int out(String outAccount, BigDecimal money);
public int in(String inAccount, BigDecimal money);
}
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int out(String outAccount, BigDecimal money) {
String sql = "update account set money= money - ? where name= ?";
return this.jdbcTemplate.update(sql, money, outAccount);
}
@Override
public int in(String inAccount, BigDecimal money) {
String sql = "update account set money=money + ? where name = ?";
return this.jdbcTemplate.update(sql, money, inAccount);
}
}
public interface AccountService {
public void transfer(final String outAccount, final String inAccount, final BigDecimal money, final boolean mockException);
}
业务实现类
@Service("accountService0")
public class AccountService0Impl implements AccountService {
@Autowired
private AccountDao accountDao;
@Autowired
private PlatformTransactionManager txManager;
@Override
public void transfer(String outAccount, String inAccount, BigDecimal money, boolean mockException) {
//定义事务
DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
defaultTransactionDefinition.setPropagationBehaviorName("PROPAGATION_REQUIRED");
//启动事务
TransactionStatus txStatus = txManager.getTransaction(defaultTransactionDefinition);
try {
accountDao.out(outAccount, money);
if (mockException) {
int d = 1 / 0;
}
accountDao.in(inAccount, money);
txManager.commit(txStatus);
} catch (Throwable e) {
txManager.rollback(txStatus);
throw e;
}
}
}
测试用例
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DataSourceConfiguration.class)
public class TestMain {
@Resource(name = "accountService")
private AccountService accountService;
@Test
public void testTransferNoException() {
accountService.transfer("jannal", "tom", BigDecimal.valueOf(1000), false);
}
@Test(expected = ArithmeticException.class)
public void testTransferHasException() {
accountService.transfer("jannal", "tom", BigDecimal.valueOf(1000), true);
}
}
业务实现类,这里我们看一下TransactionTemplate的源码
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Autowired
private TransactionTemplate transactionTemplate;
@Override
public void transfer(final String outAccount, final String inAccount, final BigDecimal money, final boolean mockException) {
//TransactionCallbackWithoutResult是没有返回值的
//TransactionCallback是有返回值的
transactionTemplate.executeWithoutResult(transactionStatus -> {
accountDao.out(outAccount, money);
if (mockException) {
int d = 1 / 0;
}
accountDao.in(inAccount, money);
});
}
}
测试用例
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DataSourceConfiguration.class)
public class TestMain {
@Resource(name = "accountService0")
private AccountService accountService;
@Test
public void testTransferNoException() {
accountService.transfer("jannal", "tom", BigDecimal.valueOf(1000), false);
}
@Test(expected = ArithmeticException.class)
public void testTransferHasException() {
accountService.transfer("jannal", "tom", BigDecimal.valueOf(1000), true);
}
}
@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该注解来覆盖类级别的定义。
@Transactional注解可以被继承,即:在父类上声明了这个注解,则子类中的所有public方法也都是会开事务的。Spring 小组建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效
@Transactional注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的(可以通过AspectJ解决)。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
AbstractFallbackTransactionAttributeSource类
这个方法会检查目标方法的修饰符是不是 public,若不是 public,就不会获取@Transactional 的属性配置信息,最终会造成不会用 TransactionInterceptor 来拦截该目标方法进行事务管理
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
// First try is the method in the target class.
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if (txAttr != null) {
return txAttr;
}
// Second try is the transaction attribute on the target class.
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
if (specificMethod != method) {
// Fallback is to look at the original method.
txAttr = findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
// Last fallback is the class of the original method.
txAttr = findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}
return null;
}
默认情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常)或者 Error,则 Spring 将回滚事务;除此之外,Spring 不会回滚事务。若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。
RollbackRuleAttribute 源码
private int getDepth(Class<?> exceptionClass, int depth) {
if (exceptionClass.getName().contains(this.exceptionName)) {
// Found it!
return depth;
}
// If we've gone as far as we can go and haven't found it...
if (exceptionClass == Throwable.class) {
return -1;
}
return getDepth(exceptionClass.getSuperclass(), depth + 1);
}
属性信息
| name | 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。 |
|---|---|
| propagation | 事务的传播行为,默认值为 REQUIRED。 |
| isolation | 事务的隔离度,默认值采用 DEFAULT。 |
| timeout | 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
| read-only | 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 |
| rollback-for | 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。 |
| no-rollback- for | 抛出 no-rollback-for 指定的异常类型,不回滚事务。 |
在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题(可以通过AspectJ解决)。若同一类中的其他没有@Transactional 注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
//外部调用此方法事务不会回滚
@Override
public void transferNoTransactionalInvokeTransactionalException(String outAccount, String inAccount, BigDecimal money) {
transferThrowRuntimeExcetion(outAccount, inAccount, money);
}
@Override
@Transactional
public void transferThrowRuntimeExcetion(String outAccount, String inAccount, BigDecimal money) {
accountDao.out(outAccount, money);
accountDao.in(inAccount, money);
int d = 1 / 0;
}
}
Spring 2.x引入了命名空间,结合使用命名空间。不需要针对每一个业务service建立一个代理对象了
<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">
<!-- 配置事务增强,通过事务通知的方式实现事务 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 事务管理属性配置,配置哪些方法要使用什么样的事务配置,没有匹配到的方法不会为其管理事务 -->
<tx:attributes>
<!-- 可选属性配置
name:方法名称,将匹配的方法注入事务管理,可用通配符
propagation:事务传播行为
isolation:事务隔离级别,默认为DEFAULT
read-only:是否只读,默认为 false,表示不是只读
timeout:事务超时时间,单位为秒,默认 -1,表示事务超时将依赖于底层事务系统
rollback-for:需要触发回滚的异常定义,多个以逗号","分割,默认任何 RuntimeException 都将导致事务回滚,而任何 Checked Exception 将不导致事务回滚
no-rollback-for:不被触发进行回滚的 Exception(s)。多个以逗号","分割
-->
<!-- 设置进行事务操作的方法匹配规则 -->
<tx:method name="insert*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
<tx:method name="transferThrow*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
<tx:method name="transferNoException" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
<tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 切面配置 -->
<aop:config>
<!-- 切入点配置: cn.jannal.tx.declarative.tx.account.service 包-->
<aop:pointcut expression="execution(* cn.jannal.tx.declarative.tx.*.service..*(..))" id="txPonitcut"/>
<!-- 通知与切入点关联 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPonitcut"/>
</aop:config>
</beans>
Spring Aop代理不支持内部调用。比如A方法里调用带@Transactional注解的B方法。可以通过在当前类注入自己的代理对象来解决自调用问题。也可以通过AopContext.currentProxy()来获取当前类的代理对象,但是这样会导致硬编码。
引入依赖(这里直接在spring源码中调试,所以依赖的是内部模块)
compile(project(":spring-aop"))
compile(project(":spring-tx"))
compile(project(":spring-aspects"))
compile("org.aspectj:aspectjweaver")
开启AspectJ支持
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
增加agent
在IDEA的启动类上增加JVM参数
-javaagent:/Users/jannal/aspectj1.9/lib/aspectjweaver.jar
如果通过jar执行
java -jar app.jar -javaagent:/Users/jannal/aspectj1.9/lib/aspectjweaver.jar
使用AspectJ的时候需要指定META-INF/aop.xml。因为spring-aspects模块里已经有这个文件了
<aspectj>
<aspects>
<aspect name="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"/>
<aspect name="org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect"/>
<aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect"/>
<aspect name="org.springframework.transaction.aspectj.JtaAnnotationTransactionAspect"/>
<aspect name="org.springframework.cache.aspectj.AnnotationCacheAspect"/>
<aspect name="org.springframework.cache.aspectj.JCacheCacheAspect"/>
aspects>
aspectj>
终端输出很多**[Xlint:cantFindType],怎么去掉? 在项目resoures目录新建一个META-INF/aop.xml。将目标类限定在自己的项目包下。-showWeaveInfo**表示显示织入目标类的信息,便于查看
<aspectj>
<aspects>
<aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect"/>
<aspect name="org.springframework.transaction.aspectj.JtaAnnotationTransactionAspect"/>
aspects>
<weaver options="-showWeaveInfo -XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler">
<include within="cn.jannal.tx.declarative.annotation.account.service.*"/>
weaver>
aspectj>
或者直接将Xlint去掉(不建议)
<aspectj>
<weaver options="-showWeaveInfo -Xlint:ignore" >weaver>
aspectj>
事务不生效的原因
事务方法没有被Spring容器管理或者未配置事务管理器
方法没有被public修饰
同一个类的A没有添加注解,B方法添加了事务注解,A调用B,方法B的事务会失效
默认情况下,抛出了检查异常
如果要在程序中使用多个事务管理器(主要是针对多数据源的情况),可以通过以下的方式实现,每个事务都会绑定各自的独立的数据源,进行各自的事务管理
手动指定不同的事务管理器
public class UserService {
@Transactional("transactionManager0")
public void delete(Long id){}
@Transactional("transactionManager2")
public void delete(Long id){}
}
以上方式不够优雅,可以自定义一个绑定到特定事务管理器的注解,然后直接使用这个自定义的注解进行特定数据源的事务管理(这相当于运用了组合注解)
/**
* 自定义一个绑定到特定事务管理器的注解
*
* @Transactional默认的事务管理器名称为transactionManager
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Transactional("transactionManager0")
public @interface Transactional_0 {
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Transactional("transactionManager1")
public @interface Transactional_1 {
}
public class UserService {
@Transactional_0
public void delete(Long id){}
@Transactional_1
public void delete(Long id){}
}
项目完整代码https://github.com/jannal/transaction/tree/master/spring
配置代码
@Configuration
@PropertySource(value = {"classpath:jdbc.properties"})
@ComponentScan({"cn.jannal.tx.txmanager.account"})
@EnableTransactionManagement
public class MulitManagerDataSourceConfiguration {
@Bean(name = "datasource0")
public HikariDataSource datasource0(
@Value("${jdbc0.username}") String jdbcUsername,
@Value("${jdbc0.password}") String jdbcPassword,
@Value("${jdbc0.driver}") String jdbcDriverClass,
@Value("${jdbc0.url}") String jdbcUrl) {
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setPoolName("datasource0");
hikariDataSource.setJdbcUrl(jdbcUrl);
hikariDataSource.setUsername(jdbcUsername);
hikariDataSource.setDriverClassName(jdbcDriverClass);
hikariDataSource.setPassword(jdbcPassword);
return hikariDataSource;
}
@Bean(name = "datasource1")
public HikariDataSource datasource1(
@Value("${jdbc1.username}") String jdbcUsername,
@Value("${jdbc1.password}") String jdbcPassword,
@Value("${jdbc1.driver}") String jdbcDriverClass,
@Value("${jdbc1.url}") String jdbcUrl) {
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setPoolName("datasource1");
hikariDataSource.setJdbcUrl(jdbcUrl);
hikariDataSource.setUsername(jdbcUsername);
hikariDataSource.setDriverClassName(jdbcDriverClass);
hikariDataSource.setPassword(jdbcPassword);
return hikariDataSource;
}
@Bean(name = "transactionManager0")
public DataSourceTransactionManager transactionManager0(@Qualifier(value = "datasource0") DataSource dataSource0) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource0);
return dataSourceTransactionManager;
}
@Bean(name = "jdbcTemplate0")
public JdbcTemplate jdbcTemplate0(@Qualifier(value = "datasource0") DataSource datasource0) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(datasource0);
return jdbcTemplate;
}
@Bean(name = "jdbcTemplate1")
public JdbcTemplate jdbcTemplate1(@Qualifier(value = "datasource1") DataSource datasource1) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(datasource1);
return jdbcTemplate;
}
}
自定义注解
/**
* 自定义一个绑定到特定事务管理器的注解
* @Transactional默认的事务管理器名称为transactionManager
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Transactional("transactionManager0")
public @interface Transactional_0 {
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Transactional("transactionManager1")
public @interface Transactional_1 {
}
sql语句
1.创建test_tx和test_tx1两个数据库
2. 两个数据库都执行如下SQL
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(50) COLLATE utf8_bin NOT NULL COMMENT '账户名',
`money` decimal(20,2) NOT NULL COMMENT '金额',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
BEGIN;
INSERT INTO `account` VALUES (1, 'jannal', 2000.00);
INSERT INTO `account` VALUES (2, 'tom', 500.00);
COMMIT;
Jdbc.properties配置
jdbc0.username=root
jdbc0.password=root
jdbc0.url=jdbc:mysql://127.0.0.1:3306/test_tx?useUnicode=true&autoReconnect=true&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&characterEncoding=utf8&&charaterSetResults=utf8&useSSL=false&serverTimezone=GMT%2B8
jdbc0.driver=com.mysql.jdbc.Driver
jdbc1.username=root
jdbc1.password=root
jdbc1.url=jdbc:mysql://127.0.0.1:3306/test_tx1?useUnicode=true&autoReconnect=true&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&characterEncoding=utf8&&charaterSetResults=utf8&useSSL=false&serverTimezone=GMT%2B8
jdbc1.driver=com.mysql.jdbc.Driver
业务代码
public interface MulitTxManagerAccountDao {
public int out(String outAccount, BigDecimal money, JdbcTemplate jdbcTemplate);
public int in(String inAccount, BigDecimal money, JdbcTemplate jdbcTemplate);
}
@Repository
public class MulitTxManagerAccountDaoImpl implements MulitTxManagerAccountDao {
@Override
public int out(String outAccount, BigDecimal money, JdbcTemplate jdbcTemplate) {
String sql = "update account set money= money - ? where name= ?";
return jdbcTemplate.update(sql, money, outAccount);
}
@Override
public int in(String inAccount, BigDecimal money, JdbcTemplate jdbcTemplate) {
String sql = "update account set money=money + ? where name = ?";
return jdbcTemplate.update(sql, money, inAccount);
}
}
public interface MulitTxManagerAccountService {
public void transferNoException0(final String outAccount, final String inAccount, final BigDecimal money);
public void transferNoException1(final String outAccount, final String inAccount, final BigDecimal money);
}
@Service("mulitTxManagerAccountService")
public class MulitTxManagerAccountServiceImpl implements MulitTxManagerAccountService {
@Autowired
private MulitTxManagerAccountDao accountDao;
@Resource(name = "jdbcTemplate0")
private JdbcTemplate jdbcTemplate0;
@Resource(name = "jdbcTemplate1")
private JdbcTemplate jdbcTemplate1;
private void transfer0(final String outAccount, final String inAccount, final BigDecimal money, JdbcTemplate jdbcTemplate) {
accountDao.out(outAccount, money, jdbcTemplate);
accountDao.in(inAccount, money, jdbcTemplate);
}
@Override
@Transactional_0
public void transferNoException0(String outAccount, String inAccount, BigDecimal money) {
transfer0(outAccount, inAccount, money, jdbcTemplate0);
}
@Override
@Transactional_1
public void transferNoException1(String outAccount, String inAccount, BigDecimal money) {
transfer0(outAccount, inAccount, money, jdbcTemplate1);
}
}
测试代码
public class MulitMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MulitManagerDataSourceConfiguration.class);
MulitTxManagerAccountService accountService = (MulitTxManagerAccountService) context.getBean("mulitTxManagerAccountService");
transferNoException0(accountService);
transferNoException1(accountService);
}
public static void transferNoException0(MulitTxManagerAccountService accountService) {
accountService.transferNoException0("jannal", "tom", BigDecimal.valueOf(1000));
}
public static void transferNoException1(MulitTxManagerAccountService accountService) {
accountService.transferNoException1("jannal", "tom", BigDecimal.valueOf(1000));
}
}