Spring5学习笔记(八)事务管理(声明式事务)
1、事务概念
什么是事务?
事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败。
典型应用
银行转账
事务的四个特性(ACID)
- 原子性(Atomicity): 事务最基本的操作单元,要么全部成功,要么全部失败,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性(Consistency): 事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
- 隔离性(Isolation): 指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
- 持久性(Durability): 指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
2、环境搭建(入门小案例)
1.创建数据库表
CREATE TABLE `t_account` (
`id` varchar(20) NOT NULL,
`username` varchar(50) DEFAULT NULL,
`money` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
bean.xml
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.dong"></context:component-scan>
<!-- 配置连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/userDB?serverTimezone=Asia/Shanghai&useSSL=true"></property>
<property name="username" value="root"></property>
<property name="password" value="wad07244058664"></property>
</bean>
<!-- 配置jdbcTemplate对象,注入DataSource -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 注入dataSource(set方法注入) -->
<property name="dataSource" ref="dataSource"></property>
</bean>
创建service,搭建dao,完成对象创建和注入的关系
public interface UserDao {
//多钱
public void addMoney();
//少钱
public void reduceMoney();
}
@Repository
public class UserDaoImpl implements UserDao{
@Autowired
JdbcTemplate jdbcTemplate;
//多钱
@Override
public void addMoney() {
String sql = "update t_account set money=money+? where username=?";
jdbcTemplate.update(sql,100,"lucy");
}
//少钱
@Override
public void reduceMoney() {
String sql = "update t_account set money=money-? where username=?";
jdbcTemplate.update(sql,100,"mary");
}
}
@Service
public class UserService {
//注入dao
@Autowired
UserDao userDao;
//转账
public void accountMoney() {
//lucy少100
userDao.reduceMoney();
//mary多100
userDao.addMoney();
}
}
测试
@Test
public void testAccount() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
UserService bean = context.getBean("userService",UserService.class);
bean.accountMoney();
}
3、事务引入
上面的入门小案例,正常来说是没有什么问题的,但我们就是要做到不正常的时候也是没问题的,当案例运行中如果出现异常、断网等一系列问题的时候,问题就会体现出来,如下↓
我们手动加个异常,看看情况
可见看见mary钱减少了了,但lucy钱没有增加,
解决方法
使用Spring事务
3.1、Spring事务管理介绍
1.事务添加到JavaEE三层结构里面的Service层(业务逻辑层)
2.在Spring进行事务管理操作
方式一:编程式事务
方式二:声明式事务管理(推荐)
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配置文件开启事务注解
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
3.在service类上面(或者service类里面方法上面)添加事务注解
1.@Transactional,这个注解添加到类上面,也可以添加方法上面
2.如果把这个注解添加类上面,这个类里面所有的方法都添加事务
3.如果把这个注解添加方法上面,则是为这个方法添加事务
继续测试
发现出现异常,但是数据库值没有改变,成功!
3.2.1、声明式事务管理参数配置
推荐使用:↓
propagation: 事务的传播行为
ioslation: 事务的隔离级别
timeout: 超时时间,超出后自动终止并回滚
readonly: 是否只读(默认为false)
rollbackFor: 哪些异常事务可以回滚
noRollbackFor: 哪些异常事务可以不回滚
测试timeout
//转账
@Transactional(timeout = 3)
public void accountMoney() throws InterruptedException {
//lucy少100
userDao.reduceMoney();
//睡三秒
Thread.sleep(3000);
int i = 10/0;
//mary多100
userDao.addMoney();
}
数据库没有改变
测试readOnly
如果只有查询可以设置readOnly=true可以加快查询速度,但如果不仅仅只有查询就不能使用。
测试noRollbackFor
@Transactional(timeout = 3,noRollbackFor = {ArithmeticException.class})
public void accountMoney() throws InterruptedException {
//lucy少100
userDao.reduceMoney();
int i = 10/0;
//mary多100
userDao.addMoney();
}
我们设置数学异常可以不会滚,运行后可以看见事务没有回滚,money改变
测试rollbackFor
异常来说分为运行时异常和编译时异常
运行时异常(非检查异常):可以不用处理,默认都回滚。
编译时异常(检查异常):要么try-catch,要么在方法上声明throws,默认不会滚。
数据改变
数据没有改变
3.2.2、声明式事务的传播行为
简介
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
Spring定义的7种传播行为
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
测试REQUIRED和REQUIRES_NEW
//多钱
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addMoney() {
int i = 10/0;
String sql = "update t_account set money=money+? where username=?";
jdbcTemplate.update(sql,100,"lucy");
}
//少钱
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void reduceMoney() {
String sql = "update t_account set money=money-? where username=?";
jdbcTemplate.update(sql,100,"mary");
}
@Transactional
public void accountMoney() throws FileNotFoundException {
//lucy少100
userDao.reduceMoney();
//mary多100
userDao.addMoney();
}
没有改变
//少钱
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void reduceMoney() {
String sql = "update t_account set money=money-? where username=?";
jdbcTemplate.update(sql,100,"mary");
}
发生改变
结论:REQUIRES_NEW 开启新的事务不受外围事务的影响,而REQUIRED跟外围事务公用,所以受到影响。
觉得写的不错的小伙伴,可以点赞关注和收藏哦,博主会持续发布与大家共同学习!
- 点赞
- 收藏
- 关注作者
评论(0)