1 事务的回顾

1.1 什么是事务?

事务就是由一组 SQL 组成的单元,该单元要么整体执行成功,要么整体执行失败。

1.2 事务的 ACID 属性

  • 原子性(Atomicity):指事务中包含所操作的 SQL 是一个不可分割的工作单位,要么都执行成功,要么都执行失败,其中只要有一条 SQL 出现错误都会回滚到原来的状态。
  • 一致性(Consistency):事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。比如 A 和 B 两者的钱加起来一共是 1000,那么不管 A 和 B 之间如何转账、转几次账,事务结束后两个用户的钱相加起来应该还得是 1000,并且在当前事务中,A 减了多少钱,B 加了多钱这个中间状态是不可见的,这就是事务的一致性。
  • 隔离性(Isolation):一个事务所做的修改在最终提交以前,对其他事务是不可见的。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。比如 A 正在从一张银行卡中取钱,在 A 取钱结束前,B 不能向这张卡转账。
  • 持久性(Durability):指的是一个事务一旦被提交,数据就被永远的存储到磁盘上了,即使系统发生故障,数据仍然不会丢失。

1.3 事务执行过程中的并发问题

  • 脏读:事务 A 读取了事务 B 更新并且未提交的数据,然后 B 回滚操作,那么 A 读取到的数据是脏数据
  • 初始状态:数据库中 age 字段数据的值是 20
  • T 1 把 age 修改为了 30
  • T 2 读取了 age 现在的值:30
  • T 1 回滚了自己的操作,age 恢复为了原来的 20
  • 此时 T 2 读取到的 30 就是一个不存在的“脏”的数据
  • 不可重复读:事务 A 多次读取同一数据,事务 B 在事务 A 多次读取的过程中,对数据作了更新并提交,导致事务 A 多次读取同一数据时,结果不一致。
  • T 1 第一次读取 age 是 20
  • T 2 修改 age 为 30 并提交事务,此时 age 确定修改为了 30
  • T 1 第二次读取 age 得到的是 30
  • 幻读:事务 A 从一个表中读取了一个字段,然后 B 在该表中插入/删除了一些新的行。之后, 如果 A 再次读取同一个表, 就会多/少几行,就好像发生了幻觉一样,这就叫幻读。
  • T 1 第一次执行 count(*) 返回 500
  • T 2 执行了 insert 操作
  • T 1 第二次执行 count(*) 返回 501,感觉像是出现了幻觉

补充:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

1.4 事务的隔离级别

SQL 标准定义了 4 种隔离级别(从低到高),分别对应可能出现的数据不一致的情况:

事务隔离级别脏读不可重复读幻读
读未提交(read-uncommitted)
读已提交(read-committed)
可重复读(repeatable-read)
串行化(serializable)

4 种隔离级别的描述:

  • 读未提交(read-uncommitted):允许 A 事务读取其他事务未提交和已提交的数据
  • 读已提交(read-committed):只允许 A 事务读取其他事务已提交的数据
  • 可重复读(repeatable-read):确保事务可以多次从一个字段中读取相同的值。在这个事务持续期间,禁止其他事务对这个字段进行更新;注意:mysql 中使用了 MVCC 多版本控制技术,在这个级别也可以避免幻读。
  • 串行化(serializable):锁定整个表,让对整个表的操作全部排队串行执行。能解决所有并发问题,安全性最好,但是性能极差,基本不用。

2 Spring 事务的相关接口

Spring 事务管理的相关接口有三个,如下:

  • PlatformTransactionManager:事务管理器,为不同的数据访问技术的事务提供不同的接口实现
  • TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
  • TransactionStatus: 事务的运行状态

2.1 PlatformTransactionManager: 事务管理接口

Spring 的事务机制是用统一的机制来处理不同数据访问技术的事务处理,Spring 并不直接管理事务,而是提供了多种事务管理器。Spring 的事务机制提供了一个org. Springframework. Transaction. PlatformTransactionManager 接口,将事务管理的职责委托给 JDBC 或者 Hibernate 等持久化机制所提供的相关平台框架的事务来实现。通过这个接口,Spring 为各个平台如 JDBC、Hibernate 等都提供了对应的事务管理器,其具体的实现就是各个平台自己的事情了,对应的相关实现如下表所示。

数据库访问技术实现
JDBCDataSourceTransactionManager
JPAJpaTransactionManager
HibernateHibernateJpaTransactionManager
JDOJdoTransactionManager
分布式事务JtaTransactionManager
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface PlatformTransactionManager {
 //获得事务
 TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
 //提交事务
 void commit(TransactionStatus var1) throws TransactionException;
 //回滚事务
 void rollback(TransactionStatus var1) throws TransactionException;
}

2.2 TransactionDefinition: 事务属性

事务管理器接口 PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition) 方法来得到一个事务,这个方法里面的参数是 TransactionDefinition 类,这个类就定义了一些基本的事务属性。

那么什么是 事务属性 呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。
事务属性包含了 5 个方面:

  1. 只读属性 - readOnly
  2. 超时属性 - timeout
  3. 回滚策略 - rollbackFor 等
  4. 隔离级别 - isolation
  5. 传播行为 - propagation

TransactionDefinition 接口中定义了 5 个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等。

package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface TransactionDefinition {
 int PROPAGATION_REQUIRED = 0;
 int PROPAGATION_SUPPORTS = 1;
 int PROPAGATION_MANDATORY = 2;
 int PROPAGATION_REQUIRES_NEW = 3;
 int PROPAGATION_NOT_SUPPORTED = 4;
 int PROPAGATION_NEVER = 5;
 int PROPAGATION_NESTED = 6;
 int ISOLATION_DEFAULT = -1;
 int ISOLATION_READ_UNCOMMITTED = 1;
 int ISOLATION_READ_COMMITTED = 2;
 int ISOLATION_REPEATABLE_READ = 4;
 int ISOLATION_SERIALIZABLE = 8;
 int TIMEOUT_DEFAULT = -1;
 // 返回事务的传播行为,默认值为 REQUIRED。
 int getPropagationBehavior();
 //返回事务的隔离级别,默认值是 DEFAULT
 int getIsolationLevel();
 // 返回事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
 int getTimeout();
 // 返回是否为只读事务,默认值为 false
 boolean isReadOnly();
 @Nullable
 String getName();
}

2.3 TransactionStatus: 事务状态

TransactionStatus 接口用来记录事务的状态该接口定义了一组方法, 用来获取或判断事务的相应状态信息。
PlatformTransactionManager.getTransaction(…) 方法返回一个 TransactionStatus 对象。

TransactionStatus 接口接口内容如下:

public interface TransactionStatus{
 boolean isNewTransaction(); // 是否是新的事务
 boolean hasSavepoint(); // 是否有恢复点
 void setRollbackOnly(); // 设置为只回滚
 boolean isRollbackOnly(); // 是否为只回滚
 boolean isCompleted; // 是否已完成
}

3 Spring 事务的管理

Spring 框架中对事务的支持有两种:

  • 编程式事务管理
  • 声明式事务管理(推荐)

3.1 编程式事务管理

编程式事务管理:事务的相关操作完全由开发人员通过编码实现。 所以编程式事务管理是侵入性事务管理,使用 TransactionTemplate 或者直接使用 PlatformTransactionManager,对于编程式事务管理,Spring 推荐使用 TransactionTemplate。但是我们基本不推荐使用编程式事务。 下图展示的是编程式事务的实现,完全有程序员来实现。

使用 TransactionTemplate 进行编程式事务管理的示例代码如下:

@Autowired
private TransactionTemplate transactionTemplate;
 
public void testTransaction() {
 transactionTemplate.execute(new TransactionCallbackWithoutResult() {
	 @Override
	 protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
		 try {
			 // .... 业务代码
		 } catch(Exception e){
			 transactionStatus.setRollbackOnly();//回滚
		 }
	 }
 });
}

使用 TransactionManager 进行编程式事务管理的示例代码如下:

@Autowired
private PlatformTransactionManager transactionManager;
 
public void testTransaction() {
	 TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
	 try {
		 // .... 业务代码
		 transactionManager.commit(status);
	 } catch(Exception e) {
		 transactionManager.rollback(status);
	 }
}

3.2 声明式事务管理

声明式事务管理:事务的控制交给 Spring 框架来管理,开发人员只需要在 Spring 框架的配置文件中声明你需要的功能即可。

Spring 中声明式事务管理的底层是基于 AOP 来完成的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。声明式事务它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。

4 基于注解的声明式事务

4.1 注解作用范围

在 Spring 中使用声明式事务一般会使用注解来实现,即 @Transactional 注解,该注解可以使用在类、接口和方法上:

  • 作用在类:表示所有该类的 public 方法都配置相同的事务属性信息。
  • 作用在方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。
  • 作用于接口:不推荐这种使用方法,因为一旦标注在 Interface 上并且配置了 Spring AOP 使用 CGLib 动态代理,将会导致@Transactional 注解失效。

因为 service 层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在 service 层处理。一个方法对应一个事务。

public class BookService {
	 @Transactional
	 public void buyBook(int boodID, int customID) {...}
}

需要特别注意的是,此@Transactional 注解来自 org.springframework.transaction.annotation 包,而不是 javax.transaction

4.2 注解源码

@Transactional 注解源码如下,里面包含了基本事务属性的配置:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
	@AliasFor("transactionManager")
	String value() default "";
	@AliasFor("value")
	String transactionManager() default "";
	Propagation propagation() default Propagation.REQUIRED;
	Isolation isolation() default Isolation.DEFAULT;
	int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
	boolean readOnly() default false;
	Class<? extends Throwable>[] rollbackFor() default {};
	String[] rollbackForClassName() default {};
	Class<? extends Throwable>[] noRollbackFor() default {};
	String[] noRollbackForClassName() default {};
}

4.3 注解参数

@Transactional 注解中常用参数:

  • value :当在配置文件中有多个 TransactionManager,可以用该属性指定选择哪个事务管理器。
  • propagation :事务的传播行为,默认值为 REQUIRED
  • isolation :事务的隔离级别,默认值为 DEFAULT,即采用数据库的默认隔离级别。
  • timeout :事务的超时时间(单位是秒),默认值为 -1。如果超过该时间限制但事务还未提交,则自动回滚事务。
  • readOnly :用于指定事务是否为只读事务,默认值为 false。为了忽略那些不需要事务的方法,比如 select 读取数据,可以设置 readOnly = true。
  • rollbackFor :指定能够触发事务回滚的异常类型,可以指定多个异常类型。
  • rollbackForClassName 指定需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
  • noRollbackFor :指定不用回滚事务的异常类型,可以指定多个异常类型。
  • noRollbackForClassName :指定不需要回滚的异常类类名。可以指定多个异常类名。

4.4 注解简单使用

下面是@Transactional 注解的简单使用(定义的是异常类是 class 对象):

@Transactional(
 Propagation = Propagation. REQUIRED, // 传播行为
 Isolation = Isolation.DEFAULT, // 隔离级别
 timeout = 1000, // 事务的超时时间(单位是秒)
 readOnly = true, // 事务是否为只读
 rollbackFor = Exception.Class, // 能够触发事务回滚的异常类型
 noRollbackFor = Exception.Class // 不用回滚事务的异常类型
)
public void doSomething() {
        //查询图书的价格
        Integer price = bookDao.getPriceByBookId(bookId);
        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId, price);
}

注意:在 Spring 中使用事务还需要在 xml 配置文件中配置如下内容:

<!-- 1.配置事务管理器的bean -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
	<!-- 给事务管理器装配数据源 -->
	<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 2.开启基于注解的声明式事务 -->
<!-- 在transaction-manager属性中指定前面配置的事务管理器的bean的id -->
<!-- transaction-manager属性的默认值是transactionManager,如果正好前面bean的id就是这个默认值,那么transaction-manager属性可以省略不配 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 3.配置自动扫描的包 -->
<context:component-scan base-package="com.thr.service"/>

整个过程可参考:1 Spring 事务

4.5 全注解配置事务

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
 
@Configuration
@ComponentScan("com.atguigu.spring6")
@EnableTransactionManagement
public class SpringConfig {
 
    @Bean
    public DataSource getDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }
 
    @Bean(name = "jdbcTemplate")
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
 
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}

4.6 注解原理

我们知道,@Transactional 的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口, 会使用 CGLIB 动态代理。

多提一嘴:createAopProxy() 方法决定了是使用 JDK 还是 Cglib 来做动态代理,源码如下:

Public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
 
	@Override
	Public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		If(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config. GetTargetClass();
			If(targetClass == null) {
				Throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			If(targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				Return new JdkDynamicAopProxy(config);
			}
			Return new ObjenesisCglibAopProxy(config);
		}
		Else {
			Return new JdkDynamicAopProxy(config);
		}
	}
 .......
}

如果一个类或者一个类中的 public 方法上被标注 @Transactional 注解的话,Spring 容器就会在启动的时候为其创建一个代理类,在调用被 @Transactional 注解的 public 方法的时候,实际调用的是,TransactionInterceptor 类中的 invoke() 方法。这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。

TransactionInterceptor 类中的 invoke() 方法内部实际调用的是 TransactionAspectSupport 类的 invokeWithinTransaction() 方法。由于新版本的 Spring 对这部分重写很大,而且用到了很多响应式编程的知识,这里就不列源码了。

4.7 Spring AOP 自调用问题

若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法,有 @Transactional 注解的方法的事务会失效。

这是由于 Spring AOP 代理的原因造成的,因为只有当 @Transactional 注解的方法在类以外被调用的时候,Spring 事务管理才生效。

MyService 类中的 method 1() 调用 method 2() 就会导致 method 2() 的事务失效。

@Service
Public class MyService {
 
Private void method 1() {
 Method 2();
 //......
}
@Transactional
 Public void method 2() {
 //......
 }
}

解决办法就是避免同一类中自调用或者使用 AspectJ 取代 Spring AOP 代理。

5 事务属性

5.1 传播行为

事务的传播机制一般用在事务的嵌套中,当事务方法被另一个事务方法调用时,则应该指定事务如何传播。比如事务方法 A 直接或间接调用了方法 B,那么这两个方法是各自作为独立的方法提交,还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。

注:事务的传播行为和隔离级别都定义在TransactionDefinition接口中:TransactionDefinition 事务属性

事务的传播行为如下表所示(主要学习前两个即可,其它的简单了解):

事务传播行为描述
PROPAGATION_REQUIRED支持外层事务。这是 Spring 默认的传播机制,能满足绝大部分业务需求,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,则创建一个新的事务。
PROPAGATION_REQUIRES_NEW不支持外层事务。该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可。
PROPAGATION_SUPPORTS支持外层事务。如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务。
PROPAGATION_NOT_SUPPORTED不支持外层事务。该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码
PROPAGATION_NEVER不支持外层事务。该传播机制不支持外层事务,即如果外层有事务就抛出异常
PROPAGATION_MANDATORY支持外层事务。与 NEVER 相反,如果外层没有事务,则抛出异常
PROPAGATION_NESTEDSpring 所特有的。该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚,等价于 TransactionDefinition. PROPAGATION_REQUIRED。

为了方便使用,Spring 定义了枚举类

Package org. Springframework. Transaction. Annotation;
Import org. Springframework. Transaction. TransactionDefinition;
Public enum Propagation {
	REQUIRED(TransactionDefinition. PROPAGATION_REQUIRED),
	SUPPORTS(TransactionDefinition. PROPAGATION_SUPPORTS),
	MANDATORY(TransactionDefinition. PROPAGATION_MANDATORY),
	REQUIRES_NEW(TransactionDefinition. PROPAGATION_REQUIRES_NEW),
	NOT_SUPPORTED(TransactionDefinition. PROPAGATION_NOT_SUPPORTED),
	NEVER(TransactionDefinition. PROPAGATION_NEVER),
	NESTED(TransactionDefinition. PROPAGATION_NESTED);
	Private final int value;
	Propagation(int value) {		this. Value = value;	}
	Public int value() {		return this. Value;	}
}

简单测试 REQUIRED 和 REQUIRES_NEW 两种传播行为:

①、在 EmployeeServiceImpl 中增加了两个方法:updateOne() 和 updateTwo():

image

②、创建一个 PropagationServiceImpl 类

image|500

③、junit 测试代码:

image|500

④、测试结论:

测试 REQUIRED:两个方法的操作都没有生效,updateTwo() 方法回滚,导致 updateOne() 也一起被回滚,因为他们都在 propagationService. Update() 方法开启的同一个事务内。

测试 REQUIRES_NEW:把 updateOne() 和 updateTwo() 这两个方法上都使用下面的设置:

@Transactional(readOnly = false, propagation = Propagation. REQUIRES_NEW)

结果:

  • UpdateOne()没有受影响,成功实现了更新
  • UpdateTwo() 自己回滚

原因:上面两个方法各自运行在自己的事务中。

更多关于事务传播行为的内容请看这篇文章:《太难了~面试官让我结合案例讲讲自己对 Spring 事务传播行为的理解。》

5.2 隔离级别

事务的隔离级别定义了一个事务可能受其他并发事务影响的程度。隔离级别可以不同程度的解决脏读、不可重复读、幻读。

  • ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ 隔离级别,Oracle 默认采用的 READ_COMMITTED 隔离级别。
  • ISOLATION_READ_UNCOMMITTED:不可提交读,允许读取尚未提交事务的数据,可能会导致脏读、不可重复读、幻读。
  • ISOLATION_READ_COMMITTED:读已提交,读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • ISOLATION_REPEATABLE_READ:可重复读,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • ISOLATION_SERIALIZABLE:串行化,这种级别是最高级别,完全服从 ACID 的隔离级别,确保阻止脏读、不可重复读以及幻读。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰。但是严重影响程序的性能。几乎不会用到该级别。

同样,Spring 定义了一个枚举类

Public enum Isolation {
	DEFAULT(TransactionDefinition.isOLATION_DEFAULT),
	READ_UNCOMMITTED(TransactionDefinition.isOLATION_READ_UNCOMMITTED),
	READ_COMMITTED(TransactionDefinition.isOLATION_READ_COMMITTED),
	REPEATABLE_READ(TransactionDefinition.isOLATION_REPEATABLE_READ),
	SERIALIZABLE(TransactionDefinition.isOLATION_SERIALIZABLE);
	Private final int value;
	Isolation(int value) {		this. Value = value;	}
	Public int value() {		return this. Value;	}
}

5.3 只读属性

一个事务如果是做查询操作,可以设置为只读,此时数据库可以针对查询操作来做优化,有利于提高性能。实际开发时建议把查询操作设置为只读。

@Transactional(readOnly = true)
Public void doSomething() { }

如果是针对增删改方法设置只读属性,则会抛出下面异常:

表面的异常信息:TransientDataAccessResourceException: PreparedStatementCallback
根本原因:SQLException: Connection is read-only. Queries leading to data modification are not allowed(连接是只读的。查询导向数据的修改是不允许的。)

