Spring 事务 学习

笔记简述
本学习笔记主要是介绍了事务相关的基础知识,学习编程式事务和声明式事务等不同的事务使用方法。不过现在实际开发中,越来越多的服务都是分布式,单纯的spring事务已经无法解决数据问题了,但还是有必要去了解事务的知识点。
Spring更多可查看Spring 源码学习

目录

Spring 事务 学习
1、事务
2、Spring 事务介绍
2.1、TransactionDefinition 属性
2.2、事务传递行为
2.3、事务隔离级别
3、Spring 事务使用方法
3.1、编程式事务
3.2、声明式事务
3.2.1、TransactionInterceptor 方法
3.2.2、TransactionProxyFactoryBean 方法
3.2.3、命名空间 方法
3.2.4、Transactional 注解 方法
4、总结
5、参考链接

1、事务

事务是值多个操作单元组成的集合,这多个操作单元组合在一起成为一个完整的工作单元,在执行过程中要么成功,要么失败,如果失败了则就相当于什么都没有发生一样,为了确保数据的完整性和一致性。就拿常见的取钱的例子吧,张三在ATM机器上取1000元钱,但是出现意外了,银行成功扣款,但是ATM机器却因为硬件故障导致出钞失败,张三就损失了1000元钱;如果银行扣款失败,但是却顺利取出了钱,银行就损失了1000元钱。在现实中这种情况需要绝对被解决,这就可以使用事务去解决了,银行扣款和ATM出钞分为2个操作单元,只有2个都成功了,才意味着成功取钱,否则就认为操作失败,所有的数据都回滚到发生之前。

事务一般和数据连接绑定在一起使用,在操作数据库时,一般都是1、3、4步,但是加上事务必须得捕获异常,然后进行回滚操作,也就多了2、5两步了。其中回滚主要是使用类似binlog等方式恢复数据

try{
    1. con = getConnection();  // 获取连接
    2. con.setAutoCommit(false);  // 设置是否进行自动提交
    3. doing....             // 拼接sql
    4. con.commit();      // 提交操作
} catch(RuntimeException ex){  // 不一定就是运用时的异常才会被捕获,看用户自定义设置
    5. con.rollback();   //回滚事务
} 

多说一句,在接下来的学习中会了解到如今的实际场景中,其实事务使用的并不是很多。事务其实就类似于对一个数据库连接进行锁操作一样(这是自己描述的,并没有这个具体的说法,只是为了便于理解),而现在很多服务都是分布式架构的,必然存在多个数据库连接,而各个数据库连接之间没有关系,锁住或者控制一个数据库连接无法解决问题,况且还有更重要的幂等性问题

事务包含了4个特性,分别是原子性(Atomicity),一致性(Consistency),隔离性(Isolation),持久性(Durability)

  • 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
  • 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
  • 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
  • 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。

2、Spring 事务介绍

主要介绍下spring中的事务的基本情况。在spring中必须得由Spring中的某些对象接管原本的数据库连接,然后通过各种方式添加如上伪代码显示的2、5两步完成添加事务的操作。

spring中提供事务的接口是PlatformTransactionManager接口类,具体实现有jdbc管理类等,具体如下图


image
// 获得需要的TransactionStatus对象,是一个事物的属性对象
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// 提交
void commit(TransactionStatus status) throws TransactionException;
// 回滚
void rollback(TransactionStatus status) throws TransactionException;

2.1、TransactionDefinition 属性

事务管理中必然需要管理各个事务的属性信息,而这些都存储在TransactionDefinition 接口类中

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

2.2、事务传递行为

事务传递是指,在开始进行事务处理的时候,已经存在了一个上下文,此时该如何执行的这么一个过程。

  • TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行; 如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

2.3、事务隔离级别

事务隔离是指多个事务之间的隔离程度,当两个事务对数据库的同一条数据进行读写操作时,就会因为不同等级的隔离,出现不同的情况,当然隔离的程度越大性能消耗的也更多。

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

3、Spring 事务使用方法

编写一些demo,实践中如何具体使用事务达到我们想要的目的(如下例子有参考网上实例,觉得这个例子挺好)

数据库表结构


image
public class Money {

    private Long id;
    private String name;
    private Long moneyNum;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getMoneyNum() {
        return moneyNum;
    }

    public void setMoneyNum(Long moneyNum) {
        this.moneyNum = moneyNum;
    }

