Java第三十九天,Spring框架系列,銀行轉賬案例(一)


一、無事務處理的缺陷分析

1.錯誤分析

在該函數中,一共建立了四個數據庫連接;前面的三個可以順利完成並且提交事務,但是后面的一個卻因異常而無法提交;即事務處理放在了持久層,而沒有放在業務層;需要注意,一切事務處理都需要在業務層;最終導致資金錯誤的情況;

2.解決辦法:

解決的辦法就是將四個連接合並為一個連接,要么一起成功,要么一起失敗;即使用ThreadLocal對象把Connection和當前線程綁定,從而使一個線層只有一個能控制事物的對象

二、項目代碼

1.項目整體構造

2.持久層接口

package com.huhai.dao;

import com.huhai.domain.Account;

import java.util.List;

/**
 * 賬戶的持久層接口
 */
public interface IAccountDao {

    /**
     * 查詢所有
     * @return
     */
    List<Account> findAllAccount();

    /**
     * 查詢一個
     * @return
     */
    Account findAccountById(Integer accountId);

    /**
     * 保存
     * @param account
     */
    void saveAccount(Account account);

    /**
     * 更新
     * @param account
     */
    void updateAccount(Account account);

    /**
     * 刪除
     * @param acccountId
     */
    void deleteAccount(Integer acccountId);

    /**
     * 
     * 如果有唯一的結果就返回
     * 如果沒有結果就返回Null
     * 如果有多個結果就拋出異常
     */
    Account findAccountByName(String accountName);
}

3.持久層接口實現類

package com.huhai.dao.impl;

import com.huhai.dao.IAccountDao;
import com.huhai.domain.Account;
import com.huhai.utils.ConnectionUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.util.List;

/**
 * 賬戶的持久層實現類
 */
public class AccountDaoImpl implements IAccountDao {

    private QueryRunner runner;
    private ConnectionUtil connectionUtil;

