天天看点

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/