Spring AOP+dbutils+druid+mysql+junit整合事务
文章目录
-
- 关系图
- bean.xml
- pom.xml
- service
- dao
- utils
- domain
- junit test
- sql
关系图
上图中实现事务管理的关键在Transaction类, 核心是ConnectionUtils, 从中获取同一个Connection对象(与QueryRunner使用的相同), 为了完成这个操作, 在ConnectionUtils中使用ThreadLocal进行线程中的数据对象管理(这里是Connection对象), 保证在同一个线程操作的是同一个对象(这里不明白的可以先了解下ThreadLocal类).
AOP技术实现的核心是动态代理, 依赖Proxy来对Service方法进行增强, 从而减少了Service中多余的事务操作代码.
把上图和下面的代码结合起来, 你会有更深刻的理解, 希望对你有帮助.
bean.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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="accountService" class="com.qichun.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<bean id="accountDao" class="com.qichun.dao.impl.AccountDaoImpl">
<property name="runner" ref="queryRunner"/>
<property name="connectionUtils" ref="connectionUtils"/>
</bean>
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"/>
<!--druid连接池配置, 使用的是mysql8.0-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///spring_db?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="initialSize" value="3"/>
<property name="minIdle" value="3"/>
<property name="maxActive" value="20"/>
<property name="maxWait" value="60000"/>
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<property name="minEvictableIdleTimeMillis" value="300000"/>
<property name="validationQuery" value="SELECT 'x'"/>
<property name="testOnReturn" value="false"/>
<property name="filters" value="stat"/>
</bean>
<bean id="transactionManager" class="com.qichun.utils.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"/>
</bean>
<bean id="connectionUtils" class="com.qichun.utils.ConnectionUtils">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- AOP config -->
<aop:config>
<!--自定义切入点-->
<aop:pointcut id="pc1" expression="execution(* com.qichun.service.impl.*.*(..))"/>
<!--一个切面-->
<aop:aspect id="accountAspect" ref="transactionManager">
<aop:before method="beginTransaction" pointcut-ref="pc1"/>
<aop:after-returning method="commitTransaction" pointcut-ref="pc1"/>
<aop:after-throwing method="rollbackTransaction" pointcut-ref="pc1"/>
<aop:after method="releaseSource" pointcut-ref="pc1"/>
</aop:aspect>
</aop:config>
</beans>
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>
<groupId>com.qichun</groupId>
<artifactId>aop_transaction_template</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.8</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
service
interface
package com.qichun.service;
public interface AccountService {
/**
* 转账方法
* @param fromId
* @param toId
* @param money
* @return
*/
boolean transTo(int fromId,int toId,double money);
}
implement class
package com.qichun.service.impl;
import com.qichun.dao.AccountDao;
import com.qichun.domain.Account;
import com.qichun.service.AccountService;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
//--------------------------------------------------------
@Override
public boolean transTo(int fromId, int toId,double money) {
//业务逻辑主体
try{
Account from = accountDao.findById(fromId);
Account to = accountDao.findById(toId);
from.setMoney(from.getMoney()-money);
// int n = 1/0;//test transaction
to.setMoney(to.getMoney()+money);
accountDao.update(from);
accountDao.update(to);
}catch (Exception e){
e.printStackTrace();
}
return false;
}
}
dao
interface
package com.qichun.dao;
import com.qichun.domain.Account;
public interface AccountDao {
/**
* id查询方法
* @param id
* @return
*/
Account findById(int id);
/**
* 更新记录方法
* @param acc
*/
void update(Account acc);
}
implement class
package com.qichun.dao.impl;
import com.qichun.dao.AccountDao;
import com.qichun.domain.Account;
import com.qichun.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.util.List;
/**
* dao接口实现类
* 注意每个dao方法, Connection对象并不是写死的
*/
public class AccountDaoImpl implements AccountDao {
//JDBC工具类
private QueryRunner runner;
//Connection工具类
private ConnectionUtils connectionUtils;
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
//----------------------------------------------------
@Override
public Account findById(int id) {
try{
List<Account> accountList = runner.query(connectionUtils.getThreadConnection(),"select * from account where id=?", new BeanListHandler<Account>(Account.class), id);
if(accountList == null || accountList.size() == 0){
throw new Exception("无此账户");
}
if(accountList.size()>1){
throw new Exception("查询结果出问题, 出现多个用户");
}
return accountList.get(0);
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public void update(Account acc){
try{
runner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id=?",acc.getName(),acc.getMoney(),acc.getId());
}catch (Exception e){
e.printStackTrace();
}
}
}
utils
TransactionManager.class
package com.qichun.utils;
import java.sql.SQLException;
/**
* 事务管理工具类
* 事务的各种操作
*/
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
//---------------------------------------------------------------------
/**
* 开启事务
*/
public void beginTransaction() {
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e2) {
e2.printStackTrace();
}
}
/**
* 提交事务
*/
public void commitTransaction() {
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e2) {
e2.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollbackTransaction() {
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e2) {
e2.printStackTrace();
}
}
/**
* 释放资源
*/
public void releaseSource() {
try {
connectionUtils.getThreadConnection().close();//还回连接池
connectionUtils.removeConnection();//将Connection于当前线程解绑
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
ConnectionUtils.class
package com.qichun.utils;
import javax.sql.DataSource;
import java.sql.Connection;
/**
*
*/
public class ConnectionUtils {
//关键!!
private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
//---------------------------------------------------------
/**
* 获取当前线程上的连接
* thread 把数据绑定到当前线程上, 如果没有, 则新建一个
*/
public Connection getThreadConnection() {
try {
// 1.想尝试获取连接
Connection conn = threadLocal.get();
//判断是否存在
if (conn == null) {
//3.取出一个连接
conn = dataSource.getConnection();
threadLocal.set(conn);//把数据绑定给线程, 使线程操作的始终是一个对象
}
return conn;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 把对象和线程解绑
*/
public void removeConnection() {
threadLocal.remove();
}
}
domain
Account.class
package com.qichun.domain;
public class Account {
Integer id;
String name;
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 +
'}';
}
}
junit test
AccountTest.class
package com.qichun;
import com.qichun.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
//使用spring-test整合junit--加载spring核心容器
@RunWith(SpringJUnit4ClassRunner.class)
//配置bean路径
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountTest {
@Test
public void test__() {
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:bean.xml");
AccountService accountService = ac.getBean("accountService",AccountService.class);
//测试事务
accountService.transTo(1,2,200);
}
}
sql
create table account(
id int primary key auto_increment,
name varchar(50),
money float
)character set utf8 collate utf8_general_ci;
insert into account(name,money) values('张三',2000);
insert into account(name,money) values('李四',2000);
Q&A 请指正! 感觉不错点个赞.
想了解作者更多,请移步我的个人网站,欢迎交流、留言~
极客技术空间:https://elltor.com/