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/