    @Override
    public String toString() {
        return "Money{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", moneyNum=" + moneyNum +
                '}';
    }
}
@Component("moneyDao")
public class MoneyDao {

    @Resource
    private JdbcTemplate jdbcTemplate;

    public void add(Long id, String name, Long moneyNum){

        String sql = "insert into test_money(id, name, money) value(?, ?, ?)";

        int record = jdbcTemplate.update(sql, id, name, moneyNum);
        System.out.println(record);
    }

    public void update(Long id, Long moneyNum){
        String sql = "update test_money set money = ? where id = ?";

        int record = jdbcTemplate.update(sql, moneyNum, id);
        System.out.println(record);
    }
}
<context:component-scan base-package="com.demo.jdbc" />

<bean id="dataSource"
      class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/test"/>
    <property name="username" value="root"/>
    <property name="password" value="......"/>
</bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <constructor-arg name="dataSource" ref="dataSource" />
</bean>

3.1、编程式事务

编程式代码也就是硬编码的形式,TransactionTemplate模板类用于简化事务管理,事务管理由模板类定义,而具体操作需要通过TransactionCallback回调接口或TransactionCallbackWithoutResult回调接口指定,通过调用模板类的参数类型为TransactionCallback或TransactionCallbackWithoutResult的execute方法来自动享受事务管理。

  • TransactionCallback 实现doInTransactionWithoutResult方法,里面填充事务需要管理的代码,有返回值
  • TransactionCallbackWithoutResult 继承自TransactionCallback接口,无需返回数据

如果有看这两个代码,会发现其实调用的是同一个地方,只是无数据返回的是null罢了

我们当前就选择无数据返回作为例子

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

<!--配置事务管理模板,Spring为了简化事务管理的代码而提供的类-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="transactionManager"/>
    <!--  这里还可以设置事务隔离的级别属性-->
</bean>
public void change(){
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
            moneyDao.update(1L, 30L);
            // 添加70,移到李四上,当然这只是例子没有做查询减少的操作,直接更新罢了
            test();
            moneyDao.update(2L, 120L);
        }
    });
}

/**
 * 假设抛出了错误
 */
private void test(){
    throw new IndexOutOfBoundsException("demo test");
}

如上代码所示,其中就有使用到上面说的TransactionTemplate模板类,在这里没有贴出具体的调用方法,自行注入执行,两个更新sql代码操作包裹在doInTransactionWithoutResult方法中。

其中包含了抛出个运行时错误,如果没有事务,那第一个操作会成功,第二个会失败,但是这就导致了数据错误的情况,添加了事务,则同时成功或者回滚到操作前。

在运行前还需要提一点的是,上一段话说了是抛出运行时错误,可是如果是sql本身或者事务具体执行的导致的异常呢?这个就需要取到TransactionStatus这个对象的数据,进行rollback操作,具体的原因后续的源码分析中会解答的。

如下图,在有运行中抛出运行时错误之后,进行了回滚操作,并把异常,数据库的数据并没有更新


image

如果把抛出异常这步去掉,那么就可以正常更新数据了


image
image

总结

这种编程式的事务方法其实叫做基于 TransactionTemplate 的编程式事务管理,另外还有一种是基于底层 API 的编程式事务管理,他是利用了PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 三个核心接口的API完成对事物的管理,不过本质来说只是表现形式不同而已,换汤不换药

3.2、声明式事务

在真正的开发中,如果需要管理的事务很多,使用编程式去硬编码完成,一方面使得代码耦合度提高了,另一方面再去修改成本也很大,维护难度提高了,最好还是无侵入式的方法最好,也就是我们现在所说的声明式事务,充分的使用spring的AOP功能,具体的AOP学习可以看Spring AOP学习,当然了按照spring的套路肯定提供了xml配置和注解两种方法了。

3.2.1、TransactionInterceptor 方法

    <bean id="transactionInterceptor"
        class="org.springframework.transaction.interceptor.TransactionInterceptor">
        <property name="transactionManager" ref="transactionManager"/>
        <property name="transactionAttributes">
            <props>
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>

    <bean id="moneyServiceProxy"
        class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="moneyService"/>
        <property name="interceptorNames">
            <list>
                <idref bean="transactionInterceptor"/>
            </list>
        </property>
    </bean>

在获取bean的时候,获取通过ProxyFactoryBean包装好的moneyServiceProxy,其他不需要做任何操作

