在Java中利用動態代理實現資料庫連線與事務的自動管理【轉】
AOP(Aspect Oriented Programming,面向方面程式設計)是如今比較火的概念之一,再加上Spring框架的流行,很多程式設計師更是言必稱AOP,如果對AOP不瞭解就像土老帽似的。AOP中的Aspect其實就是程式設計師關注的某一方面,如某些方法有沒有被訪問過、某些方法執行時間有多長、把某些方法的執行置於事務之下等等,具體實現方法就是在某些方法執行前後自動執行一些操作,就像攔截器一樣。其實一些框架中的攔截器功能正是通過AOP實現的。
Java反射功能中有一項就是動態代理,但代理的物件必須實現了介面,也就是Jdk目前僅支援介面的代理。其原理是首先檢索被代理物件的所有介面,然後動態生成一個實現了被代理物件介面的Class(這也是為什麼叫動態代理的原因),最後把這個Class的一個例項返回。因為通過Java動態代理之後,您所使用的物件就像狸貓換太子一樣被掉包了,執行一個方法時,其實是執行的動態生成例項的方法,裡面會有一個地方呼叫原物件的方法,從而達到在原物件方法執行前後執行特定程式碼的目的。在Spring中,利用了cglib,如果被代理物件實現了介面,就用Java的動態代理,如果僅僅是一個沒有介面的類,則用cglib中繼承的方式進行代理。
在Spring中有用Java註解(annotation)的方法實現事務管理的功能,也就是宣告式事務,去除了程式碼中繁瑣的事務控制程式碼。在本文中,利用Java的annotation、dynamic proxy實現一個簡單的管理資料庫連線與事務的框架,而不使用任何現成的框架。
首先是一個獲取資料庫連線的工具類,此處是使用的是mysql資料庫,執行時需要mysql的jdbc驅動包。程式碼如下:
package demo.dynamicproxy;
import java.sql.Connection;
import java.sql.DriverManager;
public class DbUtil {
/**
* 獲取資料庫連線,如果使用其他資料庫,修改這裡就可以了。
* @return
*/
public static Connection getConnection() {
Connection conn = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/st", "root", "");
}
catch(Exception e) {
e.printStackTrace();
}
return conn;
}
}
對於需要資料庫操作的業務類,需要實現IDb介面:
package demo.dynamicproxy;
import java.sql.Connection;
public interface IDb {
/**
* 設定資料庫連線
* @param conn
*/
void setConnection(Connection conn);
/**
* 獲取資料庫連線。
* 如果沒有設定返回null。
* @return
* @throws Exception
*/
Connection getConnection();
/**
* 釋放資料庫連線
* @param conn
* @throws Exception
*/
void closeConnection();
}
為了使用方便,建立一個IDb介面的預設實現類:
package demo.dynamicproxy;
import java.sql.Connection;
import java.sql.Statement;
public class DbImpl implements IDb {
private Connection conn;
@Override
public void closeConnection() {
try {
if (conn != null) conn.close();
conn = null;
System.out.println("close conn");
}
catch(Exception e) {
e.printStackTrace();
}
}
@Override
public Connection getConnection() {
return conn;
}
@Override
public void setConnection(Connection conn) {
this.conn = conn;
System.out.println("set conn");
}
protected int executeNonQuery(String sql) throws Exception {
System.out.println("executeNonQuery begin:" + sql);
Statement stmt = null;
int result = 0;
try {
stmt = getConnection().createStatement();
result = stmt.executeUpdate(sql);
}
catch(Exception e) {
throw e;
}
finally {
if (stmt != null) stmt.close();
}
System.out.println("executeNonQuery complete:" + sql);
return result;
}
}
對於需要資料庫連線和事務的方法,加上一個註解就可以了,這就是那個註解:
package demo.dynamicproxy.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 被註解的方法可以自動獲取資料庫連線並開啟事務。
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Transactional {
boolean autoCommit() default false;
}
被代理方法執行時的處理器,動態代理的核心就在這裡:
package demo.dynamicproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import demo.dynamicproxy.annotation.Transactional;
/**
* 自動處理資料庫連線、事務的呼叫處理器
*/
public class TransactionalInvocationHandler implements InvocationHandler {
private Object target;
private IDb db;
/**
* 構造方法
* @param target
*/
public TransactionalInvocationHandler(Object target) {
this.target = target;
this.db = (IDb) target;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("method invoke begin:" + method.getName());
Object result = null;
boolean isNeedTransaction = false;
boolean isAutoCommit = false;
if (method.isAnnotationPresent(Transactional.class)) {
isNeedTransaction = true;
isAutoCommit = method.getAnnotation(Transactional.class).autoCommit();
}
boolean isLocalOpen = false;
boolean rollback = false;
Connection conn = db.getConnection();
System.out.println("isNeedTransaction:" + isNeedTransaction);
System.out.println("isAutoCommit:" + isAutoCommit);
System.out.println("isLocalOpen:" + isLocalOpen);
try {
if (isNeedTransaction) {
if (conn == null) {
isLocalOpen = true;
try {
conn = DbUtil.getConnection();
if (conn == null) throw new Exception("資料庫連線獲取錯誤");
conn.setAutoCommit(isAutoCommit);
}
catch (Exception e) {
throw e;
}
db.setConnection(conn);
}
}
result = method.invoke(this.target, args);
}
catch(Exception e) {
rollback = true;
e.printStackTrace();
throw e;
}
finally {
if (conn != null && isLocalOpen) {
if (!isAutoCommit) {
if (rollback) conn.rollback();
else conn.commit();
}
db.closeConnection();
}
}
System.out.println("method invoke complete:" + method.getName());
return result;
}
}
獲取代理物件的工廠類:
package demo.dynamicproxy;
import java.lang.reflect.Proxy;
public class DefaultProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target) throws Exception {
if (!(target instanceof IDb)) throw new Exception("target must be instance of IDb");
ClassLoader classLoader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
TransactionalInvocationHandler handler = new TransactionalInvocationHandler(target);
return (T) Proxy.newProxyInstance(classLoader, interfaces, handler);
}
}
至此,一個簡單的框架就完成了。為了測試是否好用,做一個簡單的例子,每個訪問資料庫的業務類都需要一個介面:
package demo.business;
import demo.dynamicproxy.annotation.Transactional;
public interface IDml {
void insert() throws Exception;
void update() throws Exception;
void delete() throws Exception;
void select() throws Exception;
@Transactional(autoCommit=false)
void execute() throws Exception;
}
業務類既需要實現業務介面,又需要實現IDb介面:
package demo.business;
import java.sql.ResultSet;
import java.sql.Statement;
import demo.dynamicproxy.DbImpl;
import demo.dynamicproxy.DefaultProxyFactory;
public class Dml extends DbImpl implements IDml {
@Override
public void delete() throws Exception{
executeNonQuery("delete from t where id=2");
System.out.println("delete 2");
}
@Override
public void execute() throws Exception{
System.out.println("begin");
insert();
update();
//if (true) throw new Exception("an error"); //此處測試異常
select();
delete();
}
@Override
public void insert() throws Exception{
executeNonQuery("insert into t(id) values(1)");
System.out.println("insert 1");
}
@Override
public void select() throws Exception{
Statement stmt = null;
ResultSet rs = null;
try {
stmt = getConnection().createStatement();
rs = stmt.executeQuery("select id from t");
while (rs.next()) {
System.out.println("select :" + rs.getString("ID"));
}
}
catch(Exception e) {
throw e;
}
finally {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
}
}
@Override
public void update() throws Exception{
executeNonQuery("update t set id=2 where id=1");
System.out.println("update 2");
}
public static void main(String[] args) {
try {
Dml dmlImpl = new Dml();
IDml dml= DefaultProxyFactory.createProxy(dmlImpl);
dml.execute();
Thread.sleep(20000); //延遲程式退出,可以檢視資料庫連線有沒有釋放
System.out.println("over");
}
catch(Exception e) {
e.printStackTrace();
}
}
}
main方法中是一個使用業務物件進行業務處理的例子。這樣,程式就可以執行了,在測試時可以在mysql資料庫中執行“show full processlist;”檢視資料庫連線情況。在使用時,主要注意在業務物件的介面方法中加上註解“@Transactional”,獲取的業務物件需要從工廠中獲得“IDml dml= DefaultProxyFactory.createProxy(dmlImpl);”。
這樣一個簡單的框架也許沒有什麼用,在實際專案應用時有現成的開源框架可以用,但可以學習框架實現的原理,對知識有更加深入的瞭解。
使用動態代理方法可以獲得程式碼上的簡潔,缺陷是犧牲了程式執行的效率,在對效率要求不太高的地方可以使用。
相關文章
- Java資料庫事務管理:ACID屬性的實現與應用Java資料庫
- java jdbc深入理解(connection與threadlocal與資料庫連線池和事務實)JavaJDBCthread資料庫
- Java 資料庫連線的那些事Java資料庫
- 第77節:Java中的事務和資料庫連線池和DBUtilesJava資料庫
- 利用FreeSql.Generator自動根據資料庫表動態生成實體類SQL資料庫
- Java中的靜態代理和動態代理Java
- 在Rainbond中實現資料庫結構自動化升級AI資料庫
- SAP ABAP和Java的動態代理實現Java
- 【Java】Spring和Tomcat自帶的連線池實現資料庫操作JavaSpringTomcat資料庫
- C#連線Oracle資料庫,通過EF自動生成與資料庫表相關的實體類C#Oracle資料庫
- django中的資料庫連線池實現Django資料庫
- 分散式事務之資料庫事務與JDBC事務實現(一)分散式資料庫JDBC
- FastHook——巧妙利用動態代理實現非侵入式AOPASTHook
- java基本資料型別與自動轉換Java資料型別
- 動態連結庫與靜態連結庫
- 動態連結的步驟與實現
- Java動態代理 jdk和cglib的實現比較JavaJDKCGLib
- Java代理(jdk靜態代理、動態代理和cglib動態代理)JavaJDKCGLib
- 在scrapy的pipelines中連線資料庫資料庫
- JDBC連線資料庫實現增刪改查前端互動JDBC資料庫前端
- JAVA 靜態代理 & 動態代理Java
- 資料庫連線池實現資料庫
- 在Linux中,如何管理服務的自啟動?Linux
- java動態代理Java
- 企業業務場景如何實現自動化連線?
- Java代理設計模式(Proxy)的四種具體實現:靜態代理和動態代理Java設計模式
- Java中的超程式設計與動態代理技術Java程式設計
- 聊聊如何利用apollo與druid整合實現資料來源動態熱切UI
- java動態代理動態在哪裡?Java
- 淺析Spring中AOP的實現原理——動態代理Spring
- 代理IP在廣告管理和自動化中的應用
- 資料庫連線池設計和實現(Java版本)資料庫Java
- 利用 GitHub Actions 實現自動部署靜態部落格Github
- 資料庫與動態規劃資料庫動態規劃
- 如何利用遠端桌面連線動態IPvps?
- java連線資料庫Java資料庫
- 資料庫分散式事務的實現原理!資料庫分散式
- 記在 Hyperf 中多庫連線操作事務注意事項