SpringAOP入门基础银行转账实例------------事务处理
AOP为Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP编程思想
AOP面向切面是一种编程思想,是oop的延续,是软件开发中的一个热点,也是Spring框架中的一个重要的内容,是函数式编程的一种衍生泛型。利用AOP可以对业务逻辑的各个部门进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP得应用场景:
事务管理
权限校验
日志记录
性能监控
Spring 中常用的术语
Joinpoint(连接点) :所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。真正增强的方法
Advice(通知/增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
通知的类型:前置通知,正常返回通知,最终通知,环绕通知
Introduction(引介,了解):引介是一种特殊的通知在不修改类代码的前提下,Introduction 可以在运行期为类动态地添加一些方法或Field.
Target(目标对象):代理的目标对象
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
Weaving(织入):是指把增强应用到目标对象来创建新的对香港的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期植入
Aspect(切面):是切入点和通知(引介)的结合,描述了 增强具体应用的位置。
AOP的作用及优势
作用:在程序运行期间,在不修改源代码的情况下对方法进行功能增强。体现了java语言的动态性(反射)
优势:减轻重复代码,提高开发效率,并且便于维护
Spring 实现转账案例
实现所需:
一个account表用于存储用户的信息和基本金额:
建立maven的项目模块文件:
注意:其中这里的工具类ConnectionUtil、TransactionUtil是后面做测试用的可以不用建立
entity:Accoun.java
package com.etime.entity;
import java.io.Serializable;
public class Account implements Serializable {
private Integer id;
private String name;
private double money;
public Account() {
}
public Account(Integer id, String name, double money) {
this.id = id;
this.name = name;
this.money = 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 +
'}';
}
}
dao:AccountDao.java
package com.etime.dao;
import com.etime.entity.Account;
import java.sql.SQLException;
public interface AccountDao {
Account getByName(String name);
int updateAccount(Account account);
}
pom.xml
<?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>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>7</source>
<target>7</target>
</configuration>
</plugin>
</plugins>
</build>
<groupId>com.etime</groupId>
<artifactId>day05</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
<!--导入spring的context坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--导入Jdbc模块依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- DBUtils -->
<!-- <dependency>-->
<!-- <groupId>commons-dbutils</groupId>-->
<!-- <artifactId>commons-dbutils</artifactId>-->
<!-- <version>1.6</version>-->
<!-- </dependency>-->
<!-- 数据库相关 -->
<!-- <dependency>-->
<!-- <groupId>mysql</groupId>-->
<!-- <artifactId>mysql-connector-java</artifactId>-->
<!-- <version>5.1.6</version>-->
<!-- </dependency>-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<!--c3p0-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- 添加测试依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- dbutils依赖的添加-->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<!-- 添加aop配置依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
</project>
SpringConfig.java
package com.etime.util;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
//指定当前类的配置类相当于application.xml
@Configuration
@Import(DataSourceConfig.class)//导入连接数据
@ComponentScan("com.etime")//扫描文件
//@EnableAspectJAutoProxy//开启spring对注解AOP的支持
public class SpringConfig {
}
jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db_school?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=h123456
DataSourceConfig.java
package com.etime.util;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfig {
@Value("${jdbc.driver}")
private String driverClass;
@Value("${jdbc.url}")
private String jdbcUrl;
@Value("${jdbc.username}")
private String user;
@Value("${jdbc.password}")
private String password;
public DataSource getDataSource(){
ComboPooledDataSource ds= new ComboPooledDataSource();
try {
ds.setDriverClass(driverClass);
ds.setJdbcUrl(jdbcUrl);
ds.setUser(user);
ds.setPassword(password);
} catch (PropertyVetoException e) {
e.printStackTrace();
}
return ds;
}
@Bean(name="jdbcTemplate")
public JdbcTemplate getJdbcTemplate(){
return new JdbcTemplate(getDataSource());
}
// @Bean(name ="connection")
// public Connection getConnection() throws SQLException {
// return getDataSource().getConnection();
// }
//
// @Bean(name = "queryRunner")
// public QueryRunner getQueryRunner(){
// return new QueryRunner();
// }
}
dao.impl:AccountDaoImpl.java
package com.etime.dao.impl;
import com.etime.dao.AccountDao;
import com.etime.entity.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
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.Connection;
import java.sql.SQLException;
@Repository("ad")
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
// @Autowired
// private Connection connection;
//
// @Autowired
// private QueryRunner queryRunner;
public Account getByName(String name){
Account account=null;
String sql="select * from account where name=?";
account=jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<>(Account.class),name);
return account;
}
public int updateAccount(Account account){
int rows=0;
String sql="update account set money=? where id=?";
rows=jdbcTemplate.update(sql,account.getMoney(),account.getId());
return rows;
}
}
service:AccountService.java
package com.etime.service;
import java.sql.SQLException;
public interface AccountService {
void transferAccount(String name1,String name2,double money);
}
service:AccountServiceImpl.java
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){
// 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();
// }
}
}
测试转账业务
正常的数据测试
AccountDemo.java
package com.etime.demo;
import com.etime.service.AccountService;
import com.etime.util.SpringConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.sql.SQLException;
@RunWith(SpringJUnit4ClassRunner.class)
//注意文件的配置,莫要写错
@ContextConfiguration(classes = {SpringConfig.class})
//@ContextConfiguration(locations = {"classpath:application.xml"})
public class AccountDemo {
@Autowired
private ApplicationContext context;
@Test
public void t01(){
AccountService accountService=(AccountService)context.getBean("as");
accountService.transferAccount("hh","hu",30000);
}
}
运行结果:
没有事务异常的情况下正常运行:
数据异常会有事务异常需要处理,并且数据前后需要一致性:
异常情况下:即转账金额大于现有金额就会有事务处理异常
如图的转账明显不合理,本来没有金额,还把金额加上
处理事务问题
在DataSourceConfig中添加QueryRunner对象的初始化以及Connection对象的初始化
DataSourceConfig.java
package com.etime.util;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfig {
@Value("${jdbc.driver}")
private String driverClass;
@Value("${jdbc.url}")
private String jdbcUrl;
@Value("${jdbc.username}")
private String user;
@Value("${jdbc.password}")
private String password;
public DataSource getDataSource(){
ComboPooledDataSource ds= new ComboPooledDataSource();
try {
ds.setDriverClass(driverClass);
ds.setJdbcUrl(jdbcUrl);
ds.setUser(user);
ds.setPassword(password);
} catch (PropertyVetoException e) {
e.printStackTrace();
}
return ds;
}
// @Bean(name="jdbcTemplate")
// public JdbcTemplate getJdbcTemplate(){
// return new JdbcTemplate(getDataSource());
// }
@Bean(name ="connection")
public Connection getConnection() throws SQLException {
return getDataSource().getConnection();
}
@Bean(name = "queryRunner")
public QueryRunner getQueryRunner(){
return new QueryRunner();
}
}
编写事务管理工具
TransactionUtil是管理事务的工具类,主要定义了和事务相关的方法,事务开启,事务提交,事务回滚等。该工具目前需要手动创建和管理,使用Spring进行事务管理后,该工具类由Spring提供,不需要手动编写和管理。所以该工具类重在理解,为Spring进行事务管理做好铺垫
TransactionUtil.java
package com.etime.util;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.SQLException;
@Component("transactionUtil")
//声明为切面
@Aspect
public class TransactionUtil {
@Autowired
private Connection connection;
//
// @Pointcut("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
// private void point(){}
//
// //把当前方法看成是前置通知
// //@Before("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
@Before("point()")
public void startTransaction(){
try {
System.out.println("开启事务");
connection.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
//
// //把当前方法看作是后置通知
// //@AfterReturning("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
// //@AfterReturning("point()")
public void commitTransaction(){
try {
System.out.println("事务提交");
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
//
// //把当前方法看作是异常通知
// //@AfterThrowing("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
// //@AfterThrowing("point()")
public void rollBackTransaction(){
try {
System.out.println("事务回滚");
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
//
// //把当前的方法看作是运行结束通知的方法
// //@After("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
// //@After("point()")
public void closeTransaction(){
try {
System.out.println("释放资源");
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//
// //环绕通知方法
// //把当前方法看成是环绕通知
// //@Around("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
// @Around("point()")
// public Object transactionAround(ProceedingJoinPoint pjp){
// Object result = null;
// try {
// //获取调用切入点方法时传入的参数
// Object[] args = pjp.getArgs();
// //启动事务
// startTransaction();
// //运行切入点方法
// result = pjp.proceed(args);
// commitTransaction();
// } catch (Throwable e) {
// //运行有误需要回滚
// rollBackTransaction();
// e.printStackTrace();
// }finally {
// //运行结束释放资源
// closeTransaction();
// }
// return result;
// }
}
dao:AccountDao.java
package com.etime.dao;
import com.etime.entity.Account;
import java.sql.SQLException;
public interface AccountDao {
Account getByName(String name) throws SQLException;
int updateAccount(Account account) throws SQLException;
}
dao:AccountDaoImpl.java
package com.etime.dao.impl;
import com.etime.dao.AccountDao;
import com.etime.entity.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
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.Connection;
import java.sql.SQLException;
@Repository("ad")
public class AccountDaoImpl implements AccountDao {
// @Autowired
// private JdbcTemplate jdbcTemplate;
@Autowired
private Connection connection;
@Autowired
private QueryRunner queryRunner;
public Account getByName(String name) throws SQLException {
Account account=null;
String sql="select * from account where name=?";
account=queryRunner.query(connection,sql,new BeanHandler<>(Account.class),name);
return account;
}
public int updateAccount(Account account) throws SQLException {
int rows=0;
String sql="update account set money=? where id=?";
rows=queryRunner.update(sql,account.getMoney(),account.getId());
return rows;
}
}
service:AccountService.java
package com.etime.service;
import java.sql.SQLException;
public interface AccountService {
void transferAccount(String name1,String name2,double money);
}
service:AccountServiceImpl.java
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){
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();
}
}
}
测试:
AccountDemo.java
package com.etime.demo;
import com.etime.service.AccountService;
import com.etime.util.SpringConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.sql.SQLException;
@RunWith(SpringJUnit4ClassRunner.class)
//注意文件的配置,莫要写错
@ContextConfiguration(classes = {SpringConfig.class})
//@ContextConfiguration(locations = {"classpath:application.xml"})
public class AccountDemo {
@Autowired
private ApplicationContext context;
@Test
public void t01(){
AccountService accountService=(AccountService)context.getBean("as");
accountService.transferAccount("hh","hu",30000);
}
}
运行结果:
处理了事务,如果遇到抛出问题,数据库进行操作的不满足加连之前的操作全部回滚,这样才满足数据操作前后一致性。