本篇章中所有的代码都将会放置到git仓库中去,并且会做一个简要的说明。
Spring中所谓的AOP就是在不修改源码的情况下,来进行增强。所谓的增强其实就是在方法执行前后添加一些额外操作。
所谓的增强,就是我们如何来对方法(以类中的方法为基本单位)处理。处理方法有五种:前置增强、后置增强等等
但是最为常用的还是利用环绕通知来进行增强,习惯于手动控制,更加精细化操作。
简单利用画图说明一下上面的描述:
以这里的CourseController和UserController为例,希望在CourseService和UserService类中的每个都开始事务操作,而且还不在修改CourseService和UserService类中源码的情况下来进行操作,不破坏原来的代码的完整性。
那么首先来写个简单的Demo来体验一下SpringAOP的强大之处。
因为工作中需要,需要利用到注解+xml的方法,所以下面没有提供纯注解的方式来进行操作。
在UserService接口中提供了三个方法来模拟实际开发过程中接口中的方法:
public interface UserService {
void save();
void save(String name);
void save(String name, Integer age);
}
UserServiceImpl是对接口UserService的实现:
@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无参方法的时候,打印我们的日志。
那么来写一个增强类即可:
@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来配置扫描类和动态代理生成过程:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.guang.service.impl"/>
<context:component-scan base-package="com.guang.advice"/>
<aop:config>
<aop:aspect ref="myAdvice">
<aop:around method="aroundMethod" pointcut="execution(public void com.guang.service..UserServiceImpl.save())"/>
aop:aspect>
aop:config>
beans>
那么来写一段代码测试:
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);
}
}
打印结果如下所示:
调用业务方法之前,我希望看下是否已经走了动态代理
UserService.save()
调用业务方法之后,我希望看下业务方法执行之后的结果
当前业务方法调用过程中花费的是时间有:838毫秒
无论业务方法执行过程中出现了怎样的问题,那么这行代码最终都要来执行
---------------------
UserService.save(String)
---------------------
UserService.save(String, Integer)
从这里可以看到,尽管利用userService调用了三个无参方法,但是只有save无参方法进行了增强。那么到底是如何做到的呢?
下面我们先介绍概念,然后从概念来进行入手,着手分析,然后再进行分析。
目标对象(Target):要代理的/要增强的目标对象。
代理对象(Proxy):目标对象被AOP织入增强后,就得到一个代理对象
连接点(JoinPoint):能够被拦截到的点,在Spring里指的是方法
目标类里,所有能够进行增强的方法,都是连接点
切入点(PointCut):要对哪些连接点进行拦截的定义
已经增强的连接点,叫切入点
通知/增强(Advice):拦截到连接点之后要做的事情
对目标对象的方法,进行功能增强的代码
切面(Aspect):是切入点和通知的结合
织入(Weaving):把增强/通知 应用到 目标对象来创建代理对象的过程。Spring采用动态代理技术织入,而AspectJ采用编译期织入和装载期织入
那么画图来描述一下,我觉得更为稳妥,如下所示:
以例子来进行说明:
目标对象:就是courseservice所表示的单例对象;
代理对象:就是需要对courseservice对应的单例对象来进行代理的对象;
所谓的连接点:以上面的courseservice来举例,可以认为是courseservice类中的所有方法;
所谓的切入点:就是我们需要筛选courseservice类中的个别方法来作为特殊的点(如何筛选,那么就需要我们手写point表达式来进行选择);
增强:所谓的增强就是要对原来的方法来做何种操作;
切面:切入点+增强;
UserServiceImpl
,有通知类MyAdvice
UserServiceImpl
的方法进行增强<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.0.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.9version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.0.2.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
com.guang.aop.UserServiceImpl
public class UserService{
void save();
}
public class UserServiceImpl {
public void save(){
System.out.println("UserServiceImpl.save......");
}
}
com.guang.aop.MyAdvice
public class MyAdvice {
public void before(){
System.out.println("前置通知...");
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="com.guang.service.impl.UserServiceImpl"/>
<bean id="myAdvice" class="com.guang.aop.MyAdvice"/>
<aop:config>
<aop:aspect ref="myAdvice">
<aop:before method="before" pointcut="execution(void com.guang.service.impl.UserServiceImpl.save())"/>
aop:aspect>
aop:config>
beans>
注意:在xml中增加了aop的名称空间如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private UserService userService;
@Test
public void testQuickStart(){
userService.save();
}
}
spring-context, aspectjweaver
<aop:config>
<aop:aspect ref="通知对象">
<aop:before method="通知对象里的通知方法" pointcut="切入点表达式"/>
aop:aspect>
aop:config>
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(* *..*.*(..)) --不建议使用
<aop:通知类型 method="通知中的方法" pointcut="切点表达式">aop:通知类型>
名称 | 标签 | 说明 |
---|---|---|
前置通知 |
|
通知方法在切入点方法之前执行 |
后置通知 |
|
在切入点方法正常执行之后,执行通知方法 |
异常通知 |
|
在切入点方法抛出异常时,执行通知方法 |
最终通知 |
|
无论切入点方法是否有异常,最终都执行通知方法 |
环绕通知 |
|
通知方法在切入点方法之前、之后都执行 |
注意:通知方法的名称随意,我们这里是为了方便理解,才起名称为:before, after等等
前置通知
MyAdvice
的before
方法:public void before(){
System.out.println("前置通知");
}
<aop:before method="before"
pointcut="execution(void com.guang.service..*.save())"/>
后置通知
public void afterReturning(){
System.out.println("后置通知");
}
<aop:after-returning method="afterReturning"
pointcut="execution(void com.guang.service..*.save())"/>
环绕通知
/**
* @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;
}
<aop:around method="around"
pointcut="execution(void com.guang.service..*.save())"/>
异常抛出通知
public void afterThrowing(){
System.out.println("抛出异常通知");
}
<aop:after-throwing method="afterThrowing"
pointcut="execution(void com.guang.service..*.save())"/>
最终通知
public void after(){
System.out.println("最终通知");
}
<aop:config>
<aop:pointcut id="myPointCut"
expression="execution(void com.guang.service..*.save())"/>
<aop:aspect ref="myAdvice">
<aop:before method="before" pointcut-ref="myPointCut"/>
<aop:after-returning method="afterReturning" pointcut-ref="myPointCut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointCut"/>
<aop:after method="after" pointcut-ref="myPointCut"/>
aop:aspect>
aop:config>
<aop:config>
<aop:pointcut id="xxx" expression="切入点表达式"/>
<aop:aspect ref="通知对象">
<aop:before method="通知对象里的通知方法" pointcut-ref="xxx"/>
<aop:after-returning method="通知对象里的通知方法" pointcut-ref="xxx"/>
<aop:after-throwing method="通知对象里的通知方法" pointcut-ref="xxx"/>
<aop:after method="通知对象里的通知方法" pointcut-ref="xxx"/>
<aop:around method="通知对象里的通知方法" pointcut-ref="xxx"/>
aop:aspect>
aop:config>
public Object aroundMethod(ProceedingJoinPoint pjp){
Object reuslt = null;
try{
//写前置通知代码
//调用目标对象的方法
result = pjp.proceed(pjp.getArgs());
//写后置通知代码
}catch(Throwable t){
//写异常通知代码
}finally{
//写最终通知代码
}
}
UserServiceImpl
,有通知类MyAdvice
UserServiceImpl
的方法进行增强@Component
标注两个类,配置成为bean对象aspectjweaver
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.0.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.9version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.0.2.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
使用注解标注两个类,配置成为bean对象
@Repository
, @Service
, @Controller
注解,按照分层进行配置在通知类中,使用注解配置织入关系
com.guang.aop.Target
public class UserService{
void save();
}
@Service("userService")
public class UserServiceImpl {
public void save(){
System.out.println("UserServiceImpl.save......");
}
}
com.guang.aop.MyAdvice
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;
}
}
applicationContext.xml
中
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.guang"/>
<aop:aspectj-autoproxy/>
beans>
如果要使用纯注解开发,可以使用配置类代替
applicationContext.xml
,配置类如下:@Configuration //标记当前类是:配置类 @ComponentScan(basePackage="com.guang") //配置注解扫描 @EnableAspectJAutoProxy //开启AOP自动代理 public class AppConfig{ }
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private UserService userService;
@Test
public void testQuickStart(){
userService.save()
}
}
@通知注解("切入点表达式")
名称 | 注解 | 说明 |
---|---|---|
前置通知 | @Before |
通知方法在切入点方法之前执行 |
后置通知 | @AfterRuturning |
通知方法在切入点方法之后执行 |
异常通知 | @AfterThrowing |
通知方法在抛出异常时执行 |
最终通知 | @After |
通知方法无论是否有异常,最终都执行 |
环绕通知 | @Around |
通知方法在切入点方法之前、之后都执行 |
前置->最终->后置/异常
@Aspect
标的类)上增加方法,在方法上使用@Pointcut
注解定义切入点表达式,@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;
}*/
}
@Aspect
,声明成一个切面@Before/@AfterReturning/@AfterThrowing/@After/@Around
,配置切入点表达式
@Configuration
@ComponentScan(basePackages="com.guang")//开启组件扫描
@EnableAspectJAutoProxy //开启AOP的自动代理
public class AppConfig{
}
@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来进行处理,而是我们手动的来进行管理!!!
TransactionDefinition
表示PlatformTransactionManager
来完成TransactionStatus
用于表示一个运行着的事务的状态对于PlatformTransactionManager和TransactionStatus来说,是固定的套路,但是我们最需要关注的是这里的事务规则,也就是TransactionDefinition,在下面会重点关注和介绍。
编程式事务管理:通过编写代码的方式实现事务管理
编程式事务管理,因事务管理与业务功能耦合性太强,不方便维护,目前已经基本不用,但是与SpringAOP结合起来使用更佳。
spring 2.0 就已经提供了 xml配置的声明式事务管理的支持
以下API仅做介绍了解,用于了解Spring事务相关的API,并回顾事务相关的概念
PlatformTransactionManager
PlatformTransactionManager
是接口类型,不同的dao层技术有不同的实现,例如:
DataSourceTransactionManager
。而我们最常用的就是mybatis,所以肯定使用的是DataSourceTransactionManager。HibernateTransactionManager
方法 | 返回值 | 说明 |
---|---|---|
getTransaction(TransactionDefinition td) |
TransactionStatus |
开启事务,并得到事务状态 |
commit(TransactionStatus status) |
提交事务 | |
rollback(TransactionStatus status) |
回滚事务 |
TransactionDefinition
方法 | 参数 | 返回值 | 说明 |
---|---|---|---|
getIsolationLevel() |
int |
获取事务的隔离级别 | |
getPropogationBehavior() |
int |
获取事务的传播行为 | |
getTimeout() |
int |
获取超时时间 | |
isReadOnly() |
boolean |
是否只读的事务 |
ISOLATION_DEFAULT
:默认事务隔离级别
repeatable read
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
的操作超时后事务自动回滚
TransactionStatus
方法 | 返回值 | 说明 |
---|---|---|
hasSavePoint() |
boolean |
事务是否有回滚点 |
isCompleted() |
boolean |
事务是否已经完成 |
isNewTransaction() |
boolean |
是否是新事务 |
isRollbackOnly() |
boolean |
事务是否是要回滚的状态 |
ISOLATION_DEFAULT
PROPAGATION_REQUIRED
下面也是来给出例子来进行说明展示:
给出实体类:
public class Account {
private Integer id;
private String name;
private Float money;
// 省略get/set方法
}
给出业务逻辑代码:
public interface AccountService {
void transfer(String from, String to, Float money);
}
具体实现类中的代码:
@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代码:
public interface AccountDao {
void edit(Account account) throws SQLException;
Account findByName(String name) throws SQLException;
}
实现类代码:
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<Account> 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);
}
}
}
然后给出配置文件代码
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<context:component-scan base-package="com.guang"/>
<bean id="txDefinition" class="org.springframework.transaction.support.DefaultTransactionDefinition">
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
<property name="isolationLevelName" value="ISOLATION_DEFAULT"/>
<property name="timeout" value="-1"/>
<property name="readOnly" value="false"/>
bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<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/spring"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"/>
bean>
beans>
从上面来看业务逻辑操作的话,可以看到代码侵入性太高!很明显不适合来使用。
@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而已。
那么首先来分析一下我原来的思路(即按照切面类的想法)
1、针对读写类型来定义事务规则,这里涉及到四个属性,在DefaultTransactionDefinition中存在:
之前我想的是定义多个事务规则对象,然后配置多个环绕通知方法,在开启事务的时候,根据不同的事务规则对象来获取得到事务。
2、注入事务管理器,然后传入事务规则定义,获取得到当前的事务状态;
3、利用事务管理器通过状态来进行提交或者是回滚事务;
上面可以通过AOP切面类来进行配置,但是看到了TransactionTemplate之后,我发现根本就不需要使用到切面类就可以来进行操作。
那么首先来看下TransactionTemplate类的继承体系:
分析一下继承体系:
1、继承了DefaultTransactionDefinition类,所以可以用来定义事务规则信息对象;
2、实现了InitializingBean接口,那么肯定是要在初始化方法来做操作;
3、实现了TransactionOperations接口,从接口名称中可以知道这里代表的是事务;
而在TransactionTemplate类中,只有一个属性PlatformTransactionManager,而这是又是我们所需要的事务管理器。
而在InitializingBean接口的初始化方法中做了校验,如下所示:
public void afterPropertiesSet() {
if (this.transactionManager == null) {
throw new IllegalArgumentException("Property 'transactionManager' is required");
}
}
因为TransactionTemplate中存在无参方法,没有注入transactionManager对象,说明我们需要手动注入当前容器中的transactionManager对象。那么配置TransactionTemplate的时候可以配置如下所示:
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
<!--针对于不同的类型来配置传播行为、隔离级别、超时时间和是否只读,因为可以继承父类中的属性-->
<!--还可以配置传播行为、隔离级别、超时时间和是否只读,因为可以继承父类中的属性-->
</bean>
如下所示,配置多个事务定义规则:
<bean id="transactionTemplate1" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
<!--事务传播特性-->
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
<!--事务隔离级别-->
<property name="isolationLevelName" value="ISOLATION_DEFAULT"/>
<!--事务超时时间-->
<property name="timeout" value="-1"/>
<!--事务是否只读-->
<property name="readOnly" value="false"/>
</bean>
<bean id="transactionTemplat2" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
<!--事务传播特性-->
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
<!--事务隔离级别-->
<property name="isolationLevelName" value="ISOLATION_DEFAULT"/>
<!--事务超时时间-->
<property name="timeout" value="3"/>
<!--事务是否只读-->
<property name="readOnly" value="false"/>
</bean>
<bean id="transactionTemplat3" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
<!--事务传播特性-->
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
<!--事务隔离级别-->
<property name="isolationLevelName" value="ISOLATION_DEFAULT"/>
<!--事务超时时间-->
<property name="timeout" value="3"/>
<!--事务是否只读-->
<property name="readOnly" value="true"/>
</bean>
那么只需要在使用的时候,通过以下方式注入即可:
@Autowired
@Qulifier("transactionTemplat1")
private TransactionTemplate transactionTemplate
@Autowired
@Qulifier("transactionTemplat2")
private TransactionTemplate transactionTemplate
@Autowired
@Qulifier("transactionTemplat3")
private TransactionTemplate transactionTemplate
通过这种方式来进行注入即可,也是比较方便的。
那么上面说完事务规则定义这块,这块应该有个疑问:在TransactionTemplate从哪里根据事务管理器获取得到事务状态的代码呢?
类似如下的代码:
@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接口,提前看下:
public interface TransactionCallback<T> {
T doInTransaction(TransactionStatus status);
}
接口中只有一个抽象方法,又被称之为函数式接口。不懂的话接着向下看
这里的方法也是非常简单呐,相当于是已经帮助我们做好了。
给出一个使用代码示例:
这里给的是没有返回值的,所以这里给的是Void类型。
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<Void>() {
public Void doInTransaction(TransactionStatus status) {
// 在这里执行事务操作
// 可以进行数据库操作、调用其他需要事务支持的方法等
return null;
}
});
}
}
再给出一个有值的代码且可以自己来处理事务状态。不利用事务管理中的回滚方法而已!
public Object getObject(String str) {
/*
* 执行带有返回值
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus transactionStatus) {
try {
...
//....... 业务代码
return new Object();
// 注意:这里没有提交代码的逻辑
} catch (Exception e) {
// 回滚,这里并不是利用事务管理器进行提交的,这里只是设置了一个标记而已
// 在事务管理进行事务提交的时候会来检查这里的状态而已
transactionStatus.setRollbackOnly();
return null;
}
}
});
}
在上面的分析代码中,给出示例代码二:
首先给出配置xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<context:component-scan base-package="com.guang.dao"/>
<context:component-scan base-package="com.guang.service.impl1."/>
<!--事务的定义-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<!--配置事务管理器-->
<property name="transactionManager" ref="txManager"/>
<!--事务传播特性!如果调用其他方法的时候,直接使用当前方法中的事务-->
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
<!--事务隔离级别,使用数据库的操作-->
<property name="isolationLevelName" value="ISOLATION_DEFAULT"/>
<!--事务超时时间-->
<property name="timeout" value="-1"/>
<!--事务是否只读!因为是有更新操作,所以这里进行修改操作-->
<property name="readOnly" value="false"/>
</bean>
<!--定义事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--定义连接池-->
<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/spring"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--定义JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"/>
</bean>
</beans>
然后给出代码:
package com.guang.service.impl1;
import com.guang.dao.AccountDao;
import com.guang.domain.Account;
import com.guang.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
@Service("accountService")
public class AccountServiceImpl1 implements AccountService {
@Autowired
private AccountDao accountDao;
@Autowired
private TransactionTemplate transactionTemplate;
@Override
public void transfer(String from, String to, Float money) {
transactionTemplate.execute((status)-> {
try {
// 来做事务操作
Account fromAccount = accountDao.findByName(from);
Account toAccount = accountDao.findByName(to);
fromAccount.setMoney(fromAccount.getMoney() - money);
toAccount.setMoney(toAccount.getMoney() + money);
accountDao.edit(fromAccount);
int i = 1 / 0;
accountDao.edit(toAccount);
} catch ( Exception exception) {
System.out.println("执行SQL阶段出现异常!不能够进行提交");
status.setRollbackOnly();
}
return null;
});
System.out.println("使用事务管理器来执行代码完成");
}
}
然后将代码放置到仓库中去