事务之spring事务管理

原生JDBC事务的弊端

  1. 如果需要操作多个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();
    	}
    }
    
  2. 为了解决上述问题,可以通过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
    }
    
  3. 架构图
    在这里插入图片描述

Spring事务管理核心API

  1. Spring事务管理高层抽象主要包括3个接口

    • PlatformTransactionManager:事务管理器真正用来进行事务管理对象
    • TransactionDefinition:事务定义信息(隔离、传播、超时、只读)
    • TransactionStatus:事务具体运行状态
  2. PlatformTransactionManager根据 TransactionDefinition 信息来进行事务管理, 在管理事务过程中,每个时间点都可以获取事务状态 (TransactionStatus )

    //返回当前活动的事务或创建一个新的事务
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
    		throws TransactionException;
    //根据给定事务的状态提交给定事务
    void commit(TransactionStatus status) throws TransactionException;
    //执行给定事务的回滚
    void rollback(TransactionStatus status) throws TransactionException;
    
  3. 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实现来管理事务,在一个事务跨越多个资源时必须使用
  4. 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;
    	}
    
    }
    
  5. TransactionStatus:代表当前事务的状态,也可以对当前事务进行控制

    public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
    
     //当前事务是否有保存点
    	boolean hasSavepoint();
    
    	//用于刷新底层会话中的修改到数据库,一般用于刷新如Hibernate/JPA的会话
    	@Override
    	void flush();
    
    }
    

传播行为

  1. 事务传播行为,并不是数据库内部支持特性,而是Spring 为了解决企业开发中实际事务管理问题而设计的

    隔离级别含义
    PROPAGATION_REQUIRED(默认值)支持当前事务,如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务
    PROPAGATION_SUPPORTS业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行
    PROPAGATION_MANDATORY业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出异常。
    PROPAGATION_REQUIRES_NEW不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行。原有事务和新事务的事务提交、回滚互不影响,两个事务是互补想干的独立事务
    PROPAGATION_NOT_SUPPORTED声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行
    PROPAGATION_NEVER业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出异常,只有业务方法没有关联到任何事务,才能正常执行
    PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。底层数据源必须基于JDBC3.0,需要支持保存点事务机制
  2. 归类

    • 支持当前事务,被调用方法和原来方法可以处于同一个事务中(REQUIRED 、SUPPORTS 、MANDATORY)
    • 不支持当前事务,被调用方法和原来方法 一定不会处于同一个事务(REQUIRES_NEW、NOT_SUPPORT 、NEVER)
    • NESTED 是最特殊的一种 ,嵌套事务执行 ,原理就是SavePoint 回滚点技术。嵌套事务,仍然使用是同一个事务 ,可以在事务执行过程中设置回滚点,如果被调用方法出错后,可以选择将程序回滚到回滚点 (只对DataSourceTransactionManager有效 )
  3. 常用的总结

    • REQUIRED: 两个操作,处于同一个事务中,要么都成功,要么都失败
    • REQUIRES_NEW : 两个操作分别处于两个不同事务,彼此之间不会互相影响。比如ATM机取完钱然后要打印凭条,如果打印凭条失败了,取钱的方法是否需要回滚(其实不需要回滚) 。
    • NESTED : 两个操作,处于同一个事务,但是内部采用 SavePoint机制,在一个操作出现问题时,回滚到保存点,继续操作

传播行为案例

  1. 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;
    	}
    }
    
  2. 解决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;
    	}
    }  
    
  3. 解决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事务管理方式

  1. 数据库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;
    

编程式事务管理

  1. 编写程序式的事务管理可以清楚的定义事务的边界,可以实现细粒度的事务控制,比如你可以通过程序代码来控制你的事务何时开始,何时结束等,与后面介绍的声明式事务管理相比,它可以实现细粒度的事务控制。在实际应用中很少使用,通过TransactionTemplate手动管理事务

