SpringBoot事務註解實現原理
阿新 • • 發佈:2019-01-08
首先定義註解
@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;
};