1. 程式人生 > 其它 >轉賬問題演示及解決(Aop的引出:ConnectionUtils(ThreadLocal)+TransactionManager)

轉賬問題演示及解決(Aop的引出:ConnectionUtils(ThreadLocal)+TransactionManager)

轉賬問題分析事務

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>