Spring 事务管理及遇到的坑

原创
2019/03/18 09:57
阅读数 3K

什么是事务?

事务是逻辑上的一组操作,要么都执行,要么都不执行.

事物的特性(ACID)

原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;

一致性: 执行事务前后,数据保持一致;

隔离性: 并发访问数据库时,一个用户的事物不被其他事物所干扰,各并发事务之间数据库是独立的;

持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

事务属性

事务隔离级别

脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。

丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。

例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。

不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

不可重复度和幻读区别:

不可重复读的重点是修改,幻读的重点在于新增或者删除。

例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。

例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。

事务隔离级别

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

事务传播行为

所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。

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

事务超时

所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。

默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。

事务只读属性

只读事务用于客户代码只读但不修改数据的情形,对于一个函数,如果执行的只是单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;如果执行多条查询语句,例如统计查询,报表查询等,则多条查询SQL必须保证整体的读一致性;否则,若在前后两条查询SQL执行的间隙,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的情况,此时,应该启用事务支持。注意,是一次执行多次查询来统计某些信息,这时为了保证数据整体的一致性,要用只读事务。

spring事务回滚规则

spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。

默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。

@Transactional注解

属性 类型 描述
value String 可选的限定描述符,指定使用的事务管理器
propagation Propagation 可选的事务传播行为设置
isolation Isolation 可选的事务隔离级别设置
readOnly boolean 读写或只读事务,默认读写
timeout int 事务超时时间设置
rollbackFor Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
noRollbackFor Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
noRollbackForClassName 类名数组,必须继承自Throwable 不会导致事务回滚的异常类名字数组

@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
 
  public Foo getFoo(String fooName) {
    // do something
  }
 
  // these settings have precedence for this method
  //方法上注解属性会覆盖类注解上的相同属性
  @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  public void updateFoo(Foo foo) {
    // do something
  }
}

@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。

默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。

public class DefaultFooService implements FooService {
   @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  public Foo getFoo(String fooName) {
    // do something
  }
  @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  public void updateFoo(Foo foo) {
       //这样的调用事务级别将不会起作用
	   getFoo();
  }
}

程序演示

sql 语句

DROP TABLE IF EXISTS `user`;  
CREATE TABLE `user` (  
  `id` bigint(20) NOT NULL,  
  `account` varchar(255) NOT NULL,  
  `balance` float DEFAULT NULL COMMENT '用户余额',  
  PRIMARY KEY (`id`)  
) ENGINE=InnoDB DEFAULT CHARSET=utf8;  
  
-- ----------------------------  
-- Records of user  
-- ----------------------------  
INSERT INTO user VALUES ('1', 'itdragon', '100');  

DROP TABLE IF EXISTS `product`;  
CREATE TABLE `product` (  
  `id` bigint(20) NOT NULL,  
  `sku` varchar(255) NOT NULL COMMENT '商品的唯一标识',  
  `price` float NOT NULL COMMENT '商品价格',  
  `stock` int(11) NOT NULL COMMENT '商品库存',  
  PRIMARY KEY (`id`)  
) ENGINE=InnoDB DEFAULT CHARSET=utf8;  
  
-- ----------------------------  
-- Records of product  
-- ----------------------------  
INSERT INTO product VALUES ('1', 'java', '40', '10');  
INSERT INTO product VALUES ('2', 'spring', '50', '10');  

ssm程序如下

/**
 * @program: ssm
 * @description:
 * @author: lee
 * @create: 2019-03-15
 **/
@Controller
public class TransactionTest {

    @Autowired
    private PurchaseService purchaseService;

    @Autowired
    private BatchPurchase batchPurchase;

    /**
     * 用户买一本书
     * 基本用法-事务回滚
     * 把@Transactional 注释。假设当前用户余额只有10元。单元测试后,用户余额没有变,spring的库存却减少了。赚了!!!
     * 把@Transactional 注释打开。假设当前用户余额只有10元。单元测试后,用户余额没有变,spring的库存也没有减少。这就是回滚。
     * 回滚:按照业务逻辑,先更新库存,再更新余额。现在是库存更新成功了,但在余额逻辑抛出异常。最后数据库的值都没有变。也就是库存回滚了。
     */
    @RequestMapping("/static/test")
    public void test(){
        System.out.println("^^^^^^^^^^^^^^^^^@Transactional 最基本的使用方法");
        purchaseService.purchase("itdragon", "spring");
    }

