Spring事务管理


Spring的事务管理

目录
  • Spring的事务管理
    • 1、前言
    • 2、Spring事务管理
      • 2.1、Spring操作事务管理重要的类和接口
        • TransactionDefinition
          • 事务的隔离级别:
          • 事务的传播行为:
          • 事务运行的超时时间:
          • 是否只读:
        • TransactionStatus
        • PlatformTransactionManager
      • 小结
    • 3、SpringAOP来进行转账案例演示
      • 传统方式演示
      • 使用动态代理
      • SpringAOP演示
      • Spring编程式事务
        • xml版本
    • 4、声明式事务
      • 4.1、准备工作,不考虑事务的操作
      • 4.2、声明式事务
      • 4.3、基于XML的声明式事务控制
          • 需要明确的事项
            • 实现步骤
          • 配置详解
            • aop:config:切面配置
            • tx:advice:事务通知配置
      • 4.4、基于注解版的声明式事务控制
            • 需求描述
            • 实现步骤
            • 功能实现
          • 配置详解
            • 注解@Transactional
            • 开启事务的注解驱动

1、前言

SpringAOP的中的概念和使用已经完成,下面来看下Spring事务基于SpringAOP基本使用。

正如在前一章节演示的一样,在目标方法执行之前开启事务,在目标方法正常执行之后提交事务或者是在切入点方法执行异常之后回滚异常。

2、Spring事务管理

下面来介绍一下Spring中的事务管理

事务作用:在对数据库中来做多个操作的时候,保证的是原子性。需要保证的是当前事务和其他事务之间的关系。

事务管理:按照给定的事务规则,来执行提交事务或者是回滚事务操作。

  • 给定的事务规则:在Spring中,用TransactionDefinition这个类来进行定义;
  • 按照给定的事务规则,来执行提交事务或者是回滚事务操作:在Spring中,用PlatformTransactionManager来进行完成;
  • TransactionStatus用来表示的是一个运行着的事务状态;

2.1、Spring操作事务管理重要的类和接口

TransactionDefinition

指定的事务规则,Spring可以针对事务来进行设置属性。

方法 参数 返回值 说明
getIsolationLevel() int 获取事务的隔离级别
getPropogationBehavior() int 获取事务的传播行为
getTimeout() int 获取超时时间
isReadOnly() boolean 是否只读的事务
事务的隔离级别:
  • ISOLATION_DEFAULT:默认事务隔离级别
    • MySql默认隔离级别:repeatable read
    • Oracle默认隔离级别:read committed
  • ISOLATION_READ_UNCOMMITTED:读未提交--存在脏读、不可重复读、幻读
  • ISOLATION_READ_COMMITTED:读已提交--存在不可重复读、幻读
  • ISOLATION_REPEATABLE_READ:重复读--存在幻读
  • ISOLATION_SERIALIZABLE:串行化--没有并发问题
事务的传播行为:

用于解决业务方法调用业务方法时,事务的统一性问题的

以下三个,是要当前事务的

  • PROPAGATION_REQUIRED需要有事务。默认
    • 如果有事务,就使用这个事务
    • 如果没有事务,就创建事务。
  • PROPAGATION_SUPPORTS:支持事务
    • 如果有事务,就使用当前事务,
    • 如果没有事务,就以非事务方式执行(没有事务)
  • PROPAGATION_MANDATORY:强制的
    • 如果有事务,就使用当前事务
    • 如果没有事务,就抛异常

以下三个,是不要当前事务的

  • PROPAGATION_REQUIRES_NEW:新建的
    • 如果有事务,就把事务挂起,再新建事务
    • 如果没有事务,新建事务
  • PROPAGATION_NOT_SUPPORTED:不支持的
    • 如果有事务,就把事务挂起,以非事务方式执行
    • 如果没有事务,就以非事务方式执行
  • PROPAGATION_NEVER:非事务的
    • 如果有事务,就抛异常
    • 如果没有事务,就以非事务方式执行

最后一个,是特殊的

  • PROPAGATION_NESTED:嵌套的
    • 如果有事务,就在事务里再嵌套一个事务执行
    • 如果没有事务,就是类似REQUIRED的操作
事务运行的超时时间:

超时后事务自动回滚

  • 默认值-1,表示没有超时限制,也就是永不超时
  • 如果有,可以以秒为单位进行设置
是否只读:
  • 如果设置为只读,那么方法只能查询,不能增删改
  • 通常是查询方法设置为只读

