一、無事務處理的缺陷分析
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);