Spring 事务的用法、传播机制、原理

1、Spring的事务简介

https://www.cnblogs.com/sonng/p/6587139.html

在一个业务的实现过程中,可能需要多条sql完成对数据库的操作,比如账户登录,需要匹配用户名和密码,然后要增加积分,还要记录登录的ip和时间,这可能需要三个sql语句,这三个语句应当是一个整体,任意一个sql执行不成功,都表示这个业务没有执行完成,这就有了事务的概念。

事务是数据库中的概念,就是对数据库的一组操作,由一条或多条sql组成。

事务具有同步的特点,一条sql执行失败,其他sql都不会执行,即要么都执行,要么都不执行。

用START TRANSACTION开启一个事务,这之后执行的sql语句,在用COMMIT提交事务之前,都没有被"写死"到数据库中,可以用ROLLBACK进行回滚操作。

Spring在jdbc中提供了一个事务管理组件:org.springframework.jdbc.datasource.DataSourceTransactionManager

<!-- 配置事务管理组件 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dbcp">  <!-- dbcp是连接池组件(org.apache.commons.dbcp2.BasicDataSource)的bean -->
</bean>

1.1 事务的2种使用方式

使用事务管理的功能,跟创建bean一样,可以采用注解和xml配置两种方式。当然可能还有别的方式,还没学到

  • 注解方式:@Transactional
<!-- 采用注解方式:有源码的情况下,将注解加在方法上 -->
<!-- 开启事务注解标记@Transactional,当调用带@Transactional标记的方法时,将txManager的事务管理功能切入进去 -->
<tx:annotation-driven transactional-manager="txManager" />
<!-- 在需要事务管理的方法上加上@Transactional注解即可 -->

  • xml配置方式:通过aop机制完成事务管理
<!-- 采用xml配置的方式:使用别人写好的功能,没有源码,就可以用xml配置 -->
<tx:advice id="txAdvice" transaction-manager="txManager" >   <!-- 仍然使用txManager作为事务管理组件 -->
    <tx:attributes>
        <tx:method name="updateTitleAndBody" />   <!-- 在哪些方法上添加事务管理 -->
        <tx:method name="register" />             <!-- 这里写方法名 -->
        <tx:method name="checkLogin" />           <!-- 支持通配符 -->
        <tx:method name="listNotebook" />
        <tx:method name="getDeletedNotes" />
    </tx:attributes>
</tx:advice>
<!-- 通过aop机制完成事务管理 -->
<aop:config>                                      <!-- 作用在哪些组件上 -->
    <aop:pointcut id="target" expression="within(net.sonng.note.service.UserServiceImpl)" />
                                                  <!-- 这个expression的写法有讲究 -->
    <aop:advisor advice-ref="txAdvice"  pointcut-ref="target"/> 
</aop:config>

其他:MyBatis应该也提供了事务管理的组件

2、注解:实现 声明式事务

https://blog.csdn.net/yeson6/article/details/4954330

使用注解来实现声明式事务, 下面详细说说这个方法:

第一步:引入< tx:>命名空间 ,在spring的配置文件中修改, beans根元素里多了三行,如下
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:tx="http://www.springframework.org/schema/tx"  
        xsi:schemaLocation="http://www.springframework.org/schema/beans   
         http://www.springframework.org/schema/beans/spring-beans-2.0.xsd   
         http://www.springframework.org/schema/tx   
         http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">  

第二步:在spring的配置文件中修改,将所有具有@Transactional 注解的bean 自动配置为 声明式事务支持

 <!--JDBC事务管理器,根据你的情况使用不同的事务管理器,如果工程中有Hibernate,就用Hibernate的事务管理器 -->     
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">   
             <property name="dataSource">   
                 <ref local="dataSource"/>   
             </property>   
    </bean>      
               
    <!-- 用注解来实现事务管理 -->   
    <tx:annotation-driven transaction-manager="transactionManager"/>  

第三步: 在接口或类的声明处 ,写一个@Transactional. 要是在接口上写, 接口的实现类就会继承下来.

接口的实现类的具体方法,还可以覆盖类声明处的设置.

 @Transactional  
    public class TestPOAOImpl extends POAOBase implements TestPOAO   
    {      
        @Transactional(isolation = Isolation.READ_COMMITTED)   
        public void test1()   
         {   
             String sql = "INSERT INTO sy_test (NAME,AGE) VALUES('注解赵云',30)";   
             execute(sql);   
      
             sql = "INSERT INTO sy_test (NAME,AGE) VALUES('注解张飞',26)";   
             execute(sql);   
      
            int a = 9 / 0; //异常   
      
             sql = "INSERT INTO sy_test (NAME,AGE) VALUES('注解关羽',33)";   
             execute(sql);   
             System.out.println("走完了");   
         }   
    //execute() 方法略...   
    }  

