Spring事务开发详解

  • 1.什么是事务
  • 1.1 事务概念
  • 1.2 事务四大特性 ACID
  • 1.3 如何控制事务
  • 1.4 事务的核心:AOP编程
  • 2.事务开发步骤
  • 3.事务属性详解
  • 3.1 隔离属性
  • 3.2 传播属性
  • 3.3 只读属性
  • 3.4 超时属性
  • 3.5 异常属性(有两种取值配置方式)
  • 4.事物属性常见配置总结:
  • 5.事务属性配置方式
  • 5.1 注解方式
  • 5.2 配置文件方式
  • 6.测试


1.什么是事务

1.1 事务概念

事务其实是数据库中的一个概念,它是保证业务操作完整性的一种机制,Spring将其接管并优化,操作更方便

1.2 事务四大特性 ACID

不懂就百度嘛!嘿嘿

A:原子性  C:一致性  I:隔离性  D:持久性

1.3 如何控制事务

  • JDBC: 手动开启
    Connection.setAutoCommit(false);Connection.commit();Connection.rollback();
  • Mybatis:
    Mybatis 自动开启事务sqlSession.commit();底层还是调用的 Connection
    sqlSession.rollback();底层还是调用的 Connection
  • 结论:控制事务的底层,都是通过 Connection 对象完成的。

1.4 事务的核心:AOP编程

<tx:annotation-driven transaction-manager="dataSourceTransactionManager" proxy-target-class="true"/> 小细节:proxy-target-class切换动态代理模式 JDK Cglib

2.事务开发步骤

  1. 原始对象:原始对象–>原始方法–>额外功能(业务处理+DAO调用) ,DAO作为Service的成员变量,依赖注入的方式,提供setter
  2. 额外功能:以前采用MethodInterceptor接口,将事务开启提交等操作写在实现类中,或者使用@Aspect注解+五大通知相关注解联合开发。
    现在采用Spring框架提供的:
    org.springframework.jdbc.datasource.DataSourceTransactionManager +注入DataSource(连接池内含操作事务所需的Connection)。
  3. 切入点:@Transactional 事务的额外功能加入给那些业务方法
  • 类上:类中所有的方法加入事务
  • 方法上:此方法加入事务
  1. 组装切面:将第二步创建的Manager引入
    <tx:annotation-driven transaction-manager=""/>

3.事务属性详解

3.1 隔离属性

隔离属性:描述了事务解决并发问题的特征

并发:多个事务(用户)在同一时间(存在细微的时间差,计算机能分辨),访问操作了相同的数据


并发带来的问题:

  • 脏读:一个事务读取了另一个事务没有提交的数据(回滚),会在本事务中产生数据不一致问题
    解决方案:@Transactional(isolation = Isolation.READ_COMMITTED)
  • 不可重复读:一个事务中 多次读取了相同的数据(例:id相同),但是读取的结果不一样。产生数据不一致问题,注意,不是脏读,它描述的是在一个事务中
    解决方案:@Transactional(isolation = Isolation.REPEATABLE_READ)
    本质:加上行锁,后来者必须等待前者操作完
  • 幻读:一个事务中,多次对整表进行查询统计,但是结果不一样,会在本事务中产生数据不一致问题,被其他事务影响了
    解决方案:@Transactional(isolation = Isolation.SERIALIZABLE)
    本质:表锁

如何解决:隔离属性设置不同的值解决并发处理过程中的值


总结:
并发安全:SERIALIZABLE>REPEATABLE_READ>READ_COMMITTED
运行效率:READ_COMMITTED>REPEATABLE_READ>SERIALIZABLE


数据库对隔离属性的支持:

  • MySQL三者都支持;
  • Oracle不支持REPEATABLE_READ 采用多版本比对的方式 解决不可重复读的问题。

默认隔离属性:ISOLATION_DEFAULT:调用不同数据库所设置的默认隔离属性

  • MySQL: REPEATABLE_READ
  • Oracle: READ_COMMITTED

隔离属性在实战中的建议:使用默认值即可



3.2 传播属性

解决问题:大的事务嵌套若干小的事务,彼此影响导致外部事务丧失原子性
什么是融合?
放弃自己的事务,融入外部事务

默认值:REQUIRED
建议使用

  • 增删改:默认的
  • 查询:SUPPORT

传播属性的值及其用法

spring 非事务方法调用事务方法_mysql

3.3 只读属性

只读属性:针对只进行查询操作的业务方法,可以只加入只读属性,提高运行效率,不会加各种锁
默认值:false
用例login(User user):查询操作
在类加上@Transaction注解的基础上,方法上再加入@Transaction(propagation=Propagation.SUPPORT,readOnly=True)进行覆盖,保证其他方法使用类上的设置,而查询方法单独设置。

3.4 超时属性

超时属性:为什么进行事务等待?访问数据时,有可能访问的数据被别的事物加锁了,本事务必须进行等待,单位为秒。
用例:@Transaction(timeout=2)
默认值:-1,最终由对应的数据库来指定,一般用默认值

3.5 异常属性(有两种取值配置方式)

异常属性:发生异常之后是否回滚
默认

  • RuntimeException及其子类采用的是回滚策略
  • Exception及其子类采用的是提交策略

自定义和上面反着的效果:后面跟着的是一个数组!!!

  • rollbackFor = {java.lang.Exception,xxx,xxx}
  • noRollbackFor = {java.lang.RuntimeException,xxx,xxx}

建议:使用默认值,使用RuntimeException及其子类

4.事物属性常见配置总结:

隔离属性:默认值
传播属性:增删改(Required默认值) 查询(Supports)
只读属性:增删改(false默认值) 查询(true)
超时属性:默认值-1,让对应数据库决定
异常属性:默认值

开发时配置:

  • 增删改操作: 类上一个@Transaction就够了
  • 查询操作: 相应方法上@Transaction(propagation=Propagation.SUPPORT,readOnly=True)进行覆盖

5.事务属性配置方式

5.1 注解方式

上面使用的就是注解的方式

5.2 配置文件方式

按着开发步骤,前两步相同,后两步在配置文件中使用标签代替@Transactional注解(配置事务属性)

第三步:

<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
     <tx:attributes>
        <tx:method name="register" isolation="DEFAULT",propagation="DEFAULT"></tx:method>
        <tx:method name="login" isolation="DEFAULT",propagation="DEFAULT"></tx:method>
     </tx:attributes>
</tx:advice>

第四步:

<aop:config>
     <aop:pointcut id="pc" expression="execution(* zyc.stu.service.*.*(..))"></aop:pointcut>
     <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"></aop:advisor>
</aop:config>

实战中:使用 * 通配,以后写方法命名注意这个规范就可以一劳永逸了

<!-- 配置事务增强(如何管理事务,只读、读写...) -->
        <tx:advice id="txAdvice" transaction-manager="txManager">
  		     <tx:attributes>
  			     <tx:method name="save*" propagation="REQUIRED" />
  		         <tx:method name="insert*" propagation="REQUIRED" />
  			     <tx:method name="delete*" propagation="REQUIRED" />
  			     <tx:method name="update*" propagation="REQUIRED" />
  			     <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
  			     <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
  			     <tx:method name="select*" propagation="SUPPORTS" read-only="true" />
  		     </tx:attributes>
  	     </tx:advice>
<!-- aop配置,拦截哪些方法(切入点表达式,拦截上面的事务增强) -->
  	        <aop:config>
  		        <aop:pointcut id="pt"
  			        expression="execution(* zyc.stu.servcies.impl.*.*(..))" />
  		        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt" />
  	        </aop:config>

6.测试

接上一篇的项目,演示Spring控制事务

package zyc.stu.Spring5_107_139.service;

import zyc.stu.Spring5_107_139.bean.User;

public interface UserService {
    void addUser(User user);
}
package zyc.stu.Spring5_107_139.service;

import org.springframework.transaction.annotation.Transactional;
import zyc.stu.Spring5_107_139.bean.User;
import zyc.stu.Spring5_107_139.dao.UserDao;

@Transactional
public class UserServiceImpl implements UserService{
    private UserDao dao;

    public void setDao(UserDao dao) {
        this.dao = dao;
    }

    //@Transactional目前作用于类上,类中所有方法都会添加事务:
    // 下面展示手动抛异常,测试回滚
    public void addUser(User user) {
        dao.addUser(user);
        //throw new RuntimeException("测试");
    }
}
@Test
    public void test2(){
        ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext3.xml");
        UserService userService = (UserService) context.getBean("userService");

        userService.addUser(new User("小王八1",18,"男"));
    }

集成log4j日志框架之后,能在控制台看到事务的创建和启动:
Creating new transaction with name [zyc.stu.Spring5_107_139.service.UserServiceImpl.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT