美文网首页我爱编程
spring事务管理

spring事务管理

作者: 广西年轻人 | 来源:发表于2018-03-31 00:58 被阅读42次

    1. 事务管理的基本原理

    spring事务管理实际上数据库对事务的支持,在java中使用的是JDBC的事务管理机制,就是利用原生jdbc的java.sql.Connection来完成对事务的管理(提交,回滚),使用原生jdbc的事务管理一般来说的操作是:

    Connection connection = DriverManager.getConnection();
    try {  
        connection .setAutoCommit(false);  //取消自动提交               
         // 执行CRUD操作 ...
        connection .commit();      //事务完成后手动提交  
    } catch (Exception e) {  
        connection .rollback();    //事务期间发生异常,回滚事务
        e.printStackTrace();  
    } finally {
        connection .colse();        //关闭连接
    }
    
    

    事务,简单来说就是一些操作或者说一组sql,他们执行的特点就是要么全部都执行,要么全部都不执行,当然jdbc事务的管理可以设置savePoint,也就是回滚点,不一定要全部回滚,但是一般来说都是要么全部执行,要么全部不执行,回滚到事务开始的状态,这样就能够保数据的一致性和晚完整性,避免了因为数据不一致带来的后续错误。例如转账扣钱成功,加钱失败,导致系统逻辑上出现问题,数据也不一致。

    2. 和数据库事务相关理论

    ACID

    A(Atomicity):原子性,事务是一个原子操作,由一系列操作组成,这一系列操作组合起来应该是一个原子的,要么都不完成,要么都完成。

    C(Consistency):一致性 ,事务在执行完成后,数据的一致性应该可以保证的。

    I(Isolation):隔离性,多个事务并发期间他们之间应该是隔离的,即一个事务执行期间不会 对其他事务产生影响,这个需要事务的隔离级别来保证,

    D(Durability):持久性,事务一旦完成提交后,数据的改变就持久化到数据库。

    在应用中,对数据库的并发访问时必然存在的,那么事务的并发可能带来的问题就包括:

    脏读:一个事务读取了另外一个事务未提交的数据。
    不可重复读:一个事务在读取同一数据期间,被其他事务更新(update,delete)了数据,导致多次读取不一致。
    幻读:一个事务在读取同一数据期间,被其他事务了插入(insert)了新的数据,导致多次读取不一致
    丢失更新问题:回滚事务时,把其他事务提交的更新覆盖了。

    隔离性级别

    既然事务的并发会产生诸多问题,那么事务的隔离级别就很重要了,jdcb定义了五种事务的隔离级别来结局事务并发产生的问题:

    TRANSACTION_NONE JDBC 驱动不支持事务 
    TRANSACTION_READ_UNCOMMITTED 读未提交,允许脏读、不可重复读和幻读
    TRANSACTION_READ_COMMITTED 读已提交,禁止脏读,但允许不可重复读和幻读。
    TRANSACTION_REPEATABLE_READ 重复读,禁止脏读和不可重复读,但是幻读。 
    TRANSACTION_SERIALIZABLE 串行化,禁止脏读、不可重复读和幻读。
    
    
    *********************************************
    msyql默认REPEATABLE-READ
    oracle支持READ COMMITTED 和 SERIALIZABLE,默认READ COMMITTED
    sql server 默认READ COMMITTED
    

    事务的隔离性越高,也就意味着并发性越差,性能也就越低,原生jdbc中可通过connection.setTransactionLevel去设置需要的隔离级别。

    JDBC虽然定义了统一的事务支持以上隔离级别,但是具体的数据库厂商的支持可能不尽相同,处于性能的考虑一般设置为TRANSACTION_READ_COMMITTED就可以了,剩下的问题就通过数据库的锁来解决。

    对于丢失更新问题,不管读取会产生一致性问题,写入数据也存在这样的问题,例如以下操作:
    事务A和事务B都同时获取了同一数据,做修改,
    事务A完成后提交,
    事务B完成后提交,那么事务A的更新就丢失了,反之,事务B的更新就丢失了。

    解决这样的问题,主要采取悲观锁或者乐观锁的方式

    悲观锁:
    悲观锁采用的是数据库的一种锁机制,在sql语句后面添加 for update子句,当一个事务操作该条记录的时候,这条记录就被锁定了,只有当这个事务提交后,锁才被释放,其他事务才能操作这条记录。

    乐观锁: 乐观锁是人为的采用添加版本号的机制来解决的。
    原理:为表添加一个version字段,默认值为0,当事务t1在修改完后,提交事务的时候会验证数据库中version字段是否和先前查询出来的version一致,一致才可以提交事务,并且修改版本号为1。接下来t2事务同样这样做,更新版本。。。。。。

    spring事务管理

    对于基本jdbc事差不多就涉及这些,那么spring事务管理做的是什么?
    有了spring事务管理,再也不需要去获取连接,关闭连接,异常回滚,隔离级别设置等,这也是spring的一个特点(对于这样模板化的操作,提供统一的方式去操作,或者说叫又做了一层封装,简化开发)。

    实际上spring并不是直接管理事务,而是提供了一个事务管理器,将事务管理的职责交给jdbc事务管理,Hibernate或者更强大的JTA这样的事务管理的平台框架的事务管理。

    spring环境下统一的事务管理器平台,对于使用不同持久化技术,平台统一了事务管理。
    
        *在spring JdbcTemplate,原生JDBC,或者mybatis这样的半自动化的工具持久化数据的时候,使用org.springframework.jdbc.datasource.DataSourceTransactionManager,事务管理器。
        * 在hibernate这样全自动orm框架下,使用org.springframework.orm.hibernate5.HibernateTransactionManager作为事务管理器。
    

    例如,使用hibernate的时候配置的org.springframework.orm.hibernate5.HibernateTransactionManager

    使用mybatis的时候配置的org.springframework.jdbc.datasource.DataSourceTransactionManager

    spring提供了事务管理核心接口PlatformTransactionManager 事务管理器通过过getTransaction(TransactionDefinition definition)方法返回当前的事务负责创建一个事务,参数definition就定义了基本的事务属性,例如事务的传播行为和隔离级别。

    事务的传播属性

    前面提到,spring的对于事务的支持其实只是充当一代理的角色,都是借助其他具体的事务管理来支持的,但是的事务的传播行为是spring凭借自身框架提供的功能,显得非常棒。

    spring的事务传播属性包括

    
    PROPAGATION_REQUIRED    0   支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择,也是Spring默认的事务的传播。
    PROPAGATION_SUPPORTS    1   支持当前事务,如果当前没有事务,就以非事务方式执行。
    PROPAGATION_MANDATORY   2   支持当前事务,如果当前没有事务,就抛出异常。
    PROPAGATION_REQUIRES_NEW    3   新建事务,如果当前存在事务,把当前事务挂起。
    PROPAGATION_NOT_SUPPORTED   4   以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    PROPAGATION_NEVER   5   以非事务方式执行,如果当前存在事务,则抛出异常。
    PROPAGATION_NESTED  6   如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
    

    具体的说, 假设事务传播属性为PROPAGATION_REQUIRED,一般来说将事务设置在service层,那么service层需要调用其他service的时候,那么就可以保证这两个操作在同一个事务中。

    事务的隔离级别

    和jdbc事务里面定义的差不多。

    ISOLATION_DEFAULT   -1  这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应
    ISOLATION_READ_UNCOMMITTED  1   这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。
    ISOLATION_READ_COMMITTED    2   保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
    ISOLATION_REPEATABLE_READ   4   这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻读。
    ISOLATION_SERIALIZABLE  8   这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻读。
    

    spring事务的配置

    配置事务管理器

    例如使用hibernate的时候

    <!--配置一个事务管理器-->
       <bean id="hibernateTransactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
           <property name="sessionFactory" ref="sessionFactory"/>
       </bean>
    

    使用mybatis-spring的时候

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

    这个配置不是唯一的,可以根据项目具体选择的orn框架来配置事务管理器。

    配置好事务管理器外,事务的具体管理操作还需要手动去做,spring提供了两种事务管理的方式:编程式事务和声明式事务。

    编程式事务

    编程式事务也就是同通过PlatformTransactionManager来实现对事务的管理,当然,spring也提供了模板类来管理事务,只需要在ioc容器中配置一个bean,就可以实现采用模板类来操作事务。

    配置一个事务模板:

    <!--配置事务模板,使用事务模板执行事务中的操作-->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="dataSourceTransactionManager"/>
    </bean>
    

    在事务的地方(service)通过模板来编程式的控制事务。

     public void transfer(String from, String to, Double money) {
            /*
                编程式事务:在service层注入一个事务模板,将事务中的多个操作放置到模板中进行。
                    spring的事务模板对象会根据配置的事务模板执行事务。
    
    
    
             */
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    //转账过程中,加钱和扣钱应该是一个事务,才能保证数据库数据的完整性。
                    //扣钱
                    accountDao.outMoney(from, money);
                    int i = 1 / 0;
                    //加钱
                    accountDao.inMoney(to, money);
                }
            });
        }
    

    声明式事务

    声明式事务管理有两种常用的方式,一种是基于tx和aop命名空间的xml配置文件,一种是基于@Transactional注解

    XML配置方式

    例如:

    <!-- 通知 -->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <!-- 传播行为 -->
                <tx:method name="save*" propagation="REQUIRED"/>
                <tx:method name="insert*" propagation="REQUIRED"/>
                <tx:method name="add*" propagation="REQUIRED"/>
                <tx:method name="create*" propagation="REQUIRED"/>
                <tx:method name="delete*" propagation="REQUIRED"/>
                <tx:method name="update*" propagation="REQUIRED"/>
                <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
                <tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
                <tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
            </tx:attributes>
        </tx:advice>
        <!-- 切面 -->
        <aop:config>
            <!-- 切入点表达式 -->
            <aop:pointcut id="pointCut" expression="execution(* com.abc.service.*.*(..))"/>
            <!-- 配置事务增强 -->
            <aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut"/>
        </aop:config>
    

    @Transaction注解配置方式

    这种方式显得更加简洁,
    第一步,只需要在配置文件中开启对注解事务管理的支持:

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

    第二步,在需要事务管理的地方加上@Transactional注解:

       @Transactional(rollbackFor=Exception.class)
       public void transfer(String from, String to, Double money) {
    
                   //转账过程中,加钱和扣钱应该是一个事务,才能保证数据库数据的完整性。
                    //扣钱
                    accountDao.outMoney(from, money);
                    int i = 1 / 0;
                    //加钱
                    accountDao.inMoney(to, money);
        }
    

    相关文章

      网友评论

        本文标题:spring事务管理

        本文链接:https://www.haomeiwen.com/subject/gcpwcftx.html