注意的几点:

  • @Transactional只能被应用到public方法上,对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能.
  • 默认情况下,一个有事务方法, 遇到RuntimeException 时会回滚 . 遇到 受检查的异常 是不会回滚 的. 要想所有异常都回滚要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .

@Transactional 的所有可选属性如下:
这里写图片描述
事务的隔离级别 有如下可选:
可以去看spring源码 : org.springframework.transaction.annotation.Isolation

  • DEFAULT采用数据库默认隔离级别
  • READ_UNCOMMITTED
  • READ_COMMITTED
  • REPEATABLE_READ
  • SERIALIZABLE

数据库提供了四种事务隔离级别, 不同的隔离级别采用不同的锁类开来实现.
在四种隔离级别中, Serializable的级别最高, Read Uncommited级别最低.
大多数数据库的默认隔离级别为: Read Commited,如Sql Server , Oracle.
少数数据库默认的隔离级别为Repeatable Read, 如MySQL InnoDB存储引擎
即使是最低的级别,也不会出现 第一类 丢失 更新问题 .

  • Read Uncommited :读未提交数据( 会出现脏读,不可重复读,幻读 ,避免了 第一类丢失 更新 )
  • Read Commited :读已提交的数据(会出现不可重复读,幻读)
  • Repeatable Read :可重复读(会出现幻读)
  • Serializable :串行化

丢失 更新 :

当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题。每个事务都不知道其它事务的存在。最后的更新将重写由其它事务所做的更新,这将导致数据丢失。   
例:
事务A和事务B同时修改某行的值,
1.事务A将数值改为1并提交
2.事务B将数值改为2并提交。
这时数据的值为2,事务A所做的更新将会丢失。
解决办法:对行加锁,只允许并发一个更新事务。

脏读: 一个事务读到另一个事务未提交的更新数据

例:
1.Mary的原工资为1000, 财务人员将Mary的工资改为了8000(但未提交事务)
2.Mary读取自己的工资 ,发现自己的工资变为了8000,欢天喜地!
3.而财务发现操作有误,回滚了事务,Mary的工资又变为了1000, 像这样,Mary记取的工资数8000是一个脏数据。

不可重复读: 在同一个事务中,多次读取同一数据,返回的结果有所不同. 换句话说就是,后续读取可以读到另一个事务已提交的更新数据. 相反"可重复读"在同一事务多次读取数据时,能够保证所读数据一样,也就是后续读取不能读到另一事务已提交的更新数据。

例:
1.在事务1中,Mary 读取了自己的工资为1000,操作并没有完成
2.在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务
3.在事务1中,Mary 再次读取自己的工资时,工资变为了2000
解决办法:如果只有在修改事务完全提交之后才可以读取数据,则可以避免该问题。

幻读: 一个事务读取到另一个事务已提交的insert数据.
例:
第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时 (此时第一事务还未提交) ,第二个事务向表中插入一行新数据。这时第一个事务再去读取表时,发现表中还有没有修改的数据行,就好象发生了幻觉一样。

事务的传播属性 ,有如下可选
可以去看spring源码 : org.springframework.transaction.annotation.Propagation
这里写图片描述

3、spring事务的基本原理

https://blog.csdn.net/ibcve/article/details/79237384

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。

对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行:

  • 1、获取连接 Connection con = DriverManager.getConnection() ;

  • 2、开启事务con.setAutoCommit(true/false);

  • 3、执行CRUD

  • 4、提交事务/回滚事务 con.commit() / con.rollback();

  • 5、关闭连接 conn.close();

使用Spring的事务管理功能后,我们可以不再写步骤 2 和 4 的代码而是由Spirng 自动完成。那么Spring是如何在我们书写的 CRUD 之前之后 开启事务 和 关闭事务的呢?解决这个问题,也就可以从整体上理解Spring的事务管理实现原理了。下面简单地介绍下,注解方式为例子:

  • 1、配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识。

  • 2、spring 在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法并且为这些类和方法生成代理,并 根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务异常回滚事务)。

    通过 TransactionProxyFactoryBean接口来使用AOP功能,为需要事务管理的Bean生成proxy代理对象通过 TransactionInterceptor 完成 对代理方法 的拦截,并在method.invoke方法前后 为其加上 合适的 事务管理代码这样就实现了Spring式的事务管理

  • 3、真正的数据库层的事务提交和回滚是通过undo log或者redo log实现的。

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值