1. 程式人生 > >SpringBoot事務註解實現原理

SpringBoot事務註解實現原理

首先定義註解

@Retention(RetentionPolicy.RUNTIME)//讓註解保留到執行時期
@Target(ElementType.METHOD)//標誌這個註解是對方法的註解
public @interfase MyTransaction{
    String isolation() default "Read Committed";
    /*隔離級別
        read uncommitted:允許讀取其他會話中未進行事務提交的資料
        read committed :只能讀到提交後的資料,一般預設級別都是這個
        repeated read:同一事務中進行多次查詢都和第一次查詢的結果是一樣的,不會出現第一次讀取完成後,其他事務改變該資料,然後再讀取該資料時發現數據不一樣的情況
        serializable:每次讀資料都獲得整個表的鎖,讀寫互相阻塞。
    */
int Timeout() default 100;//超時等待 String propagation() deault "NONE"; /* PROPAGATION_REQUIRED:為每個方法對應一個邏輯事務,但他們都是對應相同的物理事務,如果其中一個設定為rollback-only 而另一個進行了commit操作就會丟擲unexpectedRollbackException PROPAGATION_REQUIRES_NEW:為每個方法對應一個物理事務,方法之間的rollback 和commit 相互獨立 這裡我也不太清楚具體會應用於什麼場景 黃老師快教教我 */
}

然後定義一個session 管理類:

public class SessionUtil{
    static ThreadLocal<SqlSession> sessionHolder = new ThreadLocal<SqlSession>();
    //處於多執行緒安全考慮利用ThreadLocal為每個執行緒建立建立屬於自己的執行緒區域性變數,每一個執行緒都可以獨立地改變自己的副本,而不會和其它執行緒的副本衝突,在ThreadLocal類中有一個Map,用於儲存每一個執行緒的變數的副本
    private static SqlSessionFactory sessionFactory = null
; //session工廠用於例項出session static{ try{ sessionFactory = getSqlSessionFactory(); }catch(IOException e){ e.printStackTrace(); } }

在類載入的時候例項化sessionFactory

public static SqlSession getSession(){
        if(sessionHolder.get()==null){
            sessionHolder.set(sessionFactory.getCurrentSession());
            /*在 SessionFactory 啟動的時候,Hibernate 會根據配置建立相應的 CurrentSessionContext ,
            在 getCurrentSession() 被呼叫的時候,實際被執行的方法是 CurrentSessionContext.currentSession()
            在 currentSession() 執行時,如果當前 Session 為空, currentSession 會呼叫 SessionFactory 的 openSession 。
            所以 getCurrentSession() 對於 Java EE 來說是更好的獲取 Session 的方法。*/
            return sessionHolder.get();
        }else{
            return sessionHolder.get();
        }
    }

    public static SqlSessionFactory getSqlSessionFactory() throws IOException{
        if(sessionFactory == null){
            String resource = "mybatis-config.xml";
            InputStream iStream = Resources.getResourceAsStream(resource);
            //根據mybatis配置檔案建立對應資料庫的io流
            sessionFactory = new SqlSessionFactoryBuilder().build(iStream);
            //根據io流建立對應的sessionFactory
        }
        return sessionFactory;
    }
    public static void removeSession(){
        if(sessionHolder.get()!=null){
            sessionHolder.remove();
        }
    }
}

此外還定義了一個代理類將被我們自己定義的@MyTransaction標記的方法抽取出來進行代理

public class TransactionalManager {
    public static DataProcessService getDataService() {
        Enhancer en = new Enhancer();
        en.setSuperclass(DataProcessService.class);//定義代理類的父類
        en.setCallback(new MethodInterceptor() {//定義對所有方法的都使用的回撥函式
            @Override
            public Object intercept(Object arg0, Method method, Object[] args,
                    MethodProxy arg3) throws Throwable {
                if (!method.isAnnotationPresent(MyTransaction.class)) {//如果不是用我們的註解標記的方法,直接用DataProcessService類實現,
                    return method.invoke(new DataProcessService(), args);
                }
                //否則
                SqlSession session = SessionUtil.getSession();
                //因為用了ThreadLocal 所以這裡拿到的session和session中例項出來的session會是同一個物件
                try {
                    MyTransaction annotation = method
                            .getAnnotation(MyTransaction.class);
                    Object o = method.invoke(new DataProcessService(), args); 
                    //呼叫DataProcessService類實現,如果出異常會被catch並rollback
                    session.commit(); 
                    //沒有出異常會提交結果並關閉session
                    session.close();
                    SessionUtil.removeSession(); 
                    //將這個執行緒對應的SessionHolder中的資料清除,此後這個物件就不在被引用,之後jvm會回收掉這個session物件
                    return o;
                } catch (Exception se) {
                    session.rollback();
                    return null;
                }
            }
        });
        return (DataProcessService) en.create();
    }
}

在主函式中

public class MainTest {
    public static void main(String []args){
        DataProcessService service = TransactionalManager.getDataService();//將我們的代理物件註冊進spring
        service.insertPosition();//呼叫測試類
    }
}
//在使用註解時像這樣:
@MyTransaction(Timeout=100)
    public RsPosition insertPosition(){
        SqlSession session = SessionUtil.getSession();
        RsPosition rsPosition = new RsPosition();
        rsPosition.setName("hahaha");
        int result = session.insert("RsPositionMapper.insertRsPosition",rsPosition);
        rsPosition.setId(result);
        return rsPosition;
    };