TransactionStatus

提供了查询事务具体运行状态的方法,常用方法如下:

方法 返回值 说明
hasSavePoint() boolean 事务是否有回滚点
isCompleted() boolean 事务是否已经完成
isNewTransaction() boolean 是否是新事务
isRollbackOnly() boolean 事务是否是 要回滚的状态

PlatformTransactionManager

PlatformTransactionManager是Spring提供的事务管理器接口,它提供了我们常用的操作事务的方法:开启事务、提交事务、回滚事务等

因为Spring可以整合很多不同的DAO层的操作数据库的框架,所以设置了这样的一个接口,来适配不同的DAO层的技术实现。如:

  • dao层是jdbcTemplate或Mybatis时,实现类是:DataSourceTransactionManager
  • dao层是Hibernate时,实现类是:HibernateTransactionManager
方法 返回值 说明
getTransaction(TransactionDefinition td) TransactionStatus 开启事务,并得到事务状态
commit(TransactionStatus status) void 提交事务
rollback(TransactionStatus status) void 回滚事务

小结

事务管理器是按照指定的事务规则,来进行事务的提交和回滚操作。

所以现在已经需要两个组件:事务管理器和事务定义。而事务操作是和当前的数据库连接是有关系的,所以事务管理需要从数据库连接池中获取得到一个连接,然后针对的是当前的一条数据库连接来进行操作的,操作完成之后,需要将连接返回给数据库连接池中去。

3、SpringAOP来进行转账案例演示

需要分析:Tom给Jerry进行转账(简单模板)

思路分析:

  • 1、从数据库连接池中来获取得到连接;
  • 2、将数据库的自动提交方式设置为手动提交;
  • 3、找到用户Tom和Jerry;
  • 4、开始进行转账;
  • 5、转账执行成功,事务提交;转账执行失败,将事务进行回滚;
  • 6、将连接归还给数据库连接池;

注意:从获取得到连接的到归还到数据库连接池中这一阶段,使用的都是同一个数据库连接,因为事务是和当前连接进行绑定的。

传统方式演示

自定义事务管理器

直接查看关键代码:

@Component
public class TransactionManager {


    private static final ThreadLocal tl = new ThreadLocal<>();

    //需要注入一个连接池
    @Autowired
    private DataSource dataSource;

    /**
     * 开启事务
     */
    public void startTransaction() throws Exception{
        //1.创建连接对象
        Connection connection = dataSource.getConnection();
        //2.开启事务
        connection.setAutoCommit(false);
        //3.使用ThreadLocal对象,把连接绑定到当前线程上
        tl.set(connection);
    }

    /**
     * 从当前线程上获取绑定的那个连接对象
     */
    public Connection getConnection(){
        return tl.get();
    }