很多人就会疑问了,为什么我一个数据查询操作还要启用事务支持呢?

拿 MySQL 的 innodb 举例子,根据官网 https://dev.mysql.com/doc/refman/5.7/en/innodb-autocommit-commit-rollback.html 描述:

MySQL 默认对每一个新建立的连接都启用了 autocommit 模式。在该模式下,每一个发送到 MySQL 服务器的 sql 语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务,并开启一个新的事务。

但是,如果你给方法加上了 Transactional 注解的话,这个方法执行的所有 sql 会被放在一个事务中。如果声明了只读事务的话,数据库就会去优化它的执行,并不会带来其他的什么收益。

如果不加 Transactional,每条 sql 会开启一个单独的事务,中间被其它事务改了数据,都会实时读取到最新值。

分享一下关于事务只读属性,其他人的解答:

  1. 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持 SQL 执行期间的读一致性;
  2. 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询 SQL 必须保证整体的读一致性,否则,在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。

5.4 事务回滚的异常

在@Transactional 注解中如果不配置 rollbackFor 属性,那么事物只会在遇到 RuntimeException 和 Error 的时候才会回滚,遇到检查型 (checked) 不会回滚。

加上 rollbackFor=Exception.Class,可以让事物在遇到非运行时异常时也回滚。

image|500

设置方式如下所示(实际开发时通常也建议设置为根据 Exception 异常回滚):

@Transactional(
 Propagation = Propagation. REQUIRED, // 传播行为
 Isolation = Isolation. DEFAULT, // 隔离级别
 Timeout = 3000, // 事务的超时时间
 ReadOnly = true, // 事务是否为只读
 RollbackFor = Exception. Class, // 能够触发事务回滚的异常类型
)
Public void doSomething() { }

5.5 超时属性

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

@Transactional(timeout = 60) //单位为秒
Public void doSomething() {  }

6 基于 XML 的声明式事务

使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代理。当目标类较多,配置文件会变得非常臃肿。
使用 XML 配置顾问方式可以自动为每个符合切入点表达式的类生成事务代理。其用法很简单,只需将前面代码中关于事务代理的配置删除,再替换为如下内容即可。

基于 XML 的方式配置声明式事务也比较的简单,其配置的方式如下所示:

<!-- 1.配置事务管理器的bean -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
	<!-- 给事务管理器装配数据源 -->
	<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置基于XML的声明式事务 -->
<aop:config>
	<!-- 配置事务切面的切入点表达式 -->
	<aop:pointcut id="txPointCut" expression="execution(* *..*Service.*(..))"/>
	<!-- 将切入点表达式和事务通知关联起来 -->
	<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
<!-- 配置事务通知:包括对事务管理器的关联,还有事务属性 -->
<!-- 如果事务管理器的bean的id正好是transactionManager,则transaction-manager属性可以省略 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
	<!-- 给具体的事务方法配置事务属性 -->
	<tx:attributes>
		<!-- 指定具体的事务方法 -->
		<tx:method name="get*" read-only="true"/>
		<tx:method name="query*" read-only="true"/>
		<tx:method name="count*" read-only="true"/>
		<!-- 增删改方法 -->
		<tx:method name="update*" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
		<tx:method name="insert*" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
		<tx:method name="delete*" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
	</tx:attributes>
</tx:advice>

注意事项:

  1. 虽然切入点表达式已经定位到了所有需要事务的方法,但是在 tx: attributes 中还是必须配置事务属性。这两个条件缺一不可。缺少任何一个条件,方法都加不上事务。
  2. 另外,tx: advice 导入时需要注意名称空间的值,不要导错了,因为导错了很难发现。

image 3. 基于 XML 配置事务,需要引入 AspectJ 仓库

<dependency>
	<groupId>org. springframework</groupId>
	<artifactId>spring-aspects</artifactId>
	<version>6.0.2</version>
</dependency>