Java中银行转账的一些问题
寫在前面,
一個經典的例子
銀行轉賬
????測試方法執行后a賬戶將會減少100元,b會增加100元。這是正常的操作然而如果在代碼中
int i=1/0; //模擬轉賬異常這一句代碼放開注釋,模擬轉賬異常,結果發現
a減少了100元但b賬戶的金額沒有變化。
原因
因為這里面的四個事務與數據庫的操作沒有封裝到一個事務,或者一個連接,一次執行中。所以報錯,這是存在非常大的安全隱患。而是4個連接,沒有放到一個連接中。
第一種解決辦法
用一個ThreadLocal對象把Connection和當前線程綁定,從而一個線程只有一個連接。
事務的控制其實都應該在業務層。
編寫一個連接的工具類,它用于從數據源中獲取一個連接,并且實現和線程的綁定。
接下來就是對事務進行操作,要把事務設為手動開啟
package com.itheima.utils;/*** 和事務管理相關的工具類,它包含了,開啟事務,提交事務,回滾事務和釋放連接*/ public class TransactionManager {private ConnectionUtils connectionUtils;public void setConnectionUtils(ConnectionUtils connectionUtils) {this.connectionUtils = connectionUtils;}/*** 開啟事務*/public void beginTransaction(){try {connectionUtils.getThreadConnection().setAutoCommit(false);}catch (Exception e){e.printStackTrace();}}/*** 提交事務*/public void commit(){try {connectionUtils.getThreadConnection().commit();}catch (Exception e){e.printStackTrace();}}/*** 回滾事務*/public void rollback(){try {connectionUtils.getThreadConnection().rollback();}catch (Exception e){e.printStackTrace();}}/*** 釋放連接*/public void release(){try {connectionUtils.getThreadConnection().close();//還回連接池中connectionUtils.removeConnection();}catch (Exception e){e.printStackTrace();}} }這樣寫完,因為事務都在業務層操作,所以之前的代碼就改為
package com.itheima.service.impl;import com.itheima.dao.IAccountDao; import com.itheima.domain.Account; import com.itheima.service.IAccountService; import com.itheima.utils.TransactionManager;import java.util.List;/*** 賬戶的業務層實現類** 事務控制應該都是在業務層*/ public class AccountServiceImpl_OLD implements IAccountService{private IAccountDao accountDao;//事務工具類private TransactionManager txManager;public void setTxManager(TransactionManager txManager) {this.txManager = txManager;}public void setAccountDao(IAccountDao accountDao) {this.accountDao = accountDao;}// @Overridepublic List<Account> findAllAccount() {try {//1.開啟事務txManager.beginTransaction();//2.執行操作List<Account> accounts = accountDao.findAllAccount();//3.提交事務txManager.commit();//4.返回結果return accounts;}catch (Exception e){//5.回滾操作txManager.rollback();throw new RuntimeException(e);}finally {//6.釋放連接txManager.release();}}// @Overridepublic Account findAccountById(Integer accountId) {try {//1.開啟事務txManager.beginTransaction();//2.執行操作Account account = accountDao.findAccountById(accountId);//3.提交事務txManager.commit();//4.返回結果return account;}catch (Exception e){//5.回滾操作txManager.rollback();throw new RuntimeException(e);}finally {//6.釋放連接txManager.release();}}// @Overridepublic void saveAccount(Account account) {try {//1.開啟事務txManager.beginTransaction();//2.執行操作accountDao.saveAccount(account);//3.提交事務txManager.commit();}catch (Exception e){//4.回滾操作txManager.rollback();}finally {//5.釋放連接txManager.release();}}// @Overridepublic void updateAccount(Account account) {try {//1.開啟事務txManager.beginTransaction();//2.執行操作accountDao.updateAccount(account);//3.提交事務txManager.commit();}catch (Exception e){//4.回滾操作txManager.rollback();}finally {//5.釋放連接txManager.release();}}// @Overridepublic void deleteAccount(Integer acccountId) {try {//1.開啟事務txManager.beginTransaction();//2.執行操作accountDao.deleteAccount(acccountId);//3.提交事務txManager.commit();}catch (Exception e){//4.回滾操作txManager.rollback();}finally {//5.釋放連接txManager.release();}}// @Overridepublic void transfer(String sourceName, String targetName, Float money) {try {//1.開啟事務txManager.beginTransaction();//2.執行操作//2.1根據名稱查詢轉出賬戶Account source = accountDao.findAccountByName(sourceName);//2.2根據名稱查詢轉入賬戶Account target = accountDao.findAccountByName(targetName);//2.3轉出賬戶減錢source.setMoney(source.getMoney()-money);//2.4轉入賬戶加錢target.setMoney(target.getMoney()+money);//2.5更新轉出賬戶accountDao.updateAccount(source);int i=1/0;//2.6更新轉入賬戶accountDao.updateAccount(target);//3.提交事務txManager.commit();}catch (Exception e){//4.回滾操作txManager.rollback();e.printStackTrace();}finally {//5.釋放連接txManager.release();}} }然后再進行測試,發現進行轉賬后,事務控制成功了,轉賬沒有進行。
第二種解決方法
第一種解決方法,會進行大量的配置和工具類的編寫,而且方法之間的依賴太嚴重了。用動態代理的方式來增強方法以及
動態代理的詳解請看Java動態代理介紹
這時之前的業務層那么多復雜的方法就不用寫的那么繁雜,
把方法增強一下寫到一個factory中,之后就用這個來實現,這樣代碼的編寫比較簡單,而且也實現來方法間的解耦。
但是這種方法的配置是非常復雜的,要解決這個問題就要引入Spring中的AOP概念
為什么需要Java事務?
事務控制實際上就是控制數據的安全訪問。
事務功能:
? ? ? ?主要用于處理操作量大,復雜度高的數據。比如說,在人員管理系統中,你刪除一個人員,你既需要刪除人員的基本資料,也要刪除和該人員相關的信息,如信箱,文章等等,這樣,這些數據庫操作語句就構成一個事務!
事務四大特性
? ? ? ? ?一般來說,事務是必須滿足4個條件(ACID)::原子性(Atomicity,或稱不可分割性)、一致性(Consistency)、隔離性(Isolation,又稱獨立性)、持久性(Durability)。
原子性:一個事務(transaction)中的所有操作,要么全部完成,要么全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。
一致性:在事務開始之前和事務結束以后,數據庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、串聯性以及后續數據庫可以自發性地完成預定的工作。
隔離性:數據庫允許多個并發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務并發執行時由于交叉執行而導致數據的不一致。事務隔離分為不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重復讀(repeatable read)和串行化(Serializable)。
持久性:事務處理結束后,對數據的修改就是永久的,即便系統故障也不會丟失。
事務并發處理可能引起的問題
臟讀(dirty read):一個事務讀取了另一個事務尚未提交的數據,
不可重復讀(non-repeatable read) :一個事務的操作導致另一個事務前后兩次讀取到不同的數據
幻讀(phantom read) :一個事務的操作導致另一個事務前后兩次查詢的結果數據量不同。
舉例:
?????事務A、B并發執行時,當A事務update后,B事務select讀取到A尚未提交的數據,此時A事務rollback,則B讀到的數據是無效的”臟”數據。
?????當B事務select讀取數據后,A事務update操作更改B事務select到的數據,此時B事務再次讀去該數據,發現前后兩次的數據不一樣。
?????當B事務select讀取數據后,A事務insert或delete了一條滿足A事務的select條件的記錄,此時B事務再次select,發現查詢到前次不存在的記錄(“幻影”),或者前次的某個記錄不見了。
jdbc操作事務
在JDBC中處理事務,都是通過Connection完成的,同一事務中所有的操作,都在使用同一個Connection對象.JDBC事務默認是開啟的,并且默認提交.,所以jdbc中的每一句sql語句都是一個事務
在connection類中提供了3個控制事務的方法:
(1) setAutoCommit(Boolean autoCommit):設置是否自動提交事務;
(2) commit();提交事務;
(3) rollback();撤消事務;
jdbc操作數據庫
()1(獲取JDBC連接
(2) 聲明SQL
(3) 預編譯SQL
(4) 執行SQL
(5) 處理結果集
(6) 釋放結果集
(7) 釋放Statement
(8) 提交事務
(9) 處理異常并回滾事務
(10) 釋放JDBC連接
事務回滾
回滾就是取消先前的操作
事務提交
提交事務,會將磁盤緩存中的數據寫入磁盤的數據庫中,一般數據庫是自動提交,因此修改以后數據庫就會發生變化
總結
以上是生活随笔為你收集整理的Java中银行转账的一些问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Win7纯净版系统Windows未能启动
- 下一篇: 快速上手SpyGlass——基本流程