Spring+AOP一个小Demo记录
开发环境准备
- idea2020 + MySQL8.0.19 + maven3 + java1.8
项目需求
- 使用spring管理对象
- 实现转账功能
- 保持转账业务的一致性(使用事务)
- 使用AOP技术完成事务控制
数据准备
- 数据表一个,3个主要字段,ID、name、money;
create table account(
id int primary key auto_increment,
name varchar(40),
money float
)ENGINE=InnoDB character set utf8 collate utf8_general_ci;
- 插入两条测试数据
insert into account(name,money) values('zhangsan',1000);
insert into account(name,money) values('lisi',1000);
insert into account(name,money) values('wangwu',1000);
技术选型
- 数据库连接池使用 druid 配合 QeuryRunner () + mysql
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.20</version>
</dependency>
<!--QueryRunner-->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<!--MySQL连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
- spring核心+aop解析+junit整合
<!--spring核心-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!--junit整合-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--AOP解析-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
- 添加maven编译字符集
<properties>
<!-- 文件拷贝时的编码 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- 编译时的编码 -->
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
到这里基本上pom.xml基本搞定,个人还可以添加一些自己常用的包的坐标,有这些包就能正常运行今天要做的项目了。
项目部分
创建项目
- 创建一个空的maven项目
- 将上述坐标导入pom.xml中
- 创建一个SpringConfiguration.xml
- 创建所需要的几个包 如下文件目录
在pojo中创建实体类
public class Account {
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 +
'}';
}
}
配置SpringConfiguration.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"
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>
这里推荐官网去看看:https://docs.spring.io/spring/docs/5.2.6.RELEASE/spring-framework-reference/core.html#resources 里面有很多介绍
加入xml相关配置
<!--开启注解配置-->
<context:annotation-config/>
<!--扫描范围-->
<context:component-scan base-package="com.moro"/>
<!--引入外部文件-->
<context:property-placeholder location="db.properties"/>
<!--开启注解配置aop-->
<aop:aspectj-autoproxy/>
<!--配置QueryRunner-->
<bean id="querRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"/>
<!--配置数据源-->
<bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
<!--驱动-->
<property name="driverClassName" value="${jdbc.driver}"/>
<!--数据库地址-->
<property name="url" value="${jdbc.url}"/>
<!--用户名-->
<property name="username" value="${jdbc.username}"/>
<!--密码-->
<property name="password" value="${jdbc.password}"/>
<!--初始化数据源数量-->
<property name="initialSize" value="3"/>
<!--最大连接-->
<property name="maxActive" value="10" />
<!--最大等待-->
<property name="maxWait" value="30000" />
</bean>
db.properties这个文件里面就是我们的数据库池的相关配置,可以不引用直接写,也可以自己配置,这里方便演示,两种都有用到:
db.properties文件如下:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/[你的数据库名称]?
serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true
jdbc.username=root
jdbc.password=root
创建一个工具类让每次的查询使用的连接都是同一个
/**
* @author Moro
* @createTime 2020/5/22 -12:00
* @projectName Moro_Demo_Spring05
* 控制connection的唯一性
*/
@Component("connectionUtil")
public class ConnectionUtil {
//使用ThreadLocal控制connection的独立性
private final ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
//注入数据源
@Autowired
private final DataSource dataSource = null;
private Connection connection = null;
/**
* 获取一个线程连接
* @return 安全的线程连接
*/
@Bean("threadConnection")
public Connection getConnection(){
//每次获取连接时会去判断ThreadLocal内是否有一个连接
if (threadLocal.get() == null){
try {
//如果没有连接就从连接池中拿出一个连接
connection = dataSource.getConnection();
//放入ThreadLocal中
threadLocal.set(connection);
System.out.println("已获取连接:"+threadLocal.get());
//并将获取的连接返回
return threadLocal.get();
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("获取连接失败");
}
}
//如果有连接直接返回连接
return threadLocal.get();
}
/**
* 使用完后解除连接和线程的绑定
*/
public void removeConnect(){
threadLocal.remove();
System.out.println("线程与事务已经解除绑定");
}
创建AccountDao和AccountDaoImpl
- AccountDao 接口
public interface AccountDao {
/**
* 查询所有
* @return Account列表
*/
List<Account> findAll();
/**
* 使用名称查询
* @return 一个Account
*/
Account findAccountByName(String name);
/**
* 保存账户信息
*
*/
void saveAccount(Account account);
}
- AccountDaoImpl 实现接口AccountDao
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
QueryRunner runner = null;
@Autowired
ConnectionUtil connectionUtil = null;
/**
* 查询所有
*
* @return Account列表
*/
public List<Account> findAll() {
try {
//每次进行数据库操作都要去获取同一个连接对象,以保持数据是正常的
return runner.query(connectionUtil.getConnection(), "select * from account", new BeanListHandler<Account>(Account.class));
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("sql异常---------------");
}
}
/**
* @param name 账户名称
* @return 账户
*/
public Account findAccountByName(String name) {
try {
return runner.query(connectionUtil.getConnection(), "select * from account where name = ?", new BeanHandler<Account>(Account.class),name);
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("sql异常---------------");
}
}
/**
* @param account 一个账户
*/
public void saveAccount(Account account) {
try {
runner.update(connectionUtil.getConnection(), "update account set money =? where id= ?",account.getMoney(),account.getId());
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("sql异常---------------");
}
}
}
创建AccountService和AccountServiceImpl
- AccountService 接口
public interface AccountService {
/**
* 查询所有
* @return Account列表
*/
List<Account> findAll();
/**
* 转账
* @return 是否转账成功
*/
boolean transferAccount(String targetName, String sourceName, Double transferMoney);
}
- AccountServiceImpl 实现接口AccountService
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private final AccountDao dao = null;
/**
* @return 所有用户
*/
public List<Account> findAll() {
return dao.findAll();
}
/**
* 转账
* @param targetName 转出账户
* @param sourceName 接收账户
* @param transferMoney 转的数量
* @return 是否正常
*/
public boolean transferAccount(String targetName,String sourceName,Double transferMoney) {
try {
//获取两个需要操作的账户
Account targetAccount = dao.findAccountByName(targetName);
Account sourceAccount = dao.findAccountByName(sourceName);
//进行转账
targetAccount.setMoney(targetAccount.getMoney() - transferMoney);
sourceAccount.setMoney(sourceAccount.getMoney() + transferMoney);
//保存账户信息
dao.saveAccount(targetAccount);
dao.saveAccount(sourceAccount);
} catch (Exception e) {
e.printStackTrace();
System.out.println("有异常情况");
return false;
}
return true;
}
}
创建事务管理工具类
@Component("transactionUtil")
public class TransactionUtil {
//获取连接工具类
@Autowired
private final ConnectionUtil connectionUtil = null;
//获取当前使用的连接保持跟dao中使用的一致
@Autowired
private Connection connection = null;
/**
* 开启事务
*/
public void startTransaction(){
try {
//如果连接已经归还则重新获取一个
if (connection == null){
connection = connectionUtil.getConnection();
}
connection.setAutoCommit(false);
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("事务开启失败");
}
}
/**
* 提交事务
*/
public void commitTransaction(){
try {
connection.commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("事务提交失败");
}
}
/**
* 回顾事务
*/
public void rollbackTransaction(){
try {
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("事务回滚失败");
}
}
/**
*释放资源
*/
public void releaseTransaction(){
try {
//回收连接
connection.close();
//置空连接对象
connection = null;
//解除连接绑定
connectionUtil.removeConnect();
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("资源释放失败");
}
}
}
创建切面增强类
@Aspect
@Component("accountServiceAop")
public class AccountServiceAop {
//指出切点
@Pointcut("execution(* com..impl.AccountServiceImpl.*(..))")
private void pointCut(){}
@Autowired
private final TransactionUtil transactionUtil = null;
Object result = null;
@Around("pointCut()")
public Object aroundAdviceForAccountService(ProceedingJoinPoint pjp){
try {
//获取方法执行所需要的参数
Object[] agrs = pjp.getArgs();
System.out.println("开启事务");
transactionUtil.startTransaction();
//执行方法
result = pjp.proceed(agrs);
transactionUtil.commitTransaction();
System.out.println("提交事务");
return result;
} catch (Throwable throwable) {
throwable.printStackTrace();
transactionUtil.rollbackTransaction();
throw new RuntimeException("有异常状况");
} finally {
if (pjp.getSignature().getName() != null){
System.out.println("当前执行的方法是:"+pjp.getSignature().getName());
}
transactionUtil.releaseTransaction();
System.out.println("释放资源");
}
}
}
测试
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:SpringConfiguration.xml")
public class TestAop {
@Autowired
private AccountService service = null;
@Test
public void testTransfer(){
boolean flag = service.transferAccount("zhangsan", "lisi", 200.00);
if (flag){
System.out.println("转账成功!!!!");
List<Account> accountList = service.findAll();
for (Account account : accountList) {
System.out.println(account);
}
}else {
System.out.println("转账失败!!!!");
}
}
}