案例准备

  1. 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
    
  2. 事务配置

    @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;
    	}
    }
    
  3. 业务操作代码

    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的事务管理

  1. 业务实现类

    @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;
    		}
    	}
    }
    
  2. 测试用例

    @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的编程事务管理

  1. 业务实现类,这里我们看一下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);
    		});
    	}
    }
    
  2. 测试用例

    @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);
    	}
    }
    

声明式事务管理

  1. 如果你并不需要细粒度的事务控制,你可以使用声明式事务,在Spring中,你只需要在Spring配置文件中做一些配置,即可将操作纳入到事务管理中,解除了和代码的耦合, 这是对应用代码影响最小的选择。当你不需要事务管理的时候,可以直接从Spring配置文件中移除该设置

基于注解的

  1. @Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该注解来覆盖类级别的定义。

  2. @Transactional注解可以被继承,即:在父类上声明了这个注解,则子类中的所有public方法也都是会开事务的。Spring 小组建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效

  3. @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;
    	}
    
  4. 默认情况下,如果在事务中抛出了未检查异常(继承自 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);
    }
    
  5. 属性信息

    name当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
    propagation事务的传播行为,默认值为 REQUIRED。
    isolation事务的隔离度,默认值采用 DEFAULT。
    timeout事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
    read-only指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
    rollback-for用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
    no-rollback- for抛出 no-rollback-for 指定的异常类型,不回滚事务。
  6. 在 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命名空间的

  1. 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的问题

  1. Spring Aop代理不支持内部调用。比如A方法里调用带@Transactional注解的B方法。可以通过在当前类注入自己的代理对象来解决自调用问题。也可以通过AopContext.currentProxy()来获取当前类的代理对象,但是这样会导致硬编码。

  2. 引入依赖(这里直接在spring源码中调试,所以依赖的是内部模块)

    compile(project(":spring-aop"))
    compile(project(":spring-tx"))
    compile(project(":spring-aspects"))
    compile("org.aspectj:aspectjweaver")
    
  3. 开启AspectJ支持

    @EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
    
  4. 增加agent

    在IDEA的启动类上增加JVM参数
    -javaagent:/Users/jannal/aspectj1.9/lib/aspectjweaver.jar
    如果通过jar执行
    java -jar app.jar -javaagent:/Users/jannal/aspectj1.9/lib/aspectjweaver.jar
    

常见问题

  1. 使用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>
    
  2. 终端输出很多**[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>
    

事务不生效

  1. 事务不生效的原因

    • 事务方法没有被Spring容器管理或者未配置事务管理器

    • 方法没有被public修饰

    • 同一个类的A没有添加注解,B方法添加了事务注解,A调用B,方法B的事务会失效

    • 默认情况下,抛出了检查异常

多个事务管理器

  1. 如果要在程序中使用多个事务管理器(主要是针对多数据源的情况),可以通过以下的方式实现,每个事务都会绑定各自的独立的数据源,进行各自的事务管理

    手动指定不同的事务管理器
    public class UserService {
    	@Transactional("transactionManager0")
    	public void delete(Long id){}
      @Transactional("transactionManager2")
    	public void delete(Long id){}
    }
    
  2. 以上方式不够优雅,可以自定义一个绑定到特定事务管理器的注解,然后直接使用这个自定义的注解进行特定数据源的事务管理(这相当于运用了组合注解)

    /**
     * 自定义一个绑定到特定事务管理器的注解
     *
     * @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){}
    }
    

完整的案例代码

  1. 项目完整代码https://github.com/jannal/transaction/tree/master/spring

  2. 配置代码

    @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;
    	}
    }
    
  3. 自定义注解

    /**
     * 自定义一个绑定到特定事务管理器的注解
     * @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 {
    }
    
  4. 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;
    
  5. 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
    
  6. 业务代码

    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);
    	}
    }
    
  7. 测试代码

    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));
    	}
    
    }
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值