项目需求
- 利用事务管理器和AOP配置,实现事务控制
- 基于XML的IoC配置
项目说明
- 整合spring和junit
- 数据源采用c3p0
- 利用DBUtils对jdbc简单封装
DBUtils百科介绍
Commons DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能。 [1]
AspectJ百科介绍
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
编写pom文件
<?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.cncs</groupId>
<artifactId>day04_02account_aop_xml</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</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.9.4</version>
</dependency>
</dependencies>
</project>
新建实体类Account
package com.cncs.domain;
import java.io.Serializable;
public class Account implements Serializable {
private int id;
private String name;
private float money;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getMoney() {
return money;
}
public void setMoney(float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
新建accountService接口及实现类
accountService接口
package com.cncs.service;
import com.cncs.domain.Account;
import java.util.List;
/**
* 账户的业务层接口
*/
public interface AccountService {
List<Account> findAll();
Account findById(int accountId);
void saveAccount(Account account);
void updateAccount(Account account);
void deleteAccount(int accountId);
void transfer(String sourceName, String targetName);
}
accountService实现类
package com.cncs.service.impl;
import com.cncs.dao.AccountDao;
import com.cncs.domain.Account;
import com.cncs.service.AccountService;
import com.cncs.utils.TransactionManager;
import java.util.List;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public List<Account> findAll() {
return accountDao.findAll();
}
@Override
public Account findById(int accountId) {
return accountDao.findById(accountId);
}
@Override
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
@Override
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
@Override
public void deleteAccount(int accountId) {
accountDao.deleteAccount(accountId);
}
@Override
public void transfer(String sourceName, String targetName) {
System.out.println("transfer...");
//2.根据名称查找source账户
Account sourceAccount = accountDao.findAccountByName(sourceName);
//3.根据名称查找target账户
Account targetAccount = accountDao.findAccountByName(targetName);
//4.source账户减少钱
sourceAccount.setMoney(sourceAccount.getMoney() - 200f);
//5.target账户增加钱
targetAccount.setMoney(targetAccount.getMoney() + 200f);
//更新source账户
accountDao.updateAccount(sourceAccount);
int a = 1 / 0;
//更新target账户
accountDao.updateAccount(targetAccount);
}
}
新建accountDao接口及实现类
accountDao接口
package com.cncs.dao;
import com.cncs.domain.Account;
import java.util.List;
/**
* 账户持久层的接口
*/
public interface AccountDao {
List<Account> findAll();
Account findById(int accountId);
Account findAccountByName(String accountName);
void saveAccount(Account account);
void updateAccount(Account account);
void deleteAccount(int accountId);
}
accountDao实现类
package com.cncs.dao.impl;
import com.cncs.dao.AccountDao;
import com.cncs.domain.Account;
import com.cncs.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.SQLException;
import java.util.List;
public class AccountDaoImpl implements AccountDao {
private QueryRunner runner;
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
@Override
public List<Account> findAll() {
try {
return runner.query("select * from account", new BeanListHandler<Account>(Account.class));
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
@Override
public Account findById(int accountId) {
try {
return runner.query("select * from account where id = ?", new BeanHandler<Account>(Account.class), accountId);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
@Override
public void saveAccount(Account account) {
try {
runner.update("insert into account(name,money) values(?,?)", account.getName(), account.getMoney());
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void updateAccount(Account account) {
try {
runner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void deleteAccount(int accountId) {
try {
runner.update("delete from account where id = ?", accountId);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public Account findAccountByName(String accountName) {
try {
List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ?", new BeanListHandler<Account>(Account.class), accountName);
if (accounts.size() == 1){
return accounts.get(0);
}else{
throw new RuntimeException("查询结果不合法");
}
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
新建ConnectionUtils工具类
说明:该类用于获取连接,保证每次拿出的连接都是同一线程上的连接。
package com.cncs.utils;
import javax.sql.DataSource;
import java.sql.Connection;
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Connection getThreadConnection() {
try {
//获取连接
Connection connection = tl.get();
//判断当前线程是否有连接
if (connection == null) {
//从数据源中获取一个连接,存入连接池
connection = dataSource.getConnection();
tl.set(connection);
}
return connection;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void removeConnection() {
tl.remove();
}
}
新建TransactionManager类,进行事务管理
说明:该类实现事务管理,包括事务的自动提交是否开启,事务回滚,事务提交以及线程关闭,线程解绑等操作。
package com.cncs.utils;
import java.sql.Connection;
import java.sql.SQLException;
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void startTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 事务回滚
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit(); //提交事务
} catch (SQLException e) {
e.printStackTrace();
}
}
public void release(){
try {
connectionUtils.getThreadConnection().close(); //关闭连接
connectionUtils.removeConnection(); //将连接与线程解绑
} catch (SQLException e) {
e.printStackTrace();
}
}
}
新建spring配置文件bean.xml
说明:
- 配置accountService
- 配置accountDao
- 配置QueryRunner,并且此处需要使用多例模式,因为不是每个操作都需要使用同一线程。
- 配置数据源
- 配置事务管理器
- 配置connectionUtils
- 配置AOP,切面,切入点表达式以及事务通知
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="accountService" class="com.cncs.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<bean id="accountDao" class="com.cncs.dao.impl.AccountDaoImpl">
<property name="runner" ref="runner"></property>
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl"
value="jdbc:mysql://127.0.0.1:3306/spring_learn?useUnicode=true&characterEncoding=utf-8"></property>
<property name="user" value="root"></property>
<property name="password" value="1705640"></property>
</bean>
<bean id="transactionManager" class="com.cncs.utils.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<bean id="connectionUtils" class="com.cncs.utils.ConnectionUtils">
<property name="dataSource" ref="dataSource"></property>
</bean>
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.cncs.service.impl.*.*(..))"></aop:pointcut>
<aop:aspect ref="transactionManager">
<aop:before method="startTransaction" pointcut-ref="pt1"></aop:before>
<aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
<aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
<aop:after method="release" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
</beans>
新建测试类AccountTest
说明:
此处测试账户转账的操作,在transfer业务中,共进行了两次数据修改,在两次操作之间通过添加一个异常模拟转账过程中遇见的转账失败,验证事务的一致性。
package com.cncs.test;
import com.cncs.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:bean.xml"})
public class AccountTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer(){
accountService.transfer("aaa","bbb");
}
}