您好,登錄后才能下訂單哦!
原生的jdbc 對事務管理也是比較繁瑣的, 需要手工進行提交和回滾, 還要一堆try-catch. 而熟悉spring 的同學都知道, spring采用了聲明式事務方式來管理事務, 使事務管理變得很簡單. Spring 事務很強大, 筆者這里僅使用jdbc 來模擬簡單的幾個屬性.
1. 聲明式事務方案設計
聲明式事務主要依據java 動態代理實現
通過將Connection 存放在ThreadLocal 變量中, 來解決并發問題. Spring 底層也是用的ThreadLocal.
通過記錄Connection 的創建者, 來解決事務的嵌套問題.
自定義注解@EnableTranscation: 用于標明方法是否開啟事務
Service工廠: 用于模擬Spring容器創建Bean過程, 如果Service 中包含使用@EnableTranscation修飾的方法, 則創建Service的代理對象, 否則返回Service 實例
自定義Dao時, 不能直接創建Connection, 需要獲取當前線程中保存的Connection.
2. 連接池管理
筆者對數據庫的連接采用c3p0 連接池.
2.1 c3po 配置
root
root
com.mysql.jdbc.Driver
jdbc:mysql://localhost:3306/learn-jdbc?characterEncoding=UTF-8
10
5
5
50
100
10
2.2 數據庫連接工具類
封裝獲取數據庫連接和關閉數據庫連接資源的方法
public class DbConnUtil {
// c3P0配置名
private static final String c3p0PoolName = "myC3p0Pool";
// 配置數據源
private static final DataSource dataSource = new ComboPooledDataSource(c3p0PoolName);
// 配置本地連接
private static ThreadLocal txConnectionLocal = new ThreadLocal<>();
/** 獲取數據庫連接
* @param autoCommitTx 是否開啟提供提交事務
* @return Connection 數據庫連接
* @since 1.0
* @author zongf
* @created 2019-07-18
*/
public static Connection getConnection(boolean autoCommitTx) {
try {
Connection connection = dataSource.getConnection();
connection.setAutoCommit(autoCommitTx);
return connection;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
/**
* @Description: 獲取本線程連接
* @return: Connection 數據庫連接
* @author: zongf
* @time: 2019-06-26 14:37:00
* @since 1.0
*/
public static TxConnection getTxConnection() {
TxConnection txConnection = null;
// 如果ThreadLocal 中連接為空, 則創建新的連接
if (txConnectionLocal.get() == null || txConnectionLocal.get().getConnection() == null) {
txConnection = new TxConnection(getConnection(true));
txConnectionLocal.set(txConnection);
} else {
txConnection = txConnectionLocal.get();
}
return txConnection;
}
/** 獲取當前線程內的數據庫連接
* @return Connection
* @since 1.0
* @author zongf
* @created 2019-07-18
*/
public static Connection getLocalConnection() {
return getTxConnection().getConnection();
}
/** 獲取當前線程的數據庫連接對象
* @return ThreadLocal
* @since 1.0
* @author zongf
* @created 2019-07-18
*/
public static ThreadLocal getLocalTxConnection() {
return txConnectionLocal;
}
/** 當歸還連接時, 需要設置自動提交事務為true.
* @param connection
* @return null
* @since 1.0
* @author zongf
* @created 2019-07-18
*/
public static void release(Connection connection) throws SQLException {
try {
if (connection != null && !connection.isClosed()) {
connection.setAutoCommit(true);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
connection.close();
}
}
}
2.3 定義事務連接對象
由于在事務嵌套時, 需要遵循哪一層動態代理開啟的事務, 由哪一層動態代理負責事務的開啟和回滾, 因此需要記錄事務的開啟者. 因此筆者創建了一個TxConnneciton 對象.
public class TxConnection {
private Connection connection;
private String creator;
// 省略setter/getter 方法
}
3. 自定義開啟事務注解
定義一個類似于spring @Transcation 的注解, 用于開啟事務.
openNewTx 用于模擬spring七種事務傳播行為之一的 Propagation.REQUIRES_NEW
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableTranscation {
// 是否開啟新的事務
boolean openNewTx() default false;
}
4. 動態代理處理器
/**事務動態代理處理器
* @since 1.0
* @author zongf
* @created 2019-07-18
*/
public class TranscationHandler implements InvocationHandler {
private Object target;
public TranscationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 獲取當前數據庫連接
TxConnection txConnection = DbConnUtil.getTxConnection();
// 保存老的連接對象
TxConnection oldTxConnection = null;
try {
// 獲取目標對象方法
Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
// 看當前方法是否開啟了事務
boolean enableTx = targetMethod.isAnnotationPresent(EnableTranscation.class);
// 如果開啟事務, 則設置當前連接為手動提交事務
if (enableTx) {
// 獲取注解信息
EnableTranscation annotation = targetMethod.getAnnotation(EnableTranscation.class);
// 獲取是否開啟新事務
boolean openNewTx = annotation.openNewTx();
if (!txConnection.getConnection().getAutoCommit()) { //為false, 表示已經開啟了事務
if (openNewTx) { // 如果需要開啟的事務
// 保存原數據庫連接
oldTxConnection = txConnection;
// 獲取新的連接
txConnection = new TxConnection(DbConnUtil.getConnection(false), this.toString());
// 替換當前線程中的數據庫連接
DbConnUtil.getLocalTxConnection().set(txConnection);
}
} else { // 為true, 表示未開啟事務
// 沒有開啟事務, 設置自動提交為false. 表示已經開始了事務
txConnection.getConnection().setAutoCommit(false);
txConnection.setCreator(this.toString());
}
}
// 執行目標方法
Object object = targetMethod.invoke(this.target, args);
// 如果事務是當前handler對象創建, 那么提交事務
if (this.toString().equals(txConnection.getCreator())) {
txConnection.getConnection().commit();
}
return object;
} catch (Exception e) {
if (txConnection != null && this.toString().equals(txConnection.getCreator())) {
if (txConnection.getConnection() != null && !txConnection.getConnection().isClosed()) {
txConnection.getConnection().rollback();
txConnection.getConnection().setAutoCommit(true);
}
}
throw new RuntimeException("發生異常, 事務已回滾!", e);
} finally {
// 釋放數據庫連接
if (txConnection != null && this.toString().equals(txConnection.getCreator())) {
DbConnUtil.release(txConnection.getConnection());
}
// 如果新連接不為null, 則表示開啟了新事務. 則回滾原連接
if (oldTxConnection != null) {
DbConnUtil.getLocalTxConnection().set(oldTxConnection);
}
}
}
}
5. ServiceFactory 工廠
創建Service 工廠類, 用于模擬Spring 容器. 當目標Service中包含@EnableTransaction 注解時, 創建Service 的動態代理, 否則創建Service 對象.
/** Service工廠, 模擬spring 容器
* @since 1.0
* @author zongf
* @created 2019-07-18
*/
public class ServiceFactory {
/** 獲取Service 實例
* @param clz Service 實現類類型
* @return T Service 對象或動態代理對象
* @since 1.0
* @author zongf
* @created 2019-07-18
*/
public static T getService(Class clz) {
T t = null;
try {
t = clz.newInstance();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("創建對象失敗");
}
// 判斷不能是接口, 接口不能創建實現類
if(clz.isInterface()){
throw new RuntimeException("接口不能創建實例!");
}
// 是否開啟動態代理
boolean enableTx = false;
// 遍歷所有非私有方法, 如果方法有@EnableTx注解, 則說明需要創建代理
Method[] methods = clz.getMethods();
for (Method method : methods) {
if (method.getAnnotation(EnableTranscation.class) != null) {
enableTx = true;
break;
}
}
// 如果需要創建代理, 則返回代理對象
if (enableTx) {
return (T) Proxy.newProxyInstance(clz.getClassLoader(), clz.getInterfaces(), new TranscationHandler(t));
}
return t;
}
}
6. 聲明式事務測試
測試用例, 筆者借助于之前寫的BaseDao來簡化基本步驟的開發.
1.1 定義接口
public interface IMixService {
// 模擬正常
void success();
// 模擬異常操作, 事務回滾
void error();
void show();
}
6.2 定義實現類
public class MixService implements IMixService {
private IUserService userService = ServiceFactory.getService(UserService.class);
private IPersonService personService = ServiceFactory.getService(PersonService.class);
@EnableTranscation
@Override
public void success() {
this.userService.save(new UserPO("user-01", "123456"));
this.personService.save(new PersonPO("person-01", "abcdefg"));
}無錫婦科醫院 http://www.bhnnk120.com/
@EnableTranscation
@Override
public void error() {
this.userService.save(new UserPO("user-01", "123456"));
this.personService.save(new PersonPO("person-01", "abcdefg"));
// 模擬異常會館
int a = 1/0;
}
@Override
public void show() {
List userPOS = this.userService.queryAll();
List personPOS = this.personService.queryAll();
System.out.println("\n****** t_user: *****");
userPOS.forEach(System.out::println);
System.out.println("\n****** t_person: *****");
personPOS.forEach(System.out::println);
}
}
6.3 測試用例
public class MixService implements IMixService {
private IUserService userService = ServiceFactory.getService(UserService.class);
private IPersonService personService = ServiceFactory.getService(PersonService.class);
@EnableTranscation
@Override
public void success() {
this.userService.save(new UserPO("user-01", "123456"));
this.personService.save(new PersonPO("person-01", "abcdefg"));
}
@EnableTranscation
@Override
public void error() {
this.userService.save(new UserPO("user-01", "123456"));
this.personService.save(new PersonPO("person-01", "abcdefg"));
// 模擬異常會館
int a = 1/0;
}
@Override
public void show() {
List userPOS = this.userService.queryAll();
List personPOS = this.personService.queryAll();
System.out.println("\n****** t_user: *****");
userPOS.forEach(System.out::println);
System.out.println("\n****** t_person: *****");
personPOS.forEach(System.out::println);
}
}
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。