Spring 事务管理

事务的基本原理

Spring事务的本质其实就是数据库对数据的支持,使用JDBC的事务的管理机制,就是利用java.sql.Connection对象完成对事务的提交,那么没有Spring帮我们管理事务之前,我们要怎么做。。。

Connection conn=DriverManager.getConnection();
try{
    conn.setAutoCommit(false);//将自动的提交设置为false
    //执行CRUD操作
    conn.commit();//当两个操作成功后手动的提交
}catch(Exception e){
    conn.rollback();//一旦其中一个操作出错都将回滚,所有的操作都不成功
    e.printStrackTrace();
}finally{
    conn.close();
}

事务是一系列的动作,一旦其中有一个动作出现错误,必须的全部回滚,系统将事务中的对数据库的所有的已经完成的操作全部的撤销,滚回到事务开始的状态,,避免出现由于数据不一致而导致的一系列的错误。。事务的出现是为了确保数据的完整性和一致性,在目前企业级的应用开发中,事务管理是必不可少的。

与事务相关的理论知识

众所周知,事务有四大特性(ACID)

1,原子性(Atomicity)事务是一个原子的操作,由一系列的动作组成。。事务的原子性确保动作要么全部完成,要吗完全的不起作用。。

2,一致性(Consistency)事务在完成的时候,必须是所有的数据都保持一致的状态。。

3,隔离性(lsolation)并发事务执行之间无影响,在一个事务内部的操作对其他的事务不产生影响,这需要事务隔离级别来指定隔离性。

4,持久性(Durability)一旦事务完成,数据库的改变必须是持久化的。

在企业级应用中,多数的访问数据库是常见的场景,这就是所谓的事务的并发。事务的并发可能存在的问题。。

1,脏读:一个事务读到另外一个事务的未提交的数据。。

2,不可重复读:一个事务两次的读到同一行数据,可是这两次读取的数据不一样。。。

3,幻读:一个事务执行两次的查询,但是第二次插叙比着第二次查询多了一些数据行。。

4,丢失更新:撤销一个事务的时候,把其他的事务已经提交的更新的数据给覆盖了。。

JDBC定义了五种事务隔离级别来解决这些并发导致的问题;;

/**
 * A constant indicating that transactions are not supported. 
 */
int TRANSACTION_NONE         = 0;

/**
 * A constant indicating that
 * dirty reads, non-repeatable reads and phantom reads can occur.
 * This level allows a row changed by one transaction to be read
 * by another transaction before any changes in that row have been
 * committed (a "dirty read").  If any of the changes are rolled back, 
 * the second transaction will have retrieved an invalid row.
 */
int TRANSACTION_READ_UNCOMMITTED = 1;

/**
 * A constant indicating that
 * dirty reads are prevented; non-repeatable reads and phantom
 * reads can occur.  This level only prohibits a transaction
 * from reading a row with uncommitted changes in it.
 */
int TRANSACTION_READ_COMMITTED   = 2;

/**
 * A constant indicating that
 * dirty reads and non-repeatable reads are prevented; phantom
 * reads can occur.  This level prohibits a transaction from
 * reading a row with uncommitted changes in it, and it also
 * prohibits the situation where one transaction reads a row,
 * a second transaction alters the row, and the first transaction
 * rereads the row, getting different values the second time
 * (a "non-repeatable read").
 */
int TRANSACTION_REPEATABLE_READ  = 4;

/**
 * A constant indicating that
 * dirty reads, non-repeatable reads and phantom reads are prevented.
 * This level includes the prohibitions in
 * <code>TRANSACTION_REPEATABLE_READ</code> and further prohibits the 
 * situation where one transaction reads all rows that satisfy
 * a <code>WHERE</code> condition, a second transaction inserts a row that
 * satisfies that <code>WHERE</code> condition, and the first transaction
 * rereads for the same condition, retrieving the additional
 * "phantom" row in the second read.
 */
int TRANSACTION_SERIALIZABLE     = 8;

翻译过来这几个常量就是 
TRANSACTION_NONE JDBC 驱动不支持事务 
TRANSACTION_READ_UNCOMMITTED 允许脏读、不可重复读和幻读。 
TRANSACTION_READ_COMMITTED 禁止脏读,但允许不可重复读和幻读。 
TRANSACTION_REPEATABLE_READ 禁止脏读和不可重复读,单运行幻读。 
TRANSACTION_SERIALIZABLE 禁止脏读、不可重复读和幻读。

隔离的级别越高,意味着数据库事务的并发的执行性能就越差劲,能处理的操作就越少。

JDBC规范虽然定义了事务的以上的支持的行为,但是各个JDBC驱动,数据库厂商对事务的支持的程度可能各不相同。。

处于性能的考虑,我们一般设置TRANSACTION_READ_COMMITTED就差不多了,剩下的通过使用数据库的锁来帮助我们处理别的。。

 

了解基本的JDBC事务,有了Spring之后,事务管理会有什么新的改变。。

有了Spring,我们再也无需要去处理获得连接,关闭连接,事务提交和回滚等操作,,使得我们把更多的精力放在处理业务上,事实上Spring并不直接管理事务,而是提供了多种事务管理器。他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。

sprnig事务管理

Spring事务管理的核心的接口是PlatformTransactionManager

事务管理器的接口通过getTransaction(TransactionDefinition definition)方法根据指定的传播行为返回当前活动的事务或者创建一个新的事务,这个方法里面的参数是TransactionDefintion这个类,这个类就定义了一些基本的事务的属性。。。。

在TransactionDefinition接口中定义了它自己的传播行为和隔离的级别。。

除去常量,主要的方法有:

