spring事务的面试题 —— 事务的特性、传播机制、隔离机制、注解
目录
1、事务的四个特性
2、如何使用事务@Transactional
3、事务的传播机制
3.1 事务传播机制的概念
3.2 事务传播机制的分类
1、PROPAGATION_REQUIRED (默认)
2、PROPAGATION_SUPPORTS
3、PROPAGATION_MANDATORY
4、PROPAGATION_REQUIRES_NEW
5、PROPAGATION_NOT_SUPPORTED
6、PROPAGATION_NEVER
7、PROPAGATION_NESTED
4、事务的隔离机制
4.1 事物隔离机制的概念
4.2 幻读、不可重复读取、脏读的概念
4.3 事务隔离级别
4.4 Spring隔离级别可选值
4.5 spring如何设置隔离级别(代码)
5、事务注解
1. @Transactional (最核心注解)
2.使用范围:
1、事务的四个特性
特性 | 含义 | 作用 | 关键点/并发问题 | 实现机制 |
---|---|---|---|---|
原子性 | 事务被视为一个不可分割的最小工作单元。事务中的所有操作要么全部成功执行,要么全部不执行。不存在部分执行的情况。 | 确保操作完整性。例如银行转账的两个操作要么都成功,要么都不生效。 | 强调“不可分割”,没有中间状态。 | 主要通过回滚日志实现,记录事务前状态,失败时恢复到之前的状态。 |
一致性 | 事务执行的结果必须使数据库从一个一致性状态转换到另一个一致性状态。一致性是指数据满足业务规则和约束。 | 保证数据逻辑正确性和有效性。例如转账前后总金额不变(业务规则)。 | 数据库在事务执行前后都必须处于一致状态;依赖应用层逻辑和数据库约束(主键、外键等)。 | 应用层逻辑 + 数据库约束 + 其他ACID特性(原子性、隔离性、持久性)共同保障。 |
隔离性 | 多个事务并发执行时,彼此互不影响。每个事务如同独占整个数据库一样执行。 | 防止并发执行导致的数据不一致问题。 | 常见并发问题包括:脏读、不可重复读、幻读。 | 主要通过锁机制和多版本并发控制(MVCC)实现,并提供不同隔离级别供选择(如读未提交、串行化等)。 |
持久性 | 一旦事务成功提交,它对数据库的修改就是永久性的,即使系统发生故障也不会丢失。 | 确保已提交的事务不会因系统崩溃而丢失,可以恢复到最后一致状态。 | 强调“永久性”。 | 主要通过重做日志(Redo Log)实现,事务提交前先写入日志文件,重启后可依据日志恢复数据。 |
2、如何使用事务@Transactional
使用事务的注解@Transactional
@EnableTransactionManagement 在启动类上开启全局事务支持
@Transactional注解加在类上,表示这个类中的所有方法都需要事务管理
事务属性的介绍:
1. readOnly = true
-
含义: 声明事务为只读。
-
默认值:
false
(读写事务)。
2. rollbackFor = Exception.class
-
含义: 指定哪些异常类型会触发事务回滚。
-
示例:
@Transactional(rollbackFor = {SQLException.class, IOException.class})
3. noRollbackFor = {SomeException.class}
-
含义: 指定哪些异常类型发生时不回滚事务。
-
示例:
@Transactional(noRollbackFor = {BusinessValidationException.class})
4. timeout = 10
-
含义: 设置事务的超时时间(秒)。
-
默认值: 使用底层数据库系统的默认超时(通常无限制)。
5. propagation = Propagation.REQUIRED
-
含义: 定义事务的传播行为(即当多个事务方法相互调用时,事务如何传递)。
-
a---调用--->b b是否能够使用事务的问题
6. isolation = Isolation.DEFAULT
-
含义: 设置事务的隔离级别(控制并发事务之间的可见性)。
-
权衡: 隔离级别越高,数据一致性越强,但并发性能越低。
3、事务的传播机制
3.1 事务传播机制的概念
事务传播描述的事务与事务之间的传播关系, 常见的场景是在一个嵌套调用的方法中,外部方法和内部每个方法都添加了事务管理, 不同的传播行为配置,决定了最终是这些方法是使用同一个事务,还是在不同的事务中运行。
3.2 事务传播机制的分类
传播行为 (Propagation) | 中文名称 | 核心特性 |
---|---|---|
REQUIRED (默认) | 必需事务 | 存在事务则加入,不存在则新建事务(最常用) |
REQUIRES_NEW | 新建事务 | 无论是否存在事务,都创建独立新事务(新旧事务完全隔离) |
SUPPORTS | 支持事务 | 存在事务则加入,不存在则以非事务方式运行 |
MANDATORY | 强制事务 | 必须存在事务才能执行,否则抛出异常 |
NOT_SUPPORTED | 非事务支持 | 强制以非事务方式运行,若存在事务则挂起 |
NEVER | 强制非事务 | 必须无事务才能执行,否则抛出异常 |
NESTED | 嵌套事务 | 在当前事务内创建嵌套子事务(可独立回滚,依赖数据库 Savepoint 机制) |
1、PROPAGATION_REQUIRED (默认)
支持当前事务,如果当前已经存在事务,就加入到事务中执行,如果当前没有事务,就新建一个事务。这是最常见的选择。
public class Demo{void a(){b();//新建事务}@Transactionalvoid a(){b(); //使用a的事务}@Transactionalvoid b();}
2、PROPAGATION_SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方式执行。
public class Demo{void a(){b();//不要事务}@Transactionalvoid a(){b(); //使用a的事务}@Transactionalvoid b();}
3、PROPAGATION_MANDATORY
支持当前事务,如果当前没有事务,就抛出异常。
public class Demo{void a(){b();//报错}@Transactionalvoid a(){b(); //使用a的事务}@Transactionalvoid b();}
4、PROPAGATION_REQUIRES_NEW
新建事务,如果当前存在事务,把当前事务挂起。
public class Demo{void a(){b();//新建}@Transactionalvoid a(){b(); //挂起a事务 新建事务}@Transactionalvoid b();}
5、PROPAGATION_NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
public class Demo{void a(){b(); }@Transactionalvoid a(){b(); //挂起a事务}@Transactionalvoid b();}
6、PROPAGATION_NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。
public class Demo{void a(){b(); }@Transactionalvoid a(){b(); //报错}@Transactional(传播级别)void b();}
7、PROPAGATION_NESTED
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
嵌套事务: 与REQUIRED不同, NESTED支持真正意义上的子事务, 父事务回滚会将所有子事务都回滚,子事务回滚不会引起父事务的回滚。而REQUIRED其实只有一个物理事务。
public class Demo{void a(){b(); //新建}@Transactional 父事务void a(){b(); // 新建事务 子事务}@Transactional(传播级别)void b();}
4、事务的隔离机制
4.1 事物隔离机制的概念
事务的隔离级别描述的是多个事务之间的可见性问题。比如一个事务还未提交时,其他事务是否能看到被未提交事务修改的数据。
4.2 幻读、不可重复读取、脏读的概念
问题类型 | 脏读 | 不可重复读 | 幻读(虚读) |
---|---|---|---|
现象描述 | 事务读取了其他未提交事务修改的数据。 | 同一事务中多次读取同一记录,结果不同(因为其他事务已提交更新)。 | 同一事务中多次查询返回的记录数量不同(插入或删除导致)。 |
示例说明 | T1 更新记录但未提交,T2 读取该记录,之后 T1 回滚,T2 读取到无效数据。 | T1 读取某记录,T2 更新并提交该记录,T1 再次读取发现数据变化。 | T1 查询是否存在某记录,结果为无;准备插入时,T2 插入该记录并提交,T1 插入失败或删除成功却找不到记录。 |
允许的并发操作 | 一个事务在另一个事务未提交时可以读取其修改的数据 | 一个事务在另一个事务提交后可以读取其更新的数据 | 一个事务在另一个事务提交后可以插入/删除记录影响查询结果集 |
数据状态 | 未提交数据 | 已提交的修改数据 | 已提交的新增/删除数据 |
操作类型 | 读未提交UPDATE | 读已提交UPDATE | 读已提交INSERT/DELETE |
影响范围 | 单行数据 | 单行数据 | 多行结果集 |
根本原因 | 事务隔离失效 | 数据版本变更 | 数据集范围变化 |
导致结果 | 读到了“脏”数据(无效、可能被回滚的数据) | 同一条记录在同一个事务中读取不一致 | 同一范围查询返回不同的行数(像是“幻觉”出现或消失) |
4.3 事务隔离级别
隔离级别 | 别名 | 描述 | 可能出现的并发问题 | 是否加锁 | 应用场景说明 |
---|---|---|---|---|---|
读未提交 | Read Uncommitted | 事务可以读取其他事务未提交的数据。 | 脏读、不可重复读、幻读 | 否 | 最低隔离级别,性能最好但数据一致性最差,实际很少使用。 |
读已提交 | Read Committed | 事务只能读取已经提交的数据,避免脏读。 | 不可重复读、幻读 | 否 | 多数数据库默认级别(除MySQL外),适用于对一致性要求不高的场景。 |
可重复读 | Repeatable Read | 在同一个事务中多次读取的结果一致,InnoDB引擎的默认隔离级别。会出现“幻读”现象。 | 幻读 | 是(范围锁) | MySQL默认级别,保证同一查询结果不变,但可能插入新记录导致幻读。 |
串行化 | Serializable | 所有事务串行执行,通过锁表来强制事务串行化,避免所有并发问题。 | 无 | 是(表锁) | 最高隔离级别,安全性最高,但性能差,容易产生锁竞争和超时,实际应用较少使用。 |
- Mysql默认隔离级别:REPEATABLE-READ 可重复读
- Oracle默认隔离级别:Read committed 读已提交
4.4 Spring隔离级别可选值
隔离级别 | 说明 |
---|---|
ISOLATION_DEFAULT(默认级别) | 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别. |
ISOLATION_READ_UNCOMMITTED(读未提交) | 这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。 这种隔离级别会产生脏读,不可重复读和幻像读。 |
ISOLATION_READ_COMMITTED(读已提交) | 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。 这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读(因为在事务提交前允许别的事务更新和插入操作)。 |
ISOLATION_REPEATABLE_READ(可重复读) | 它保证了一个事务不能修改已经由另一个事务读取但还未提交的数据,也就是说同一个事务内读取的数据都是一致的。这种事务隔离级别可以防止脏读、不可重复读。但是可能出现幻像读(一个事务在提交前不允许别的事务更新,但是允许插入)。 |
ISOLATION_SERIALIZABLE(串行化) | 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 除了防止脏读,不可重复读外,还避免了幻像读(一个事务在提交前不允许别的事务读取、更新、插入)。 |
4.5 spring如何设置隔离级别(代码)
注解
@Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,timeout = 5000,readOnly = true,rollbackFor = Exception.class,noRollbackFor = NullPointerException.class)
示例代码:
@RequestMapping("/transfer")
public Object transfer(Integer fromId, Integer toId, Double money){//接收数据,传递数据到业务层boolean flag = accountService.transfer(fromId, toId, money);//返回结果给客户端return flag;
}
@RequestMapping("/findById")
public void findAccountById(Integer id){accountService.query(id);
}
public interface AccountService {public boolean transfer(Integer fromId, Integer toId, double money);
public void query(Integer id);
}
package com.hl.springboot4.service;
import com.hl.springboot4.dao.AccountDao;
import com.hl.springboot4.pojo.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/*
@EnableTransactionManagement 开启全局事务支持
@Transactional 使用事务readOnly = true, 只读事务 当前事务中查询出的数据,不能用户更新操作rollbackFor = Exception.class 当遇到特定异常,执行rollback回滚noRollbackFor = {} 当发生某种异常,不回滚timeout=数值 超时时间propagation = 事务的传播机制 a--调用-->b b是否能够使用事务的问题Propagation.REQUIREDisolation = 事务的隔离行为 两个方法在使用事务时,对方操作的数据是否可见*/
@Service
@Transactional(isolation = Isolation.SERIALIZABLE)
public class AccountServiceImpl implements AccountService{@Autowiredprivate AccountDao accountDao;@Override
// @Transactional//事务注解public boolean transfer(Integer fromId, Integer toId, double money) {//转出方法int num1 = accountDao.transferOut(fromId,money);
// System.out.println(1/0);//转入方法int num2 = accountDao.transferIn(toId,money);return num1==num2&&num1>0 ? true : false;}
/*事务隔离机制:isolation = Isolation.READ_UNCOMMITTEDREAD_UNCOMMITTED 读未提交 读取到其他事务修改但是尚未提交的数据 可能出现脏数据READ_COMMITTED 读已提交 读取其他事务已经提交的数据REPEATABLE_READ 可重复读 一个事务在执行期间,不受其他会话影响,多次读取结果保持一致SERIALIZABLE 可串行化 锁表 第一个事务commit提交后,第二个事务才能执行*/@Overridepublic void query(Integer id) {Account account = accountDao.getAccountById(id);System.out.println(account);
Account account2 = accountDao.getAccountById(id);System.out.println(account);System.out.println(account2);}
}
package com.hl.springboot4.dao;
import com.hl.springboot4.pojo.Account;
public interface AccountDao {//转出public int transferOut(Integer fromId,Double money);//转入public int transferIn(Integer toId,Double money);//查询public Account getAccountById(Integer id);
}
package com.hl.springboot4.dao;
import com.hl.springboot4.pojo.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Repository
public class AccountDaoImpl implements AccountDao{@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic int transferOut(Integer fromId, Double money) {String sql = "update account set money = money - ? where id = ?";return jdbcTemplate.update(sql, money, fromId);}
@Overridepublic int transferIn(Integer toId, Double money) {String sql = "update account set money = money + ? where id = ?";return jdbcTemplate.update(sql, money, toId);}
@Overridepublic Account getAccountById(Integer id) {String sql = "select * from account where id = ?";List<Account> list = jdbcTemplate.query(sql,new BeanPropertyRowMapper<Account>(Account.class),id);return list!=null ? list.get(0) : null;}
}
@SpringBootTest
class Springboot4ApplicationTests {@Autowiredprivate AccountController accountController;@Testvoid tes1() {accountController.findAccountById(1);}@Testvoid tes2() {accountController.transfer(1,2,300.0);}
}
5、事务注解
1. @Transactional
(最核心注解)
属性 | 说明 | 示例 |
---|---|---|
value /transactionManager | 指定事务管理器 Bean 名称(多数据源时必填) | @Transactional("orderTxManager") |
propagation | 事务传播行为(默认 Propagation.REQUIRED ) | propagation = Propagation.REQUIRES_NEW |
isolation | 事务隔离级别(默认 Isolation.DEFAULT ) | isolation = Isolation.READ_COMMITTED |
timeout | 事务超时时间(秒),-1 表示永不超时 | timeout = 30 |
readOnly | 是否只读事务(默认 false ) | readOnly = true |
rollbackFor | 触发回滚的异常类型(数组) | rollbackFor = {SQLException.class} |
noRollbackFor | 不触发回滚的异常类型(数组) | noRollbackFor = NullPointerException.class |
label | 事务标签(Spring 5.3+ 用于添加描述性标签) | label = "order-service" |
2.使用范围:
@Transactional // 类级:所有public方法生效
@Service
public class OrderService {@Transactional(propagation = Propagation.REQUIRES_NEW) // 方法级:覆盖类配置public void createOrder() { ... }
}