这里有一点需要主要的是,transactionInterceptor中的transactionAttributes属性信息,当前demo中的key是"*",实际上有各种可配置的,具体的配置是传播行为 [,隔离级别] [,只读属性] [,超时属性] [不影响提交的异常] [,导致回滚的异常]

  • 传播行为就是上面所述的传播行为的属性,隔离级别也是类似
  • 只读属性是readOnly字段
  • 超时属性是必须以TIMEOUT_开头,后面跟着一个数字,表示事务允许超时多少的最大表秒数
  • 不影响提交的异常是指出现该些异常,事务正常提交,不会发生回滚,需要加上+,例如+RuntimeException
  • 导致回滚的异常是值出现该些异常,事务将会回滚,需要加上-,例如-Exception

同样的key中的表示方法名称,可以模糊匹配,如果是*则表示所有的函数都有事务,例如key="change",则意味着只有change函数需要添加事务

3.2.2、TransactionProxyFactoryBean 方法

上述的TransactionInterceptor虽然实现了无侵入式的方法,但是如果需要添加事务的类过多则就意味着所有的类都必须有这样的配置,spring提供了一个新的bean TransactionProxyFactoryBean,不过我个人觉得也没改善太多,就是把两个bean的内容组合到一起,减少了一些配置而已。

    <bean id="moneyServiceProxy2"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="target" ref="moneyService" />
        <property name="transactionManager" ref="transactionManager"/>
        <property name="transactionAttributes">
            <props>
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>

在具体使用的使用,使用moneyServiceProxy2这个bean即可,如果仔细查看配置的话,确实和TransactionInterceptor没有太多的区别

3.2.3、命名空间 方法

何所谓命名空间呢,就是利用spring本身的包含的各种NamespaceHandler去解析处理各种自定义的xml标签,自动注入各种bean去完成相关任务,在spring中提供了tx以及aop实现该功能

    <tx:advice id="transAdvice" transaction-manager="transactionManager">
        <!--配置事务传播性,隔离级别以及超时回滚等问题 -->
        <tx:attributes>
            <tx:method name="*" 
                propagation="REQUIRED"
                rollback-for="Exception" 
                timeout="10"
                read-only="true"
                isolation="DEFAULT"
                no-rollback-for="Exception" 
                <!-- 以上都是配置的属性而已 -->
            />
        </tx:attributes>
    </tx:advice>
    
    <aop:config>
        <!--配置事务切点 -->
        <aop:pointcut id="services"
            expression="execution(* com.demo.jdbc.MoneyService.*(..))" />
        <aop:advisor pointcut-ref="services" advice-ref="transAdvice" />
    </aop:config>

先是设置了一些包含事务的方法,并且设置有传递行为、隔离级别等属性,然后利用AOP的切面功能去实现事务处理

3.2.4、Transactional 注解 方法

既然xml配置都已经存在了,再支持注解的方法也是完全可以的

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    ...

可以支持在类上和方法上添加该注解,如果再类上面则该类中的public方法都会被添加上具体的事务方法的,例如如下配置

@Transactional(propagation = Propagation.REQUIRED)
public void change() {
    moneyDao.update(1L, 30L);        
    test();
    moneyDao.update(2L, 120L);
}

还有个点别忘记了,添加了注解还需要添加支持解析该注解的功能,在xml中添加上
<tx:annotation-driven transaction-manager="transactionManager"/>

4、总结

主要是介绍了事务的基本信息以及如何具体的使用事务,从本质来说上面几种方法没有太多的差异,只是spring提供了更加便捷的方法去实现同样的功能,后续会学习源码层面,了解spring如何实现该功能的。以及异常如何被捕获回滚操作、幂等性的问题等还需要解决。

5、参考链接

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,563评论 18 139
  • 很多人喜欢这篇文章,特此同步过来 由浅入深谈论spring事务 前言 这篇其实也要归纳到《常识》系列中,但这重点又...
    码农戏码阅读 4,697评论 2 59
  • Spring 事务属性分析 事务管理对于企业应用而言至关重要。它保证了用户的每一次操作都是可靠的,即便出现了异常的...
    壹点零阅读 1,286评论 0 2
  • spring,mybatis事务管理配置与@Transactional注解使用 概述 事务管理对于企业应用来说是至...
    tenlee阅读 4,100评论 0 11
  • 这真不是一个广告。 这个必须先说明一下,因为昨天获得点赞最高的是什么值得买。 什么值得买: 1、什么值得买是高大上...
    英文秀阅读 558评论 0 3