Spring事务原理分析--手写Spring事务

时间:2023-03-09 23:08:52
Spring事务原理分析--手写Spring事务

一、基本概念和原理

1、Spring事务 基于AOP环绕通知和异常通知的

2、Spring事务分为编程式事务、声明事务。编程事务包括注解方式和扫包方式(xml)

Spring事务底层使用编程事务(自己去begin,Commit,Rollback等)+AOP技术进行包装 = 声明式事务

3、事务的特性:ACID

原子性,一致性,隔离性,持久性

1) 原子性:原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。

2) 一致性:一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

  拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性

3) 隔离性: 隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

  即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。

4)持久性:持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。

二、Spring技术:IOC和AOP

1、什么是AOP技术

  面向切面编程,解决代码复用问题。 Spring AOP原理

核心点: 在方法之前后之后处理事情

  AOP底层实现原理:代理设计模式

2、AOP技术应用场景

广泛应用于处理一些具有横切性质的系统服务,如日志输出,安全控制(权限),事务管理,缓存,对象池,异常处理等 Spring AOP原理

3、为什么要用AOP技术

  复用和解耦

4、AOP技术

关注点: 重复代码

  切面: 关注点形成的类,可以叫做切面类,将重复的代码抽取处理,在运行的时候业务方法上动态植入。

  切入点: 执行的目标对象。

三、AOP Demo1  

1、创建工程

Spring事务原理分析--手写Spring事务

下一步

Spring事务原理分析--手写Spring事务

2、工程结构

Spring事务原理分析--手写Spring事务

3、引入Spring-AOP等相关Jar

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>springaopdemo1</artifactId>
<version>0.0.1-SNAPSHOT</version> <dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.0.6.RELEASE</version>
</dependency> <!-- 引入Spring-AOP等相关Jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_2</version>
</dependency> <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
</dependencies> </project>

  

4、创建接口

public interface UserService {
public void add();
}

  

5、接口实现

@Service
public class UserServiceImpl implements UserService { public void add() {
System.out.println("往数据库添加数据..."); } }

  

6、打印日志切面类

// 切面类
@Component
@Aspect
public class AopLog {
//aop 编程里面有几个通知: 前置通知 后置通知,运行通知,异常通知,环绕通知 // 前置通知
@Before("execution(* com.example.service.UserService.add(..))")
public void begin() {
System.out.println("前置通知");
} //
// 后置通知
@After("execution(* com.example.service.UserService.add(..))")
public void commit() {
System.out.println("后置通知");
} // 运行通知
@AfterReturning("execution(* com.example.service.UserService.add(..))")
public void returning() {
System.out.println("运行通知");
} // 异常通知
@AfterThrowing("execution(* com.example.service.UserService.add(..))")
public void afterThrowing() {
System.out.println("异常通知");
} // 环绕通知
@Around("execution(* com.example.service.UserService.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知开始");
proceedingJoinPoint.proceed();
System.out.println("环绕通知结束");
} }

  

7、spring.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.example"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 开启事物注解 -->
</beans>

  

8、测试类

public class Test001 {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService)applicationContext.getBean("userServiceImpl");
userService.add();
}
}

  

9、打印结果

Spring事务原理分析--手写Spring事务

10. 模拟异常通知

1) 增加异常

Spring事务原理分析--手写Spring事务

2)运行结果

Spring事务原理分析--手写Spring事务

三、Spring 事务环境搭建

1、MySql数据库创建表t_users

Spring事务原理分析--手写Spring事务

注意:引擎为InnoDB,这样才能支持事务。

Spring事务原理分析--手写Spring事务

2、spring.xml 增加配置

	<!-- 1. 数据源对象: C3P0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean> <!-- 2. JdbcTemplate工具类实例 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean> <!-- 3.配置事务 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>

  完整的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.example"></context:component-scan> <!-- . 数据源对象: C3P0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
<property name="user" value="root"></property>
<property name="password" value=""></property>
</bean> <!-- . JdbcTemplate工具类实例 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean> <!-- .配置事务 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean> <!-- 开启事物注解 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>

3、增加数据库操作

package com.example.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository; @Repository
public class UserDao { @Autowired
private JdbcTemplate jdbcTemplate; public void add(String name, Integer age) {
String sql = "INSERT INTO t_users(NAME, age) VALUES(?,?);";
int updateResult = jdbcTemplate.update(sql, name, age);
System.out.println("updateResult:" + updateResult);
}
}

  

4)UserServiceImpl调用UserDao

@Service
public class UserServiceImpl implements UserService { @Autowired
private UserDao userDao; public void add() { userDao.add("test001", 20);
System.out.println("##############");
userDao.add("test002", 22);
} }

  

5、运行代码,测试结果

Spring事务原理分析--手写Spring事务

6、如果两个数据库操作,中间抛出异常

Spring事务原理分析--手写Spring事务

这样,第一条数据插入了数据,第二条没有执行,因为前面已经抛出异常了。

四、手写Spring编程事务

方式1:非AOP方式

1、事务工具类

package com.example.transaction;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute; // 编程事务(手动begin,手动回滚, 手动提交)
@Component
public class TransactionUtils { //获取事务源
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager; //开启事务
public TransactionStatus begin() {
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
return transaction;
} //开启事务
public void commit(TransactionStatus transaction) {
dataSourceTransactionManager.commit(transaction);
} //开启事务
public void rollback(TransactionStatus transaction) {
dataSourceTransactionManager.rollback(transaction);
} }

2、

@Service
public class UserServiceImpl implements UserService { @Autowired
private UserDao userDao; @Autowired
private TransactionUtils transactionUtils; public void add() {
TransactionStatus transactionStatus = null;
try{
//开启事务
transactionStatus = transactionUtils.begin();
userDao.add("test001", 20);
int i = 1/0;
System.out.println("##############");
userDao.add("test002", 22);
//提交事务
if(transactionStatus != null){
transactionUtils.commit(transactionStatus);
}
}catch(Exception e){
e.printStackTrace();
//回滚事务
if(transactionStatus != null){
transactionUtils.rollback(transactionStatus); }
} } }

  

 

 

方式二、使用AOP类统一处理

1、AOP类

package com.example.transaction;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.TransactionAspectSupport; @Component
@Aspect
public class AopTransaction { @Autowired
private TransactionUtils transactionUtils; // // 异常通知
@AfterThrowing("execution(* com.example.service.UserService.add(..))")
public void afterThrowing() {
System.out.println("程序已经回滚");
// 获取程序当前事务 进行回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
} // 环绕通知
@Around("execution(* com.example.service.UserService.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("开启事务");
TransactionStatus begin = transactionUtils.begin();
proceedingJoinPoint.proceed();
transactionUtils.commit(begin);
System.out.println("提交事务");
} }

  

2、UserServiceImpl 如下

@Service
public class UserServiceImpl implements UserService { @Autowired
private UserDao userDao; public void add() {
userDao.add("test001", 20);
int i = 1/0;
System.out.println("##############");
userDao.add("test002", 22); } }

  这样,AOP类统一处理了。当出现异常时,AOP类进如异常处理,然后回滚。