基于AOP的声明事务控制
Spring事务编程概述
- 事务是开发过程中必不可少的东西,使用JDBC开发时,我们使用connection对事务进行控制,使用MyBatis时,我们使用SqlSession对事物进行控制,缺点显而易见,当我们切换数据库访问技术时,事务控制的方法总会改变。Spring就在这些技术的基础上,提供了统一的控制事务接口。Spring的事务分为:编程式事务和声明式事务控制。
|---------|----------------------------------------------------------------------------------|
| 事务控制方式 | 解释 |
| 编程式事务控制 | Spring提供了事务控制的类和方法,使用编程的方法对业务代码进行事务控制,事务控制代码和业务操作代码耦合在一起,开发中不使用 |
| 声明式事务 | Spring将事务控制的代码封装起来,对外提供xml和注解的配置方式,通过配置的方式完成事务的控制,可以达到事务控制和业务操作代码的解耦,开发中推荐使用 |
-
Spring事务编程相关的类主要有以下三个
类名 功能 PlatformTransactionManager 平台事务管理器,抽象了不同的事务技术(如 JDBC、JTA)下的事务管理器。它定义了事务的开始、提交和回滚等操作接口,由具体实现提供相应的实现。Spring 提供了多种实现,不同持久层有不同实现方案,如 DataSourceTransactionManager、HibernateTransactionManager 和 JpaTransactionManager 等。 TransactionDefinition 事务定义,用于定义事务的隔离级别、超时时间等属性。Spring 定义了多种常量值,如 ISOLATION_DEFAULT
、ISOLATION_READ_COMMITTED
、ISOLATION_REPEATABLE_READ
等隔离级别;TIMEOUT_DEFAULT
、TIMEOUT_NONE
等超时时间。TransactionStatus 事务状态,包括是否新事务、是否已完成、是否回滚等状态。将该状态对象传递给事务管理器的 commit()
或rollback()
方法可以控制事务的提交或回滚操作。- 虽然编程式事务控制我们不学习,但是编程式事务控制对应的类我们需要理解一下,因为我们在通过配置的方式进行声明式事务控制时也会看到这些类的影子。
搭建测试环境
- 搭建转账的环境,dao层一个转出钱的方法,service是一个转账业务的方法,内部分别调用dao层转出钱和转入钱的方法,准备工作如下
-
数据库准备一个账户表tb_account;
-
dao层准备一个AccountMapper,包括incrMoney和decrMoney两个方法
*javapackage com.example.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Update; import org.springframework.stereotype.Repository; @Repository("accountMapper") public interface AccountMapper { @Update("update tb_account set money = money + #{money} where account_name=#{accountName}") public void incrMoney(@Param("accountName") String accountName, @Param("money") Integer money); @Update("update tb_account set money = money - #{money} where account_name=#{accountName}") public void decrMoney(@Param("accountName") String accountName, @Param("money") Integer money); }
-
service层(作为目标类,使用了注解的方式将其交给Spring容器管理,不需要再到xml配置文件中去配置)准备一个transferMoney方法,分别调用incrMoney和decrMoney方法
*javapackage com.example.Service.ServiceImpl; import com.example.Mapper.AccountMapper; import com.example.Service.AccountService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service("accountService") public class AccountServiceImpl implements AccountService { @Autowired private AccountMapper accountMapper; @Override public void transferMoney(String outAccount, String inAccount, Integer money) { accountMapper.decrMoney(outAccount, money); System.out.println(outAccount + "转出" + money + "元"); accountMapper.incrMoney(inAccount, money); System.out.println(inAccount + "转入" + money + "元"); } }
-
在applicationContext文件中进行Bean的管理配置
*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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 指定Spring组件扫描范围--> <context:component-scan base-package="com.example"/> <!--配置数据源信息--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/db02"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <!-- 配置SqlSessionFactoryBean,作用将SqlSessionFactoryBean存储到Spring容器中--> <bean class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.Mapper"/> </bean> </beans>
测试正常转账和异常转账
- 正常测试代码
```java package com.example.Test; //import com.example.Config.MyBatisConfig; import com.example.Service.AccountService; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestAccount { public static void main(String[] args) { // ApplicationContext context = new AnnotationConfigApplicationContext(MyBatisConfig.class); ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountService accountService = (AccountService) context.getBean("accountService"); accountService.transferMoney("tom", "lucy", 500); } } ```
-
运行结果如下:
-
错误测试,在业务层中加入错误
```java package com.example.Service.ServiceImpl; import com.example.Mapper.AccountMapper; import com.example.Service.AccountService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service("accountService") public class AccountServiceImpl implements AccountService { @Autowired private AccountMapper accountMapper; @Override public void transferMoney(String outAccount, String inAccount, Integer money) { accountMapper.decrMoney(outAccount, money); System.out.println(outAccount + "转出" + money + "元"); int i = 1 / 0; accountMapper.incrMoney(inAccount, money); System.out.println(inAccount + "转入" + money + "元"); } } ```
-
同样运行上述测试代码
-
运行结果如下(出现数据无法对应)
-
基于xml声明式事务控制
-
综合我们上面学到的AOP技术,很容易想到,可以使用AOP对Service的方法进行事务增强
- 目标类:自定义的AccounServiceImpl,内部的方法是切点。不需要在xml配置文件中再次进行配置,已经使用注解方式,将其交给Spring容器管理。
- 通知类:Spring提供的,通知方法已经定义好,只需配置即可。
-
分析
- 通知类是Spring提供的,需要导入Spring事务相关的坐标
- 配置目标类AccountServiceImpl
- 使用advisor标签配置切面
-
具体的配置文件如下
*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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 指定Spring组件扫描范围--> <context:component-scan base-package="com.example"/> <!--配置数据源信息--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/db02"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <!-- 配置SqlSessionFactoryBean,作用将SqlSessionFactoryBean存储到Spring容器中--> <bean class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.Mapper"/> </bean> <!-- 配置平台事务管理器--> <bean name="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置Spring提供的通知(advice)--> <tx:advice id="AccountServiceAdvice" transaction-manager="transactionManager1"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- 事务增强的AOP--> <aop:config> <!-- 配置切点表达式--> <aop:pointcut id="AccountService" expression="execution(* com.example.Service.ServiceImpl.AccountServiceImpl.*(..))"/> <!-- 配置织入关系,通知引用Spring所提供的--> <aop:advisor advice-ref="AccountServiceAdvice" pointcut-ref="AccountService"/> </aop:config> </beans>
对于事务、AOP、代理对象的产生等相关知识,可以前往我的主页进行关键字搜索,自行查阅。
- 上述配置文件在最初配置文件的基础之上,使用AOP将目标方法进行事务管理
- 具体的做法如下
- 首先设置切点表达式,确定目标方法后,然后将切点表达式和Spring提供的通知进行织入,而此时需要配置Spring提供的通知,在配置该通知时需要配置事务平台管理器,最后将事务平台管理器配置好之后,就完成了对于目标方法的事务管理。
- 事务平台管理器的作用
-
事务的创建:事务平台管理器通过获取事务配置,从而创建一个新的事务对象,用于执行目标方法。
-
事务的提交:在目标方法正常执行结束后,事务平台管理器会将事务提交,从而将所有修改操作永久保存到数据库。
-
事务的回滚:在目标方法执行发生异常时,事务平台管理器会自动回滚事务,将所有修改操作撤销,保持系统数据的一致性。
-
事务的传播行为:事务平台管理器可以根据配置,将事务从一个方法传播到另一个方法,以保证不同方法之间的数据一致性。
-
事务隔离级别:事务平台管理器可以设置事务的隔离级别,从而决定不同事务之间可见的数据范围和并发度等问题。
-
Spring 支持多种事务平台管理器,例如 DataSourceTransactionManager、HibernateTransactionManager、JpaTransactionManager 等。不同的事务平台管理器对应着不同的持久化技术和数据库访问框架,在使用时需要注意选择合适的方案。
当我们使用 Spring 进行事务管理时,事务平台管理器就是整个事务处理流程中的核心组件。它通过管理和控制事务,保证了数据操作的原子性、一致性、隔离性和持久性等特性,从而维护了数据库的完整性和系统的稳定性。
-
- 再次运行最初存在人为错误的情况下,测试代码
- 运行结果如下
数据一致性得到了保障,AOP进行事务管理成功
详解
- 明天再来,p98