来了解一下Spring声明式事务


准备代码及数据库

在这里插入图片描述

<?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: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 https://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 https://www.springframework.org/schema/aop/spring-aop.xsd">


    <!--扫描包-->
    <context:component-scan base-package="cn.tulingxueyuan"></context:component-scan>

    <!--配置第三方bean-->
       <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
           <property name="username" value="${mysql.username}"></property>
           <property name="password" value="${mysql.password}"></property>
           <property name="url"  value="${mysql.url}"></property>
           <property name="driverClassName" value="${mysql.driverClassName}"></property>
       </bean>

    <!--引入外部属性资源文件-->
    <context:property-placeholder location="db.properties"></context:property-placeholder>

    <!--配置JdbcTemplate Bean组件-->
    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <property name="dataSource" ref="dataSource" ></property>
    </bean>

    <bean class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" id="namedParameterJdbcTemplate">
        <constructor-arg type="javax.sql.DataSource" ref="dataSource"/>
    </bean>

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

    </bean>

    <!--基于注解方式的事务,开启事务的注解驱动
    如果基于注解的和xml的事务都配置了会以注解的优先
    -->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

</beans>

一、不开启事务

在这里插入图片描述

示例:

	/**
     * 转账
     */
    public void trans(){
        sub();  // 张三扣钱
        int i=1/0;
        save();
    }
 /**
     * 张三扣钱
     * 扣钱
     */
    @Override
    public void sub() {
        jdbcTemplate.update("update t_user set balance=balance-200 where id=1");
    }

    /**
     * 李四加钱
     */
    @Override
    public void save() {
        jdbcTemplate.update("update t_user set balance=balance+200 where id=2");
    }

在这里插入图片描述

在这里插入图片描述

二、开启事务

加上注解@Transactional

    @Transactional()
    public void trans(){
        sub();  // 张三扣钱
        int i=1/0;
        save();
    }

在这里插入图片描述
可以发现数据库并没有扣钱
在这里插入图片描述

@Transactional注解应该写在哪:
@Transactional 可以标记在类上面(当前类所有的方法都运用上了事务)
@Transactional 标记在方法则只是当前方法运用事务
也可以类和方法上面同时都存在, 如果类和方法都存在@Transactional会以方法的为准。如果方法上面没有@Transactional会以类上面的为准
建议:@Transactional写在方法上面,控制粒度更细, 建议@Transactional写在业务逻辑层上,因为只有业务逻辑层才会有嵌套调用的情况。

事务配置的属性

isolation:设置事务的隔离级别
propagation:事务的传播行为
noRollbackFor:那些异常事务可以不回滚
noRollbackForClassName:填写的参数是全类名
rollbackFor:哪些异常事务需要回滚
rollbackForClassName:填写的参数是全类名
readOnly:设置事务是否为只读事务
timeout:事务超出指定执行时长后自动终止并回滚,单位是秒

1.事务隔离级别

Spring默认的隔离级别,是使用底层数据库的默认事务隔离级别。
顺便说一句,如果使用的MySQL,可以使用"select @@tx_isolation"来查看默认的事务隔离级别
#MYSQL:REPEATABLE-READ
#ORACLE: READ_COMMITTED

用来解决并发事务所产生一些问题:

 并发: 同一个时间,多个线程同时进行请求。
 什么时候会生成并发问题:在并发情况下,对同一个数据(变量、对象)进行读写操作才会产生并发问题

并发会产生什么问题?

  1. 脏读
  2. 不可重复读
  3. 幻读

1.脏读

一个事务,读取了另一个事务中没有提交的数据,会在本事务中产生的数据不一致的问题
在这里插入图片描述
解决方式:@Transactional(isolation = Isolation.READ_COMMITTED)

读已提交:READ COMMITTED, 要求Transaction01只能读取Transaction02已提交的修改。

2.不可重复读

一个事务中,多次读取相同的数据, 但是读取的结果不一样, 会在本事务中产生数据不一致的问题。

在这里插入图片描述

 注意啊,这个800不是脏数据,而是1个事务里,数据读取的不一致。

解决方式:@Transactional(isolation = Isolation.REPEATABLE_READ)

可重复读:REPEATABLE READ

确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。(行锁)

3.幻读

一个事务中,多次对数据进行整表数据读取(统计),但是结果不一样, 会在本事务中产生数据不一致的问题。

在这里插入图片描述
解决方式:@Transactional(isolation = Isolation.SERIALIZABLE)

串行化SERIALIZABLE

确保Transaction01可以多次从一个表中读取到相同的行,
在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。(表锁)


不可重复读和幻读的区别

提示:不可重复读针对的是某一行的数据,幻读针对的是整表的数据

在这里插入图片描述

2.事务的传播特性

事务的传播特性指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行?

通常是一个service调用另外一个service时,并且都加了事务注解。

1.REQUIRED(默认) 开启新的事务 融合到外部事务中

@Transactional(propagation = Propagation.REQUIRED)适用增删改查

比如:

	/**
     * 转账
     */
    @Transactional()
    public void trans(){
        sub();  // 张三扣钱
        save(); // 李四加钱
    }
    @Transactional()
    public  void sub() {
        System.out.println("张三扣钱");
        userDao.sub();
    }
    @Transactional
    public void save(){
        System.out.println("李四加钱");
        int i=1/0;
        userDao.save();
    }

运行结果:在这里插入图片描述
在这里插入图片描述
Spring默认的就是在这里插入图片描述
其实说白了这种,里面的注解是加不加都行的。因为会融入到外部事物。

2.SUPPORTS 不开启新的事务 融合到外部事务中

什么意思呢?
比如我转账,查余额,单独的查询余额我不想开启事务,转账再查余额,我希望查余额融合到外部事务当中
就用这个

一般子方法是个查询方法可以用这个。因为单独调用查询的时候可以不开启事务。

给大家看个例子:

	/**
     * 扣钱
     */
    @Transactional(propagation = Propagation.SUPPORTS)   // 只适用于该事务方法是一个查询
    public  void sub() {
        System.out.println("张三扣钱");
        userDao.sub();
        save();//李四加钱
    }
    /**
     * 存钱
     * @return
     */
    @Transactional
    public void save(){
        System.out.println("李四加钱");
        int i=1/0;
        userDao.save();
    }

很明显,张三扣钱了,李四没加钱,这是不对的
在这里插入图片描述

3.REQUIRES_NEW 开启新的事务 挂起外部事务,创建新的事务

@Transactional(propagation = Propagation.REQUIRES_NEW)
适用内部事务和外部事务不存在业务关联情况,如日志

注意挂起外部事物,创建新的事务,必须保证父方法和子方法不在同一个类里

@Service
public class LogServiceImpl implements ILogService {

    @Autowired
    IUserDao userDao;

    @Override
    // 如果事务传播行为是挂起事务  需要将父事务方法和子事务方法写在不同的类里面
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log() {
        System.out.println("xxxx在xxxx做了什么");
        userDao.sub();
    }
}
	/**
     * 转账
     */
    @Transactional()
    public void trans(){
        logService.log();
        sub();  // 张三扣钱
        save(); //李四加钱
    }

    /**
     * 扣钱
     */
     @Transactional()   // 只适用于该事务方法是一个查询
    public  void sub() {
        System.out.println("张三扣钱");
        userDao.sub();
    }

    /**
     * 存钱
     * @return
     */
    @Transactional
    public void save(){
        System.out.println("李四加钱");
        int i=1/0;
        userDao.save();
    }

在这里插入图片描述

下面这几种不常用。没什么意义

4.NOT_SUPPORTED 不开启新的事务 挂起外部事务

5.NEVER 不开启新的事务 抛出异常

6.MANDATORY 抛出异常 融合到外部事务中

7.NESTED 开启新的事务 融合到外部事务中,SavePoint机制,外层影响内层, 内层不会影响外层

我们再看下异常属性

异常属性

设置 当前事务出现的那些异常就进行回滚或者提交。

默认对于RuntimeException 及其子类采用的是回滚的策略。
默认对于Exception 及其子类 采用的是提交的策略。
1、设置哪些异常不回滚(noRollbackFor)
2、设置哪些异常回滚(rollbackFor )

@Transactional(timeout = 3,rollbackFor = {FileNotFoundException.class})

设置事务只读(readOnly)

readonly:只会设置在查询的业务方法中
connection.setReadOnly(true) 通知数据库,当前数据库操作是只读,数据库就会对当前只读做相应优化
当将事务设置只读 就必须要你的业务方法里面有增删改。

	如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
	如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,
	在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,
	应该启用事务支持(如:设置不可重复读、幻读级别)。
	对于只读事务,可以指定事务类型为readonly,即只读事务。
	由于只读事务不存在数据的修改,因此数据库将会为只读事务提供一些优化手段 

总结

在实战中事务的使用方式
如果当前业务方法是一组 增、改、删 可以这样设置事务
@Transactional
如果当前业务方法是一组 查询 可以这样设置事务
@Transactionl(readOnly=true)
如果当前业务方法是单个查询 可以这样设置事务
@Transactionl(propagation=propagation.SUPPORTS ,readOnly=true)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值