本教程原始碼請訪問:tutorial_demo
上一篇教程我們使用純注解方式結合Apache Commons DbUtils實作單表的CRUD操作,但是這篇教程里面的操作的是不支持事務的,在這片教程里我們根據現有的知識,將其改成支持事務的版本,為后續學習做準備,
一、轉賬操作問題分析
接下來我們實作一個轉賬操作,分析一下問題存在的問題,
1.1、在業務層介面IAccountService中添加相應的方法
//新增加的轉賬方法
void transfer(Integer srcId, Integer dstId, Float money);
1.2、在業務層實作類AccountServiceImpl中實作新添加的方法
//轉賬操作
@Override
public void transfer(Integer srcId, Integer dstId, Float money) {
//根據Id查詢需要轉賬的用戶
Account src = https://www.cnblogs.com/codeaction/p/accountDao.findById(srcId);
Account dst = accountDao.findById(dstId);
if(src == null) {
throw new RuntimeException("轉出用戶不存在");
}
if(dst == null) {
throw new RuntimeException("轉入用戶不存在");
}
if(src.getMoney() < money) {
throw new RuntimeException("轉出賬戶余額不足");
}
//一個錢減少,一個錢增加
src.setMoney(src.getMoney() - money);
dst.setMoney(dst.getMoney() + money);
//在資料庫中更新
accountDao.update(src);
accountDao.update(dst);
}
1.3、在測驗類中添加測驗方法
@Test
public void testTrans() {
accountService.transfer(1, 2, 10F);
}
運行測驗方法,測驗成功,似乎沒有問題,
1.4、Service層轉賬方法修改
//轉賬操作
@Override
public void transfer(Integer srcId, Integer dstId, Float money) {
//根據Id查詢需要轉賬的用戶
Account src = https://www.cnblogs.com/codeaction/p/accountDao.findById(srcId);
Account dst = accountDao.findById(dstId);
if(src == null) {
throw new RuntimeException("轉出用戶不存在");
}
if(dst == null) {
throw new RuntimeException("轉入用戶不存在");
}
if(src.getMoney() < money) {
throw new RuntimeException("轉出賬戶余額不足");
}
//一個錢減少,一個錢增加
src.setMoney(src.getMoney() - money);
dst.setMoney(dst.getMoney() + money);
//在資料庫中更新
accountDao.update(src);
//新增加的內容,人為制造一個例外
int i = 100/0;
//在資料庫中更新
accountDao.update(dst);
}
運行測驗方法,程式出現例外,但是src在資料庫中錢減少,
轉賬流程分析:
- 轉賬流程大的原則是源賬戶錢減少,目的賬戶錢增加;
- 源賬戶錢減少,目的賬戶錢增加的操作要么同時成功,要么同時失敗;
- 為了滿足同時成功,同時失敗應該將相關操作包裹在同一個事務當中;
- 同一個事務操作應該使用同一個連接(Connection),
目前存在的問題:當前轉賬流程沒有開啟事務,所有操作使用獨立的連接,
二、代碼升級
根據前面的分析,我們要對工程進行修改,使其支持事務,應該滿足的原則如下:
- 在Service層處理事務(開啟事務,提交,回滾);
- Dao層只負責資料庫操作不負責業務,也就是說每個Dao操作只進行最細粒度的CRUD操作;
- Dao層不處理例外,出現的例外拋給Service層處理,
2.1、創建支持事務的工具類
package org.codeaction.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@Component
public class JdbcUtils {
private static DataSource dataSource;
//用來保存當前執行緒的連接
private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
//獲取連接池物件
public static DataSource getDataSource() {
return dataSource;
}
//這個位置注意
@Autowired
public void setDataSource(DataSource dataSource) {
JdbcUtils.dataSource = dataSource;
}
//獲取連接
public static Connection getConnection() throws SQLException {
System.out.println(dataSource);
Connection conn = tl.get();
if(conn == null) {
return dataSource.getConnection();
}
return conn;
}
//開啟事務
public static void beginTransaction() throws SQLException {
Connection conn = tl.get();
if(conn != null) {
throw new SQLException("已經開啟事務,不能重復開啟");
}
conn = getConnection();
conn.setAutoCommit(false);
tl.set(conn);
}
//提交事務
public static void commitTransaction() throws SQLException {
Connection conn = tl.get();
if(conn == null) {
throw new SQLException("連接為空,不能提交事務");
}
conn.commit();
conn.close();
tl.remove();
}
//回滾事務
public static void rollbackTransaction() throws SQLException {
Connection conn = tl.get();
if (conn == null) {
throw new SQLException("連接為空,不能回滾事務");
}
conn.rollback();
conn.close();
tl.remove();
}
}
說明:
- 這個工程使用了ThreadLocal,保證能夠在多執行緒環境下使用;
- dataSource屬性沒有在它上面使用@AutoWired,而是在它的set方法上使用了@AutoWired,原因是因為dataSource是static型別,static型別變數創建早于Spring容器的創建,類加載器加載靜態變數時,Spring背景關系尚未加載,所以類加載器不會在bean中正確注入靜態類,并且會失敗,
2.2、修改Dao的實作類
package org.codeaction.dao.impl;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.codeaction.dao.IAccountDao;
import org.codeaction.domain.Account;
import org.codeaction.util.JdbcUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner queryRunner;
public void setQueryRunner(QueryRunner queryRunner) {
this.queryRunner = queryRunner;
}
@Override
public List<Account> findAll() throws SQLException {
Connection conn = JdbcUtils.getConnection();
String sql = "select * from account";
List<Account> list = queryRunner.query(conn, sql, new BeanListHandler<Account>(Account.class));
return list;
}
@Override
public Account findById(Integer id) throws SQLException {
Connection conn = JdbcUtils.getConnection();
String sql = "select * from account where id = ?";
Account account = queryRunner.query(conn, sql, new BeanHandler<Account>(Account.class), id);
return account;
}
@Override
public void save(Account account) throws SQLException {
Object[] params = {account.getName(), account.getMoney()};
Connection conn = JdbcUtils.getConnection();
String sql = "insert into account(name, money) values(?, ?)";
queryRunner.update(conn, sql, params);
}
@Override
public void update(Account account) throws SQLException {
Object[] params = {account.getName(), account.getMoney(), account.getId()};
Connection conn = JdbcUtils.getConnection();
String sql = "update account set name=?, money=? where id=?";
queryRunner.update(conn, sql, params);
}
@Override
public void delete(Integer id) throws SQLException {
Object[] params = {id};
Connection conn = JdbcUtils.getConnection();
String sql = "delete from account where id=?";
queryRunner.update(conn, sql, id);
}
}
2.3、修改Service層實作類
package org.codeaction.service.impl;
import org.codeaction.dao.IAccountDao;
import org.codeaction.domain.Account;
import org.codeaction.service.IAccountService;
import org.codeaction.util.JdbcUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.sql.SQLException;
import java.util.List;
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public List<Account> findAll() {
List<Account> list = null;
try {
//1.開啟事務
JdbcUtils.beginTransaction();
//2.操作
list = accountDao.findAll();
//3.提交事務
JdbcUtils.commitTransaction();
} catch (Exception e) {
e.printStackTrace();
try {
//回滾事務
JdbcUtils.rollbackTransaction();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
return list;
}
@Override
public Account findById(Integer id) {
Account account = null;
try {
//1.開啟事務
JdbcUtils.beginTransaction();
//2.操作
account = accountDao.findById(id);
//3.提交事務
JdbcUtils.commitTransaction();
} catch (Exception e) {
e.printStackTrace();
try {
//回滾事務
JdbcUtils.rollbackTransaction();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
return account;
}
@Override
public void save(Account account) {
try {
//1.開啟事務
JdbcUtils.beginTransaction();
//2.操作
accountDao.save(account);
//3.提交事務
JdbcUtils.commitTransaction();
} catch (Exception e) {
e.printStackTrace();
try {
//回滾事務
JdbcUtils.rollbackTransaction();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
}
@Override
public void update(Account account) {
try {
//1.開啟事務
JdbcUtils.beginTransaction();
//2.操作
accountDao.update(account);
//3.提交事務
JdbcUtils.commitTransaction();
} catch (Exception e) {
e.printStackTrace();
try {
//回滾事務
JdbcUtils.rollbackTransaction();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
}
@Override
public void delete(Integer id) {
try {
//1.開啟事務
JdbcUtils.beginTransaction();
//2.操作
accountDao.delete(id);
//3.提交事務
JdbcUtils.commitTransaction();
} catch (Exception e) {
e.printStackTrace();
try {
//回滾事務
JdbcUtils.rollbackTransaction();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
}
@Override
public void transfer(Integer srcId, Integer dstId, Float money) {
try {
//1.開啟事務
JdbcUtils.beginTransaction();
//2.操作
Account src = https://www.cnblogs.com/codeaction/p/accountDao.findById(srcId);
Account dst = accountDao.findById(dstId);
if(src == null) {
throw new RuntimeException("轉出用戶不存在");
}
if(dst == null) {
throw new RuntimeException("轉入用戶不存在");
}
if(src.getMoney() < money) {
throw new RuntimeException("轉出賬戶余額不足");
}
src.setMoney(src.getMoney() - money);
dst.setMoney(dst.getMoney() + money);
accountDao.update(src);
//int x = 1/0;//注意這里
accountDao.update(dst);
//3.提交事務
JdbcUtils.commitTransaction();
} catch (Exception e) {
e.printStackTrace();
try {
//回滾事務
JdbcUtils.rollbackTransaction();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
}
}
2.4、修改JdbcConfig配置類
package org.codeaction.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.codeaction.util.JdbcUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class JdbcConfig {
@Value("${jdbc.driverClass}")
private String driverClass;
@Value("${jdbc.jdbcUrl}")
private String jdbcUrl;
@Value("${jdbc.user}")
private String user;
@Value("${jdbc.password}")
private String password;
@Bean("queryRunner")
public QueryRunner queryRunner() {
return new QueryRunner();
}
@Bean("dataSource")
public DataSource dataSource() {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
try {
dataSource.setDriverClass(driverClass);
dataSource.setJdbcUrl(jdbcUrl);
dataSource.setUser(user);
dataSource.setPassword(password);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
}
2.5、運行測驗方法
@Test
public void testTrans() {
accountService.transfer(1, 2, 10F);
}
運行該測驗方法轉裝能夠成功;
取消2.4中int i = 1/0;的注釋,運行測驗方法,出現例外,能夠正常回滾,
2.6、目前代碼存在的問題
目前的代碼已經能夠實作對事務的支持了,但是仍然存在一些問題,問題如下:
- 代碼過于繁瑣,Service層實作類相比于上一個版本太繁瑣了,除了要負責業務還要負責事務的操作;
- 如果JdbcUtils類中的和事務操作相關的方法有一個修改,那么Service層代碼相應的呼叫位置都要修改,如果專案很龐大,對于開發人員來說這簡直就是噩夢,
后面的幾節我們會深入的學習Spring AOP,從而解決上述問題,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/178302.html
標籤:Java
