Spring 事务管理

一、基本概念

  • 在JavaEE企业级开发的应用领域,为了保证数据的完整性和一致性,必须引入数据库事务的概念
  • 事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行,要么都不执行。
  • 事务四个特性(ACID)
    • 原子性(atomicity):“原子”的本意是“不可再分”,要么都执行,要么都不执行。
    • 一致性(consistency):“一致”指的是数据的一致,一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将数据恢复到事务执行之前的状态,这就是回滚
    • 隔离性(isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰。
    • 持久性(durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来。通常情况下,事务对数据的修改应该被写入到持久化存储器中。

二、Spring 事务管理

2.1 编程式事务

  • 使用原生的JDBC API进行事务管理
    • 获取数据库连接Connection对象
    • 取消事务的自动提交
    • ]执行操作
    • 正常完成操作时手动提交事务
    • 执行失败时回滚事务
    • 关闭相关资源
  • 评价:使用原生的JDBC API实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余。

2.2 声明式事务

  • 大多数情况下声明式事务比编程式事务管理更好:它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
  • 事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。
  • Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制。
  • Spring既支持编程式事务管理,也支持声明式的事务管理

2.3 事务管理器

  • Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
  • 事务管理器的主要实现
    • DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取。
    • JtaTransactionManager :在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理
    • HibernateTransactionManager:用Hibernate框架存取数据库

三、事务操作

  • 事务一般添加到JavaEE三层结构里面Service层(业务逻辑层)

3.1 基于注解的配置

3.1.1 数据库

  • 建立数据库
    USE spring;
    DROP TABLE IF EXISTS account;
    CREATE TABLE account(
    	id 	INT 		PRIMARY KEY AUTO_INCREMENT,
    	`name`	VARCHAR(30) 	DEFAULT NULL,
    	money 	DECIMAL		NOT NULL
    );
    
    INSERT INTO 
    account(`name`, money)
    VALUES
    ("Mary", 1000),
    ("Jerry", 1000);
    

3.1.2 工程目录

  • 文件目录

3.1.3 配置文件

  • 修改spring的配置文件的名称空间,添加以下内容
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    ">
    
  • 开启组件注解扫描
    <context:component-scan base-package="com.du.spring"/>
    
  • 实例化数据库连接池
        <!--  实例化数据库连接池  -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url">
            <value>
                <![CDATA[jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&rewriteBatchedStatements=true]]></value>
        </property>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    
  • 实例化JdbcTemplate
    <!--实例化JdbcTemplate-->
    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
  • 配置事务管理器
    <!--配置事务管理器-->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
  • 开启事务注解
    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    

3.1.4 Java文件

  • 建立对应的Java Bean
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Account {
      private int id;
      String name;
      BigDecimal money;
    }
    
  • DAO层
    public interface AccountDao {
      /**
       * 修改账户余额,正为转入,负为转出
       * @param id
       * @param money
       */
      int changeAccount(int id, BigDecimal money);
    }
    
    @Repository
    public class AccountDaoImpl implements AccountDao {
      JdbcTemplate jdbcTemplate; // 自动注入
    
      @Override
      public int changeAccount(int id, BigDecimal money) {
        String sql = "update account set money = money + ? where id = ?";
        return jdbcTemplate.update(sql, money, id);
      }
    
      @Autowired
      public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
      }
    }
    
  • Service层。@Transactional既可以在方法上标注也可以在类上做标注。
    @Service
    @Transactional  // 事务注解,该类下面的所有方法都遵循事务
    public class AccountService{
      AccountDao accountDao;
    
      public int transfer(int fromId, int toId, BigDecimal money) {
        int res1 = accountDao.changeAccount(fromId, money.negate());
        //System.out.println(10 / 0);
        int res2 = accountDao.changeAccount(toId, money);
        if (res1 > 0 && res2 > 0)
          return 1;
        else
          return 0;
      }
    
      @Autowired
      public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
      }
    }
    

    注意:如果类有接口,事务的AOP会用JDK代理,然后就会报错…只能用没有接口的类,让AOP用cglib

  • 测试
    @Test
      public void transferTest() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        AccountService accountService = context.getBean("accountService", AccountService.class);
        int res = accountService.transfer(1, 2, new BigDecimal("100"));
        if (res > 0)
          System.out.println("转账成功");
        else
          System.out.println("转账失败");
      }
    

四、@Transactional 添加事务注解

  • @Transactional,可以添加在类上面,也可以添加到方法上面
  • 如果把这个注解添加类上面,这个类里面所有的方法都添加事务
  • 如果把这个注解添加到方法上面,为这个方法添加事务
  • 在这个注解内,可以添加一些参数

4.1 事务传播行为(propagation属性)

  • propagation:事务传播行为(其实就是事务嵌套的处理方法)

  • 常用的是REQUIREDREQUIRED_NEW

    • 被标注为REQUIRED的相当于,如果大事务有开事务,就和它“坐在同一条船上”,如果中间翻车了,那就一起翻车,都不生效。
    • 而如果标注为REQUIRED_NEW,就相当于自己开一条船,只要自己不翻车,完成了任务,就好立马提交,大事务翻车,不影响自己。(但是,如果还没轮到自己开,前面就翻车了,那就没得开,还是不行。)(如果自己翻车了,大事务没办法继续执行了,也会跟着翻车)
      在这里插入图片描述
  • 上图的第一个是小事务所有都是REQUIRED的,第二个是所有小事务都是REQUIRED_NEW的。

  • 注意,大事务调用小事务的时候,不能在同一个类下面,不然就是简单的方法调用了。(因为事务是基于aop进行的,而aop是代理对象之后执行的,所以需要在类的外部拿到小事务所属类的代理对象,再调用,事务才会正常执行。)

4.2 隔离级别(isolation属性)

  • isolation:事务隔离等级.多事务之间需要考虑隔离性,否则会出现一些问题.
  • 脏读:一个未提交事务读取到另一个未提交事务的数据,比如:
    • Transaction01将某条记录的AGE值从20修改为30。
    • Transaction02读取了Transaction01更新后的值:30。
    • Transaction01回滚,AGE值恢复到了20。
    • Transaction02读取到的30就是一个无效的值。
  • 不可重复读:一个未提交事务读取到另一提交事务修改数据,比如:
    • Transaction01读取了AGE值为20。
    • Transaction02将AGE值修改为30。
    • Transaction01再次读取AGE值为30,和第一次读取不一致。
  • 虚(幻)读:一个未提交事务读取到另一提交事务添加数据
    • Transaction01读取了STUDENT表中的一部分数据。
    • Transaction02向STUDENT表中插入了新的行。
    • Transaction01读取了STUDENT表时,多出了一些行。
  • 解决:设置隔离级别
  • 读未提交:READ UNCOMMITTED,允许Transaction01读取Transaction02未提交的修改。
  • 读已提交:READ COMMITTED,要求Transaction01只能读取Transaction02已提交的修改。
  • 可重复读:REPEATABLE READ,确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新
  • 串行化:SERIALIZABLE,确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

4.3 超时(timeout属性)

  • timeout:事务需要在一定时间内进行提交,如果不提交就会进行回滚。(输入的数字,单位为秒)

4.4 只读(read-only属性)

  • readOnly():是否只读。默认为false,表示可以查询,也可以添加修改操作;设置为true时,只能查询。
  • 设置成read-only效率会更高,但一定不能修改,否则会异常。

4.5 为…回滚

  • 默认事务只会在遇到运行时异常RuntimeExceptionError时回滚,而捕获到编译时异常不回滚。
  • 可以通过设置rollbackFor,添加某些编译时异常到触发回滚行列中。
  • 设置noRollbackFor,将某些运行时异常从触发回滚的行列中删除。

五、基于XML文档的声明式事务配置

<!-- 配置事务切面 -->
	<aop:config>
		<aop:pointcut 
			expression="execution(* com.atguigu.tx.component.service.BookShopServiceImpl.purchase(..))" 
			id="txPointCut"/>
		<!-- 将切入点表达式和事务属性配置关联到一起 -->
		<aop:advisor advice-ref="myTx" pointcut-ref="txPointCut"/>
	</aop:config>
	
	<!-- 配置基于XML的声明式事务  -->
	<tx:advice id="myTx" transaction-manager="transactionManager">
		<tx:attributes>
			<!-- 设置具体方法的事务属性 -->
			<tx:method name="find*" read-only="true"/>
			<tx:method name="get*" read-only="true"/>
			<tx:method name="purchase" 
				isolation="READ_COMMITTED" 
	no-rollback-for="java.lang.ArithmeticException,java.lang.NullPointerException"
				propagation="REQUIRES_NEW"
				read-only="false"
				timeout="10"/>
		</tx:attributes>
	</tx:advice>
  • 默认数据库连接池、事务管理器都已经注册。目前需要对切面、切入点、事务管理器进行配置。
  • 首先,如普通的aop一样,需要在<aop:config>中,对aop进行配置。通过<aop:pointcut>设置切入点。但是,和往常设置切面不一样的是,在这不需要设置切面,而需要设置事务增强<aop:advisor>
  • 通过<tx:advice>,设置事务增强的具体内容。需要绑定事务管理器。在<tx:attributes>中设置具体的方法,在所有切入点中选择需要使用事务的方法,并且可以在其中设置一些注解中也有的属性。

六、完全注解开发

  • 与xml配置的内容一致,只是将内容通过注解的方式配置
    @Configuration
    @ComponentScan(basePackages = "com.du.spring") // 组件扫描
    @EnableTransactionManagement  // 开启事务管理器
    public class TxConfig {
      // 实例化bean并返回
      @Bean
      public DruidDataSource getDruidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&rewriteBatchedStatements=true");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
      }
    
      @Bean
      public JdbcTemplate getJdbcTemplate(DruidDataSource dataSource) {  // 将需要配置的对象放在形参中,会autowired到里面
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
      }
    
      @Bean
      public DataSourceTransactionManager getDataSourceTransactionManager(DruidDataSource dataSource) {
        DataSourceTransactionManager manager = new DataSourceTransactionManager();
        manager.setDataSource(dataSource);
        return manager;
      }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值