    /**
     * 提交事务并关闭连接
     */
    public void commitAndClose(){
        try {
            //从当前线程上获取连接
            Connection connection = tl.get();
            //提交事务
            connection.commit();
            //关闭连接
            connection.close();
            //把连接从当前线程上清理掉
            tl.remove();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 回滚事务并关闭连接
     */
    public void rollbackAndClose(){
        try {
            //从当前线程上获取连接
            Connection connection = tl.get();
            //回滚事务
            connection.rollback();
            //关闭连接
            connection.close();
            //把连接从当前线程上清理掉
            tl.remove();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

然后查看一下转账代码:

@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Autowired
    private TransactionManager txManager;

    @Override
    public void transfer(String out, String in, Float money) {
        try {
          	// 开启事务
            txManager.startTransaction();

            Account outAccount = accountDao.findByName(out);
            Account inAccount = accountDao.findByName(in);

            outAccount.setMoney(outAccount.getMoney() - money);
            inAccount.setMoney(inAccount.getMoney() + money);

            accountDao.edit(outAccount);
            int i = 1/0;
            accountDao.edit(inAccount);
		    // 正常执行之后,回来提交代码
            txManager.commitAndClose();
        } catch (Exception e) {
            e.printStackTrace();
          	// 执行失败之后会来进行回滚
            txManager.rollbackAndClose();
        }
    }
}

使用动态代理

@Component("accountServiceProxyFactory")
public class AccountServiceProxyFactory {

    @Autowired
    private AccountService accountService;

    @Autowired
    private TransactionManager txManager;

    public AccountService createProxy(){
        return (AccountService) Proxy.newProxyInstance(
                accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object result = null;

                        try {
                            //开启事务
                            txManager.startTransaction();
                            //调用目标对象的方法:执行转账
                            result = method.invoke(accountService, args);
                            //提交事务
                            txManager.commitAndClose();
                        } catch (Exception e) {
                            e.printStackTrace();
                            //回滚事务
                            txManager.rollbackAndClose();
                        }
                        return result;
                    }
                }
        );
    }
}

SpringAOP演示

Spring编程式事务

xml版本

在xml中来进行配置:

<?xml version="1.0" encoding="UTF-8"?>


    

    
    
        
        
        
        
        
        
        
        
    
    
    
        
    
    
    
        
        
        
        
    
    
    
        
    

然后看下核心操作:

@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private TransactionDefinition txDefinition;
    @Autowired
    private PlatformTransactionManager txManager;

    @Override
    public void transfer(String from, String to, Float money) {
        //根据事务规则开启一个事务,得到事务状态。那么也就意味着可以获取得到事务状态也可以来进行操作。
        TransactionStatus txStatus = txManager.getTransaction(txDefinition);
        try {
            //操作dao
            Account fromAccount = accountDao.findByName(from);
            Account toAccount = accountDao.findByName(to);

            fromAccount.setMoney(fromAccount.getMoney() - money);
            toAccount.setMoney(toAccount.getMoney() + money);

            accountDao.edit(fromAccount);

            //int i = 1/0;

            accountDao.edit(toAccount);

            //提交事务
            txManager.commit(txStatus);
        } catch (Exception e) {
            //回滚事务
            txManager.rollback(txStatus);
            e.printStackTrace();
        }
    }
}

4、声明式事务

如果说是简单的操作,也就是说占用数据库连接的时间不是很长,可以来使用声明式事务。但是对于复杂操作来说,可能需要长时间占据数据库连接,那么这种情况下就不建议来使用声明式事务。

4.1、准备工作,不考虑事务的操作

引入依赖:


    
    
        mysql
        mysql-connector-java
        5.1.47
    
    
    
        c3p0
        c3p0
        0.9.1.2
    
    
    
        org.springframework
        spring-context
        5.0.2.RELEASE
    
    
    
        org.springframework
        spring-jdbc
        5.0.2.RELEASE
    
    
    
        org.aspectj
        aspectjweaver
        1.8.9
    
    
    
        org.springframework
        spring-test
        5.0.2.RELEASE
    
    
    
        junit
        junit
        4.12
    

创建javabean

public class Account {
    private Integer id;
    private String name;
    private Double money;

	//get/set...
    //toString...
}

创建接口和实现:

public interface AccountDao {
    void edit(Account account) throws SQLException;
    Account findByName(String name) throws SQLException;
}


@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void edit(Account account) throws SQLException {
        jdbcTemplate.update("update account set name=?, money=? where id=?", account.getName(), account.getMoney(), account.getId());
    }

    @Override
    public Account findByName(String name) throws SQLException {
        return jdbcTemplate.queryForObject("select * from account where name = ?", new BeanPropertyRowMapper<>(Account.class), name);
    }
}

public interface AccountService {
    void transfer(String from, String to, Float money) throws SQLException;
}

@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Override
    public void transfer(String from, String to, Float money) throws SQLException {
        //1.查询帐号信息
        Account fromAccount = accountDao.findByName(from);
        Account toAccount = accountDao.findByName(to);
        //2.修改帐号金额
        fromAccount.setMoney(fromAccount.getMoney() - money);
        toAccount.setMoney(toAccount.getMoney() + money);
        //3.把数据更新到数据库
        accountDao.edit(fromAccount);
        //int i = 1/0;
        accountDao.edit(toAccount);
    }
}

配置xml

<?xml version="1.0" encoding="UTF-8"?>


    
    

    
    
        
        
        
        
    

    
    
        
    

来进行测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TransferTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void transfer() throws SQLException {
        accountService.transfer("tom", "jerry", 1000f);
    }
}

4.2、声明式事务

概念:声明式事务控制,是采用声明的方式进行事务管理。所谓的声明,指的就是在配置文件中进行配置。通过声明式(配置)的方式来处理事务,代替编码式事务控制

