自己動手實現簡單的事務管理(動態代理+註解)二
阿新 • • 發佈:2019-01-22
上一篇介紹了ThreadLocalJDBCUtils(用於獲得與執行緒繫結的connection的工具類)。不熟悉的可以看下面的連結。
自己動手實現簡單的事務管理(動態代理+註解)一
動態代理不做贅述,下面的連結希望可以幫助你理解。
動態代理快速理解
我們用一個簡單的儲存使用者說明問題:
service層程式碼如下,這裡不貼UserService介面的程式碼了。
public class UserServiceImpl implements UserService {
private UserDao dao = new UserDaoImpl();
@Override
public void saveUser(User user) {
dao.saveUser(user);
}
}
User實體類如下
public class User {
private String name;
private Integer age;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}' ;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
動態代理管理事務類
public class TransactionProxyManager {
public Object proxyFor(Object object){
/*
三個引數:
1. 類載入器:真實物件.getClass().getClassLoader()
2. 介面陣列:真實物件.getClass().getInterfaces()
3. 處理器:new InvocationHandler()
*/
return Proxy.newProxyInstance(object.getClass().getClassLoader(),
object.getClass().getInterfaces(), new InvocationHandler() {
/*
1. proxy:代理物件
2. method:代理物件的方法,被封裝成為物件
3. args:代理物件呼叫的方法時,傳遞的實際引數
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果沒有被註解的話,直接執行方法,不進行事務管理。
Method originalMethod = object.getClass().getMethod(method.getName(), method.getParameterTypes());
if(!originalMethod.isAnnotationPresent(MyTransactional.class)){
return method.invoke(object, args);
}
//開啟事務
ThreadLocalJDBCUtils.beginTransaction();
System.out.println("開啟事務");
Object result = null;
try {
//順利的話就提交事務
result = method.invoke(object, args);
ThreadLocalJDBCUtils.commit();
System.out.println("提交事務");
}catch (Exception e){
//出錯就回滾事務
ThreadLocalJDBCUtils.rollback();
System.out.println("回滾事務");
}finally {
//釋放資源
ThreadLocalJDBCUtils.close();
System.out.println("釋放資源");
}
return result;
}
});
}
}
註解程式碼如下,指定使用在方法上.。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTransactional {
}
測試程式碼:
public class UserTest {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
TransactionProxyManager manager = new TransactionProxyManager();
UserService proxyUserService = (UserService) manager.proxyFor(userService);
User user = new User();
user.setName("東西");
user.setAge(10);
proxyUserService.saveUser(user);
}
}
service程式碼處理異常,在方法上加上自定義註解
public class UserServiceImpl implements UserService {
private UserDao dao = new UserDaoImpl();
@Override
@MyTransactional
public void saveUser(User user) {
try {
dao.saveUser(user);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
dao層程式碼
public class UserDaoImpl implements UserDao {
@Override
public void saveUser(User user) throws SQLException {
QueryRunner qr = new QueryRunner();
String sql = "insert into users values (?,?)";
Integer age = user.getAge();
String name = user.getName();
int row = qr.update(ThreadLocalJDBCUtils.getCurrentConnection(), sql, age, name);
}
}
執行結果如下:
檢視資料庫有沒有更新:
當我們取消在方法上的自定義註解@MyTransactional,執行UserTest。
可見事務並沒有進行處理,但是資料庫又插入了一條資料。我們知道查詢是不用進行事務處理的,在我們處理查詢方法時,就可以不加自定義的註解,從而不對其進行事務管理。
下面我們在UserServiceImpl加入int i = 1/0;使其報異常
public class UserServiceImpl implements UserService {
private UserDao dao = new UserDaoImpl();
@Override
@MyTransactional
public void saveUser(User user) {
try {
dao.saveUser(user);
int i = 1/0;
} catch (SQLException e) {
e.printStackTrace();
}
}
}
執行結果:
且資料沒有插入。
這裡提一個踩到的巨坑。如果回滾事務後仍然插入了資料,那麼大概率是資料庫引擎問題。之前遇到過這個問題,看了一下表的引擎是MyISAM,好像不支援事務,改成InnoDB就好了。