天天看點

Spring AOP+dbutils+druid+mysql+junit整合事務

Spring AOP+dbutils+druid+mysql+junit整合事務

文章目錄

    • 關系圖
    • bean.xml
    • pom.xml
    • service
    • dao
    • utils
    • domain
    • junit test
    • sql

關系圖

Spring AOP+dbutils+druid+mysql+junit整合事務

上圖中實作事務管理的關鍵在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/