SptingAOP
用Spring AOP(面向切面编程)编写简单转账功能实例:
代码结构图
1.准备数据库存储数据(在MySQL中编写)
1 # 删除spring_aop数据库 2 drop database if exists spring_aop; 3 4 # 创建spring_aop数据库 5 create database spring_aop; 6 7 # 使用spring_aop数据库 8 use spring_aop; 9 10 # 创建account表 11 create table account ( 12 id int(11) auto_increment primary key, 13 accountNum varchar(20) default NULL, 14 money int(8) default 0 15 ); 16 17 # 新增数据 18 insert into account (accountNum, money) values 19 ("622200001",1000),("622200002",1000);
2.导入Spring基础包(pop.xml)
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <modelVersion>4.0.0</modelVersion> 6 7 <groupId>org.example</groupId> 8 <artifactId>spring-aop-zhuangshuhui</artifactId> 9 <version>1.0-SNAPSHOT</version> 10 11 <dependencies> 12 <!-- https://mvnrepository.com/artifact/org.springframework/spring-core --> 13 <dependency> 14 <groupId>org.springframework</groupId> 15 <artifactId>spring-core</artifactId> 16 <version>5.2.13.RELEASE</version> 17 </dependency> 18 <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans --> 19 <dependency> 20 <groupId>org.springframework</groupId> 21 <artifactId>spring-beans</artifactId> 22 <version>5.2.13.RELEASE</version> 23 </dependency> 24 <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> 25 <dependency> 26 <groupId>org.springframework</groupId> 27 <artifactId>spring-context</artifactId> 28 <version>5.2.13.RELEASE</version> 29 </dependency> 30 <!-- https://mvnrepository.com/artifact/org.springframework/spring-expression --> 31 <dependency> 32 <groupId>org.springframework</groupId> 33 <artifactId>spring-expression</artifactId> 34 <version>5.2.13.RELEASE</version> 35 </dependency> 36 <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop --> 37 <dependency> 38 <groupId>org.springframework</groupId> 39 <artifactId>spring-aop</artifactId> 40 <version>5.2.13.RELEASE</version> 41 </dependency> 42 <!-- https://mvnrepository.com/artifact/org.springframework/spring-jcl --> 43 <dependency> 44 <groupId>org.springframework</groupId> 45 <artifactId>spring-jcl</artifactId> 46 <version>5.2.13.RELEASE</version> 47 </dependency> 48 <!-- https://mvnrepository.com/artifact/org.springframework/spring-test --> 49 <dependency> 50 <groupId>org.springframework</groupId> 51 <artifactId>spring-test</artifactId> 52 <version>5.2.13.RELEASE</version> 53 <scope>test</scope> 54 </dependency> 55 <!-- https://mvnrepository.com/artifact/commons-dbutils/commons-dbutils --> 56 <dependency> 57 <groupId>commons-dbutils</groupId> 58 <artifactId>commons-dbutils</artifactId> 59 <version>1.7</version> 60 </dependency> 61 <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> 62 <dependency> 63 <groupId>mysql</groupId> 64 <artifactId>mysql-connector-java</artifactId> 65 <version>8.0.23</version> 66 </dependency> 67 <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 --> 68 <dependency> 69 <groupId>com.mchange</groupId> 70 <artifactId>c3p0</artifactId> 71 <version>0.9.5.5</version> 72 </dependency> 73 <!-- https://mvnrepository.com/artifact/junit/junit --> 74 <dependency> 75 <groupId>junit</groupId> 76 <artifactId>junit</artifactId> 77 <version>4.13.2</version> 78 <scope>test</scope> 79 </dependency> 80 <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> 81 <dependency> 82 <groupId>org.aspectj</groupId> 83 <artifactId>aspectjweaver</artifactId> 84 <version>1.9.3</version> 85 </dependency> 86 </dependencies> 87 88 89 </project>
3.核心配置文件(applicationContext.xml)
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <modelVersion>4.0.0</modelVersion> 6 7 <groupId>org.example</groupId> 8 <artifactId>spring-aop-zhuangshuhui</artifactId> 9 <version>1.0-SNAPSHOT</version> 10 11 <dependencies> 12 <!-- https://mvnrepository.com/artifact/org.springframework/spring-core --> 13 <dependency> 14 <groupId>org.springframework</groupId> 15 <artifactId>spring-core</artifactId> 16 <version>5.2.13.RELEASE</version> 17 </dependency> 18 <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans --> 19 <dependency> 20 <groupId>org.springframework</groupId> 21 <artifactId>spring-beans</artifactId> 22 <version>5.2.13.RELEASE</version> 23 </dependency> 24 <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> 25 <dependency> 26 <groupId>org.springframework</groupId> 27 <artifactId>spring-context</artifactId> 28 <version>5.2.13.RELEASE</version> 29 </dependency> 30 <!-- https://mvnrepository.com/artifact/org.springframework/spring-expression --> 31 <dependency> 32 <groupId>org.springframework</groupId> 33 <artifactId>spring-expression</artifactId> 34 <version>5.2.13.RELEASE</version> 35 </dependency> 36 <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop --> 37 <dependency> 38 <groupId>org.springframework</groupId> 39 <artifactId>spring-aop</artifactId> 40 <version>5.2.13.RELEASE</version> 41 </dependency> 42 <!-- https://mvnrepository.com/artifact/org.springframework/spring-jcl --> 43 <dependency> 44 <groupId>org.springframework</groupId> 45 <artifactId>spring-jcl</artifactId> 46 <version>5.2.13.RELEASE</version> 47 </dependency> 48 <!-- https://mvnrepository.com/artifact/org.springframework/spring-test --> 49 <dependency> 50 <groupId>org.springframework</groupId> 51 <artifactId>spring-test</artifactId> 52 <version>5.2.13.RELEASE</version> 53 <scope>test</scope> 54 </dependency> 55 <!-- https://mvnrepository.com/artifact/commons-dbutils/commons-dbutils --> 56 <dependency> 57 <groupId>commons-dbutils</groupId> 58 <artifactId>commons-dbutils</artifactId> 59 <version>1.7</version> 60 </dependency> 61 <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> 62 <dependency> 63 <groupId>mysql</groupId> 64 <artifactId>mysql-connector-java</artifactId> 65 <version>8.0.23</version> 66 </dependency> 67 <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 --> 68 <dependency> 69 <groupId>com.mchange</groupId> 70 <artifactId>c3p0</artifactId> 71 <version>0.9.5.5</version> 72 </dependency> 73 <!-- https://mvnrepository.com/artifact/junit/junit --> 74 <dependency> 75 <groupId>junit</groupId> 76 <artifactId>junit</artifactId> 77 <version>4.13.2</version> 78 <scope>test</scope> 79 </dependency> 80 <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> 81 <dependency> 82 <groupId>org.aspectj</groupId> 83 <artifactId>aspectjweaver</artifactId> 84 <version>1.9.3</version> 85 </dependency> 86 </dependencies> 87 88 89 </project>
代码编写:
ConnectionUtils.java (连接数据库)
1 package utils; 2 3 import com.mchange.v2.c3p0.ComboPooledDataSource; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.stereotype.Component; 6 7 import java.sql.Connection; 8 import java.sql.SQLException; 9 10 @Component 11 public class ConnectionUtils { 12 private ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); 13 @Autowired 14 private ComboPooledDataSource dataSource; 15 16 /** 17 * 获得当前线程绑定的连接 18 * 19 * @return 20 */ 21 public Connection getThreadConnection() { 22 try { 23 // 看线程是否绑了连接 24 Connection conn = tl.get(); 25 if (conn == null) { 26 // 从数据源获取一个连接 27 conn = dataSource.getConnection(); 28 // 和线程局部变量 绑定 29 tl.set(conn); 30 } 31 // 返回线程连接 32 return tl.get(); 33 } catch (SQLException e) { 34 throw new RuntimeException(e); 35 } 36 } 37 38 /** 39 * 把连接和当前线程进行解绑 40 */ 41 public void remove() { 42 tl.remove(); 43 } 44 45 public void removeConnection() { 46 tl.remove(); 47 } 48 }
Account.java (实体类)
1 package entity; 2 3 public class Account { 4 private Integer id; 5 private String accountNum; 6 private Integer money; 7 8 public String getAccountNum() { 9 return accountNum; 10 } 11 12 public Integer getMoney() { 13 return money; 14 } 15 16 public void setMoney(Integer money){ 17 this.money = money; 18 } 19 20 public Integer getId() { 21 return id; 22 } 23 public void setId(Integer id){ 24 this.id = id; 25 } 26 public void setAccountNum(String accountNum){ 27 this.accountNum = accountNum; 28 } 29 }
AccountDao.java (Dao层)
1 package dao; 2 3 import entity.Account; 4 5 public interface AccountDao { 6 /** 7 * 更新 8 * 9 * @param account 10 */ 11 void updateAccount(Account account); 12 13 /** 14 * 根据编号查询账户 15 * 16 * @param accountNum 17 * @return 如果没有结果就返回null,如果结果集超过一个就抛异常,如果有唯一的一个结果就返回 18 */ 19 Account findAccountByNum(String accountNum); 20 }
AccountDaoImpl.java (Dao层实现类)
1 package dao.impl; 2 3 import dao.AccountDao; 4 import entity.Account; 5 import org.apache.commons.dbutils.QueryRunner; 6 import org.apache.commons.dbutils.handlers.BeanListHandler; 7 import org.springframework.beans.factory.annotation.Autowired; 8 import org.springframework.stereotype.Repository; 9 import utils.ConnectionUtils; 10 11 import java.sql.SQLException; 12 import java.util.List; 13 14 @Repository("accountDao") 15 public class AccountDaoImpl implements AccountDao { 16 // 数据库查询工具类 17 @Autowired 18 private QueryRunner runner; 19 // 数据库连接工具类 20 @Autowired 21 private ConnectionUtils connectionUtils; 22 23 /** 24 * 更新 25 * 26 * @param account 27 */ 28 public void updateAccount(Account account) { 29 try { 30 runner.update(connectionUtils.getThreadConnection(), 31 "update account set accountNum=?,money=? where id=?", 32 account.getAccountNum(), account.getMoney(), account.getId()); 33 } catch (SQLException e) { 34 throw new RuntimeException(e); 35 } 36 } 37 38 /** 39 * 根据编号查询账户 40 * 41 * @param accountNum 42 * @return 如果没有结果就返回null,如果结果集超过一个就抛异常,如果有唯一的一个结果就返回 43 */ 44 public Account findAccountByNum(String accountNum) { 45 List<Account> accounts = null; 46 try { 47 accounts = runner.query(connectionUtils.getThreadConnection(), 48 "select * from account where accountNum = ? ", 49 new BeanListHandler<Account>(Account.class), 50 accountNum); 51 } catch (SQLException e) { 52 throw new RuntimeException(e); 53 } 54 if (accounts == null || accounts.size() == 0) { 55 // 如果没有结果就返回null 56 return null; 57 } else if (accounts.size() > 1) { 58 // 如果结果集超过一个就抛异常 59 throw new RuntimeException("结果集不唯一,数据有问题"); 60 } else { 61 // 如果有唯一的一个结果就返回 62 return accounts.get(0); 63 } 64 } 65 }
AccountService.java (业务层)
1 package services; 2 3 public interface AccountService { 4 /** 5 * 转账 6 * 7 * @param sourceAccount 转出账户 8 * @param targetAccount 转入账户 9 * @param money 转账金额 10 */ 11 void transfer(String sourceAccount, String targetAccount, Integer money); 12 }
AccountServiceImpl.java (业务层实现类)
1 package services.impl; 2 3 import dao.AccountDao; 4 import entity.Account; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.stereotype.Service; 7 import services.AccountService; 8 9 @Service("accountService") 10 public class AccountServiceImpl implements AccountService { 11 12 @Autowired 13 private AccountDao accountDao; 14 15 /** 16 * 转账 17 * 18 * @param sourceAccount 转出账户 19 * @param targetAccount 转入账户 20 * @param money 转账金额 21 */ 22 public void transfer(String sourceAccount, String targetAccount, Integer money) { 23 // 查询原始账户 24 Account source = accountDao.findAccountByNum(sourceAccount); 25 // 查询目标账户 26 Account target = accountDao.findAccountByNum(targetAccount); 27 // 原始账号减钱 28 source.setMoney(source.getMoney() - money); 29 // 目标账号加钱 30 target.setMoney(target.getMoney() + money); 31 // 更新原始账号 32 accountDao.updateAccount(source); 33 //手动加入异常 34 int i = 1/0; 35 // 更新目标账号 36 accountDao.updateAccount(target); 37 System.out.println("转账完毕"); 38 } 39 }
AccountTest.java(测试层)
1 import org.junit.Test; 2 import org.junit.runner.RunWith; 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.test.context.ContextConfiguration; 5 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 6 import services.AccountService; 7 8 @RunWith(SpringJUnit4ClassRunner.class) 9 @ContextConfiguration(locations = "classpath:applicationContext.xml") 10 public class AccountTest { 11 12 @Autowired 13 private AccountService accountService; 14 15 @Test 16 public void testTransfer() { 17 String sourceAccount = "622200001"; 18 String targetAccount = "622200002"; 19 Integer money = 100; 20 accountService.transfer(sourceAccount,targetAccount,money); 21 } 22 }
执行结果:
控制台输出:转账完毕
查看MySQL数据库的运行结果:
1.刷新前:两个账户各有1000元
2.刷新后:1用户减少100,2用户增加100;执行成功
引入代理模式
TransactionManager.java(事务管理器)
1 package transaction; 2 3 import org.aspectj.lang.annotation.*; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.stereotype.Component; 6 import utils.ConnectionUtils; 7 8 import java.sql.SQLException; 9 10 @Component 11 @Aspect 12 public class TransactionManager { 13 // 数据库连接工具类 14 @Autowired 15 private ConnectionUtils connectionUtils; 16 17 @Pointcut("execution(* services.*.*(..))") 18 private void transactionPointcut() { 19 } 20 21 /** 22 * 开启事务 23 */ 24 @Before("transactionPointcut()") 25 public void beginTransaction() { 26 try { 27 System.out.println("开启事务"); 28 connectionUtils.getThreadConnection().setAutoCommit(false); 29 } catch (SQLException e) { 30 e.printStackTrace(); 31 } 32 } 33 34 /** 35 * 提交事务 36 */ 37 @AfterReturning("transactionPointcut()") 38 public void commit() { 39 try { 40 System.out.println("提交事务"); 41 connectionUtils.getThreadConnection().commit(); 42 } catch (SQLException e) { 43 e.printStackTrace(); 44 } 45 } 46 47 /** 48 * 回滚事务 49 */ 50 @AfterThrowing("transactionPointcut()") 51 public void rollback() { 52 try { 53 System.out.println("回滚事务"); 54 connectionUtils.getThreadConnection().rollback(); 55 } catch (SQLException e) { 56 e.printStackTrace(); 57 } 58 } 59 60 /** 61 * 释放连接 62 */ 63 @After("transactionPointcut()") 64 public void release() { 65 try { 66 System.out.println("释放连接"); 67 connectionUtils.getThreadConnection().close(); 68 } catch (SQLException e) { 69 e.printStackTrace(); 70 } 71 connectionUtils.removeConnection(); 72 } 73 }
TransactionProxyUtils.java(事务代理工具)
1 package utils; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.stereotype.Component; 5 import services.AccountService; 6 import transaction.TransactionManager; 7 8 import java.lang.reflect.InvocationHandler; 9 import java.lang.reflect.Method; 10 import java.lang.reflect.Proxy; 11 12 @Component 13 public class TransactionProxyUtils { 14 //被代理的业务类接口 15 @Autowired 16 private AccountService accountService; 17 //提供事务管理的工具类 18 @Autowired 19 private TransactionManager transactionManager; 20 21 /** 22 * 获取AccountService代理对象 23 * 24 * @return 25 */ 26 public AccountService getAccountService() { 27 return (AccountService) Proxy.newProxyInstance( 28 accountService.getClass().getClassLoader(), 29 accountService.getClass().getInterfaces(), 30 new InvocationHandler() { 31 /** 32 * 添加事务的支持 33 * 34 * @param proxy 被代理的对象实例本身 35 * @param method 被代理对象正在执行的方法对象 36 * @param args 正在访问的方法参数对象 37 * @return 38 * @throws Throwable 39 */ 40 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 41 42 // 43 Object rtValue = null; 44 try { 45 // 执行操作前开启事务 46 transactionManager.beginTransaction(); 47 // 执行操作 48 rtValue = method.invoke(accountService, args); 49 // 执行操作后提交事务 50 transactionManager.commit(); 51 // 返回结果 52 return rtValue; 53 } catch (Exception e) { 54 // 捕捉到异常执行回滚操作 55 transactionManager.rollback(); 56 throw new RuntimeException(e); 57 } finally { 58 // 最终释放连接 59 transactionManager.release(); 60 } 61 } 62 }); 63 64 } 65 }
1.手动添加异常:int i = 1/0;
输出结果
运行报错:
数据库丢失100错误:
2.数据库数值改为正常
再次运行后数据库的数值:
事务回滚: