# spring-mybatis-transaction
**Repository Path**: guangjava/spring-mybatis-transaction
## Basic Information
- **Project Name**: spring-mybatis-transaction
- **Description**: Spring中的事务代码源码分析
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: origin
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2022-09-08
- **Last Updated**: 2025-01-23
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## Spring的AOP
本篇章中所有的代码都将会放置到git仓库中去,并且会做一个简要的说明。
### 一、个人理解描述
Spring中所谓的AOP就是在不修改源码的情况下,来进行增强。**所谓的增强其实就是在方法执行前后添加一些额外操作。**
> 所谓的增强,就是我们如何来对方法(以类中的方法为基本单位)处理。处理方法有五种:前置增强、后置增强等等
>
> 但是最为常用的还是利用环绕通知来进行增强,习惯于手动控制,更加精细化操作。
简单利用画图说明一下上面的描述:

以这里的CourseController和UserController为例,希望在CourseService和UserService类中的每个都开始事务操作,而且还不在修改CourseService和UserService类中源码的情况下来进行操作,不破坏原来的代码的完整性。
那么首先来写个简单的Demo来体验一下SpringAOP的强大之处。
### 二、案例演示
* 希望只针对UserServiceImpl类中的save无参方法来做日志打印处理。
因为工作中需要,需要利用到注解+xml的方法,所以下面没有提供纯注解的方式来进行操作。
在UserService接口中提供了三个方法来模拟实际开发过程中接口中的方法:
```java
public interface UserService {
void save();
void save(String name);
void save(String name, Integer age);
}
```
UserServiceImpl是对接口UserService的实现:
```java
@Service("userService")
public class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("UserService.save()");
try {
int randomMs = (int) (Math.random()*1000);
Thread.sleep(randomMs);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void save(String name) {
System.out.println("UserService.save(String)");
}
@Override
public void save(String name, Integer age) {
System.out.println("UserService.save(String, Integer)");
}
}
```
上面两步操作中是最为简单的!下面就要来为我们的save无参方法来进行增强。
所谓的增强,就是调用save无参方法的时候,打印我们的日志。
那么来写一个增强类即可:
```java
@Component
public class MyAdvice {
/**
* 环绕通知方法。调用者调用时,Spring会执行这个环绕通知方法
* @param pjp 由Spring传递进来的切入点对象(目标方法、目标对象、方法实参等等封装成的对象)
* @return
*/
public Object aroundMethod(ProceedingJoinPoint pjp){
Object result = null;
try {
System.out.println("调用业务方法之前,我希望看下是否已经走了动态代理");
long start = System.currentTimeMillis();
//自己调用目标对象,得到返回值
// 固定写法:调用业务方法-----所以上面的逻辑可以称之为在调用方法之前操作
result = pjp.proceed(pjp.getArgs());
// 下面的逻辑可以表示成调用业务逻辑方法之后的操作
System.out.println("调用业务方法之后,我希望看下业务方法执行之后的结果");
long end = System.currentTimeMillis();
System.out.println("当前业务方法调用过程中花费的是时间有:" + (end - start) + "毫秒");
} catch (Throwable throwable) {
System.out.println("当前方法抛出异常,对应的异常是:"+throwable);
} finally {
System.out.println("无论业务方法执行过程中出现了怎样的问题,那么这行代码最终都要来执行");
}
// 返回最终结果
return result;
}
}
```
因为利用到的是注解+xml的方式,所以再写一个xml来配置扫描类和动态代理生成过程:
```xml
```
那么来写一段代码测试:
```java
public class XmlAopTest {
@Test
public void test(){
ApplicationContext app = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
UserService userService = app.getBean("userService", UserService.class);
userService.save();
System.out.println("---------------------");
userService.save("tom");
System.out.println("---------------------");
userService.save("tom", 20);
}
}
```
打印结果如下所示:
```tex
调用业务方法之前,我希望看下是否已经走了动态代理
UserService.save()
调用业务方法之后,我希望看下业务方法执行之后的结果
当前业务方法调用过程中花费的是时间有:838毫秒
无论业务方法执行过程中出现了怎样的问题,那么这行代码最终都要来执行
---------------------
UserService.save(String)
---------------------
UserService.save(String, Integer)
```
从这里可以看到,尽管利用userService调用了三个无参方法,但是只有save无参方法进行了增强。那么到底是如何做到的呢?
下面我们先介绍概念,然后从概念来进行入手,着手分析,然后再进行分析。
### 三、AOP相关的概念
#### 3.1、AOP相关概念
* 目标对象(Target):要代理的/要增强的目标对象。
* 代理对象(Proxy):目标对象被AOP织入增强后,就得到一个代理对象
* 连接点(JoinPoint):能够被拦截到的点,在Spring里指的是方法
目标类里,所有能够进行增强的方法,都是连接点
* **切入点(PointCut)**:要对哪些连接点进行拦截的定义
已经增强的连接点,叫切入点
* **通知/增强(Advice)**:拦截到连接点之后要做的事情
对目标对象的方法,进行功能增强的代码
* **切面(Aspect)**:是切入点和通知的结合
* 织入(Weaving):把增强/通知 应用到 目标对象来创建代理对象的过程。Spring采用动态代理技术织入,而AspectJ采用编译期织入和装载期织入
那么画图来描述一下,我觉得更为稳妥,如下所示:

以例子来进行说明:
* 目标对象:就是courseservice所表示的单例对象;
* 代理对象:就是需要对courseservice对应的单例对象来进行代理的对象;
* 所谓的连接点:以上面的courseservice来举例,可以认为是courseservice类中的所有方法;
* 所谓的切入点:就是我们需要筛选courseservice类中的个别方法来作为特殊的点(如何筛选,那么就需要我们手写point表达式来进行选择);
* 增强:所谓的增强就是要对原来的方法来做何种操作;
* 切面:切入点+增强;
#### 3.2、AOP开发前要明确的事项
##### 我们要做的事情:
* 编写核心业务代码(Target目标类的目标方法)
* 编写通知类,通知类中有通知方法(Advice增强功能方法)
* 在配置文件中,配置织入关系,即将哪些通知与哪些切入点 结合,形成切面
##### Spring的AOP做的事情:
* 生成动态代理的过程(把通知织入到切入点的过程),是由Spring来实现的
* Spring会监控切入点方法的执行,一旦发现切入点方法执行,使用代理机制动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
#### 小结
* AOP相关的概念/术语
* 目标类Target:要对哪个类进行增强
* 代理对象Proxy:对目标类增强后的那个代理对象
* 连接点JoinPoint:目标类里可增强的方法
* 切入点PointCut:要增强的方法
* 通知Advice:功能增强的代码
* 切面Aspect:切入点 + 通知
* 织入Weaving:把切入点 和 通知 进行结合,生成代理对象的过程
* 使用AOP,我们要做的事情:
* 编写目标类,自己的业务代码
* 编写通知类
* 配置切面
* 使用AOP,Spring做的事情
* 根据我们配置的切面,进行织入生成代理对象
### 四、基于XML的AOP
#### 快速入门
##### 1) 需求描述
* 有目标类`UserServiceImpl`,有通知类`MyAdvice`
* 使用XML方式AOP,对目标类`UserServiceImpl`的方法进行增强
##### 2) 步骤分析
1. 创建maven项目,导入AOP相关的依赖坐标
2. 创建目标类(要增强的类,内部有切入点),创建通知类(内部有增强的方法代码)
3. 修改配置文件:
1. 把目标类和通知类都配置成为bean对象
2. 配置切入点和通知方法(增强方法)的织入关系:配置切面
4. 测试代码
##### 3) 入门实现
###### 1. 创建maven项目,导入坐标
```xml
org.springframework
spring-context
5.0.2.RELEASE
org.aspectj
aspectjweaver
1.8.9
org.springframework
spring-test
5.0.2.RELEASE
junit
junit
4.12
```
###### 2. 创建目标类和通知类
* 目标类:`com.guang.aop.UserServiceImpl`
```java
public class UserService{
void save();
}
```
```java
public class UserServiceImpl {
public void save(){
System.out.println("UserServiceImpl.save......");
}
}
```
* 通知类:`com.guang.aop.MyAdvice`
```java
public class MyAdvice {
public void before(){
System.out.println("前置通知...");
}
}
```
###### 3. 修改配置文件
1. 把目标类和通知类都配置到Spring配置文件中
2. 配置切入和通知方法(增强方法)的织入关系
```xml
```
> 注意:在xml中增加了aop的名称空间如下:
>
> 
###### 4. 测试代码
```java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private UserService userService;
@Test
public void testQuickStart(){
userService.save();
}
}
```
##### 4) 步骤小结
1. 导入jar包:`spring-context, aspectjweaver`
2. 编写目标类、编写通知类
3. 配置切面
```xml
```
#### AOP详解
##### 1) 切点表达式的写法
###### 语法:
```
execution([权限修饰符] 返回值类型 包名.类名.方法名(参数列表))
```
* 修饰符:可以省略
* 返回值类型:
* 可以指定类型。比如`String` (如果类型有歧义,就写全限定类名,比如:`java.util.Date`)
* `*`,表示任意字符。比如`Str*`,或者`*`
* 包名:
* 可以写`.`:表示当前包下的类或者子包。比如`com.guang.service`
* 可以写`..`:表示当前包里所有后代类、后代包。比如`com..service`
* `*`:表示任意字符。比如:`com.gua*`, `com.*`
* 类名:
* 可以指定类名。比如:`UserServiceImpl`
* `*` 表示任意字符。比如:`*ServiceImpl`,`*`
* 方法名:
* 可以指定方法名
* `*` 表示任意字符。比如:`save*`,`*`
* 参数列表:
* 可以指定类型。比如:`String,Integer`表示第一个参数是String,第二个参数是Integer类型
* `*`表示任意字符。比如:
* `String, *` 表示第一个参数是String,第二个参数是任意类型
* `Str*, Integer`表示第一个参数类型Str开头,第二个参数是Integer类型
* 可以使用`..`表示任意个数、任意类型的参数
###### 示例
```
execution(public void com.guang.dao.impl.UserDao.save())
execution(void com.guang.dao.impl.UserDao.*(..))
execution(* com.guang.dao.impl.*.*(..))
execution(* com.guang.dao..*.*(..))
execution(* *..*.*(..)) --不建议使用
```
##### 2) 通知的种类
###### 通知的语法
```xml
```
###### 通知的类型
| 名称 | 标签 | 说明 |
| -------- | ----------------------- | -------------------------------------------- |
| 前置通知 | `` | 通知方法在切入点方法之前执行 |
| 后置通知 | `` | 在切入点方法正常执行之后,执行通知方法 |
| 异常通知 | `` | 在切入点方法抛出异常时,执行通知方法 |
| 最终通知 | `` | 无论切入点方法是否有异常,最终都执行通知方法 |
| 环绕通知 | `` | 通知方法在切入点方法之前、之后都执行 |
###### 通知示例
> 注意:通知方法的名称随意,我们这里是为了方便理解,才起名称为:before, after等等
* 前置通知
* 通知方法定义`MyAdvice`的`before`方法:
```java
public void before(){
System.out.println("前置通知");
}
```
* xml配置
```xml
```
* 后置通知
* 通知方法定义
```java
public void afterReturning(){
System.out.println("后置通知");
}
```
* xml配置
```xml
```
* 环绕通知
* 通知方法定义
```java
/**
* @param pjp ProceedingJoinPoint:正在执行的切入点方法对象
* @return 切入点方法的返回值
*/
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕:前置通知...");
Object[] args = pjp.getArgs();
//切入点方法执行
Object proceed = pjp.proceed(args);
System.out.println("环绕:后置通知...");
return proceed;
}
```
* xml配置
```xml
```
* 异常抛出通知
* 通知方法定义
```java
public void afterThrowing(){
System.out.println("抛出异常通知");
}
```
* xml配置
```xml
```
* 最终通知
* 通知方法定义
```java
public void after(){
System.out.println("最终通知");
}
```
* xml配置
```xml
```
##### 4) 小结
* 需要我们编写的内容:
* 编写目标类,编写通知类
* 配置切面
```xml
```
* 注意环绕通知的方法
```java
public Object aroundMethod(ProceedingJoinPoint pjp){
Object reuslt = null;
try{
//写前置通知代码
//调用目标对象的方法
result = pjp.proceed(pjp.getArgs());
//写后置通知代码
}catch(Throwable t){
//写异常通知代码
}finally{
//写最终通知代码
}
}
```
### 五、扩展了解通知中获取切入点对象
#### 通知中获取切入点对象
#### 介绍
如果想要在通知方法中,获取切入点对象。可以在通知方法里直接增加以下参数:
* Spring提供的运行时连接点/切入点对象:
| 类名 | 介绍 |
| -------------------------------------- | ------------------------------------------------------------ |
| `org.aspectj.lang.JoinPoint` | 切入点对象,
用于前置、后置、异常、最终通知,作为通知方法的形参 |
| `org.aspectj.lang.ProceedingJoinPoint` | 切入点对象,是`JoinPoint`的子接口
用于环绕通知,作为通知方法的参数 |
* `org.aspectj.lang.JoinPoint`的常用方法
| 返回值 | 方法名 | 说明 |
| ----------------------- | ----------------- | ------------------------------------------------------------ |
| ` java.lang.Object[]` | `getArgs()` | 连接点的实参值. |
| ` Signature` | `getSignature()` | 连接点方法签名 |
| ` java.lang.Object` | `getTarget()` | Returns the target object. |
| ` java.lang.Object` | `getThis()` | Returns the currently executing object. |
| ` java.lang.String` | `toLongString()` | Returns an extended string representation of the join point. |
| ` java.lang.String` | `toShortString()` | Returns an abbreviated string representation of the join point. |
| ` java.lang.String` | `toString()` | |
| ` JoinPoint.StaticPart` | `getStaticPart()` | Returns an object that encapsulates the static parts of this join point. |
| ` java.lang.String` | `getKind()` | Returns a String representing the kind of join point. |
* `ProceedingJoinPoint`是`JoinPoint`的子接口,它除了上述方法,还有
| 返回值 | 方法名 | 说明 |
| ------------------- | ------------------------ | ------------------------------------------------------ |
| `java.lang.Object` | `proceed()` | 执行下一个通知;
如果后边没有通知了,调用目标方法 |
| ` java.lang.Object` | `proceed(Object[] args)` | 执行下一个通知;
如果后边没有通知了,调用目标方法 |
#### 示例
```java
public class MyAdvice {
public void before(JoinPoint jp) {
System.out.println("前置:" + jp.getSignature());
}
public void afterReturning(JoinPoint jp){
System.out.println("后置:" + jp.getSignature());
}
public void afterThrowing(JoinPoint jp){
System.out.println("异常:" + jp.getSignature());
}
public void after(JoinPoint jp){
System.out.println("最终:" + jp.getSignature());
}
public Object around(ProceedingJoinPoint pjp){
Object result = null;
try {
System.out.println("==环绕:前置通知==");
//调用对象的方法,返回方法执行结果
result = pjp.proceed(pjp.getArgs());
System.out.println("==环绕:后置通知==");
} catch (Throwable throwable) {
System.out.println("==环绕:异常通知==");
throwable.printStackTrace();
} finally {
System.out.println("==环绕:最终通知==");
}
return result;
}
}
```
### 通知中绑定参数
* 不同类型的通知,可以绑定的参数是不同的
#### 前置通知
* 在通知中,可以绑定参数:获取切入点方法的实参
* 通知方法:
```java
public void before(JoinPoint jp, Object params){
System.out.println("==前置通知==");
System.out.println("连接点:" + jp.getSignature());
System.out.println("实参:" + params);
}
```
* 切入点表达式:
```xml
```
#### 后置通知
* 在通知中,可以绑定参数:获取切入点方法的实参和返回值
* 通知方法:
```java
public void afterReturning(JoinPoint jp, Object params, Object result){
System.out.println("==后置通知==");
System.out.println("方法参数:" + params);
System.out.println("返回值:" + result);
}
```
* 切入点表达式:
```xml
```
#### 异常通知
* 在通知中,可以绑定参数:获取切入点方法的实参,和异常信息对象
* 通知方法:
```java
public void afterThrowing(Exception ex, Object params){
System.out.println("==异常通知==");
System.out.println("方法实参:" + params);
System.out.println("异常:" + ex);
}
```
* 切入点表达式:
```xml
```
#### 最终通知
* 在通知中,可以绑定参数:获取方法的实参
* 通知方法:
```java
public void after(Object params){
System.out.println("==最终通知==");
System.out.println("方法实参:" + params);
}
```
* 切入点表达式:
```xml
```
### 六、基于注解的AOP
#### 快速入门
##### 1) 需求描述
* 有目标类`UserServiceImpl`,有通知类`MyAdvice`
* 使用注解方式的AOP对目标类`UserServiceImpl`的方法进行增强
##### 2) 步骤分析
1. 创建maven项目,导入AOP需要的依赖坐标
2. 创建目标类,创建通知类
1. 使用注解`@Component`标注两个类,配置成为bean对象
2. 在通知类中,使用注解配置织入关系
3. 在配置文件中,开启组件扫描和AOP的自动代理(自动装配)
4. 测试
##### 3) 入门实现
###### 1. 创建maven项目,导入坐标
* 注意:需要增加AOP的实现包:`aspectjweaver`
```xml
org.springframework
spring-context
5.0.2.RELEASE
org.aspectj
aspectjweaver
1.8.9
org.springframework
spring-test
5.0.2.RELEASE
junit
junit
4.12
```
###### 2. 创建目标类,创建通知类
1. 使用注解标注两个类,配置成为bean对象
* 实际开发中,使用`@Repository`, `@Service`, `@Controller`注解,按照分层进行配置
2. 在通知类中,使用注解配置织入关系
* 目标类`com.guang.aop.Target`
```java
public class UserService{
void save();
}
```
```java
@Service("userService")
public class UserServiceImpl {
public void save(){
System.out.println("UserServiceImpl.save......");
}
}
```
* 通知类`com.guang.aop.MyAdvice`
```java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
//声明当前类是切面类:把切入点和通知,在这个类里进行织入,当前类就成为了一个切面类
@Aspect
@Component("myAdvice")
public class MyAdvice {
@Before("execution(void com.guang.impl..*.save())")
public void before(){
System.out.println("前置通知...");
}
@AfterReturning("execution(void com.guang.impl..*.save()))")
public void afterReturning(){
System.out.println("后置通知");
}
@After("execution(void com.guang.impl..*.save())")
public void after(){
System.out.println("最终通知");
}
@AfterThrowing("execution(void com.guang.impl..*.save())")
public void afterThrowing(){
System.out.println("抛出异常通知");
}
/**
* @param pjp ProceedingJoinPoint:正在执行的切入点方法对象
* @return 切入点方法的返回值
*/
@Around("execution(void com.guang.impl..*.save())")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕:前置通知...");
//切入点方法执行
Object proceed = pjp.proceed();
System.out.println("环绕:后置通知...");
return proceed;
}
}
```
###### 4. 开启组件扫描和AOP自动代理
* 在`applicationContext.xml`中
```xml
```
> 如果要使用纯注解开发,可以使用配置类代替`applicationContext.xml`,配置类如下:
>
> ```java
> @Configuration //标记当前类是:配置类
> @ComponentScan(basePackage="com.guang") //配置注解扫描
> @EnableAspectJAutoProxy //开启AOP自动代理
> public class AppConfig{
> }
> ```
###### 5. 测试
```java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private UserService userService;
@Test
public void testQuickStart(){
userService.save()
}
}
```
##### 4) 步骤小结
#### AOP详解
##### 1) 通知的种类
###### 通知的语法
```java
@通知注解("切入点表达式")
```
###### 通知的类型
| 名称 | 注解 | 说明 |
| -------- | ----------------- | ------------------------------------ |
| 前置通知 | `@Before` | 通知方法在切入点方法之前执行 |
| 后置通知 | `@AfterRuturning` | 通知方法在切入点方法之后执行 |
| 异常通知 | `@AfterThrowing` | 通知方法在抛出异常时执行 |
| 最终通知 | `@After` | 通知方法无论是否有异常,最终都执行 |
| 环绕通知 | `@Around` | 通知方法在切入点方法之前、之后都执行 |
* 注意:
* 注解方式配置的通知,执行顺序是:`前置->最终->后置/异常`
* 如果想要指定执行的顺序,就使用环绕通知
##### 2) 切点表达式的抽取
* 同xml的AOP一样,当多个切面的切入点表达式相同时,可以将切入点表达式进行抽取;
* 抽取方法是:
* 在增强类(切面类,即被`@Aspect`标的类)上增加方法,在方法上使用`@Pointcut`注解定义切入点表达式,
* 在增强注解中引用切入点表达式所在的方法
* 示例:
```java
@Aspect
@Component("myAdvice1")
public class MyAdvice1 {
//定义切入点表达式
@Pointcut("execution(void com.guang.service..*.save())")
public void myPointcut(){}
//引用切入点表达式
//完整写法:com.guang.aop.MyAdvice.myPointcut()
//简单写法:myPointcut(), 引入当前类里定义的表达式,可以省略包类和类名不写
@Before("myPointcut()")
public void before(){
System.out.println("前置通知...");
}
@AfterReturning("myPointcut()")
public void afterReturning(){
System.out.println("后置通知");
}
@After("myPointcut()")
public void after(){
System.out.println("最终通知");
}
@AfterThrowing("myPointcut()")
public void afterThrowing(){
System.out.println("抛出异常通知");
}
/*@Around("myPointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("前置通知...");
//切入点方法执行
Object proceed = pjp.proceed();
System.out.println("后置通知...");
return proceed;
}*/
}
```
##### 3) 小结
1. 在通知类上加注解`@Aspect`,声明成一个切面
2. 在通知类里方法上加注解`@Before/@AfterReturning/@AfterThrowing/@After/@Around`,配置切入点表达式
3. 在xml里开启aop的自动代理:``
### 七、纯注解的AOP
* 主要是把XML的配置,放到核心配置类上
```java
@Configuration
@ComponentScan(basePackages="com.guang")//开启组件扫描
@EnableAspectJAutoProxy //开启AOP的自动代理
public class AppConfig{
}
```
```java
@Aspect
@Component("myAdvice2")
public class MyAdvice2 {
//定义切入点表达式
@Pointcut("execution(void com.guang.service..*.save())")
public void myPointcut(){}
@Before("myPointcut()")
public void before(){
System.out.println("前置通知...");
}
@AfterReturning("myPointcut()")
public void afterReturning(){
System.out.println("后置通知");
}
@After("myPointcut()")
public void after(){
System.out.println("最终通知");
}
@AfterThrowing("myPointcut()")
public void afterThrowing(){
System.out.println("异常通知");
}
}
```
### 八、Spring事务管理
在Spring事务管理中,我推荐使用的是编程式事务,而不是声明式事务。
所谓的编程式事务就是我们手动来控制事务,而声明式事务则是有Spring来帮助我们来实现的。
而事务又是我们日常操作过程中最为常用且常用的,我们不知道Spring事务如何给我们操作的,所以建议不要将事务由Spring来进行处理,而是我们手动的来进行管理!!!
#### 编程式事务管理
* 所谓事务管理,即:按照给定的事务规则,来执行提交或回滚操作。其中:
* 给定的事务规则:用`TransactionDefinition`表示
* 按照..来执行提交或回滚操作:用`PlatformTransactionManager`来完成
* `TransactionStatus`用于表示一个运行着的事务的状态
对于PlatformTransactionManager和TransactionStatus来说,是固定的套路,但是我们最需要关注的是这里的事务规则,也就是TransactionDefinition,在下面会重点关注和介绍。
#### 关于编程式事务的说明以及API介绍
- 编程式事务管理:通过编写代码的方式实现事务管理
- 编程式事务管理,因事务管理与业务功能耦合性太强,不方便维护,目前已经基本不用,但是与SpringAOP结合起来使用更佳。
> spring 2.0 就已经提供了 xml配置的声明式事务管理的支持
- 以下API仅做介绍了解,用于了解Spring事务相关的API,并回顾事务相关的概念
#### ` PlatformTransactionManager`
- 是Spring提供的事务管理器**接口**,它提供了我们常用的操作事务的方法:开启事务、提交事务等
- 注意:`PlatformTransactionManager`是接口类型,不同的dao层技术有不同的实现,例如:
- dao层是jdbcTemplate或Mybatis时,实现类是:`DataSourceTransactionManager`。而我们最常用的就是mybatis,所以肯定使用的是DataSourceTransactionManager。
- dao层是Hibernate时,实现类是:`HibernateTransactionManager`
| 方法 | 返回值 | 说明 |
| ------------------------------------------ | ------------------- | ----------------------- |
| `getTransaction(TransactionDefinition td)` | `TransactionStatus` | 开启事务,并得到事务状态 |
| `commit(TransactionStatus status)` | | 提交事务 |
| `rollback(TransactionStatus status)` | | 回滚事务 |
#### `TransactionDefinition`
* 事务的定义信息对象,提供了以下常用方法:
| 方法 | 参数 | 返回值 | 说明 |
| -------------------------- | ---- | --------- | ------------------ |
| `getIsolationLevel()` | | `int` | 获取事务的隔离级别 |
| `getPropogationBehavior()` | | `int` | 获取事务的传播行为 |
| `getTimeout()` | | `int` | 获取超时时间 |
| `isReadOnly()` | | `boolean` | 是否只读的事务 |
##### 事务的隔离级别:
* `ISOLATION_DEFAULT`:默认事务隔离级别
* MySql默认隔离级别:`repeatable read`
* Oracle默认隔离级别:`read committed`
* `ISOLATION_READ_UNCOMMITTED`:读未提交--存在脏读、不可重复读、幻读
* `ISOLATION_READ_COMMITTED`:读已提交--存在不可重复读、幻读
* `ISOLATION_REPEATABLE_READ`:重复读--存在幻读
* `ISOLATION_SERIALIZABLE`:串行化--没有并发问题
##### 事务的传播行为:
用于解决业务方法调用业务方法时,事务的统一性问题的

也就是说service中的methodA方法调用methodB方法的时候,对于methodB来说,叫做传播行为,决定使用怎么样的事务来调用methodB方法。
> 以下三个,是要当前事务的
* `PROPAGATION_REQUIRED`:**需要有事务。默认**
* 如果有事务,就使用这个事务
* 如果没有事务,就创建事务。
* `PROPAGATION_SUPPORTS`:支持事务
* 如果有事务,就使用当前事务,
* 如果没有事务,就以非事务方式执行(没有事务)
* `PROPAGATION_MANDATORY`:强制的
* 如果有事务,就使用当前事务
* 如果没有事务,就抛异常
> 以下三个,是不要当前事务的
* `PROPAGATION_REQUIRES_NEW`:新建的
* 如果有事务,就把事务挂起,再新建事务
* 如果没有事务,新建事务
* `PROPAGATION_NOT_SUPPORTED`:不支持的
* 如果有事务,就把事务挂起,以非事务方式执行
* 如果没有事务,就以非事务方式执行
* `PROPAGATION_NEVER`:非事务的
* 如果有事务,就抛异常
* 如果没有事务,就以非事务方式执行
> 最后一个,是特殊的
* `PROPAGATION_NESTED`:嵌套的
* 如果有事务,就在事务里再嵌套一个事务执行
* 如果没有事务,就是类似`REQUIRED`的操作
##### 事务运行的超时时间:
超时后事务自动回滚
* 默认值-1,表示没有超时限制
* 如果有,可以以秒为单位进行设置
##### 是否只读:
* 如果设置为只读,那么方法只能查询,不能增删改
* 通常是查询方法设置为只读
#### `TransactionStatus`
* 提供了查询事务具体运行状态的方法,常用方法如下:
| 方法 | 返回值 | 说明 |
| -------------------- | --------- | ---------------------- |
| `hasSavePoint()` | `boolean` | 事务是否有回滚点 |
| `isCompleted()` | `boolean` | 事务是否已经完成 |
| `isNewTransaction()` | `boolean` | 是否是新事务 |
| `isRollbackOnly()` | `boolean` | 事务是否是要回滚的状态 |
#### 小结
* PlatformTransactionManager接口:
* 如果dao层用的是Mybatis、JdbcTemplate:用DataSourceTransactionManager
* 如果dao层用的是Hibernate:用HibernateTransactionManager
* 事务定义信息:
* 事务的隔离级别:通常使用默认`ISOLATION_DEFAULT`
* 事务的传播行为:通常使用默认`PROPAGATION_REQUIRED`
* 事务的超时时间:如果事务执行超时,会回滚。单位是秒。值为-1表示永不超时
* 事务是否是只读:如果只读,事务里只能执行查询操作,不能增删改
* 事务状态接口
* 事务是否有回滚点
* 事务是否已经完成
* 是否是新事务
* 事务是否是要回滚的状态
#### 示例代码一
下面也是来给出例子来进行说明展示:
给出实体类:
```java
public class Account {
private Integer id;
private String name;
private Float money;
// 省略get/set方法
}
```
给出业务逻辑代码:
```java
public interface AccountService {
void transfer(String from, String to, Float money);
}
```
具体实现类中的代码:
```java
@Service("accountService")
public class AccountServiceImpl implements AccountService {
// 引入事务操作
@Autowired
private AccountDao accountDao;
@Autowired
private TransactionDefinition txDefinition;
@Autowired
private PlatformTransactionManager txManager;
@Override
public void transfer(String from, String to, Float money) {
//开启事务,得到事务状态
TransactionStatus txStatus = txManager.getTransaction(txDefinition);
try {
//操作dao
Account fromAccount = accountDao.findByName(from);
Account toAccount = accountDao.findByName(to);
fromAccount.setMoney(fromAccount.getMoney() - money);
toAccount.setMoney(toAccount.getMoney() + money);
accountDao.edit(fromAccount);
accountDao.edit(toAccount);
//提交事务
txManager.commit(txStatus);
} catch (Exception e) {
//回滚事务
txManager.rollback(txStatus);
// 出现异常,进行补偿机制
}
}
}
```
那么给出dao代码:
```java
public interface AccountDao {
void edit(Account account) throws SQLException;
Account findByName(String name) throws SQLException;
}
```
实现类代码:
```java
package com.guang.dao.impl;
import com.guang.dao.AccountDao;
import com.guang.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.sql.SQLException;
import java.util.List;
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void edit(Account account) throws SQLException {
jdbcTemplate.update("update account set name=?, money=? where id=?", account.getName(), account.getMoney(), account.getId());
}
@Override
public Account findByName(String name) throws SQLException {
List accounts = jdbcTemplate.query("select * from account where name = ?", new BeanPropertyRowMapper<>(Account.class), name);
if (accounts == null || accounts.size() == 0) {
return null;
}else{
return accounts.get(0);
}
}
}
```
然后给出配置文件代码
```xml
```
从上面来看业务逻辑操作的话,可以看到代码侵入性太高!很明显不适合来使用。
```java
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private TransactionDefinition txDefinition;
@Autowired
private PlatformTransactionManager txManager;
@Override
public void transfer(String from, String to, Float money) {
//开启事务,得到事务状态
TransactionStatus txStatus = txManager.getTransaction(txDefinition);
try {
// ....
//提交事务
txManager.commit(txStatus);
} catch (Exception e) {
//回滚事务
txManager.rollback(txStatus);
e.printStackTrace();
}
}
}
```
但是从这里来看的话,无非就是多用了PlatformTransactionManager和TransactionDefinition而已。
* 那么我的想法是将上面的代码抽取起来写成一个通知类型,然后配置PointCut和Advice组成Advisor。然而在我即将要动手去写的时候,在查看DefaultTransactionDefinition类的时候,我发现有继承类TransactionTemplate,我一看这玩意儿不是我们在项目中经常使用的一个类吗?以前也写过,也遇到过,但是在这里遇到的时候,我才恍然大悟。原来我们一直就是这么用的,但是从来没有去想过背后的原理,所以这也是写这篇博客的收获之一!非常幸运。
那么首先来分析一下我原来的思路(即按照切面类的想法)
* 1、针对读写类型来定义事务规则,这里涉及到四个属性,在DefaultTransactionDefinition中存在:
* propagationBehavior,默认值是PROPAGATION_REQUIRED,即表示有事务就使用这个事务,没有事务就不用;
* isolationLevel,默认值是:ISOLATION_DEFAULT,即表示默认使用数据库的事务。如果使用的是MySQL,那么就用MySQL的RR,可重复读级别;
* timeout:默认值是:TIMEOUT_DEFAULT(-1),表示永不超时,超时时间以秒为单位;
* readOnly:默认值是:false,表示的是增删改查询使用事务,查询不使用事务;
之前我想的是定义多个事务规则对象,然后配置多个环绕通知方法,在开启事务的时候,根据不同的事务规则对象来获取得到事务。
* 2、注入事务管理器,然后传入事务规则定义,获取得到当前的事务状态;
* 3、利用事务管理器通过状态来进行提交或者是回滚事务;
上面可以通过AOP切面类来进行配置,但是看到了TransactionTemplate之后,我发现根本就不需要使用到切面类就可以来进行操作。
那么首先来看下TransactionTemplate类的继承体系:

分析一下继承体系:
* 1、继承了DefaultTransactionDefinition类,所以可以用来定义事务规则信息对象;
* 2、实现了InitializingBean接口,那么肯定是要在初始化方法来做操作;
* 3、实现了TransactionOperations接口,从接口名称中可以知道这里代表的是事务;
而在TransactionTemplate类中,只有一个属性PlatformTransactionManager,而这是又是我们所需要的事务管理器。
而在InitializingBean接口的初始化方法中做了校验,如下所示:
```java
public void afterPropertiesSet() {
if (this.transactionManager == null) {
throw new IllegalArgumentException("Property 'transactionManager' is required");
}
}
```
因为TransactionTemplate中存在无参方法,没有注入transactionManager对象,说明我们需要手动注入当前容器中的transactionManager对象。那么配置TransactionTemplate的时候可以配置如下所示:
```java
```
如下所示,配置多个事务定义规则:
```java
```
那么只需要在使用的时候,通过以下方式注入即可:
```java
@Autowired
@Qulifier("transactionTemplat1")
private TransactionTemplate transactionTemplate
@Autowired
@Qulifier("transactionTemplat2")
private TransactionTemplate transactionTemplate
@Autowired
@Qulifier("transactionTemplat3")
private TransactionTemplate transactionTemplate
```
通过这种方式来进行注入即可,也是比较方便的。
那么上面说完事务规则定义这块,这块应该有个疑问:在TransactionTemplate从哪里根据事务管理器获取得到事务状态的代码呢?
类似如下的代码:
```java
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private TransactionDefinition txDefinition;
@Autowired
private PlatformTransactionManager txManager;
@Override
public void transfer(String from, String to, Float money) {
//开启事务,得到事务状态
TransactionStatus txStatus = txManager.getTransaction(txDefinition);
try {
// ....
//提交事务
txManager.commit(txStatus);
} catch (Exception e) {
//回滚事务
txManager.rollback(txStatus);
e.printStackTrace();
}
}
}
```
来从TransactionTemplate中找,发现就提供了一个方法:execute方法,那么重点就关注一下execute方法即可。
这个方法也是非常的简单,重点是这里的TransactionCallback接口,提前看下:
```java
public interface TransactionCallback {
T doInTransaction(TransactionStatus status);
}
```
接口中只有一个抽象方法,又被称之为函数式接口。不懂的话接着向下看

这里的方法也是非常简单呐,相当于是已经帮助我们做好了。
给出一个使用代码示例:
这里给的是没有返回值的,所以这里给的是Void类型。
```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class TransactionalService {
@Autowired
private TransactionTemplate transactionTemplate;
public void performTransactionalOperation() {
// 不建议使用下面两行代码!因为如果忘记了设置会默认值,那么后面再次使用的时候默认使用的九种方式
/**nsactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);**/
transactionTemplate.execute(new TransactionCallback() {
public Void doInTransaction(TransactionStatus status) {
// 在这里执行事务操作
// 可以进行数据库操作、调用其他需要事务支持的方法等
return null;
}
});
}
}
```
再给出一个有值的代码且可以自己来处理事务状态。不利用事务管理中的回滚方法而已!
```java
public Object getObject(String str) {
/*
* 执行带有返回值