    public void setConnectionUtil(ConnectionUtil connectionUtil) {
        this.connectionUtil = connectionUtil;
    }

    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }

    @Override
    public List<Account> findAllAccount() {
        try{
            return runner.query(connectionUtil.getThreadConnection(), "select * from account",new BeanListHandler<Account>(Account.class));
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Account findAccountById(Integer accountId) {
        try{
            return runner.query(connectionUtil.getThreadConnection(), "select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void saveAccount(Account account) {
        try{
            runner.update(connectionUtil.getThreadConnection(), "insert into account(name,money)values(?,?)",account.getName(),account.getMoney());
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void updateAccount(Account account) {
        try{
            runner.update(connectionUtil.getThreadConnection(), "update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void deleteAccount(Integer accountId) {
        try{
            runner.update(connectionUtil.getThreadConnection(), "delete from account where id=?",accountId);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根據名稱返回查詢結果
     * 如果有唯一的結果就返回
     * 如果沒有結果就返回Null
     * 如果有多個結果就拋出異常
     */
    @Override
    public Account findAccountByName(String accountName){
        try{
            List<Account> accounts = runner.query(connectionUtil.getThreadConnection(), "select * from account where name = ? ", new BeanListHandler<Account>(Account.class), accountName);
            if(accounts == null || accounts.size() == 0){
                return null;
            }else if(accounts.size() > 1){
                throw new RuntimeException("結果集不唯一");
            }else{
                return accounts.get(0);
            }
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

4.持久層序列化類

package com.huhai.domain;

import java.io.Serializable;

/**
 * 賬戶的實體類
 */
public class Account implements Serializable {

    private Integer id;
    private String name;
    private Float 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 +
                '}';
    }
}

5.業務層接口

package com.huhai.service;

import com.huhai.domain.Account;

import java.util.List;

/**
 * 賬戶的業務層接口
 */
public interface IAccountService {

    /**
     * 查詢所有
     * @return
     */
    List<Account> findAllAccount();

    /**
     * 查詢一個
     * @return
     */
    Account findAccountById(Integer accountId);

    /**
     * 保存
     * @param account
     */
    void saveAccount(Account account);

    /**
     * 更新
     * @param account
     */
    void updateAccount(Account account);

    /**
     * 刪除
     * @param acccountId
     */
    void deleteAccount(Integer acccountId);

    /**
     * 轉賬操作
     */
    public void transfer(String sourceName,String targetName, float money);

}

6.業務層接口實現類

package com.huhai.service.impl;

import com.huhai.dao.IAccountDao;
import com.huhai.domain.Account;
import com.huhai.service.IAccountService;
import com.huhai.utils.TransActionManager;

import java.util.List;

/**
 * 賬戶的業務層實現類
 * 所有的事物控制應該都在業務層,而並非在持久層
 */
public class AccountServiceImpl implements IAccountService{

    private IAccountDao accountDao;
    private TransActionManager transActionManager;

    //添加set方法,讓spring自動注入
    public void setTransActionManager(TransActionManager transActionManager) {
        this.transActionManager = transActionManager;
    }

    //添加set方法,讓spring自動注入
    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public List<Account> findAllAccount() {
        try {
            //開啟事務
            transActionManager.startTransAction();
            //執行操作
            List<Account> accounts = accountDao.findAllAccount();
            //提交事務
            transActionManager.commitTransAction();
            //返回結果
            return accounts;
        }catch (Exception e){
            //回滾事物
            transActionManager.rollBackTransAction();
            throw new RuntimeException();
        }finally {
            //釋放連接
            transActionManager.releaseConnection();
        }
    }

    @Override
    public Account findAccountById(Integer accountId) {
        try {
            //開啟事務
            transActionManager.startTransAction();
            //執行操作
            Account account = accountDao.findAccountById(accountId);
            //提交事務
            transActionManager.commitTransAction();
            //返回結果
            return account;
        }catch (Exception e){
            //回滾事物
            transActionManager.rollBackTransAction();
            throw new RuntimeException();
        }finally {
            //釋放連接
            transActionManager.releaseConnection();
        }
    }

    @Override
    public void saveAccount(Account account) {
        try {
            //開啟事務
            transActionManager.startTransAction();
            //執行操作
            accountDao.saveAccount(account);
            //提交事務
            transActionManager.commitTransAction();
        }catch (Exception e){
            //回滾事物
            transActionManager.rollBackTransAction();
        }finally {
            //釋放連接
            transActionManager.releaseConnection();
        }
    }

    @Override
    public void updateAccount(Account account) {
        try {
            //開啟事務
            transActionManager.startTransAction();
            //執行操作
            accountDao.updateAccount(account);
            //提交事務
            transActionManager.commitTransAction();
        }catch (Exception e){
            //回滾事物
            transActionManager.rollBackTransAction();
        }finally {
            //釋放連接
            transActionManager.releaseConnection();
        }
    }

    @Override
    public void deleteAccount(Integer acccountId) {
        try {
            //開啟事務
            transActionManager.startTransAction();
            //執行操作
            accountDao.deleteAccount(acccountId);
            //提交事務
            transActionManager.commitTransAction();
        }catch (Exception e){
            //回滾事物
            transActionManager.rollBackTransAction();
        }finally {
            //釋放連接
            transActionManager.releaseConnection();
        }
    }

    /**
     * 轉賬操作
     * 該方法有嚴重的錯誤,在兩賬戶更新數據的過程中如果產生了異常程序奔潰,則會發生資金有轉出,無轉入,即憑空消失了若干錢,這是決不允許的
     */
    @Override
    public void transfer(String sourceName, String targetName, float money) {

        try {
            //1.開啟事務
            transActionManager.startTransAction();
            //2.執行操作

            //2.1根據名稱查詢轉出賬戶
            Account accountSource = accountDao.findAccountByName(sourceName);
            //2.2根據名稱查詢轉入賬戶
            Account accountTarget = accountDao.findAccountByName(targetName);
            //2.3轉出賬戶減錢
            accountSource.setMoney(accountSource.getMoney() - money);
            //2.4轉入賬戶加錢
            accountTarget.setMoney(accountTarget.getMoney() + money);
            //2.5更新轉出賬戶
            accountDao.updateAccount(accountSource);
            //2.6更新轉入賬戶
            accountDao.updateAccount(accountTarget);

            //3提交事務
            transActionManager.commitTransAction();
        }catch (Exception e){
            //4回滾事物
            transActionManager.rollBackTransAction();
        }finally {
            //5釋放連接
            transActionManager.releaseConnection();
        }
    }

}

7.數據庫連接池

package com.huhai.utils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * 連接的工具類,它用於從數據源中獲取一個連接,並且實現和線程的綁定
 */
public class ConnectionUtil {
    private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
    private DataSource dataSource;

    //添加set方法,讓spring自動注入
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 獲取當前線層的連接
     */
    public Connection getThreadConnection() throws SQLException {
        /**
         * 從ThreadLocal獲取
         */
        Connection conn = threadLocal.get();
        if(conn == null){
            //如果當前線程沒有連接,則從數據源中獲取一個連接,並且和當前線程綁定,即存入ThreadLocal中
            conn = dataSource.getConnection();
            threadLocal.set(conn);
        }
        //返回當前線程上的連接
        return conn;
    }

    /**
     * 解綁線程和連接
     * 無論是線程池還是連接池,調用close方法並不是關閉,而是將取出來的線程或連接還回池中
     * 而並非關閉連接或線程
     * 因此在下次使用時,我們獲取的時候還能獲取到,但是它卻不能用了(因為被close了)
     * 所以完成一次線程操作后需要解綁
     * 這也是WEB開發中需要注意的問題
     */
    public void removeConnection(){
        threadLocal.remove();
    }
}

8.事務處理管理類

package com.huhai.utils;

import java.sql.SQLException;

/**
 * 和事務管理相關的工具類
 * 包含了開啟事務,提交事務,回滾事物,釋放連接
 */
public class TransActionManager {

    private ConnectionUtil connectionUtil;

    public void setConnectionUtil(ConnectionUtil connectionUtil) {
        this.connectionUtil = connectionUtil;
    }

    //開啟事務
    public void startTransAction(){
        try {
            //關閉自動提交,設置為手動提交,以保證事務處理的正確性
            connectionUtil.getThreadConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //提交事務
    public void commitTransAction(){
        try {
            connectionUtil.getThreadConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //回滾事物
    public void rollBackTransAction(){
        try {
            connectionUtil.getThreadConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //釋放連接
    public void releaseConnection(){
        try {
            connectionUtil.getThreadConnection().close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        //解綁線程和連接
        connectionUtil.removeConnection();
    }
}

9.測試類

package com.huhai.test;

import com.huhai.domain.Account;
import com.huhai.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

/**
 * 使用Junit單元測試:測試我們的配置
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    private  IAccountService as;

    @Test
    public void testFindAll() {
        //3.執行方法
        List<Account> accounts = as.findAllAccount();
        for(Account account : accounts){
            System.out.println(account);
        }
    }

    @Test
    public void testFindOne() {
        //3.執行方法
        Account account = as.findAccountById(1);
        System.out.println(account);
    }

    @Test
    public void testSave() {
        Account account = new Account();
        account.setName("aaa");
        account.setMoney(12345f);
        //3.執行方法
        as.saveAccount(account);

    }

    @Test
    public void testUpdate() {
        //3.執行方法
        Account account = as.findAccountById(1);
        account.setMoney(23456f);
        as.updateAccount(account);
    }

    @Test
    public void testDelete() {
        //3.執行方法
        as.deleteAccount(0);
    }

    /**
     *
     * 轉出賬號名稱
     * 轉入賬號名稱
     * 轉出金額
     */
    @Test
    public void transferTest(){
        as.transfer("aaa", "bbb", 100f);
    }
}

10.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
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 配置Service -->
    <bean id="accountService" class="com.huhai.service.impl.AccountServiceImpl">
        <!-- 注入dao -->
        <property name="accountDao" ref="accountDao"></property>
        <!-- 注入事物管理器 -->
        <property name="transActionManager" ref="transActionManager"></property>
    </bean>

    <!--配置Dao對象-->
    <bean id="accountDao" class="com.huhai.dao.impl.AccountDaoImpl">
        <!-- 注入QueryRunner -->
        <property name="runner" ref="runner"></property>
        <!-- 注入ConnectoinUtil -->
        <property name="connectionUtil" ref="connectionUtil"></property>
    </bean>

    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入數據源-->
        <!--Dao中,在執行方法的同時,給QueryRunner注入了Connection之后,會從連接中取一個-->
        <!--但是本案例並不希望它自己取,所以這里就不自動注入了-->
    </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://localhost:3306/user"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

    <!--注入connectionUtil數據-->
    <bean id="connectionUtil" class="com.huhai.utils.ConnectionUtil">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--配置事物管理器-->
    <bean id="transActionManager" class="com.huhai.utils.TransActionManager">
        <property name="connectionUtil" ref="connectionUtil"></property>
    </bean>
</beans>

11.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.huhai</groupId>
<artifactId>demo17</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>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>commons-dbutils</groupId>
        <artifactId>commons-dbutils</artifactId>
        <version>1.4</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
    </dependency>

    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>
</project>

12.數據庫

create database user;

use user;

create table account(
	id int primary key auto_increment,
	name varchar(40),
	money float
)character set utf8 collate utf8_general_ci;

insert into account(name,money) values('aaa',1000);
insert into account(name,money) values('bbb',1000);
insert into account(name,money) values('ccc',1000);

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM