Spring框架事务详解(编程式和声明式事务)

事务

事务的四大特性ACID

原子性: 事务开始后 要么全部做完,要么全部不做,不能执行一半停止,如果在执行中出错,会回滚到事务开始前的状态,也就是说事务是一个不可分割的整体,就好像化学里的原子,是构成物质的基本单位
一致性: A像B转账,不可能A扣了钱,B没有收到
隔离性: 同一时间,只允许一个事务对数据操作,不同事务间没有干扰,比如A从一张银行卡取钱,在这个动作结束前,B不能向银行卡存钱
持久性: 事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚

事务的传播行为

传播行为解决的问题是:一个业务层的事务,调用另一个业务层事务,事务之间的关系如何处理.
事务的传播行为在jdbc规范中是没有定义的,jdbc规范只定义了事务的隔离级别,为什么要有事务的传播行为,就是为了方便开发.
实际开发中的问题:业务层方法之间的互相调用,例如:
删除客户的同时,也要删除与客户相关联的订单.这时就会在CustomerService的deleteCustomer方法中调用OrderSerivce中的deleteOrder方法.
如果订单删除失败了,那么就不应该删除客户,所以这两个操作必须放到同一个事务中
另一种情况:订单删除失败了,仍然要把客户删除,这两个操作也需要事务

在这里插入图片描述

在Spring中定义了七中事务传播行为:
PROPAGATION_REQUIRED 支持当前事务,如果不存在就新建一个 * 删除客户时删除订单,处于同一个事务,如果删除订单失败,删除客户也要回滚
PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务
*删除客户时删除订单,如果删除客户时没有开启事务,那么删除订单也不使用事务.
PROPAGATION_MANDATORY支持当前事务,如果不存在,抛出异常
*删除客户时删除订单,如果在删除订单时没开事务,则抛出异常.
PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
* 生成订单,发送通知邮件,通知邮件会创建一个新的事务,如果邮件失败,不影 响订单生成事务.
PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常
PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行
* 依赖于 JDBC3.0 提供 SavePoint 技术
*删除客户删除订单, 在删除客户后,设置SavePoint, 执行删除订单,删除订单 和删除客户在同一个事务,删除订单失败,事务回滚 SavePoint , 由用户控制是事务 提交还是回滚

重点:

PROPAGATION_REQUIRED
一个事务,要么都成功,要么都失败 PROPAGATION_REQUIRES_NEW
两个不同事务,彼此之间没有关系 一个事务失败了不影响另一个事务 PROPAGATION_NESTED
一个事务,在A事务调用 B过程中,
B失败了,回滚事务到之前SavePoint ,用户可以选择提交事务或者回滚事务

事务的并发问题(隔离级别导致的)

脏读: 事务A读取了事务B更新的数据,然后事务B回滚,那么事务A读取的数据为脏数据
不可重复读: 事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据操作并提交,导致事务A读取到的数据不一致
幻读: 系统管理员A将学生成绩从具体分数划分为ABCDE等级,但是这个时候系统管理B又插入了一条具体分数的记录,当A操作结束后发现还有一条记录没有改过来,就好像发生了幻觉

TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。
TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。

Spring框架分为编程式事务和声明式事务

声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。

Spring框架中的编程式事务

编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

使用底层的PlatformTransactionManager

在这里插入图片描述
TransactionDefinition接口中有很多常量:

     * ISOLATION_xxx 事务隔离级别

     * PROPAGATION_xxx  事务传播行为

     * int getTimeout()  获得超时信息

     * boolean isReadOnly()  判断事务是否只读

在这里插入图片描述
TransactionDefinition接口的默认实现:

public class DefaultTransactionDefinition implements TransactionDefinition, Serializable {
    private int propagationBehavior = PROPAGATION_REQUIRED;
    private int isolationLevel = ISOLATION_DEFAULT;
    private int timeout = TIMEOUT_DEFAULT;
    private boolean readOnly = false;
    //略
}

1.事务的传播属性为PROPAGATION_REQUIRED,即当前没有事务的时候,创建一个,如果有则使用当前事务 2.事务的隔离级别采用底层数据库默认的隔离级别 3.超时时间采用底层数据库默认的超时时间 4.是否只读为false

public interface TransactionDefinition {
    int getPropagationBehavior(); // 返回事务的传播行为
    int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
    int getTimeout();  // 返回事务必须在多少秒内完成
    boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
} 

测试代码:
xml

<?xml version="1.0" encoding="UTF-8"?>
<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: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-4.3.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.3.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
    <!--读取db.properties -->
    <context:property-placeholder location="classpath:db.properties"/>
    <!--    配置数据库连接池-->
        <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
            <!-- 数据库基本配置 -->
            <property name="driverClassName" value="${jdbc.driver}" />
            <property name="url" value="${jdbc.url}" />
            <property name="username" value="${jdbc.username}" />
            <property name="password" value="${jdbc.password}" />
            <!-- 初始化连接数量 -->
            <property name="initialSize" value="${jdbc.initialSize}"/>
            <!-- 最大并发连接数量 -->
            <property name="maxActive" value="${jdbc.maxActive}"/>
            <!-- 最小空闲连接数 -->
            <property name="minIdle" value="${jdbc.minIdle}"/>
            <!-- 配置获取连接等待超时的时间 -->
            <property name="maxWait" value="${jdbc.maxWait}" />
            <!-- 超过时间限制是否回收 -->
            <property name="removeAbandoned" value="${jdbc.removeAbandoned}" />
            <!-- 超过时间限制多长 -->
            <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}" />
            <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
            <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" />
            <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
            <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" />
            <!-- 用来检测连接是否有效的sql,要求是一个查询语句-->
            <property name="validationQuery" value="${jdbc.validationQuery}" />
            <!-- 申请连接的时候检测 -->
            <property name="testWhileIdle" value="${jdbc.testWhileIdle}" />
            <!-- 申请连接时执行validationQuery检测连接是否有效,配置为true会降低性能 -->
            <property name="testOnBorrow" value="${jdbc.testOnBorrow}" />
            <!-- 归还连接时执行validationQuery检测连接是否有效,配置为true会降低性能  -->
            <property name="testOnReturn" value="${jdbc.testOnReturn}" />
            <property name="logAbandoned" value="true" />
            <!-- 配置监控统计拦截的filters,wall用于防止sql注入,stat用于统计分析 -->
            <property name="filters" value="stat" />
        </bean>
    <!--开启事务的注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <!-- 事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 数据源 -->
        <property name="dataSource" ref="dataSource" />
    </bean>
<!--    事务定义(编程式事务:TransactionDefinition管理时配置的)-->
    <bean id="defaultTransactionDefinition" class="org.springframework.transaction.support.DefaultTransactionDefinition">
<!--        隔离级别可缺省-->
        <property name="isolationLevelName" value="ISOLATION_REPEATABLE_READ"/>
<!--        传播行为 可缺省-->
        <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
<!--        超时配置-->
        <property name="timeout" value="-1"/>
<!--        事务是否只读-->
        <property name="readOnly" value="false"/>
    </bean>
    <!-- MyBatis SqlSessionFactoryBean 配置 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!-- 自动扫描Mapping.xml文件 -->
        <property name="mapperLocations" value="classpath:/mapper/*.xml"/>
        <!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!-- 扫描bean包 xml中parameterType就可以使用类名,不用全路径 -->
        <property name="typeAliasesPackage" value="bean"/>
    </bean>

    <!-- 加载 mapper.xml对应的接口 配置文件 -->
    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 给出需要扫描mapper接口包 -->
        <property name="basePackage" value="dao"/>
        <!-- 注入sqlSessionFactory -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>

    <!-- 配置扫描@Service注解 -->
    <context:component-scan base-package="serviceImpl"/>
</beans>

测试

 @Autowired
    private DataSourceTransactionManager manager;
    @Autowired
    private DefaultTransactionDefinition definition;
    @RequestMapping(value = "/login/insert",method = RequestMethod.POST)
    public String save(User user){
        TransactionStatus transactionStatus = manager.getTransaction(definition);
        int success=0;
        try{
            success = userSVImpl.saveUser(user);
            //重复插入后出现错误,应当回滚
            success = userSVImpl.saveUser(user);
            manager.commit(transactionStatus);
            return 1 == success ? "Success" : "Fail";
        }catch (Exception e){
            e.printStackTrace();
            manager.rollback(transactionStatus);
            return 1 == success ? "Success" : "Fail";
        }
    }

使用TransactionTemplate

事务模板原理是将要执行的具体代码交给模板,模板会在执行这写代码的同时处理事务,当这写代码出现异常时则自动回滚事务,以此来简化书写

<!-- 在上述配置基础上删除事务定义 添加模板Bean-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
  <!--       传播行为隔离级别等参数  可缺省-->
  <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
  <property name="transactionManager" ref="transactionManager"/>
</bean>
public class Test2 {

    @Autowired
    private StudentMapper studentMapper;

    @Autowired
    private TransactionTemplate transactionTemplate;


    @Test
    public  void test(){
        transactionTemplate.execute(new TransactionCallback() {
            public Object doInTransaction(TransactionStatus transactionStatus) {
                Student student = studentMapper.selectByPrimaryKey(1);
                System.out.println(student);
                student.setAge(1101);
                studentMapper.updateByPrimaryKey(student);
//                int i = 1/0;
                return null;
            }
        });
    }
}

可以看到事务模板要求提供一个实现类来提交原始的数据库操作给模板,从而完成事务代码的增强

无论是纯手动管理还是利用模板,依然存在大量与业务无关的重复代码,这也是编码式事务最大的问题;

Spring框架中的声明式事务

声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。
在这里插入图片描述

基于tx和aop名字空间的xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <import resource="mybatis-beans.xml"/>
    <context:component-scan base-package="com.yh.service"/>

<!--    添加事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

<!--    事务通知-->
    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
<!--            name指定要应用的方法名称 还有其他事务常用属性如隔离级别传播行为等..-->
            <tx:method name="*" read-only="false"/>
        </tx:attributes>
    </tx:advice>

<!--    切点信息-->
    <aop:config >
<!--        根据表达式中的信息可以自动查找到目标对象 从而进行增强 先查找目标再生产代理-->
        <aop:pointcut id="pointcut" expression="execution(* com.yh.service.*.*(..))"/>
        <aop:advisor advice-ref="transactionAdvice" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

在这里插入图片描述
Service

@Service
public class StudentService {
    @Autowired
    private StudentMapper studentMapper;
    public Student getStudent(int id ){
        return studentMapper.selectByPrimaryKey(id);
    }
    public void update(Student student){
        studentMapper.updateByPrimaryKey(student);
        int i  = 1/0;
    }
}

测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext3.xml")
public class Test3 {
    @Autowired
    StudentService studentService;

    @Test
    public void test(){
        Student student = studentService.getStudent(1);
        System.out.println(student);
        student.setAge(8818);
        studentService.update(student);
    }
}

强调:事务增强应该应用到Service层,即业务逻辑层,应为一个业务方法可能涉及多个数据库操作,当某个操作遇到异常时需要将所有操作全部回滚。

基于@Transactional注解

@Transactional 实质是使用了 JDBC 的事务来进行事务控制的
@Transactional 基于 Spring 的动态代理的机制

基本属性
在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

  	<!--为了分离关注点,故将MyBatis相关配置放到其他配置文件了-->
    <import resource="mybatis-beans.xml"/>
    <context:component-scan base-package="com.yh.service"/>
  
<!--    添加事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--    开启注解事务管理-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
@Transactional(propagation = Propagation.REQUIRED,readOnly = false)
public void transactionTest(){
    Student student = getStudent(1);
    student.setAge(1);
    update(student);
    int i = 1/0;
    student.setName("jack");
    update(student);
}
//当然注解上的参数都是可选的采用默认值即可
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext4.xml")
public class Test4 {

    @Autowired
    StudentService studentService;

    @Test
    public void test(){
        studentService.transactionTest();
    }
}

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

虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。

需要明确几点

默认配置下 Spring 只会回滚运行时、未检查异常(继承自 RuntimeException 的异常)或者 Error
在这里插入图片描述

@Transactional 注解只能应用到 public 方法才有效
异常的分类:
@Transactional 默认回滚 运行时异常和error,如果想回滚所有的异常,应该加上 rollbackFor = Throwable.class

多线程中的事务管理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Spring AOP

Spring AOP 实现原理

参考博客

重在事务的实现方式:编程式事务和声明式事务

@Ttransactional详解

@Transactional详解

重在事务失效场景解读

下方的可以选择不看了,下方是学生时代的回忆。

动态代理:
JDK动态代理和CGLIB代理
AspectJ开发:
基于XML的声明式AspectJ和基于注解的声明式AspectJ
AOP的实现:主要是切点和切面

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
博客1:通俗的理解AOP机制
博客2:Spring AOP demo
博客3:SpringBoot + AOP
博客4:AOP设计原理
博客5:不知道重点就看此篇文章

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值