动态代理加持事务的转账案例
Posted jianzha
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态代理加持事务的转账案例相关的知识,希望对你有一定的参考价值。
此案例 使用动态代理 + 事务控制 完成转账
当有异常发生时,加在业务层中的事务会回滚,避免出现转出方钱已经转出,但接收放没有收到钱
pom依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-dbutils/commons-dbutils -->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.4.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
POJO类
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
public Account() {
}
public Account(String name, Float money) {
this.name = name;
this.money = money;
}
public Account(Integer id, String name, Float money) {
this.id = id;
this.name = name;
this.money = 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 Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name=‘" + name + ‘‘‘ +
", money=" + money +
‘}‘;
}
}
持久层接口
public interface AccountDao {
/**
* 查询所有
* @return
*/
List<Account> findAllAccount();
/**
* 查询一个
*/
Account findAccountById(Integer accountId);
/**
* 保存
*/
void saveAccount(Account account);
/**
* 更新
*/
void updateAccount(Account account);
/**
* 删除
*/
void deleteAccount(Integer accountId);
/**
* 根据名称查询账户
* @param accountName
* @return
*/
Account findAccountByName(String accountName);
}
持久层接口的实现类
/**
* 账户的持久层实现类
*/
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;
}
public List<Account> findAllAccount() {
try {
return runner.query(connectionUtils.getThreadConnection(),"select * from account", new BeanListHandler<Account>(Account.class));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Account findAccountById(Integer accountId) {
try {
return runner.query(connectionUtils.getThreadConnection(),"select * from account where id = ?", new BeanHandler<Account>(Account.class), accountId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void saveAccount(Account account) {
try {
runner.update(connectionUtils.getThreadConnection(),"insert into account(name, money) values(?, ?)", account.getName(), account.getMoney());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void updateAccount(Account account) {
try {
runner.update(connectionUtils.getThreadConnection(),"update account set name=?, money =? where id = ?", account.getName(), account.getMoney(), account.getId());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void deleteAccount(Integer accountId) {
try {
runner.update(connectionUtils.getThreadConnection(),"delete from account where id = ?", accountId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
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 == null || accounts.size() == 0) {
return null;
}
if(accounts.size() > 1){
throw new RuntimeException("结果集不唯一,数据有问题");
}
return accounts.get(0);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
业务层接口
/**
* 账户的业务层接口
*/
public interface AccountService {
/**
* 查询所有
* @return
*/
List<Account> findAllAccount();
/**
* 查询一个
*/
Account findAccountById(Integer accountId);
/**
* 保存
*/
void saveAccount(Account account);
/**
* 更新
*/
void updateAccount(Account account);
/**
* 删除
*/
void deleteAccount(Integer accountId);
/**
* 转账
* @param sourceName 转出账户名称
* @param targetName 转入账户名称
* @param money 转账金额
*/
void transfer(String sourceName, String targetName, Float money);
}
业务层接口的实现类
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
public Account findAccountById(Integer accountId) {
return findAccountById(accountId);
}
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
public void deleteAccount(Integer accountId) {
accountDao.deleteAccount(accountId);
}
public void transfer(String sourceName, String targetName, Float money) {
//执行操作
//1.根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//3.转出账户减钱
source.setMoney(source.getMoney() - money);
//4.转入账户价钱
target.setMoney(target.getMoney() + money);
//5.更新转出账户
accountDao.updateAccount(source);
//6.更细转入账户
int i = 1/0;
accountDao.updateAccount(target);
}
}
连接的工具类,用于从数据源中获取一个连接,并且实现和线程的绑定
/**
* 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
*/
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上的连接
*/
public Connection getThreadConnection(){
try {
//1.先从ThreadLocal上获取
Connection conn = tl.get();
//2.判断当前线程上是否有连接
if(conn == null){
//3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 线程与连接解绑
*/
public void removeConnection(){
tl.remove();
}
}
和事务管理相关的工具类,包含开启事务、提交事务、回滚事务和释放连接
/**
* 和事务管理相关的工具类,包含开启事务、提交事务、回滚事务和释放连接
*/
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();
}
}
/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 关闭事务
*/
public void release(){
try {
connectionUtils.getThreadConnection().close();
connectionUtils.removeConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
用于创建Service的代理对象工厂
/**
* 用于创建Service的代理对象工厂
*/
public class BeanFactory {
private AccountService accountService;
public final void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
private TransactionManager txManager;
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
/**
* 获取Service代理对象
* @return
*/
public AccountService getAccountService() {
AccountService proxy = (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rtValue = null;
try {
//开启事务
txManager.beginTransaction();
rtValue = method.invoke(accountService, args);
//提交事务
txManager.commit();
//返回结果
return rtValue;
} catch (Exception e) {
//回滚操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
//释放连接
txManager.release();
}
}
});
return proxy;
}
}
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置代理service-->
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
<!--配置beanFactory-->
<bean id="beanFactory" class="com.jz.factory.BeanFactory">
<property name="accountService" ref="accountService"/>
<property name="txManager" ref="txManager"/>
</bean>
<!--配置Service-->
<bean id="accountService" class="com.jz.service.impl.AccountServiceImpl">
<!--注入dao-->
<property name="accountDao" ref="accountDao"/>
</bean>
<!--配置Dao-->
<bean id="accountDao" class="com.jz.dao.impl.AccountDaoImpl">
<!--注入QueryRunner-->
<property name="runner" ref="runner"/>
<!--注入ConnectionUtils-->
<property name="connectionUtils" ref="connectionUtils"/>
</bean>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_db"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>
<!--配置Connection的工具类-->
<bean id="connectionUtils" class="com.jz.utils.ConnectionUtils">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务管理-->
<bean id="txManager" class="com.jz.utils.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"/>
</bean>
</beans>
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
@Qualifier("proxyAccountService")
private AccountService as;
@Test
public void testTransfer(){
as.transfer("aaa", "bbb", 100f);
}
}
架构
以上是关于动态代理加持事务的转账案例的主要内容,如果未能解决你的问题,请参考以下文章
Spring 从入门到精通系列 09——转账方法的事务问题与动态代理