五、事务操作
1. 事务概念
1、什么是事务
(1)事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
(2)典型场景:A 转账给 B 100 元
要么转账成功:A 少 100 元,B 多 100 元
要么转账失败:A 不少, B 不多
不能 A 少 100 元而 B 没得到 100元
2、事务四个特性(ACID)
(1)原子性(Atomicity)
一个事务要么都成功,要么全部失败回滚,不能只执行其中的一部分操作
(2)一致性(Consistenc)
事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行前后,数据库必须处于一致性状态
(3)隔离性(Isolation)
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能相互干扰
(4)持久性(Durabili)
持久性是指一个事务一旦被提交,它对数据库中数据的改变是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响
2. 搭建事务操作环境
1、创建数据库表,添加记录
2、创建 service,搭建 dao,完成对象创建和注入关系
@Repository
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;
}
@Service
public class UserService {
@Autowired
private UserDao userDao;
}
3、在 dao 创建两个方法:多钱和少钱的方法,在 service 创建方法(转账方法)
@Repository
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void reduceMoney() {
String sql = "UPDATE t_bank SET money = money - ? WHERE user_name = ?";
jdbcTemplate.update(sql, 100, "A");
}
@Override
public void addMoney() {
String sql = "UPDATE t_bank SET money = money + ? WHERE user_name = ?";
jdbcTemplate.update(sql, 100, "B");
}
}
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void accountMoney() {
userDao.reduceMoney();
userDao.addMoney();
}
}
4、上面代码,如果正常执行是没有问题的,但是如果代码执行过程中出现异常,有问题
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void accountMoney() {
userDao.reduceMoney();
// 模拟异常
int a = 1 / 0;
userDao.addMoney();
}
}
运行上面代码
可以知道 reduceMoney 方法执行后,出现错误,则 A 少了 100 元,而 B 并没有得到 100 元
在现实生活中,这种结果是万万不能接受的
比起 A 少钱而 B 没有得到而言,如果中途出错,A 既不少钱而 B 不多钱的结果是可以接受的
所以我们需要事务来进行数据库操作,要么成功,要么都失败
事务过程
public void accountMoney() {
try {
// 第一步 开启事务
// 第二步 进行业务操作
userDao.reduceMoney();
int a = 1/ 0;
userDao.addMoney();
// 第三步 没有发生异常,提交事务
} catch(Exception e) {
// 第四步 出现异常,事务回滚
}
}
3.事务操作
3.1 Spring 事务管理介绍
1、事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)
2、在 Spring 进行事务管理操作
(1)编程式事务管理,在代码中硬编码。(不推荐使用)
(2)声明式事务管理,在配置文件中配置(推荐使用)
3、声明式事务管理
(1)基于注解方式
(2)基于 xml 配置文件方式
4、在 Spring 进行声明式事务管理,底层使用 AOP 原理
5、Spring 事务管理 API
提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
3.2 注解声明式事务管理
1、在 Spring 配置文件配置事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
2、在 Spring 配置文件,开启事务注解
(1)在 Spring 配置文件引入名称空间 tx
(2) 开启事务注解
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
3、在 service 类上面(或者 service 类方法上面)添加事务注解
(1)@Transactional
注解可以加到类上面也可以加到方法上面
(2)如果把这个注解添加类上面,这个类里面所有的方法都添加事务
(3)如果把这个注解添加到方法上面,只为这个方法添加事务
3.3 声明式事务管理参数配置
1、在 service 类上面添加注解 @Transactional
,在这个注解里面可以配置事务相关参数
2、propagation:事务传播行为
(1)多事务方法进行直接进行调用,这个过程事务是如何进行管理的
(2)Spring 定义 7 种传播行为
支持当前事务的情况:
- TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
不支持当前事务的情况:
- TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
其他情况:
- TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED
3、ioslation:事务隔离级别
(1)对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题
(2)有三个读问题:脏读、不可重复读、虚(幻)读
- 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
- 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
- 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
(3)数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题
(4)一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。
隔离级别 | 描述 |
---|---|
READ UNCOMMITTED (读未提交数据) | 允许事务读取未被其他事务提交的变更,脏读、不可重复读、幻读的问题均会出现 |
READ COMMITTED (读已提交数据) | 只允许事务读取已经被其他事务提交的变更,可以避免脏读,但不可重复读、幻读的问题仍会出现 |
REPEATABLE READ (可重复读) | 确保事务可以多次从一个字段中读取到相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但幻读仍存在 |
SERIALIZABLE (串行化) | 确保事务可以从一个表中读取中读取相同行,在这个事务持续期间,禁止其他事务对该表进行插入、更新和删除操作,所有并发问题都可以避免,但性能十分低下 |
4、timeout:超时时间
(1)事务需要在一定时间内进行提交,如果不提交进行回滚
(2)默认值是 -1,设置时间以秒单位进行计算
5、readOnly:是否只读
(1)读:查询操作,写:添加修改删除操作
(2)readOnly 默认值 false,表示可以查询,可以添加修改删除操作
(3)设置 readOnly 值是 true,只能进行查询操作
6、rollbackFor:回滚
设置出现哪些异常进行事务回滚
7、noRollbackFor:不回滚
设置出现哪些异常不进行回滚
3.4 xml 声明事务管理
1、在 Spring 配置文件中进行配置
第一步 配置事务管理器
第二步 配置通知
第三步 配置切入点和切面
<!-- 1 创建事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2 配置通知 -->
<tx:advice id="txadvice">
<!-- 配置事务参数 -->
<tx:attributes>
<!-- 指定那种规则的方法上面添加事务 -->
<tx:method name="accountMoney" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 3 配置切入点和切面 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="pt" expression="execution(* com.spring5.demo.service.UserService.*(..))"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"></aop:advisor>
</aop:config>
3.5 完全注解声明式事务管理
1、创建配置类,使用配置类替代 xml 配置文件
@Configuration // 配置类
@ComponentScan(basePackages = "com.spring5.demo") // 组件扫描
@EnableTransactionManagement // 开启事务
public class TxConfig {
// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=utf8&?rewriteBatchedStatements=true");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
// 创建 JdbcTemplate 对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}