int getIsolationLevel();// 返回事务的隔离级别
String getName();// 返回事务的名称
int getPropagationBehavior();// 返回事务的传播行为
int getTimeout();  // 返回事务必须在多少秒内完成
boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的

spring的事务的传播属性

由上图可知,Spring定义了7个以PROPAGATION_开头的常量表示它的传播属性

spring的事务的隔离级别

调用PlatformTransactionManager接口的getTransaction的方法得到的是TransactionStatus接口的一个实现TransactionStatus接口。。

主要的方法有

可以看出返回的结果是一些事务的状态,可以来检索事务的状态信息。。

配置事务管理器

介绍完Spring事务的管理的流程大概是怎么走的,接下来可以手动的试试Spring是如何的配置事务管理器的,例如我在spring-mybatis中的配置的:

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

这样配置不是唯一的,可以根据自己项目选择的数据访问框架灵活配置事务管理器

配置了事务管理器之后,事务当然还是得我们自己去操作,Spring提供了两种事务管理得方式:编程式事务管理器和声明式得事务管理,让我们分别得看看他们式怎么做的吧

编程式事务管理

编程式事务管理我们可以通过PlatformTransactionManager实现来进行事务管理,同样的Spring也为我们提供了模板类TransactionTemplate进行事务管理,下面主要介绍模板类,我们需要在配置文件中配置

    <!--配置事务管理的模板-->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager"></property>
        <!--定义事务隔离级别,-1表示使用数据库默认级别-->
        <property name="isolationLevelName" value="ISOLATION_DEFAULT"></property>
        <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"></property>
    </bean>

TransactionTemplate帮我们封装了许多代码,节省了我们的工作。下面我们写个单元测试来测测。 
为了测试事务回滚,专门建了一张tbl_accont表,用于模拟存钱的一个场景。service层主要代码如下,后面会给出全部代码的github地址,有需要的朋友请移步查看。 
BaseSeviceImpl

    //方便测试直接写的sql
    @Override
    public void insert(String sql, boolean flag) throws Exception {
        dao.insertSql(sql);
        // 如果flag 为 true ,抛出异常
        if (flag){
            throw new Exception("has exception!!!");
        }
    }
    //获取总金额
    @Override
    public Integer sum(){
        return dao.sum();
    }

dao对应的sum方法

   <select id="sum" resultType="java.lang.Integer">
        SELECT SUM(money) FROM tbl_account;
    </select>

下面看看测试代码

package com.gray;

import com.gray.service.BaseSevice;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;

/**
 * Created by gray on 2017/4/8.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-test.xml"})
public class TransactionTest{
    @Resource
    private TransactionTemplate transactionTemplate;
    @Autowired
    private BaseSevice baseSevice;

    @Test
    public void transTest() {
        System.out.println("before transaction");
        Integer sum1 = baseSevice.sum();
        System.out.println("before transaction sum: "+sum1);
        System.out.println("transaction....");
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try{
                    baseSevice.insert("INSERT INTO tbl_account VALUES (100);",false);
                    baseSevice.insert("INSERT INTO tbl_account VALUES (100);",false);
                } catch (Exception e){
                    //对于抛出Exception类型的异常且需要回滚时,需要捕获异常并通过调用status对象的setRollbackOnly()方法告知事务管理器当前事务需要回滚
                    status.setRollbackOnly();
                    e.printStackTrace();
                }
           }
        });
        System.out.println("after transaction");
        Integer sum2 = baseSevice.sum();
        System.out.println("after transaction sum: "+sum2);
    }
}

当baseSevice.insert的第二个参数为false时,我们假设插入数据没有出现任何问题,测试结果如图所示: 

当第二个参数为true时,insert会抛出一个异常,这是事务就应该回滚,数据前后不应该有变化,如图所示:

声明式事务管理

声明式事务管理有两种常用的方式,一种是基于tx和aop命名空间的xml配置文件,一种是基于@Transactional注解,随着Spring和Java的版本越来越高,大家越趋向于使用注解的方式,下面我们两个都说。 

1.基于tx和aop命名空间的xml配置文件 

    <tx:advice id="advice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="insert" propagation="REQUIRED" read-only="false"  rollback-for="Exception"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="pointCut" expression="execution (* com.gray.service.*.*(..))"/>
        <aop:advisor advice-ref="advice" pointcut-ref="pointCut"/>
    </aop:config>

测试代码

    @Test
    public void transTest() {
        System.out.println("before transaction");
        Integer sum1 = baseSevice.sum();
        System.out.println("before transaction sum: "+sum1);
        System.out.println("transaction....");
        try{
            baseSevice.insert("INSERT INTO tbl_account VALUES (100);",true);
        } catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("after transaction");
        Integer sum2 = baseSevice.sum();
        System.out.println("after transaction sum: "+sum2);
    }

 

事务正常执行结果截图 

事务出现异常结果截图 

2.基于@Transactional注解 

这种方式最简单,也是最为常用的,只需要在配置文件中开启对注解事务管理的支持。

    <!-- 声明式事务管理 配置事物的注解方式注入-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

然后在需要事务管理的地方加上@Transactional注解,如:

    @Transactional(rollbackFor=Exception.class)
    public void insert(String sql, boolean flag) throws Exception {
        dao.insertSql(sql);
        // 如果flag 为 true ,抛出异常
        if (flag){
            throw new Exception("has exception!!!");
        }
    }

rollbackFor属性指定出现Exception异常的时候回滚,遇到检查性的异常需要回滚,默认情况下非检查性异常,包括error也会自动回滚。 
测试代码和上面那个一样 
事务正常执行结果截图 

 

事务出现异常结果截图 

以上就是对Spring事务进行了详细的分析和代码示例。 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值