上节简单记录了Spring整合mybatis,这节记录Spring整合事务的开发,持久层这块学的太菜了,基于事务的开发我只会用注解,比如:
@Transactional(propagation=Propagation.SUPPORTS,readOnly=true)
具体里面的细节,比如说事务中的参数定义(也就是事务属性)、使用IOC+AOP来做事务开发等等都不了解。接下来逐一分析一下:
一、关于事务
事务的原理:数据库事务的概念及其实现原理
事务的概念就是保证业务操作完整性的一种数据库机制,对应的四个特性是ACID,分别是原子性、一致性、隔离性、持久性。不同的持久层开发框架整合事务的方式也不同,比如:
JDBC:
Connection.setAutoCommit(false);
Connection.commit();
Connection.rollback();
Mybatis:
Mybatis⾃动开启事务
sqlSession(Connection).commit();
sqlSession(Connection).rollback();
调用方式虽有不同,但可以看到事务 最主要的就是三个方法:1、事务的创建 2、事务的提交 3、事务的回滚。而且控制事务的底层,都是由Connection对象来完成。
二、Spring整合事务开发
前几节写AOP时也有记到,事务是可以通过额外功能实现的,那么就一定可以被整合到Spring,满足AOP开发的四个步骤即可:
1、创建原始对象,交由工厂管理
这步没啥好说的,基本是创建在Service层,再注入Dao对象
public class XXXUserServiceImpl{
private xxxDAO xxxDAO
set get
1. 原始对象 ---》 原始⽅法 ---》核⼼功能 (业务处理+DAO调⽤)
2. DAO作为Service的成员变量,依赖注⼊的⽅式进⾏赋值
}
2、写额外功能
这里的额外功能自然就是事务的编写了,通过 org.springframework.jdbc包,工厂创建一个事务管理器对象即可,然后在里面注入连接池对象,然后将该对象和切入点做个切面就可以了(为啥没看到事务的创建、提交、回滚方法,问就是封装好了),创建及实现方式:
创建
1. org.springframework.jdbc.datasource.DataSourceTransactionManager
2. 注⼊DataSource
实现
MethodInterceptor或 @Aspect注解
3、切入点
@Transactional
后面会看到以标签方式开发切入点
事务的额外功能加⼊给那些业务⽅法。
1. 类上:类中所有的⽅法都会加⼊事务
2. ⽅法上:这个⽅法会加⼊事务
4、组装切面
引入标签自动扫描@Transactional注解 为扫描到的切入点加入额外功能
<tx:annotation-driven transaction-manager=""/>
代码测试:
引依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
潦草的实现类:
package com.jin.service;
import com.jin.dao.UserDAO;
import com.jin.entity.User;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* @author jinyunlong
* @date 2021/12/14 10:19
* @profession ICBC锅炉房保安
*/
//@Transactional(isolation = Isolation.SERIALIZABLE)
//@Transactional(timeout = 2)
@Transactional
public class UserServiceImpl implements UserService{
private UserDAO userDAO;
public UserDAO getUserDAO() {
return userDAO;
}
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
@Override
public void register(User user) {
// try {
// Thread.currentThread().sleep(3000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
userDAO.save(user);
// throw new RuntimeException("测试");
}
@Override
// @Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
public void login(String name, String password) {
}
}
配置文件,连接池对象引用上节:
<bean id="userService" class="com.jin.service.UserServiceImpl">
<property name="userDAO" ref="userDAO" />
</bean>
<!-- DataSourceTransactionManager-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
/*
用于测试:Spring的事务处理
*/
@Test
public void test2(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
UserService userService = (UserService) ctx.getBean("userService");
User user = new User();
user.setName("fuck78");
user.setPassword("hgjgm");
userService.register(user);
}
向表中插入该实体:
三、事务的属性
5种属性,隔离属性、传播属性、只读属性、超时属性、异常属性。原以为自己没用过,其实是都用的默认值。不在@Transactional中加参数即可,加参数写法:
@Transactional(isloation=,propagation=,readOnly=,timeout=,rollbackFor=,
noRollbackFor=,)
有些属性在注解里不声明值的话,那么默认值就是由数据库控制的,mysql有其默认值,oracle同理。
在开发中,相当通用的一套声明方案如下:
1. 隔离属性 默认值
2. 传播属性 Required(默认值) 增删改 Supports 查询操作
3. 只读属性 readOnly false 增删改 true 查询操作
4. 超时属性 默认值 -1
5. 异常属性 默认值
增删改操作 @Transactional
查询操作
@Transactional(propagation=Propagation.SUPPORTS,readOnly=true)
可见,增删改全默认,查询操作修改了传播属性和只读属性,那这些修改值都是个啥意思呢,下面详细看下:
隔离属性,概念很容易乱,因为可能会出现的问题不同,可能会出现脏读、不可重复读、幻影读。三种问题对表操作的影响程度不同。简单来说如果注解用隔离属性默认值的话就是加了一把行锁(mysql),其实用默认值就能解决绝大部分问题了,本质是解决并发。
1、脏读
⼀个事务,读取了另⼀个事务中没有提交的数据。会在本事务中产⽣数据不⼀致的问题
解决⽅案 @Transactional(isolation=Isolation.READ_COMMITTED)
2、不可重复读
⼀个事务中,多次读取相同的数据,但是读取结果不⼀样。会在本事务中产⽣数据不⼀致的问题
注意:1 不是脏读 2 ⼀个事务中
解决⽅案 @Transactional(isolation=Isolation.REPEATABLE_READ)
本质: ⼀把⾏锁
3、幻影读
⼀个事务中,多次对整表进⾏查询统计,但是结果不⼀样,会在本事务中产⽣数据不⼀致的问题
解决⽅案 @Transactional(isolation=Isolation.SERIALIZABLE)
本质:表锁
并发安全: SERIALIZABLE>REPEATABLE_READ>READ_COMMITTED
运⾏效率: READ_COMMITTED>REPEATABLE_READ>SERIALIZABLE
传播属性不想多记,记多了反倒更容易乱:
增删改 ⽅法:直接使⽤默认值REQUIRED
查询 操作:显示指定传播属性的值为SUPPORTS (外部不存在事务时不开启事务——就把查询当非事务处理就行,不用管ACID...)
墙裂推荐一个解析事务传播的文章,这是干什么这太清晰了吧(PROPAGATION_REQUIRED): Spring事务传播行为
只读属性,提升效率的:
针对于只进⾏查询操作的业务⽅法,可以加⼊只读属性,提供运⾏效率
默认值:false
超时属性,比较简单:
指定了事务等待的最⻓时间
1. 为什么事务进⾏等待?
当前事务访问数据时,有可能访问的数据被别的事务进⾏加锁的处理,那么此时本事务就必须
进⾏等待。
2. 等待时间 秒
3. 如何应⽤ @Transactional(timeout=2)
4. 超时属性的默认值 -1
最终由对应的数据库来指定
异常属性,比较简单:
Spring事务处理过程中
默认 对于RuntimeException及其⼦类 采⽤的是回滚的策略
默认 对于Exception及其⼦类 采⽤的是提交的策略
rollbackFor = {java.lang.Exception,xxx,xxx}
noRollbackFor = {java.lang.RuntimeException,xxx,xx}
@Transactional(rollbackFor = {java.lang.Exception.class},noRollbackFor
= {java.lang.RuntimeException.class})
建议:实战中使⽤RuntimeExceptin及其⼦类 使⽤事务异常属性的默认值
四、使用标签进行事务开发
这次不用@Transactional注解了。因为使用注解就相当于找了切入点,且组装切面需要去寻找该注解并且组装额外功能。那么如果不用注解了,那么基于AOP的第3、4步就要用标签来代替了,如下:
基于标签定义属性值
<tx:advice id="txadvice" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="register" isolation="DEFAULT" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.jin.service.UserServiceImpl.register(..))"/>
<aop:advisor advice-ref="txadvice" pointcut-ref="pc"/>
</aop:config>
测试方法和二一样,工厂可以换个xml加载,实体插入表:
这里的开发习惯,不同方法中加入事务属性使用通配方式,一般用包切入点。
<!--<!– 通配方式–>-->
<!--<!– 编程习惯 service中负责增删改操作的方法 都以modify开头 查询操作 命名无所谓 例–>-->
<!-- <tx:advice id="txadvice" transaction-manager="dataSourceTransactionManager">-->
<!-- <tx:attributes>-->
<!-- <tx:method name="modify*" />-->
<!-- <tx:method name="*" propagation="SUPPORTS" read-only="true" />-->
<!-- </tx:attributes>-->
<!-- </tx:advice>-->
<!--<!– 应用过程中,一般用包切入点,service放置到service包中–>-->
<!-- <aop:config>-->
<!-- <aop:pointcut id="pc" expression="execution(* com.jin.service.*.*(..))"/>-->
<!-- <aop:advisor advice-ref="txadvice" pointcut-ref="pc"/>-->
<!-- </aop:config>-->
持久化以前这块学的真不好啊,前有脚本小子,后有事务菜鸡。