Spring学习笔记(三)
十八、Spring整合JDBC实现转账
(一)、Spring整合DBUtils实现,未使用IOC容器功能
1 准备Account表以及对应的实体bean
package com.jn.entity;
import java.io.Serializable;
public class Account implements Serializable {
private Integer id;
private String name;
private Double money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
2 引入DBUtils依赖坐标
<?xml version="1.0" encoding="UTF-8"?>
<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>org.example</groupId>
<artifactId>SpringFrameWorkProject06</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.18</version>
</dependency>
<!--导入Jdbc模块依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.18</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!--c3p0的连接依赖-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.18</version>
</dependency>
</dependencies>
</project>
3 创建AccountDao接口以及对应的实现类
package com.jn.dao;
import com.jn.entity.Account;
public interface AccountDao {
public Account findByName(String name);
public void update(Account account);
}
package com.jn.dao.impl;
import com.jn.dao.AccountDao;
import com.jn.entity.Account;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import java.sql.SQLException;
public class AccountDaoImpl implements AccountDao {
//获取QueryRunner对象
private QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
//通过name进行查找
@Override
public Account findByName(String name) {
try {
String sql = "select * from account where name = ?";
return queryRunner.query(sql,new BeanHandler<>(Account.class),name);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
//数据库的同步更新
@Override
public void update(Account account) {
try {
String sql = "update account set money = ? where name = ?";
queryRunner.update(sql,account.getMoney(),account.getName());
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4 创建AccountService接口以及对应的实现类
package com.jn.service;
public interface AccountService {
/*
定义业务方法,实现转账
sourceAccountName 转出账户
targetAccountName 转入账户
money 转账金额
*/
public void transfer(String sourceAccountName,String targetAccountName,Double money);
}
package com.jn.service.impl;
import com.jn.dao.AccountDao;
import com.jn.dao.impl.AccountDaoImpl;
import com.jn.entity.Account;
import com.jn.service.AccountService;
public class AccountServiceImpl implements AccountService {
//通过AccountDao的继承接口得到AccountDao的实体类对象
private AccountDao accountDao = new AccountDaoImpl();
@Override
public void transfer(String sourceAccountName, String targetAccountName, Double money) {
//定义来源账户以及目标账户
Account sourceAccount = accountDao.findByName(sourceAccountName);
Account targetAccount = accountDao.findByName(targetAccountName);
//实现转装业务
sourceAccount.setMoney(sourceAccount.getMoney()-money);
targetAccount.setMoney(targetAccount.getMoney()+money);
//持久化到数据库
accountDao.update(sourceAccount);
accountDao.update(targetAccount);
}
}
5 测试转账业务
package com.jn;
import com.jn.dao.AccountDao;
import com.jn.dao.impl.AccountDaoImpl;
import com.jn.entity.Account;
import com.jn.service.AccountService;
import com.jn.service.impl.AccountServiceImpl;
import org.junit.Test;
public class transferTest {
//测试转账业务
@Test
public void transfer(){
AccountService accountService = new AccountServiceImpl();
accountService.transfer("王思梦","铁头",100.0);
System.out.println("转账成功");
}
}
5 测试没有事务正常情况和异常情况转账结果
没有遇到异常情况,转账结果正常。王思梦账户减少100元,铁头账户增加100元
package com.jn;
import com.jn.dao.AccountDao;
import com.jn.dao.impl.AccountDaoImpl;
import com.jn.entity.Account;
import com.jn.service.AccountService;
import com.jn.service.impl.AccountServiceImpl;
import org.junit.Test;
public class transferTest {
//测试转账业务
@Test
public void transfer(){
AccountService accountService = new AccountServiceImpl();
accountService.transfer("王思梦","铁头",100.0);
System.out.println("转账成功");
}
}
模拟异常情况转账结果:王思梦账户减钱,铁头账户并未加钱,数据的一致性受损
@Override
public void transfer(String sourceAccountName, String targetAccountName, Double money) {
//定义来源账户以及目标账户
Account sourceAccount = accountDao.findByName(sourceAccountName);
Account targetAccount = accountDao.findByName(targetAccountName);
//实现转装业务
sourceAccount.setMoney(sourceAccount.getMoney()-money);
targetAccount.setMoney(targetAccount.getMoney()+money);
//持久化到数据库
accountDao.update(sourceAccount);
int i=1/0;
accountDao.update(targetAccount);
}
(二)、编写ConnectionUtils工具类管理数据库连接
package com.jn.utils;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class ConnectionUtils {
/*
管理数据库连接的类
*/
// 使用ThreadLocal来存储每个线程独有的数据库连接对象,避免共享连接导致的线程安全问题
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
// 数据源对象,用于获取数据库连接
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
//获取当前线程上的连接
public Connection getThreadConnection(){
try {
//1、先从ThreadLocal上获取
Connection connection = tl.get();
//2、判断当前线程上是否有连接
if(connection==null){
//3、从数据源中获取一个连接,并存入到ThreadLocal当中
connection=dataSource.getConnection();
tl.set(connection);
}
//4、返回当前线程上的连接
return connection;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//把连接和线程解绑
public void removeConnection(){
tl.remove();
}
}
(三)、编写事务管理工具类TransactionManager
package com.jn.utils;
import java.sql.SQLException;
public class TransactionManager {
/**
* 和事务相关的工具类,它包含了开启事务、提交事务、回滚事务和释放连接
*/
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
//开启事务
public void beginTransation(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
//提交事务
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
//回滚事务
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
//清除连接
public void release(){
try {
connectionUtils.removeConnection();
connectionUtils.getThreadConnection().close(); //返回连接池中
} catch (SQLException e) {
e.printStackTrace();
}
}
}
(四)、编写业务层和持久层事务控制代码并配置Spring的IOC
AccountDaoImpl
package com.jn.dao.impl;
import com.jn.dao.AccountDao;
import com.jn.entity.Account;
import com.jn.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import java.sql.SQLException;
public class AccountDaoImpl implements AccountDao {
//获取QueryRunner对象
private QueryRunner queryRunner ;
private ConnectionUtils connectionUtils;
public void setQueryRunner(QueryRunner queryRunner) {
this.queryRunner = queryRunner;
}
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
//通过name进行查找
@Override
public Account findByName(String name) {
try {
String sql = "select * from account where name = ?";
Account account = queryRunner.query(connectionUtils.getThreadConnection(),sql,new BeanHandler<>(Account.class),name);
return account;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
//数据库的同步更新
@Override
public void update(Account account) {
try {
String sql = "update account set money = ? where name = ?";
queryRunner.update(connectionUtils.getThreadConnection(),sql,account.getMoney(),account.getName());
} catch (SQLException e) {
e.printStackTrace();
}
}
}
AccountServixce
package com.jn.service.impl;
import com.jn.dao.AccountDao;
import com.jn.entity.Account;
import com.jn.service.AccountService;
import com.jn.utils.TransactionManager;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
private TransactionManager txManager;
public AccountDao getAccountDao() {
return accountDao;
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public TransactionManager getTxManager() {
return txManager;
}
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
@Override
public void transfer(String sourceAccountName, String targetAccountName, Double money) {
try {
// 开启事务
txManager.beginTransation();
// 定义来源账户以及目标账户
Account sourceAccount = accountDao.findByName(sourceAccountName);
Account targetAccount = accountDao.findByName(targetAccountName);
// 实现转账业务
sourceAccount.setMoney(sourceAccount.getMoney() - money);
targetAccount.setMoney(targetAccount.getMoney() + money);
// 持久化到数据库
accountDao.update(sourceAccount);
int i=1/0;
accountDao.update(targetAccount);
// 提交事务
txManager.commit();
} catch (Exception e) {
// 事务回滚
txManager.rollback();
e.printStackTrace();
} finally {
// 释放资源
txManager.release();
}
}
}
applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 加载 properties 文件的配置 -->
<context:property-placeholder location="classpath:dbconfig.properties" />
<!-- 连接数据库的核心配置文件 以及数据源-->
<bean id="dataSource" 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>
<!--配置Connection的工具类ConnectionUtils-->
<bean id="connectionUtils" class="com.jn.utils.ConnectionUtils">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="com.jn.utils.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置QueryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!--配置AccountDao对象-->
<bean id="accountDao" class="com.jn.dao.impl.AccountDaoImpl">
<property name="connectionUtils" ref="connectionUtils"></property>
<property name="queryRunner" ref="queryRunner"></property>
</bean>
<!--配置AccountService对象-->
<bean id="accountService" class="com.jn.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<property name="txManager" ref="transactionManager"></property>
</bean>
</beans>
(五)、测试转账
//测试事务回滚的转账
@Test
public void testTransaction(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = (AccountService) context.getBean("accountService");
accountService.transfer("王思梦","铁头",100.0);
}
测试结果:
正常转账成功,遇到异常情况, 事务进行回滚,保证了数据的一致性。
(六)、转账业务案例中存在的问题
上一小节的代码,通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制,也产生了一个新的问题:
业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了。试想一下,如果我们此时提交,回滚,释放资源中任何一个方法名变更,都需要修改业务层的代码,况且这还只是一个业务层实现类,而实际的项目中这种业务层实现类可能有十几个甚至几十个。
以上问题 如何解决呢?接下来使用下一小节中提到的技术解决该问题。
(七)、使用动态代理实现事务控制
1.创建代理工具类
package com.jn.factory;
import com.jn.entity.Account;
import com.jn.service.AccountService;
import com.jn.utils.TransactionManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class BeanFactory {
private AccountService accountService;
private TransactionManager txManager;
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
//获取Service的代理对象
public AccountService getAccountService() {
return (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
//添加事务的支持
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnValue = null;
try {
//1、开启事务
txManager.beginTransation();
//2、执行操作
returnValue = method.invoke(accountService,args);
//3、提交事务
txManager.commit();
//4、返回结果
return returnValue;
} catch (Exception e) {
//5、回滚操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
//6、释放连接
txManager.release();
}
}
});
}
}
2.配置并测试动态代理转账业务
配置静态工厂,生产实例bean
<!--配置BeanFactory-->
<bean id="beanFactory" class="com.jn.factory.BeanFactory">
<property name="txManager" ref="transactionManager"></property>
<property name="accountService" ref="accountService"></property>
</bean>
<!--配置代理的service-->
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
业务层代码的修改:把事务相关的代码去掉
package com.jn.service.impl;
import com.jn.dao.AccountDao;
import com.jn.entity.Account;
import com.jn.service.AccountService;
import com.jn.utils.TransactionManager;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
private TransactionManager txManager;
public AccountDao getAccountDao() {
return accountDao;
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public TransactionManager getTxManager() {
return txManager;
}
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
@Override
public void transfer(String sourceAccountName, String targetAccountName, Double money) {
// 定义来源账户以及目标账户
Account sourceAccount = accountDao.findByName(sourceAccountName);
Account targetAccount = accountDao.findByName(targetAccountName);
// 实现转账业务
sourceAccount.setMoney(sourceAccount.getMoney() - money);
targetAccount.setMoney(targetAccount.getMoney() + money);
// 持久化到数据库
accountDao.update(sourceAccount);
//int i=1/0;
accountDao.update(targetAccount);
}
}
3.测试
package com.jn;
import com.jn.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class transferTest {
//@Autowired
//private AccountService accountService;
// //测试转账业务
// @Test
// public void transfer(){
// AccountService accountService = new AccountServiceImpl();
// accountService.transfer("王思梦","铁头",100.0);
// System.out.println("转账成功");
//
// }
//测试事务回滚的转账
@Test
public void testTransaction(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = (AccountService) context.getBean("accountService");
accountService.transfer("王思梦","铁头",100.0);
}
@Autowired
@Qualifier("proxyAccountService")
private AccountService accountProxyService;
//测试耦合度低的转账
@Test
public void testTransactionProxy(){
accountProxyService.transfer("王思梦","铁头",100.0);
}
}
总结:使用动态代理改造之后, 业务层代码已经和事务相关代码进行了分离,并且保证了事务的一致性。
项目整体目录结构
十九、SpringAOP机制详解
(一)AOP 概述
1.什么是 AOP
AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
2.AOP编程思想
AOP 面向切面编程是一种编程思想,是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
3.Spring中的常用术语
Joinpoint(连接点)
所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
Pointcut(切入点)
所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义
Advice(通知/ 增强)
所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
后置通知,异常通知,最终通知,环绕通知。
引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方 法或 Field。
Target(目标对象)
代理的目标对象
Proxy (代理)
一个类被 AOP 织入增强后,就产生一个结果代理类
Weaving(织入)
是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入
Aspect(切面)
是切入点和通知(引介)的结合
4 AOP 的作用及优势
作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
优势:减少重复代码,提高开发效率,并且便于维护
(二)、Spring基于XML的AOP配置
1.环境搭建
1.1 构建maven工程添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<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>org.example</groupId>
<artifactId>SpringFrameWorkProject07</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.18</version>
</dependency>
<!--导入Jdbc模块依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.18</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!--c3p0的连接依赖-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.18</version>
</dependency>
<!--添加AOP的依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
</project>
1.2 沿用转账业务的代码
准备实体类,业务层和持久层代码。我们沿用上一章节中的代码即可。
1.3 创建 Spring 的配置文件并导入约束
xmlns:aop="http://www.springframework.org/schema/aop"
1.4 配置 Spring 的 IOC
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 加载 properties 文件的配置 -->
<context:property-placeholder location="classpath:dbconfig.properties" />
<!-- 连接数据库的核心配置文件 以及数据源-->
<bean id="dataSource" 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>
<!--配置Connection的工具类ConnectionUtils-->
<bean id="connectionUtils" class="com.jn.utils.ConnectionUtils">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置QueryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!--配置AccountDao对象-->
<bean id="accountDao" class="com.jn.dao.impl.AccountDaoImpl">
<property name="connectionUtils" ref="connectionUtils"></property>
<property name="queryRunner" ref="queryRunner"></property>
</bean>
<!--配置AccountService对象-->
<bean id="accountService" class="com.jn.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置TransactionManager对象-->
<bean id="txManager" class="com.jn.advice.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
</beans>
1.5 抽取公共代码制作成通知
package com.jn.advice;
import com.jn.utils.ConnectionUtils;
import java.sql.SQLException;
public class TransactionManager {
/**
* 和事务相关的工具类,它包含了开启事务、提交事务、回滚事务和释放连接
*/
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
//开启事务
public void beginTransation(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
//提交事务
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
//回滚事务
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
//清除连接
public void release(){
try {
connectionUtils.removeConnection();
connectionUtils.getThreadConnection().close(); //返回连接池中
} catch (SQLException e) {
e.printStackTrace();
}
}
}
2.AOP 配置步骤
2.1 把通知类用 bean 标签配置起来
<!--配置TransactionManager对象-->
<bean id="txManager" class="com.jn.advice.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
2.2 使用 aop:config 声明 AOP 配置
<!--使用aop:config进行aop配置-->
<aop:config>
</aop:config>
2.3 使用 aop:aspect 配置切面
<!--使用aop:config进行aop配置-->
<aop:config>
<!--给iop进行切面配置
id:给切面提供的唯一标识
ref:引用配置好的通知类bean的id txManager-->
<aop:aspect id="tdAdvice" ref="txManager">
</aop:aspect>
</aop:config>
2.4 使用 aop:pointcut 配置切入点表达式
<!--使用aop:config进行aop配置-->
<aop:config>
<!--给iop进行切面配置
id:给切面提供的唯一标识
ref:引用配置好的通知类bean的id txManager-->
<aop:aspect id="tdAdvice" ref="txManager">
<!--用于配置切入点的表达式。就是指定对哪些类的哪些方法进行增强
id:用于给切入点表达式提供一个唯一标识‘
expression:指定切入点表达式-->
<aop:pointcut id="ponnt" expression="execution(public void com.jn.service.impl.AccountServiceImpl.transfer(java.lang.String,java.lang.String,java.lang.Double))"/>
</aop:aspect>
</aop:config>
2.5 使用 aop:xxx 配置对应的通知类型
<!--使用aop:config进行aop配置-->
<aop:config>
<!--给iop进行切面配置
id:给切面提供的唯一标识
ref:引用配置好的通知类bean的id txManager-->
<aop:aspect id="tdAdvice" ref="txManager">
<!--用于配置切入点的表达式。就是指定对哪些类的哪些方法进行增强
id:用于给切入点表达式提供一个唯一标识‘
expression:指定切入点表达式-->
<aop:pointcut id="point" expression="execution(public void com.jn.service.impl.AccountServiceImpl.transfer(java.lang.String,java.lang.String,java.lang.Double))"/>
<!--aop:before
用于配置前置通知,指定增强的方法在切入点方法前执行
method:用于指定通知类中的增强方法名称
pointcut-ref:用于指定切入点表达式的id引用
pointcut:用于指定切入点表达式-->
<aop:before method="beginTransation" pointcut-ref="point"></aop:before>
<!--aop:after-returning
用于配置后置通知,在切入点方法正常返回后执行
method:用于指定通知类中增强的方法名称
pointcut-ref:用于指定切入点表达式的id引用
return-param:用于指定接收通知方法参数,如果通知方法有参数,该参数将接收到切入点方法的返回值
-->
<aop:after-returning method="commit" pointcut-ref="point"></aop:after-returning>
<!--aop:after-throwing
用于配置异常通知,在切入点方法抛出异常后执行
method:用于指定通知类中增强的方法名称
pointcut-ref:用于指定切入点表达式的id引用
-->
<aop:after-throwing method="rollback" pointcut-ref="point"></aop:after-throwing>
<!--aop:after
用于配置最终通知,在切入点方法执行后,无论正常还是异常都会执行
method:用于指定通知类中增强的方法名称
pointcut-ref:用于指定切入点表达式的id引用
-->
<aop:after method="release" pointcut-ref="point"></aop:after>
</aop:aspect>
</aop:config>
3.测试
3.1测试方法编写
package com.jn;
import com.jn.service.AccountService;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test1(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = (AccountService) context.getBean("accountService");
accountService.transfer("王思梦","铁头",100.00);
}
}
3.2测试结果
3.2.1成功转账
3.2.2转账失败
4.切入点表达式说明
4.1 切点表达式的语法
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用星号* 代表任意
- 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
- 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表
例如:
全匹配方式
public void com.jn.service.impl.AccountServiceImpl.transfer(java.lang.String,java.lang.String,java.lang.Double)
访问修饰符可以省略
void com.jn.service.impl.AccountServiceImpl.transfer(java.lang.String,java.lang.String,java.lang.Double)
返回值可以使用*号,表示任意返回值
* com.jn.service.impl.AccountServiceImpl.transfer(java.lang.String,java.lang.String,java.lang.Double)
包名可以使用 * 号,表示任意包,但是有几级包,需要写几个 *
* *.*.*.*.AccountServiceImpl.transfer(java.lang.String,java.lang.String,java.lang.Double)
使用..来表示当前包,及其子包
* com..AccountServiceImpl.transfer(java.lang.String,java.lang.String,java.lang.Double)
类名可以使用*号,表示任意类
* com..*.transfer(java.lang.String,java.lang.String,java.lang.Double)
方法名可以使用*号,表示任意方法
* com..*.*(java.lang.String,java.lang.String,java.lang.Double)
参数列表可以使用..表示有无参数均可,有参数可以是任意类型
* com..*.*(..)
全通配方式:
* *..*.*(..)
注意: 通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
* com.jn.service.impl.*.*(..)
5.环绕通知配置事务管理
<!--使用aop:config进行aop配置-->
<aop:config>
<!--给iop进行切面配置
id:给切面提供的唯一标识
ref:引用配置好的通知类bean的id txManager-->
<aop:aspect id="tdAdvice" ref="txManager">
<!--用于配置切入点的表达式。就是指定对哪些类的哪些方法进行增强
id:用于给切入点表达式提供一个唯一标识‘
expression:指定切入点表达式-->
<aop:pointcut id="point" expression="execution(* com.jn.service.impl.*.*(..))"/>
<!--配置环绕通知
method:用于指定通知类中增强的方法名称
pointcut-ref:用于指定切入点表达式的id引用
-->
<aop:around method="aroundAdvice" pointcut-ref="point"></aop:around>
</aop:aspect>
</aop:config>
说明:它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
注意:通常情况下,环绕通知都是独立使用的
二十、Spring基于注解的AOP配置
(一)、环境搭建
1.构建maven工程添加AOP注解的相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<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.jn</groupId>
<artifactId>SpringFrameWorkProject08</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.18</version>
</dependency>
<!--导入Jdbc模块依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.18</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!--c3p0的连接依赖-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.18</version>
</dependency>
<!--添加AOP的依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
</project>
2.沿用上一章节资源
copy Account AccountDao AccountDaoImpl AccountService AccountServiceImpl
3.在配置文件中导入 context 的名称空间且配置
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
4.资源使用注解配置
<!-- 加载 properties 文件的配置 -->
<context:property-placeholder location="classpath:dbconfig.properties" />
<!-- 连接数据库的核心配置文件 以及数据源-->
<bean id="dataSource" 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>
<!--配置QueryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
5.在配置文件中指定 spring 要扫描的包
<!--在配置文件里指定spring要扫描的包-->
<context:component-scan base-package="com.jn" />
(二)、初步配置
1.通知类使用注解配置
@Component("txManager")
public class TransactionManager {
}
2.在通知类上使用@Aspect 注解声明为切面
@Component("txManager")
@Aspect
public class TransactionManager {
}
3.控制层实现类的注解配置
@Component("accountDao")
public class AccountDaoImpl implements AccountDao {
//获取QueryRunner对象
@Autowired
private QueryRunner queryRunner ;
@Autowired
private ConnectionUtils connectionUtils;
}
4.服务层实现类的注解配置
@Service("accountservice")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Autowired
private TransactionManager txManager;
}
5.管理数据库类注解配置
@Component("connectionUtils")
public class ConnectionUtils {
/*
管理数据库连接的类
*/
// 使用ThreadLocal来存储每个线程独有的数据库连接对象,避免共享连接导致的线程安全问题
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
// 数据源对象,用于获取数据库连接
@Autowired
private DataSource dataSource;
}
6.测试转账成功
package com.jn;
import com.jn.service.AccountService;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAnnotationTransaction {
@Autowired
private AccountService accountService;
@org.junit.Test
public void test(){
accountService.transfer("铁头","王思梦",100d);
}
}
7.测试转账失败
在转账过程中发生异常,然后进行测试发现“铁头”转账成功,但是“王思梦”没有收到转账。我们接着往下面配置注解
(三)、事务注解配置
1.在增强的方法上使用注解配置通知
/*
@Before:在指定的方法之前执行
value():指定切入点表达式,通过@Before注解指定切入点表达式的位置
*/
//开启事务
@Before("execution(* com.jn.service.impl.*.*(..))")
public void beginTransation(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
/*
@AfterReturning:在指定的方法返回之后执行
returning:指定切入点方法返回的结果,如果该方法为void,则返回值为null
*/
//提交事务
@AfterReturning("execution(* com.jn.service.impl.*.*(..))")
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
/*
@AfterThrowing:在指定的方法抛出异常时执行
throwing:指定一个Throwable,当该方法抛出异常时,将异常结果封装到该参数中
*/
//回滚事务
@AfterThrowing("execution(* com.jn.service.impl.*.*(..))")
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
/*
@After:在指定的方法之后执行
*/
//清除连接
@After("execution(* com.jn.service.impl.*.*(..))")
public void release(){
try {
connectionUtils.removeConnection();
connectionUtils.getThreadConnection().close(); //返回连接池中
} catch (SQLException e) {
e.printStackTrace();
}
}
2.在 spring 配置文件中开启 spring 对注解 AOP 的支持
<!--开启spring对注解的aop功能-->
<aop:aspectj-autoproxy />
3.环绕通知注解配置
@Around("execution(* com.jn.service.impl.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
//1.获取参数
Object[] args = pjp.getArgs();
//2.开启事务
this.beginTransation();
//3.执行方法
rtValue = pjp.proceed(args);
//4.提交事务
this.commit();
//5.返回结果
return rtValue;
} catch (Throwable e) {
//6.回滚事务
rollback();
e.printStackTrace();
}
return rtValue;
}
4.切入点表达式注解
@Pointcut("execution(* com.jn.service.impl.*.*(..))")
private void pt1(){}
@Around("pt1()")
public Object aroundAdvice(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
//1.获取参数
Object[] args = pjp.getArgs();
//2.开启事务
this.beginTransation();
//3.执行方法
rtValue = pjp.proceed(args);
//4.提交事务
this.commit();
//5.返回结果
return rtValue;
} catch (Throwable e) {
//6.回滚事务
rollback();
e.printStackTrace();
}
return rtValue;
}