• Spring 使用介绍(七)—— Spring事务


    一、数据库事务概述

    1、基本介绍

    事务必需满足ACID(原子性、一致性、隔离性和持久性)特性,缺一不可:

    • 原子性(Atomicity):即事务是不可分割的最小工作单元,事务内的操作要么全做,要么全不做;
    • 一致性(Consistency):在事务执行前数据库的数据处于正确的状态,而事务执行完成后数据库的数据还是处于正确的状态,即数据完整性约束没有被破坏;如银行转帐,A转帐给B,必须保证A的钱一定转给B,一定不会出现A的钱转了但B没收到,否则数据库的数据就处于不一致(不正确)的状态。
    • 隔离性(Isolation):并发事务执行之间无影响,在一个事务内部的操作对其他事务是不产生影响,这需要事务隔离级别来指定隔离性;
    • 持久性(Durability):事务一旦执行成功,它对数据库的数据的改变必须是永久的,不会因比如遇到系统故障或断电造成数据不一致或丢失。

    在实际项目开发中数据库操作一般都是并发执行的,即有多个事务并发执行,并发执行就可能遇到问题,目前常见的问题如下:

    • 丢失更新:两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失,这是由于没有加锁造成的;
    • 脏读:一个事务看到了另一个事务未提交的更新数据;
    • 不可重复读:在同一事务中,多次读取同一数据却返回不同的结果;也就是有其他事务更改了这些数据;
    • 幻读:一个事务在执行过程中读取到了另一个事务已提交的插入数据;即在第一个事务开始时读取到一批数据,但此后另一个事务又插入了新数据并提交,此时第一个事务又读取这批数据但发现多了一条,即好像发生幻觉一样。

    为了解决这些并发问题,需要通过数据库隔离级别来解决,在标准SQL规范中定义了四种隔离级别:

    • 未提交读(Read Uncommitted:最低隔离级别,一个事务能读取到别的事务未提交的更新数据,很不安全,可能出现丢失更新、脏读、不可重复读、幻读;
    • 提交读(Read Committed:一个事务能读取到别的事务提交的更新数据,不能看到未提交的更新数据,不可能可能出现丢失更新、脏读,但可能出现不可重复读、幻读;
    • 可重复读(Repeatable Read:保证同一事务中先后执行的多次查询将返回同一结果,不受其他事务影响,可能可能出现丢失更新、脏读、不可重复读,但可能出现幻读;
    • 序列化(Serializable:最高隔离级别,不允许事务并发执行,而必须串行化执行,最安全,不可能出现更新、脏读、不可重复读、幻读。

    2、事务类型

    数据库事务类型有本地事务和分布式事务:

    • 本地事务:就是普通事务,能保证单台数据库上的操作的ACID,被限定在一台数据库上;
    • 分布式事务:涉及两个或多个数据库源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成的),分布式事务旨在保证这些本地事务的所有操作的ACID,使事务可以跨越多台数据库;

    Java事务类型有JDBC事务和JTA事务:

    • JDBC事务:就是数据库事务类型中的本地事务,通过Connection对象的控制来管理事务;
    • JTA事务:JTA指Java事务API(Java Transaction API),是Java EE数据库事务规范, JTA只提供了事务管理接口,由应用程序服务器厂商(如WebSphere Application Server)提供实现,JTA事务比JDBC更强大,支持分布式事务。

    二、事务管理器

    1、Spring事务管理的核心是事务管理器抽象,由PlatformTransactionManager接口定义:

    public interface PlatformTransactionManager {  
           // 根据TransactionDefinition, 返回一个已经激活的事务或创建一个新的事务
           TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;  
           // 提交事务
           void commit(TransactionStatus status) throws TransactionException;  
           // 回滚事务
           void rollback(TransactionStatus status) throws TransactionException;  
    } 

    PlatformTransactionManager的两个内置实现:

    • DataSourceTransactionManager位于org.springframework.jdbc.datasource包中,数据源事务管理器,提供对单个javax.sql.DataSource事务管理,用于Spring JDBC抽象框架、MyBatis框架的事务管理;
    • JtaTransactionManager位于org.springframework.transaction.jta包中,提供对分布式事务管理的支持,并将事务管理委托给Java EE应用服务器事务管理器;

    2、简单实例(编程式)

    定义事务管理器

    <bean id="mysql" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> 
      ...
    </bean>
    
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="mysql"/>
    </bean>

    测试

    public class TransactionTest {
        
        private static ApplicationContext context;
        private static PlatformTransactionManager txManager;
        private static DataSource dataSource;
        private static JdbcTemplate jdbcTemplate;
        
        @BeforeClass
        public static void init() {
            context = new ClassPathXmlApplicationContext("spring-context.xml");
            txManager = context.getBean(PlatformTransactionManager.class);
            dataSource = context.getBean(DataSource.class);
            jdbcTemplate = new JdbcTemplate(dataSource);
        }
        
        @Test
        public void testPlatformTransactionManager() {
            DefaultTransactionDefinition def = new DefaultTransactionDefinition();
            def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
            def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
            TransactionStatus status = txManager.getTransaction(def);
            jdbcTemplate.update("INSERT INTO `user` VALUES(170, 'hello3', 96)");
            //txManager.commit(status);
            txManager.rollback(status);
        }
    }

    3、事务属性介绍

    事务属性通过TransactionDefinition接口实现定义,主要有事务隔离级别、事务传播行为、事务超时时间、事务是否只读

    i)隔离级别

    用于解决并发事务时出现的问题,由TransactionDefinition中的静态变量指定:

    ISOLATION_DEFAULT:默认隔离级别,即使用底层数据库默认的隔离级别
    ISOLATION_READ_UNCOMMITTED:未提交读
    ISOLATION_READ_COMMITTED:提交读,一般情况下我们使用这个
    ISOLATION_REPEATABLE_READ:可重复读
    ISOLATION_SERIALIZABLE:序列化

    ii)事务传播行为

    事务传播行为用于指定在多个事务方法间调用时,事务是如何在这些方法间传播的,Spring共支持7种传播行为,由TransactionDefinition中的静态变量指定:

    PROPAGATION_REQUIRED:如果当前存在一个逻辑事务,则加入该逻辑事务,否则将新建一个逻辑事务
    PROPAGATION_REQUIRES_NEW:每次都创建新的逻辑事务
    PROPAGATION_SUPPORTS:如果当前存在逻辑事务,就加入到该逻辑事务,否则就以非事务方式执行
    PROPAGATION_NOT_SUPPORTED:如果当前存在逻辑事务,就把当前事务暂停,以非事务方式执行
    PROPAGATION_MANDATORY:如果当前存在逻辑事务,使用当前事务执行,否则抛出异常
    PROPAGATION_NEVER:不支持事务,如果当前存在事务则抛出异常
    PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行,如果当前不存在事务,则创建一个新的事务,嵌套事务使用数据库中的保存点来实现,即嵌套事务回滚不影响外部事务,但外部事务回滚将导致嵌套事务回滚

    iii)事务超时时间

    设置事务的超时时间,单位为秒,默认为-1表示使用底层事务的超时时间;事务超时后会抛出异常,并导致自动回滚

    new DefaultTransactionDefinition().setTimeout(..); // 设置单个事务
    DataSourceTransactionManager. setDefaultTimeout(..); // 设置全局

    iv)只读

    new DefaultTransactionDefinition().setReadOnly(true); 

    三、基于XML的事务声明

    1、简单实例

    业务接口及实现

    public interface UserService {
        void addUser(String name, int age);
        void updateUserName(String name);
    }
    public class UserServiceImpl implements UserService {
        
        private DataSource dataSource;
        
        public void setDataSource(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    
        @Override
        public void addUser(String name, int age) {
            String sql = String.format("INSERT INTO `user`(user_name, age) VALUES('%s', %d)", name, age);
            new JdbcTemplate(dataSource).update(sql);
            
            this.updateUserName(name + "_" + name);
        }
    
        @Override
        public void updateUserName(String name) {
            String sql = String.format("UPDATE `user` SET user_name = '%s'", name);
            new JdbcTemplate(dataSource).update(sql);
            
            throw new RuntimeException("9965");
        }
    }

    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:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="  
        http://www.springframework.org/schema/beans        
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/aop  
        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">  
    
        <!-- 数据源 -->
        <bean id="mysql" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> 
         ...
        </bean>
        
        <!-- 业务类-->
        <bean id="userService" class="cn.matt.transaction.UserServiceImpl">
            <property name="dataSource" ref="mysql"/>
        </bean>
        
        <!-- 事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="mysql"/>
        </bean>
    
        <!-- advice -->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">  
            <tx:attributes>  
                <tx:method name="add*" propagation="REQUIRED" isolation="READ_COMMITTED"/>  
                <tx:method name="*" propagation="REQUIRED" isolation="READ_COMMITTED" read-only="true"/>  
            </tx:attributes>  
        </tx:advice>  
        
        <!-- aop -->
        <aop:config>  
            <aop:pointcut id="serviceMethod" expression="execution(* cn.matt..*.*(..))"/>  
            <aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice"/>  
        </aop:config> 
    </beans>  

    测试

    public class TransactionConfigTest {
        @Test
        public void testTransactionConfig() {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
            UserService userService = context.getBean(UserService.class);
            userService.addUser("alanx", 30);
        }
    }

    2、配置详解

    <tx:advice id="..." transaction-manager="transactionManager">  
        <tx:attributes>  
            <tx:method name="..."  
                               propagation=" REQUIRED"  
                               isolation="READ_COMMITTED"  
                               timeout="-1"  
                               read-only="false"  
                               no-rollback-for=""   
                               rollback-for=""/>  
            ...
        </tx:attributes>  
    </tx:advice> 

    详细说明:

    • <tx:advice>
      • id  指定通知的名字
      • transaction-manager  指定事务管理器,默认为“transactionManager”
    • <tx:method>
      • name      定义与事务属性相关联的方法名,可以使用"*"通配符匹配一组方法
      • propagation 定义事务传播行为,其值由TransactionDefinition的静态变量“PROPAGATION_”后边部分指定,默认为“REQUIRED”
      • isolation    定义事务隔离级别,其值可以通过TransactionDefinition的静态变量“ISOLATION_”后边部分指定,默认为“DEFAULT”
      • timeout      事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统
      • read-only   事务只读设置,默认为false,表示不是只读
      • rollback-for:定义触发回滚的异常,以“,”分割,默认任何RuntimeException 将导致事务回滚
      • no-rollback-for:不被触发进行回滚的 Exception(s),以“,”分割

     四、基于注解的事务声明

    1、简单实例

    业务接口及实现与XML方式的实例相同,不同之处是在实现方法上添加注解@Transactional

    public interface UserService {
        void addUser(String name, int age);
        void updateUserName(String name);
    }
    public class UserServiceImpl implements UserService {
        
        private DataSource dataSource;
        
        public void setDataSource(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    
        @Transactional(propagation = Propagation.REQUIRED)
        @Override
        public void addUser(String name, int age) {
            String sql = String.format("INSERT INTO `user`(user_name, age) VALUES('%s', %d)", name, age);
            new JdbcTemplate(dataSource).update(sql);
            
            this.updateUserName(name + "_" + name);
        }
    
        @Transactional(propagation = Propagation.REQUIRED)
        @Override
        public void updateUserName(String name) {
            String sql = String.format("UPDATE `user` SET user_name = '%s'", name);
            new JdbcTemplate(dataSource).update(sql);
            
            throw new RuntimeException("9965");
        }
    }

    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:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="  
        http://www.springframework.org/schema/beans        
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/aop  
        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">  
        
        <!-- 数据源 -->
        <bean id="mysql" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> 
         ...
        </bean>
        
        <!-- 业务类-->
        <bean id="userService" class="cn.matt.transaction.UserServiceImpl">
            <property name="dataSource" ref="mysql"/>
        </bean>
        
        <!-- 事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="mysql"/>
        </bean>
        
        <!-- 开启事务注解 -->
        <tx:annotation-driven transaction-manager="transactionManager"/>  
    </beans>  

    测试类与XML相同

    public class TransactionConfigTest {
        @Test
        public void testTransactionConfig() {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
            UserService userService = context.getBean(UserService.class);
            userService.addUser("jerry", 30);
        }
    }

    2、配置详解

    <tx:annotation-driven/>用于开启对注解事务管理的支持,具有以下属性:

    • transaction-manager:指定事务管理器名字,默认为transactionManager
    • proxy-target-class:表示将使用的代码机制,默认false表示使用JDK代理,如果为true将使用CGLIB代理
    • order:定义事务通知顺序,默认Ordered.LOWEST_PRECEDENCE,表示将顺序决定权交给AOP来处理

    @Transactional注解用于指定事务属性,具体属性如下:

    • value:指定事务管理器名字,默认使用<tx:annotation-driven/>指定的事务管理器,用于支持多事务管理器环境
    • propagation:指定事务传播行为,默认为Required,使用Propagation.REQUIRED指定
    • isolation:指定事务隔离级别,默认为“DEFAULT”,使用Isolation.DEFAULT指定
    • readOnly:指定事务是否只读,默认false表示事务非只读
    • timeout:指定事务超时时间,以秒为单位,默认-1表示事务超时将依赖于底层事务系统
    • rollbackFor:指定一组异常类,遇到该类异常将回滚事务
    • rollbackForClassname:指定一组异常类名字,其含义与<tx:method>中的rollback-for属性语义完全一样
    • noRollbackFor:指定一组异常类,即使遇到该类异常也将提交事务,即不回滚事务
    • noRollbackForClassname:指定一组异常类名字,其含义与<tx:method>中的no-rollback-for属性语义完全一样

    3、注意事项

    使用@Transactional注解事务管理需要特别注意以下几点:

    • 如果在接口、实现类或方法上都指定了@Transactional 注解,则优先级顺序为方法>实现类>接口
    • 建议只在实现类或实现类的方法上使用@Transactional,而不要在接口上使用,这是因为如果使用JDK代理机制是没问题,因为其使用基于接口的代理;而使用使用CGLIB代理机制时就会遇到问题,因为其使用基于类的代理而不是接口,这是因为接口上的@Transactional注解是“不能继承的”
    • 默认只对RuntimeException异常回滚
    • 在使用Spring代理时,默认只有在public可见度的方法的@Transactional 注解才是有效的,其它可见度(protected、private、包可见)的方法上即使有@Transactional 注解也不会应用这些事务属性的,Spring也不会报错,如果你非要使用非公共方法注解事务管理的话,可考虑使用AspectJ

     参考:

    第九章  Spring的事务 之 9.1 数据库事务概述 ——跟我学spring3

    第九章  Spring的事务 之 9.2 事务管理器 ——跟我学spring3

    第九章  Spring的事务 之 9.3 编程式事务 ——跟我学spring3

    第九章  Spring的事务 之 9.4 声明式事务 ——跟我学spring3

     javaweb学习总结(三十八)——事务

    事务与隔离级别笔记

  • 相关阅读:
    python之路
    go mod
    黑苹果流程
    mac go配置,环境配置
    mac重装系统
    多级分销概念 MongoDB||MySQL
    MongoDB查询mgov2的聚合方法
    linux被当矿机排查案例
    docker-compose容器中redis权限问题
    docker-compose中redis查询版本
  • 原文地址:https://www.cnblogs.com/MattCheng/p/8986662.html
Copyright © 2020-2023  润新知