    /**
     * 用户买多本书
     * 事务的传播性 -大事务中,有小事务,小事务的表现形式
     * 用@Transactional, 当前用户余额50,是可以买一本书的。运行结束后,数据库中用户余额并没有减少,两本书的库存也都没有减少。
     * 用@Transactional(propagation=Propagation.REQUIRED), 运行结果是一样的。
     * 把REQUIRED 换成 REQUIRES_NEW 再运行 结果还是一样。。。。。
     * 为什么呢??? 请看后面的嵌套事务讲解
     * 既然是事务的传播性,那当然是一个事务传播给另一个事务。
     * 需要新增一个事务类批量购买 batchPurchase事务, 包含了purchase事务。
     * 把 REQUIRED 换成 REQUIRES_NEW 运行的结果是:用户余额减少了,第一本书的库存也减少了。
     * REQUIRED:如果有事务在运行,当前的方法就在这个事务内运行。否则,就启动一个新的事务,并在自己的事务内运行。大事务回滚了,小事务跟着一起回滚。
     * REQUIRES_NEW:当前的方法必须启动新事务,并在自己的事务内运行。如果有事务在运行,应该将它挂起。大事务虽然回滚了,但是小事务已经结束了。
     */
    @RequestMapping("/static/batch")
    public void propagationTransaction() {
        System.out.println("^^^^^^^^^^^^^^^^^@Transactional(propagation) 事务的传播性");
        purchaseService.batchPurchase("itdragon", Arrays.asList("java", "spring"));
    }
    @RequestMapping("/static/batch2")
    public void propagationTransaction2() {
        System.out.println("^^^^^^^^^^^^^^^^^@Transactional(propagation) 事务的传播性");
        batchPurchase.batchPurchase("itdragon", Arrays.asList("java", "spring"));
    }
}    

service层

@Service
public class BatchPurchase {

    @Autowired
    private PurchaseService purchaseService;
    /**
     * 批量购买书籍
     * @param account
     * @param skus
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void batchPurchase(String account, List<String> skus) {
        for (String sku : skus) {
            purchaseService.purchase(account, sku);
        }
    }
}    

/**
 * @program: ssm
 * @description:
 * @author: lee
 * @create: 2019-03-15
 **/
@Service
public class PurchaseService   {

    @Autowired
    private ShopDao shopDao;


    /**
     *
     * @param account 账户
     *
     * @param sku 书籍名称
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void purchase(String account, String sku) {
        //获取书的单价
        float price = shopDao.getBookPriceBySku(sku);
        //查询库存
        int  leaveNum=shopDao.getBookPriceBySkuNum(sku);

        if (leaveNum==0){
            throw new ProductException("库存不足!再看看其他产品吧!");
        }
        //更新数的库存
        shopDao.updateBookStock(leaveNum,sku);

        /**
         * 查询当前账户余额
         */
        float balance=shopDao.findUserBalance(account);

        if(balance < price){
            throw new UserException("您的余额不足!不支持购买!");
        }
        //更新用户余额
        shopDao.updateUserBalance(account, price);
        // 测试超时用的
        /*try {
            Thread.sleep(6000);
        } catch (InterruptedException e) {
        }*/
    }

  /**
     * 批量购买书籍
     * @param account
     * @param skus
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void batchPurchase(String account, List<String> skus) {
        for (String sku : skus) {
            //使嵌套事务起作用
            ((PurchaseService) AopContext.currentProxy()).purchase(account, sku);
            // 事务传播性将不起作用
            //purchase(account,sku);
        }
    }
}

以上代码有两种方式使事务的传播性起作用,方法一是采用新建一个类来进行批量业务处理,另一种方法就是强制使用代理使事务起作用。

mapper层ShopDao

/**
 * @program: ssm
 * @description:
 * @author: lee
 * @create: 2019-03-15 15:23
 **/
public interface ShopDao {
    /**
     * 得到书的价格
     * @param sku
     * @return
     */
    float getBookPriceBySku(@Param(value = "sku") String sku);

    /**
     * 更新数的库存
     * @param leaveNum
     * @param sku
     */
    void updateBookStock(@Param(value = "leaveNum") int leaveNum,
                         @Param(value = "sku") String sku);

    /**
     * 更新用户余额
     * @param account
     * @param price
     */
    void updateUserBalance(@Param(value = "account") String account,
                           @Param(value = "price") float price);


    /**
     * 查询书籍库存
     * @param sku
     * @return
     */
    int getBookPriceBySkuNum(@Param(value = "sku") String sku);

    /**
     * 查询当前账户余额
     * @param account
     */
    float findUserBalance(@Param(value = "account") String account);
}

mapper层ShopDao.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.plantform.transactions.mapper.ShopDao">
    <update id="updateBookStock">
      UPDATE product SET stock = stock -1 WHERE sku = #{sku}
    </update>
    <update id="updateUserBalance">
        UPDATE user SET balance = balance - #{price} WHERE account = #{account}
    </update>
    <select id="getBookPriceBySku" parameterType="String" resultType="float">
        SELECT price FROM product WHERE sku = #{sku}
    </select>
    <select id="getBookPriceBySkuNum" resultType="java.lang.Integer">
        SELECT stock FROM product WHERE sku = #{sku}
    </select>
    <select id="findUserBalance" resultType="java.lang.Float">
        SELECT balance FROM user WHERE account = #{account}
    </select>
</mapper>

异常类

/**
 * @program: ssm
 * @description:
 * @author: lee
 * @create: 2019-03-15
 **/
public class ProductException extends RuntimeException{

    private static final long serialVersionUID = 1L;

    public ProductException() {
        super();
    }

    public ProductException(String message) {
        super(message);
    }

}


/**
 * @program: ssm
 * @description:
 * @author: lee
 * @create: 2019-03-15
 **/
public class UserException extends RuntimeException{

    private static final long serialVersionUID = 1L;

    public UserException() {
        super();
    }

    public UserException(String message) {
        super(message);
    }

}

service类调用自己方法事务无效

SpringMvc项目下做的测试

@Controller
public class TestController {
	@Autowired
	private TestService testService;
	
	@RequestMapping("/test")
	public void testTransactional(){
		testService.testTransactional_A();
	}
}

serviec 服务类

@Service
public class TestService {
 
 
	@Transactional
	public void testTransactional_A() {
		System.out.println("这里是调用jdbc存库");
		testTransactional_B();
	}
	
	public void testTransactional_B() {
		System.out.println("这里是调用jdbc存库");
	}
}

之前事务是这样的,我是将@Transactional 放到了方法A上,方法A是是Controller直接调用的方法,方法B也是包含在方法A的事务中,事务是可用的,但是我需要将事务只开在B方法上,A方法不需要开启事务,然后将@Transactional注解 移到B方法上,事务失效了。

@Transactional 注解只能应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。

注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据。必须在配置文件中使用配置元素,才真正开启了事务行为。(spring配置文件中,开启声明式事务)

比如如下配置 其他没用的我没有去掉,希望大家能看到一些实际的项目配置,完整的项目地址在我的码云上,ssm脚手架

applicationContext.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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 加载property文件配置 -->
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:*.properties</value>
            </list>
        </property>
    </bean>
    <!-- 组件扫描 -->
    <context:component-scan base-package="com.plantform.**"/>
    <!-- 加载mybatis文件配置 -->
    <import resource="classpath*:spring-mybatis.xml"/>
    <!--redis-->
    <import resource="classpath*:spring-redis.xml"/>
    <!--shiro-->
    <import resource="classpath*:spring-shiro.xml"/>
    <bean id="customJobFactory" class="com.plantform.quartz.utils.CustomJobFactory"/>
    <bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <!--Scheduler推迟10秒启动-->
        <property name="startupDelay" value="10"/>
        <property name="autoStartup" value="true"/>
        <property name="jobFactory" ref="customJobFactory"/>
        <property name="applicationContextSchedulerContextKey" value="applicationContextKey"/>
        <property name="configLocation" value="classpath:quartz.properties"/>
    </bean>
</beans>

spring-mybatis.xml

<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.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">


    <!-- 配置DataSource数据源 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 数据库连接 -->
        <property name="driverClassName" value="${mysql.jdbc.driverClassName}"/>
        <property name="url" value="${mysql.jdbc.url}"/>
        <property name="username" value="${mysql.jdbc.username}"/>
        <property name="password" value="${mysql.jdbc.password}"/>
        <!--监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall-->
        <property name="filters" value="stat"/>
        <!-- 连接池设置 初始化时建立物理连接的个数-->
        <property name="initialSize" value="2"/>
        <!--最大连接池数量-->
        <property name="maxActive" value="100"/>
        <!--获取连接时最大等待时间,单位毫秒-->
        <property name="maxWait" value="30000"/>
        <!--是否缓存preparedStatement,也就是PSCache。
        PSCache对支持游标的数据库性能提升巨大,比如说oracle
        。在mysql下建议关闭。-->
        <property name="poolPreparedStatements" value="false"/>
        <!-- 这里配置提交方式,默认就是TRUE,可以不用配置 -->
        <property name="defaultAutoCommit" value="false"/>

        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <property name="minEvictableIdleTimeMillis" value="300000"/>

