Spring事务开发详解
- 1.什么是事务
- 1.1 事务概念
- 1.2 事务四大特性 ACID
- 1.3 如何控制事务
- 1.4 事务的核心:AOP编程
- 2.事务开发步骤
- 3.事务属性详解
- 3.1 隔离属性
- 3.2 传播属性
- 3.3 只读属性
- 3.4 超时属性
- 3.5 异常属性(有两种取值配置方式)
- 4.事物属性常见配置总结:
- 5.事务属性配置方式
- 5.1 注解方式
- 5.2 配置文件方式
- 6.测试
1.什么是事务
1.1 事务概念
事务其实是数据库中的一个概念,它是保证业务操作完整性的一种机制,Spring将其接管并优化,操作更方便
1.2 事务四大特性 ACID
不懂就百度嘛!嘿嘿
A:原子性 C:一致性 I:隔离性 D:持久性
1.3 如何控制事务
- JDBC: 手动开启
Connection.setAutoCommit(false);
Connection.commit();
Connection.rollback();
- Mybatis:
Mybatis 自动开启事务
sqlSession.commit();
底层还是调用的 ConnectionsqlSession.rollback()
;底层还是调用的 Connection - 结论:控制事务的底层,都是通过 Connection 对象完成的。
1.4 事务的核心:AOP编程
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" proxy-target-class="true"/>
小细节:proxy-target-class切换动态代理模式 JDK Cglib
2.事务开发步骤
- 原始对象:原始对象–>原始方法–>额外功能(业务处理+DAO调用) ,DAO作为Service的成员变量,依赖注入的方式,提供setter
- 额外功能:以前采用MethodInterceptor接口,将事务开启提交等操作写在实现类中,或者使用@Aspect注解+五大通知相关注解联合开发。
现在采用Spring框架提供的:org.springframework.jdbc.datasource.DataSourceTransactionManager
+注入DataSource(连接池内含操作事务所需的Connection)。 - 切入点:@Transactional 事务的额外功能加入给那些业务方法
- 类上:类中所有的方法加入事务
- 方法上:此方法加入事务
- 组装切面:将第二步创建的Manager引入
<tx:annotation-driven transaction-manager=""/>
3.事务属性详解
3.1 隔离属性
隔离属性:描述了事务解决并发问题的特征
① 并发:多个事务(用户)在同一时间(存在细微的时间差,计算机能分辨),访问操作了相同的数据
② 并发带来的问题:
- 脏读:一个事务读取了另一个事务没有提交的数据(回滚),会在本事务中产生数据不一致问题
解决方案:@Transactional(isolation = Isolation.READ_COMMITTED) - 不可重复读:一个事务中 多次读取了相同的数据(例:id相同),但是读取的结果不一样。产生数据不一致问题,注意,不是脏读,它描述的是在一个事务中
解决方案:@Transactional(isolation = Isolation.REPEATABLE_READ)
本质:加上行锁,后来者必须等待前者操作完 - 幻读:一个事务中,多次对整表进行查询统计,但是结果不一样,会在本事务中产生数据不一致问题,被其他事务影响了
解决方案:@Transactional(isolation = Isolation.SERIALIZABLE)
本质:表锁
③ 如何解决:隔离属性设置不同的值解决并发处理过程中的值
④ 总结:
并发安全:SERIALIZABLE>REPEATABLE_READ>READ_COMMITTED
运行效率:READ_COMMITTED>REPEATABLE_READ>SERIALIZABLE
⑤ 数据库对隔离属性的支持:
- MySQL三者都支持;
- Oracle不支持REPEATABLE_READ 采用多版本比对的方式 解决不可重复读的问题。
⑥ 默认隔离属性:ISOLATION_DEFAULT:调用不同数据库所设置的默认隔离属性
- MySQL: REPEATABLE_READ
- Oracle: READ_COMMITTED
⑦ 隔离属性在实战中的建议:使用默认值即可
3.2 传播属性
解决问题:大的事务嵌套若干小的事务,彼此影响导致外部事务丧失原子性
什么是融合?
放弃自己的事务,融入外部事务
默认值:REQUIRED
建议使用:
- 增删改:默认的
- 查询:SUPPORT
传播属性的值及其用法:
3.3 只读属性
只读属性:针对只进行查询操作的业务方法,可以只加入只读属性,提高运行效率,不会加各种锁
默认值:false
用例:login(User user):查询操作
。
在类加上@Transaction注解的基础上,方法上再加入@Transaction(propagation=Propagation.SUPPORT,readOnly=True)
进行覆盖,保证其他方法使用类上的设置,而查询方法单独设置。
3.4 超时属性
超时属性:为什么进行事务等待?访问数据时,有可能访问的数据被别的事物加锁了,本事务必须进行等待,单位为秒。
用例:@Transaction(timeout=2)
默认值:-1,最终由对应的数据库来指定,一般用默认值
3.5 异常属性(有两种取值配置方式)
异常属性:发生异常之后是否回滚
默认:
- RuntimeException及其子类采用的是回滚策略
- Exception及其子类采用的是提交策略
自定义和上面反着的效果:后面跟着的是一个数组!!!
- rollbackFor = {java.lang.Exception,xxx,xxx}
- noRollbackFor = {java.lang.RuntimeException,xxx,xxx}
建议:使用默认值,使用RuntimeException及其子类
4.事物属性常见配置总结:
隔离属性:默认值
传播属性:增删改(Required默认值) 查询(Supports)
只读属性:增删改(false默认值) 查询(true)
超时属性:默认值-1,让对应数据库决定
异常属性:默认值
开发时配置:
- 增删改操作: 类上一个@Transaction就够了
- 查询操作: 相应方法上
@Transaction(propagation=Propagation.SUPPORT,readOnly=True)
进行覆盖
5.事务属性配置方式
5.1 注解方式
上面使用的就是注解的方式
5.2 配置文件方式
按着开发步骤,前两步相同,后两步在配置文件中使用标签代替@Transactional注解(配置事务属性)
第三步:
<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="register" isolation="DEFAULT",propagation="DEFAULT"></tx:method>
<tx:method name="login" isolation="DEFAULT",propagation="DEFAULT"></tx:method>
</tx:attributes>
</tx:advice>
第四步:
<aop:config>
<aop:pointcut id="pc" expression="execution(* zyc.stu.service.*.*(..))"></aop:pointcut>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"></aop:advisor>
</aop:config>
实战中:使用 * 通配,以后写方法命名注意这个规范就可以一劳永逸了
<!-- 配置事务增强(如何管理事务,只读、读写...) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
<tx:method name="select*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- aop配置,拦截哪些方法(切入点表达式,拦截上面的事务增强) -->
<aop:config>
<aop:pointcut id="pt"
expression="execution(* zyc.stu.servcies.impl.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt" />
</aop:config>
6.测试
接上一篇的项目,演示Spring控制事务
package zyc.stu.Spring5_107_139.service;
import zyc.stu.Spring5_107_139.bean.User;
public interface UserService {
void addUser(User user);
}
package zyc.stu.Spring5_107_139.service;
import org.springframework.transaction.annotation.Transactional;
import zyc.stu.Spring5_107_139.bean.User;
import zyc.stu.Spring5_107_139.dao.UserDao;
@Transactional
public class UserServiceImpl implements UserService{
private UserDao dao;
public void setDao(UserDao dao) {
this.dao = dao;
}
//@Transactional目前作用于类上,类中所有方法都会添加事务:
// 下面展示手动抛异常,测试回滚
public void addUser(User user) {
dao.addUser(user);
//throw new RuntimeException("测试");
}
}
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext3.xml");
UserService userService = (UserService) context.getBean("userService");
userService.addUser(new User("小王八1",18,"男"));
}
集成log4j日志框架之后,能在控制台看到事务的创建和启动:Creating new transaction with name [zyc.stu.Spring5_107_139.service.UserServiceImpl.addUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT