轉賬問題演示及解決(Aop的引出:ConnectionUtils(ThreadLocal)+TransactionManager)
阿新 • • 發佈:2022-04-11
轉賬問題分析事務
accountDao中的使用的是Jdbctemplate
由於更新中途出現異常,導致source的金錢扣了卻沒有轉賬到target賬戶上!!!
轉賬問題解決
該解決方法程式碼量冗餘,配置檔案邏輯複雜,仍有巨大優化空間(代理模式加強方法)
工具類部分(通過ThreadLocal繫結執行緒上的Connection)
ConnectionUtils(通過ThreadLocal繫結物件)
package com.czy.utils; import javax.sql.DataSource; import java.sql.Connection; /** * 連線工具類 * 它用於從資料來源獲取一個連線,並且實現和執行緒的繫結 */ public class ConnectionUtils { private ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); //用spring注入此物件 private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * 獲取當前執行緒上的連線 * @return */ public Connection getThreadConnection(){ //1.先從ThreadLocal上獲取 Connection conn = tl.get(); try { //2.判斷當前執行緒上是否有連線 if (conn == null) { //3.從資料來源獲取一個連線,並且和執行緒繫結(存入ThreadLocal中) conn = dataSource.getConnection(); tl.set(conn); } }catch (Exception exception){ throw new RuntimeException(exception); } //4.返回當前執行緒上的連線 return conn; } /** * 把連線和執行緒解綁 */ public void removeConnection(){ tl.remove(); } }
TransactionManager(管理事務)
package com.czy.utils; import java.sql.SQLException; /** * 事務管理相關的工具類,包含了開啟事務,提交事務,回滾事務和釋放連線 */ public class TransactionManager { //使用spring注入 private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } /** * 開啟事務 */ public void beginTransaction(){ try { connectionUtils.getThreadConnection().setAutoCommit(false); } catch (SQLException throwables) { throwables.printStackTrace(); } } /** * 提交事務 */ public void commit(){ try { connectionUtils.getThreadConnection().commit(); } catch (SQLException throwables) { throwables.printStackTrace(); } } /** * 回滾事務 */ public void rollback(){ try { connectionUtils.getThreadConnection().rollback(); } catch (SQLException throwables) { throwables.printStackTrace(); } } /** * 釋放連線 */ public void release(){ try { connectionUtils.getThreadConnection().close(); //Connection還回連線池中 connectionUtils.removeConnection(); } catch (SQLException throwables) { throwables.printStackTrace(); } } }
dao
package com.czy.dao.impl; import com.czy.dao.AccountDao; import com.czy.domain.Account; import com.czy.utils.ConnectionUtils; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import java.util.List; /** * 賬戶的持久層實現類 */ public class AccountDaoImpl implements AccountDao { private JdbcTemplate template; private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } public void setTemplate(JdbcTemplate template) { this.template = template; } public List<Account> findAllAccount() { return template.query("select * from account",new BeanPropertyRowMapper<Account>(Account.class),connectionUtils.getThreadConnection()); } public Account findAccountById(Integer id) { return template.queryForObject("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),id,connectionUtils.getThreadConnection()); } public void saveAccount(Account account) { template.update("insert into account(name,money) values (?,?)",account.getName(),account.getMoney(),connectionUtils.getThreadConnection()); } public void updateAccount(Account account) { template.update("update account set name = ?,money = ? where id = ?",account.getName(),account.getMoney(),account.getId(),connectionUtils.getThreadConnection()); } public void deleteAccount(Integer id) { template.update("delete from account where id = ?",id); } public Account findAccountByName(String accountName) { List<Account> accounts = template.query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName,connectionUtils.getThreadConnection()); if(accounts == null || accounts.size() == 0) return null; else if(accounts.size() > 1) throw new RuntimeException("結果集不唯一"); return accounts.get(0); } }
service
package com.czy.service.impl;
import com.czy.dao.AccountDao;
import com.czy.domain.Account;
import com.czy.service.AccountService;
import com.czy.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
public class AccountServiceImpl implements AccountService {
private AccountDao dao;
private TransactionManager txManager;
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public void setDao(AccountDao dao) {
this.dao = dao;
}
public List<Account> findAllAccount() {
try{
//1.開啟事務
txManager.beginTransaction();
//2.執行操作
List<Account> accounts = dao.findAllAccount();
//3.提交事務
txManager.commit();
//4.返回結果
return accounts;
}catch(Exception e){
//5.回滾操作
txManager.rollback();
throw new RuntimeException(e);
}finally {
//6.釋放資源
txManager.release();
}
}
public Account findAccountById(Integer id) {
try{
//1.開啟事務
txManager.beginTransaction();
//2.執行操作
Account account = dao.findAccountById(id);
//3.提交事務
txManager.commit();
//4.返回結果
return account;
}catch(Exception e){
//5.回滾操作
txManager.rollback();
throw new RuntimeException(e);
}finally {
//6.釋放資源
txManager.release();
}
}
public void saveAccount(Account account) {
try{
//1.開啟事務
txManager.beginTransaction();
//2.執行操作
dao.saveAccount(account);
//3.提交事務
txManager.commit();
//4.返回結果
}catch(Exception e){
//5.回滾操作
txManager.rollback();
}finally {
//6.釋放資源
txManager.release();
}
}
public void transfer(String sourceName, String targetName, Float money) {
try{
//1.開啟事務
txManager.beginTransaction();
//2.執行操作
//根據名稱查詢轉出賬戶
Account source = dao.findAccountByName(sourceName);
//根據名稱查詢轉入賬戶
Account target = dao.findAccountByName(targetName);
//轉出賬戶減錢
source.setMoney(source.getMoney()-money);
//轉入賬戶減錢
target.setMoney(target.getMoney()+money);
//更新兩者
dao.updateAccount(source);
int i = 1/0;
dao.updateAccount(target);
//3.提交事務
txManager.commit();
//4.返回結果
}catch(Exception e){
//5.回滾操作
txManager.rollback();
e.printStackTrace();
}finally {
//6.釋放資源
txManager.release();
}
}
}
配置檔案(依賴注入)
<?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 file:///E:\JAVA\spring-framework-5.3.9\schema\beans\spring-beans.xsd">
<bean id="connectionUtils" class="com.czy.utils.ConnectionUtils">
<property name="dataSource" ref="druid"></property>
</bean>
<bean id="transactionManager" class="com.czy.utils.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<bean id="DruidDataSourceFactory" class="com.czy.factory.DruidDataSourceFactory" init-method="init"></bean>
<bean id="druid" factory-bean="DruidDataSourceFactory" factory-method="getDataSource"></bean>
<!--單例物件的話多個執行緒在使用單個物件會導致執行緒問題,故使用多例物件-->
<bean id="template" class="org.springframework.jdbc.core.JdbcTemplate" scope="prototype">
<!--不再從資料來源中獲取連線而是從自己的ConnectionUtils-->
<!--<constructor-arg index="0" ref="druid"></constructor-arg>-->
</bean>
<bean id="accountDao" class="com.czy.dao.impl.AccountDaoImpl">
<property name="template" ref="template"></property>
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<bean id="accountService" class="com.czy.service.impl.AccountServiceImpl">
<property name="dao" ref="accountDao"></property>
<property name="txManager" ref="transactionManager"></property>
</bean>
</beans>