作用:事务管理不入侵开发的组件,松耦合,业务逻辑代码中,没有事务的代码,甚至不会意识到正在事务当中。事实上也应该如此,业务逻辑代码只处理业务功能,事务控制是属于系统层面的服务;如果想要更改事务,只需要在配置文件中重新配置即可;能以模板的方式使用,Spring的声明式事务以AOP为基础,但是几乎是固定的配置模板,即使不懂AOP,也可以配置实现事务管理;在不需要事务管理的时候,只需要在配置文件中进行修改,即可把事务管理移除掉,而不需要修改源码,方便维护

4.3、基于XML的声明式事务控制

需要明确的事项
  • 谁是目标类?
  • 谁是切入点?
  • 谁是通知?
  • dao层技术是JdbcTemplate,使用DataSourceTransactionManager

需求描述:通过Spring的xml配置,对银行转账功能,进行事务控制

实现步骤
  • 只需要修改applicationContext.xml即可:
    1. 在配置文件中增加aop和tx的名称空间
    2. 配置事务的通知(增强)
    3. 配置切面,把事务通知织入到转账方法中
<?xml version="1.0" encoding="UTF-8"?>


    
    

    
    
        
        
        
        
    

    
    
        
    

    
    
        
    
    
    
        
            
        
    
    
    
        
    

配置详解
aop:config:切面配置

    

  • aop:config:aop提供的用于配置切面的标签
  • aop:advisor:Spring提供的专门用于配置事务的,作用类似于aop:aspect
    • advice-ref:要引入的通知配置,必须要引用所配置的事务通知
    • pointcut:切入点表达式
tx:advice:事务通知配置

    
        
            
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    

  • tx:advice

    • id属性:唯一标识

    • transaction-manager属性:配置一个事务管理器,即PlatformTransactionManager的实现类对象

      类似于我们的自己编写的事务管理器,里边提供了事务管理的方法,例如:提交、回滚事务的方法等等

  • tx:attributes:在标签内部设置事务的属性信息(事务定义信息,TransactionDefinition)

  • tx:method:要进行事务控制的方法配置,表示 要对哪些方法,进行什么样的事务控制

    • name属性:要进行事务控制方法名称,可以使用通配符*
    • isolation属性:事务的隔离级别设置
    • propagation属性:事务传播特性
    • read-only属性:是否只读
    • timeout属性:超时时间。默认-1表示不限制,如果设置的话,单位是秒

4.4、基于注解版的声明式事务控制

需求描述
  • 通过Spring的注解配置,对银行转账功能,进行事务控制
实现步骤
  1. 在需要事务控制的方法/类上增加注解@Transactional
  2. 在配置文件applicationContext.xml中修改配置
    • 配置事务管理器
    • 开启事务的注解驱动
功能实现
  • 修改银行转账的Service类:AccountServiceImpl
public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    @Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, readOnly = false)
    public void transfer(String from, String to, Double money) {
        //1.查询帐号信息
        Account fromAccount = accountDao.findByName(from);
        Account toAccount = accountDao.findByName(to);
        //2.修改帐号金额
        fromAccount.setMoney(fromAccount.getMoney() - money);
        toAccount.setMoney(toAccount.getMoney() + money);
        //3.把数据更新到数据库
        accountDao.edit(fromAccount);
        //int i = 1/0;
        accountDao.edit(toAccount);
    }
}

修改配置文件:

<?xml version="1.0" encoding="UTF-8"?>


    
    

    
    
        
        
        
        
    

    
    
        
    

    
    
        
    
    
    

配置详解
注解@Transactional
  • 加在 需要进行事务控制的方法/类上,用于代替xml配置中的tx:advice和事务切面的aop:config
  • isolation属性:设置事务的隔离级别,从枚举Isolation中取值
  • propagation属性:设置事务的传播特性,从枚举Propagation中取值
  • readOnly属性:设置是否是只读的
  • timeout属性:设置超时时间,单位秒。-1表示不限制
开启事务的注解驱动

XML方式

  • 使用注解进行事务管理,必须要在applicationContext.xml中开启 事务的注解驱动,否则无效




纯注解方式

  • 如果是纯注解,开启事务的注解驱动,需要在核心配置类上增加注解:@EnableTransactionManagement
  • 配置示例
@Configuration
@ComponentScan("com.guang")
@EnableTransactionManagement //自动byType注入事务管理器
public class AppConfig{
    
    //连接池
    @Bean
    public DataSource dataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql:///spring96");
        dataSource.setUser("root");
        dataSource.setPassword("root");
        return dataSource;
    }

    //JdbcTemplate
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }
    
    //事务管理器
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}