概述
什么是事务?
事务是数据库操作的最基本单元,把一系列操作视为一个整体的功能被称为事务,作为事务时这一系列操作要全部成功才被视为成功,否则只要由一个操作失败就被视为失败。
单条SQL语句就是一个事务,这种事务被成为隐式事务。
事务特性ACID
- Atomic 原子性:原子是不可分割的,事务要么全部执行,要么不执行
- Consistency 一致性:事务提交前后的状态是一致的,比如银行转账,转账前后各人的账户余额可能变了,但钱的总额不变
- Isolation 隔离性:多个事务并发执行时,不被其他事务干扰
- Duration 持久性:事务完成后,修改被持久化存储
事务参数
- @Transanctional
- @EnableTransactionManagement
Transanctional参数配置
propagation事务传播行为
事务传播行为:事务方法之间发生调用时,事务该如何进行传播
传播属性 | 描述 |
---|---|
REQUIRED | 默认值,如果有事务在运行就在当前事务运行,没有事务时创建新事务,这是最常用的传播行为,已经满足绝大部分需求 |
REQUIRES_NEW | 当前方法必须启动新事务 |
SUPPORTS | 如果有事务在运行,就运行在事务中,可以不在事务中运行,这种传播级别可用于select方法,因为select可以在事务中执行,也可以不需求事务 |
NOT_SUPPORTED | 不能运行在事务中,如果有运行的事务,就将它挂起 |
MANDATORY | 必须运行在事务中,如果没有正在运行的事务就抛出异常 ,这种传播级别可用于核心更新逻辑,比如用户余额变更 |
NEVER | 必须不运行在事务中,如果有正在运行的事务就抛出异常 |
NESTED | 如果有事务在运行,就在事务中嵌套事务运行,没有事务就创建事务 |
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class AccountService {
}
isolation事务隔离级别
两个并发执行的事务,如果涉及到操作同一条记录时,可能会带来数据的不一致性,比如脏读,不可重复度,幻读等。隔离级别可以让我们有选择性的避免数据不一致的问题。
- Read Uncommited 事务可以看到其他事务未提交的数据
- Read Commited 一个事务修改过的数据提交后才能被其他事务看到
- Repeatable Read 在Read Commited的基础上,保证了不可重复读
- Serializable 代价最高最可靠的隔离级别,事务被处理为串行执行,执行效率太低
Isolation Level | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read Uncommited | Yes | Yes | Yes |
Read Commited | - | Yes | Yes |
Repeatable Read | - | - | Yes |
Serializable | - | - | - |
脏读:一个事务读到另一个事务更新后但未提交的数据,如果另一个事务回滚,当前事务读到的就是脏数据
时刻 | 事务A | 事务B |
---|---|---|
1 | SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; | SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; |
2 | BEGIN; | BEGIN; |
3 | UPDATE students SET name = ‘Bob’ WHERE id = 1; | |
4 | SELECT * FROM students WHERE id = 1; | |
5 | ROLLBACK; | |
6 | SELECT * FROM students WHERE id = 1; | |
7 | COMMIT; |
不可重复读:一个事务第二次读取同一数据时刚好读取到了另一个事务更新提交的数据,两次读取的数据不一致
时刻 | 事务A | 事务B |
---|---|---|
1 | SET TRANSACTION ISOLATION LEVEL READ COMMITTED; | SET TRANSACTION ISOLATION LEVEL READ COMMITTED; |
2 | BEGIN; | BEGIN; |
3 | SELECT * FROM students WHERE id = 1; | |
4 | UPDATE students SET name = ‘Bob’ WHERE id = 1; | |
5 | COMMIT; | |
6 | SELECT * FROM students WHERE id = 1; | |
7 | COMMIT; |
幻读:一个事务查询不到该条记录,而因为在另一个事务更新提交事务后生成了该条记录,虽然这时当前事务还是查询不到该条记录但竟然发现能对该条记录进行更新,像做梦一样
时刻 | 事务A | 事务B |
---|---|---|
1 | SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; | SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; |
2 | BEGIN; | BEGIN; |
3 | SELECT * FROM students WHERE id = 99; | |
4 | INSERT INTO students (id, name) VALUES (99, ‘Bob’); | |
5 | COMMIT; | |
6 | SELECT * FROM students WHERE id = 99; | |
7 | UPDATE students SET name = ‘Alice’ WHERE id = 99; | |
8 | SELECT * FROM students WHERE id = 99; | |
9 | COMMIT; |
@Service
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
public class AccountService {
}
timeout超时时间
设置超时时间,超时则回滚,单位为秒
readOnly只读
默认值为false,可以更新表,可以查询, 设为true只能查询
rollbackFor
设置出现哪些异常回滚
noRollbackFor
设置出现哪些异常不回滚
配置事务
配置类
- @EnableTransactionManagement
- 注入dataSource
- 注入TransactionManager
@Configuration
@ComponentScan("com.npc")
@PropertySource("jdbc.properties")
@EnableTransactionManagement
public class SpringConfig {
@Value("${jdbc.driverclass}")
String jdbcDriver
@Value("${jdbc.url}")
String jdbcUrl;
@Value("${jdbc.username}")
String jdbcUsername;
@Value("${jdbc.password}")
String jdbcPassword;
@Bean
public DruidDataSource getDruidDataSource() throws IOException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(jdbcDriver);
dataSource.setUrl(jdbcUrl);
dataSource.setUsername(jdbcUsername);
dataSource.setPassword(jdbcPassword);
return dataSource;
}
@Bean
public JdbcTemplate getJdbcTemplate(@Autowired DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(@Autowired DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
配置切入点
- @Transactional
@Service
@Transactional
public class AccountService {
public void transfer(int from, int to, int money) {
accountDao.reduceMoneny(from, money);
// 触发异常,查看事务作用
// int i = 10/0;
accountDao.addMoney(to, money);
}
}