文章目录
原生JDBC事务的弊端
-
如果需要操作多个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事务管理核心API
-
Spring事务管理高层抽象主要包括3个接口
- PlatformTransactionManager:事务管理器真正用来进行事务管理对象
- TransactionDefinition:事务定义信息(隔离、传播、超时、只读)
- TransactionStatus:事务具体运行状态
-
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接口实现
- org.springframework.jdbc.datasource.DataSourceTransactionManager:使用Spring JDBC或iBatis 进行持久化数据时使用
- org.springframework.orm.hibernate3.HibernateTransactionManager:使用Hibernate3.0版本进行持久化数据时使用
- org.springframework.orm.jpa.JpaTransactionManager:使用JPA进行持久化时使用
- org.springframework.jdo.JdoTransactionManager:当持久化机制是Jdo时使用
- org.springframework.transaction.jta.JtaTransactionManager:使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用
-
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 、SUPPORTS 、MANDATORY)
- 不支持当前事务,被调用方法和原来方法 一定不会处于同一个事务(REQUIRES_NEW、NOT_SUPPORT 、NEVER)
- NESTED 是最特殊的一种 ,嵌套事务执行 ,原理就是SavePoint 回滚点技术。嵌套事务,仍然使用是同一个事务 ,可以在事务执行过程中设置回滚点,如果被调用方法出错后,可以选择将程序回滚到回滚点 (只对DataSourceTransactionManager有效 )
-
常用的总结
- REQUIRED: 两个操作,处于同一个事务中,要么都成功,要么都失败
- REQUIRES_NEW : 两个操作分别处于两个不同事务,彼此之间不会互相影响。比如ATM机取完钱然后要打印凭条,如果打印凭条失败了,取钱的方法是否需要回滚(其实不需要回滚) 。
- NESTED : 两个操作,处于同一个事务,但是内部采用 SavePoint机制,在一个操作出现问题时,回滚到保存点,继续操作
传播行为案例
-
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; } }
Spring事务管理方式
-
数据库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;
编程式事务管理
- 编写程序式的事务管理可以清楚的定义事务的边界,可以实现细粒度的事务控制,比如你可以通过程序代码来控制你的事务何时开始,何时结束等,与后面介绍的声明式事务管理相比,它可以实现细粒度的事务控制。在实际应用中很少使用,通过TransactionTemplate手动管理事务
案例准备
-
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); }
基于底层基础API的事务管理
-
业务实现类
@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的编程事务管理
-
业务实现类,这里我们看一下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); } }
声明式事务管理
- 如果你并不需要细粒度的事务控制,你可以使用声明式事务,在Spring中,你只需要在Spring配置文件中做一些配置,即可将操作纳入到事务管理中,解除了和代码的耦合, 这是对应用代码影响最小的选择。当你不需要事务管理的时候,可以直接从Spring配置文件中移除该设置
基于注解的
-
@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; } }
基于tx命名空间的
-
Spring 2.x引入了
<tx>
命名空间,结合使用<aop>
命名空间。不需要针对每一个业务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>
基于AspectJ解决默认AOP的问题
-
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> <!-- <weaver options="-showWeaveInfo"/> --> <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)); } }