由上一节讲述的通过Connection和QueryRunner对事务进行的处理(详情可以去我之前写的博客文章:https://blog.csdn.net/m0_56245143/article/details/130069160?spm=1001.2014.3001.5501查看)
接下来由我们将对它进行简单的AOP改造
对该项目进行maven工程添加依赖:pom.xml
<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.0modelVersion>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>7source>
<target>7target>
configuration>
plugin>
plugins>
build>
<groupId>com.etimegroupId>
<artifactId>day05artifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<spring.version>5.2.5.RELEASEspring.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.11version>
dependency>
<dependency>
<groupId>com.mchangegroupId>
<artifactId>c3p0artifactId>
<version>0.9.5version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>commons-dbutilsgroupId>
<artifactId>commons-dbutilsartifactId>
<version>1.7version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.7version>
dependency>
dependencies>
project>
创建Spring的配置文件并导入约束
准备的资源:需要扫描当前项目包下的com.etime、需要配置文件内的连接数据库的基本资源、以及导入数据库连接的约束、配置JdbcTemplate模块
application.xml
<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-3.0.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">
<context:component-scan base-package="com.etime">context:component-scan>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="ds"/>
bean>
<bean id="qr" class="org.apache.commons.dbutils.QueryRunner">bean>
beans>
ConnectionUtil.java
package com.etime.util;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import sun.rmi.transport.Connection;
@Component("connection")
public class ConnectionUtil {
// @Autowired
// private JdbcTemplate jdbcTemplate;
@Autowired
private ComboPooledDataSource ds;
@Bean(name = "connection")
public Connection getConnection() throws Exception {
return (Connection) ds.getConnection();
}
}
aop:config:
作用:开始声明aop配置
< aop:config >
配置的代码
< /aop:config >
aop: aspect
作用:用于配置切面
属性:
id:给切面提供一个唯一标识
ref:引用配置好的通知类bean的id
...
< /aop:aspect>
aop:pointcut
作用:用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
属性:expression:用于定义切入点表达式。
id:用于切入点表达式提供一个唯一标识
< aop : poincut id="po" expression="execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))"/>
aop:before
作用:用于配置前置通知。指定增强的方法在切入点方法之前执行
属性:
method:用于指定通知类中的增强方法名称
ponitcut-ref:用于指定切入点的表达式的引用
ponitcut:用于指定切入点表达式
执行时间点:
切入点方法执行之前执行
aop:after-returning
作用:
用于配置后置通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:
切入点方法正常执行之后。它和异常通知只能有一个执行
aop:after-throwing
作用:
用于配置异常通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:
切入点方法执行产生异常后执行。它和后置通知只能执行一个
aop:after
作用:
用于配置最终通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:
无论切入点方法执行时是否有异常,它都会在其后面执行。
application.xml
配置后的代码:
<aop:config>
<aop:aspect id="tm" ref="transactionUtil">
<aop:pointcut id="po" expression="execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))"/>
<aop:before method="startTransaction" pointcut-ref="po">aop:before>
<aop:after-returning method="commitTransaction" pointcut-ref="po">aop:after-returning>
<aop:after-throwing method="rollBackTransaction" pointcut-ref="po">aop:after-throwing>
<aop:after method="closeTransaction" pointcut-ref="po">aop:after>
aop:aspect>
aop:config>
service:修改
package com.etime.service.impl;
import com.etime.dao.AccountDao;
import com.etime.entity.Account;
import com.etime.service.AccountService;
import com.etime.util.TransactionUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.sql.SQLException;
@Service("as")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
// @Autowired
// private TransactionUtil transactionUtil;
//这里不能将此处的异常try,catch.spring只能捕捉throws的异常
@Override
public void transferAccount(String name1, String name2, double money) throws SQLException {
// try {
// //开启事务
// transactionUtil.startTransaction();
//收到的钱
Account accountOne=accountDao.getByName(name1);
accountOne.setMoney(accountOne.getMoney()+money);
accountDao.updateAccount(accountOne);
//钱转出
Account accountTwo=accountDao.getByName(name2);
accountTwo.setMoney(accountTwo.getMoney()-money);
accountDao.updateAccount(accountTwo);
// //以上数据没有数据操作错误,就提交
// transactionUtil.commitTransaction();
// }catch (SQLException e){
// //如果数据有误,进行数据回滚
// transactionUtil.rollBackTransaction();
// e.printStackTrace();
// }finally {
//如果服务结束,事务关闭(不管是否服务成功都进行最后的事务关闭)
// transactionUtil.closeTransaction();
// }
}
}
如图所示的运行结果:异常事务能正常处理
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
例如:
全匹配方式
public void
com.etime.service.impl.AccountServiceImpl.saveAccount(com.etime.domain.Account)
访问修饰符可以省略
void com.etime.service.impl.AccountServiceImpl.saveAccount(com.etime.domain.Account)
返回值可以使用*号,表示任意返回值
* com.etime.service.impl.AccountServiceImpl.saveAccount(com.etime.domain.Account)
包名可以使用 * 号,表示任意包,但是有几级包,需要写几个 *
* *.*.*.*.AccountServiceImpl.saveAccount(com.etime.domain.Account)
使用…来表示当前包,及其子包
* com..AccountServiceImpl.saveAccount(com.etime.domain.Account)
类名可以使用*号,表示任意类
* com..*.saveAccount(com.etime.domain.Account)
方法名可以使用*号,表示任意方法
* com..*.*(com.etime.domain.Account)
参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
* com..*.*(*)
参数列表可以使用…表示有无参数均可,有参数可以是任意类型
* com..*.*(..)
全通配方式:
* *..*.*(..)
注意: 通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
execution(* com.etime.service.impl.*.*(..))
在TransactionUtil类当中添加方法
/**
* 环绕通知:
* spring 框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数。
* 在环绕通知执行时,spring 框架会为我们提供该接口的实现类对象,我们直接使用就行。
* @param pjp
* @return
*/
//环绕通知方法
public Object transactionAround(ProceedingJoinPoint pjp) {
Object result = null;
try {
//获取调用切入点方法时传入的参数
Object[] args = pjp.getArgs();
startTransaction();
//运行切入点方法
result = pjp.proceed(args);
commitTransaction();
} catch (Throwable throwable) {
rollbackTransaction();
throwable.printStackTrace();
} finally {
closeConnection();
}
return result;
}
aop:around:
作用:
用于配置环绕通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
说明:
它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
注意:通常情况下,环绕通知都是独立使用的
<aop:config>
<aop:aspect id="tm" ref="transactionUtil">
<aop:pointcut id="po" expression="execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))"/>
<aop:around method="transactionAround" pointcut-ref="po">aop:around>
aop:aspect>
aop:config>
运行结果:正常异常事务处理
AOP注解方式和XML方式完成的功能都是一样的,只是采用了两种开发方式而已。将原有的XML方式使用注解逐一替代。
接上一节所配置的环境
@Before
作用:
把当前方法看成是前置通知
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@Before("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
public void startTransaction() {
try {
System.out.println("启动事务");
connection.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
@AfterReturning
作用:
把当前方法看成是后置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用
@AfterReturning("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
public void commitTransaction() {
try {
System.out.println("提交");
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
@AfterThrowing
作用:
把当前方法看成是异常通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用
@AfterThrowing("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
public void rollBackTransaction() {
try {
System.out.println("回滚");
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
@After
作用:
把当前方法看成是最终通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用
@After("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
public void closeTransaction() {
try {
System.out.println("释放资源");
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
到这里由上述的配置后可以直接运行,也是同样对事务进行处理,这里我就不运行了,只是用注解的方式更加的简洁
环绕通知配置事务管理
在TransactionUtil类中添加环绕配置
@Around
作用:
把当前方法看成是环绕通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@Around("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
public Object transactionAround(ProceedingJoinPoint pjp) {
Object result = null;
try {
Object[] args = pjp.getArgs();
startTransaction();
result = pjp.proceed(args);
commitTransaction();
} catch (Throwable throwable) {
rollbackTransaction();
throwable.printStackTrace();
} finally {
closeConnection();
}
return result;
}
加环绕配置
@Around
作用:
把当前方法看成是环绕通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@Around("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
public Object transactionAround(ProceedingJoinPoint pjp) {
Object result = null;
try {
Object[] args = pjp.getArgs();
startTransaction();
result = pjp.proceed(args);
commitTransaction();
} catch (Throwable throwable) {
rollbackTransaction();
throwable.printStackTrace();
} finally {
closeConnection();
}
return result;
}