        <property name="validationQuery" value="SELECT 1"/>
        <!--建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,
        如果空闲时间大于timeBetweenEvictionRunsMillis,
        执行validationQuery检测连接是否有效。-->
        <property name="testWhileIdle" value="true"/>
        <!--申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。-->
        <property name="testOnBorrow" value="false"/>
        <!--归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能-->
        <property name="testOnReturn" value="false"/>
    </bean>


    <!-- (事务管理)transaction manager -->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 可通过注解控制事务 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <!-- 创建SqlSessionFactory,指定数据源 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!-- mapper和resultmap配置路径 -->
        <property name="mapperLocations">
            <list>
                <value>classpath:com/**/mapper/*.xml</value>
            </list>
        </property>
    </bean>
    <!-- Mapper接口所在包名,Spring会自动查找其下的Mapper
    myabatis使用MapperScannerConfigurer扫描模式后他会优先于PropertyPlaceholderConfigurer执行,所以这个时候,
    ${jdbc.maxActive }还没有被properties文件里面的值所替换,就出现TypeMismatchException
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定会话工厂,如果当前上下文中只定义了一个则该属性可省去 -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <property name="basePackage" value="com.**.mapper"/>
    </bean>

    <!-- 通知 -->
	 <!-- 演示程序中并没有用这里定义的事务传播行为,这是实际项目中的定义的事务传播行为 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 传播行为 -->
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="insert*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <!-- aop -->
    <aop:config>
        <aop:advisor advice-ref="txAdvice"
                     pointcut="execution(* com.*.service.impl.*.*(..))"/>
    </aop:config>

</beans>

嵌套事务

可以参考此链接 https://blog.csdn.net/yangchangyong0/article/details/51960143

嵌套是子事务套在父事务中执行,子事务是父事务的一部分,在进入子事务之前,父事务建立一个回滚点,叫save point,然后执行子事务,这个子事务的执行也算是父事务的一部分,然后子事务执行结束,父事务继续执行。重点就在于那个save point。看几个问题就明了了:

如果子事务回滚,会发生什么?

父事务会回滚到进入子事务前建立的save point,然后尝试其他的事务或者其他的业务逻辑,父事务之前的操作不会受到影响,更不会自动回滚。

如果父事务回滚,会发生什么?

父事务回滚,子事务也会跟着回滚!为什么呢,因为父事务结束之前,子事务是不会提交的,我们说子事务是父事务的一部分,正是这个道理。那么:

事务的提交,是什么情况?

是父事务先提交,然后子事务提交,还是子事务先提交,父事务再提交?答案是第二种情况,还是那句话,子事务是父事务的一部分,由父事务统一提交。

两个事务并发执行,彼此之间的数据是如何影响?

属性 说明
Serializable 最严格的级别,事务串行执行,资源消耗最大;
REPEATABLE READ 保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
READ COMMITTED 大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。
Read Uncommitted 保证了读取过程中不会读取到非法数据。

一个对照关系表:

属性 脏读 不可重复读 幻读
Serializable 不会 不会 不会
REPEATABLE READ 不会 不会
READ COMMITTED 不会
Read Uncommitted

嵌套事务回滚 1、内外都无try Catch的时候,外部异常,全部回滚。 2、内外都无try Catch的时候,内部异常,全部回滚。 3、外部有try Catch时候,内部异常,全部回滚 4、内部有try Catch,外部异常,全部回滚 5、友情提示:外层方法中调取其他接口,或者另外开启线程的操作,一定放到最后!!!(因为调取接口不能回滚,一定要最后来处理)

如果一定要将捕获,请捕获后又抛出RuntimeException(默认为异常捕获RuntimeException)。

spring 什么情况下进行事务回滚

Spring声明式事务默认情况下都是在抛出unchecked exception后才会触发事务的回滚

unchecked异常,即运行时异常runntimeException 回滚事务;

checked异常,即Exception可try{}捕获的不会回滚.当然也可配置spring参数让其回滚.

spring的事务边界是在调用业务方法之前开始的,业务方法执行完毕之后来执行commit or rollback(Spring默认取决于是否抛出runtime异常).

参考链接:https://segmentfault.com/a/1190000011379448#articleHeader3

参考链接:https://blog.csdn.net/yangchangyong0/article/details/51960143

展开阅读全文
打赏
1
4 收藏
分享
加载中
更多评论
打赏
0 评论
4 收藏
1
分享
返回顶部
顶部