1. 程式人生 > >自己動手實現簡單的事務管理(動態代理+註解)二

自己動手實現簡單的事務管理(動態代理+註解)二

上一篇介紹了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就好了。