自定義struts(二)--FakeStruts實現@Transaction 註解事務控制
接著前兩篇的:
現在結合之前寫的簡單struts以及transactionManager,完成一個能夠通過@Transaction完成事務控制的功能。
我的想法是這樣的:
隨便寫個action類,裡面的方法只要加上了@Transaction註解,在裡面呼叫dao的方法,執行的就是事務的處理。如果沒加,那就正常處理。
實現原理:
1.將action中的方法進行代理,檢視註解,如果需要事務,則新增事務操作。(cglib)
2.寫一個TransactionManager,用來控制事務操作。(ThreadLocal)
2.這裡用的是mybatis持久層,所以代理了一下他的sqlSession,這個封裝在我寫的SessionFacoty類中。(Proxy)
看一下幾個核心的類:
一、過濾器(前端控制器)
核心方法
private String doAction(HttpServletRequest request, Class clazz, Method method) { Object object = this.newTransactionProxyInstance(clazz, method); this.setParameterToField(request, object); try { String returnValue = (String) method.invoke(object, null); this.setFiledToAttribute(request, object); return returnValue; } catch (Exception e) { e.printStackTrace(); } return null; }
①.建立代理物件
②.將request引數放入到物件的屬性中
③.將屬性中的值自動setAttribute到request中
④.返回方法返回值
以上主要的就是第一步,
其他的都容易,2、3步就注意一點:這裡使用了cglib代理,所以得到的物件的類getDeclaredFields得不到要的東西,要getSuperClass().getDeclaredFields才能得到想要的屬性
private Object newTransactionProxyInstance(Class clazz, Method method) { Object object = new TransactionActionFactory(clazz, method).newAction(); return object; }
二.cglib代理
package com.aii.mybatis.transaction;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
public class TransactionActionFactory {
private Class clazz;
private Method method;
public TransactionActionFactory(Class clazz, Method method) {
this.clazz = clazz;
this.method = method;
}
public Object newAction() {
Enhancer en = new Enhancer();
// 進行代理
en.setSuperclass(clazz);
en.setCallback(new TransactionCglibProxy(method));
// 生成代理例項
return en.create();
}
}
package com.aii.mybatis.transaction;
import java.lang.reflect.Method;
import java.util.logging.Logger;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import com.aii.struts.annotation.Transaction;
import com.aii.struts.transcation.TransactionManager;
public class TransactionCglibProxy implements MethodInterceptor {
private Method method = null;
TransactionCglibProxy(Method method) {
this.method = method;
}
public Object intercept(Object arg0, Method method, Object[] arg2,
MethodProxy methodProxy) throws Throwable {
System.out.println("呼叫的方法是:" + method.getName());
if (method.isAnnotationPresent(Transaction.class)) {
System.out.println("do by transaction");
TransactionManager manager = MyBatisTransactionManager
.newInstance();
manager.startTranscation();
Object result = null;
try {
result = methodProxy.invokeSuper(arg0, arg2);
manager.commit();
} catch (Exception e) {
e.printStackTrace();
manager.rollback();
}
return result;
}
System.out.println("do without transaction");
return methodProxy.invokeSuper(arg0, arg2);
}
}
其實就是做了一件事:
檢視方法上是否有@Transaction註解,如果有,則使用TransactionManager來管理其中的方法。否則正常執行。
三、TransactionManager與SessionFactory
這裡用的是mybatis持久框架,如果用其他的也差不多。
這邊的TransactionManager與SessionFactory的耦合稍微有點高。
package com.aii.mybatis.transaction;
import org.apache.ibatis.session.SqlSession;
import com.aii.struts.transcation.TransactionManager;
public class MyBatisTransactionManager implements TransactionManager {
private static MyBatisTransactionManager newInstance = new MyBatisTransactionManager();
private MyBatisTransactionManager() {
};
private ThreadLocal<SqlSession> sessions = new ThreadLocal<SqlSession>();
private ThreadLocal<Boolean> states = new ThreadLocal<Boolean>();
public static MyBatisTransactionManager newInstance() {
return newInstance;
}
public void startTranscation() {
System.out.println("------------------開啟事務--------------------");
SqlSession sqlSession = SessionFactory.getOriginalSession();
sessions.set(sqlSession);
states.set(true);
}
public void commit() {
System.out.println("------------------提交事務--------------------");
SqlSession sqlSession = sessions.get();
sqlSession.commit();
sqlSession.close();
sessions.remove();
states.remove();
}
public void rollback() {
System.out.println("------------------回滾事務--------------------");
SqlSession sqlSession = sessions.get();
sqlSession.rollback();
sqlSession.close();
sessions.remove();
states.remove();
}
Boolean getState() {
return states.get();
}
SqlSession getSession() {
return sessions.get();
}
}
package com.aii.mybatis.transaction;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class SessionFactory {
public static SqlSessionFactory factory = null;
private static final String CONFIGURATION_PATH = "configuration.xml";
static {
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(Thread.currentThread().getContextClassLoader()
.getResourceAsStream(CONFIGURATION_PATH));
}
public static SqlSession getSession() {
MyBatisTransactionManager manager = MyBatisTransactionManager
.newInstance();
Boolean state = manager.getState();
// 否則自己產生,直接返回
if (state == null || !state) {
System.out.println("未開啟事務,正常執行");
return getOriginalSession();
}
// 如果有事務處理,則從manager中拿
System.out.println("開啟事務,返回代理過的sqlSession");
return (SqlSession) Proxy.newProxyInstance(Thread.currentThread()
.getContextClassLoader(), new Class[] { SqlSession.class },
new ProxySqlSessionHandler(manager.getSession()));
}
static SqlSession getOriginalSession() {
return factory.openSession();
}
static class ProxySqlSessionHandler implements InvocationHandler {
private SqlSession sqlSession;
public ProxySqlSessionHandler(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 檢視transactionManager狀態,根據狀態確定session.commit,session.close
Boolean state = MyBatisTransactionManager.newInstance().getState();
if (method.getName().equals("commit") && state) {
return null;
}
if (method.getName().equals("close") && state) {
return null;
}
return method.invoke(sqlSession, args);
}
}
}
package com.aii.mybatis.dao;
import java.util.HashMap;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import com.aii.mybatis.transaction.SessionFactory;
import com.aii.mybatis.vo.User;
public class UserDAO {
public User getUser(String name, String password) {
SqlSession session = null;
User user = null;
try {
session = SessionFactory.getSession();
Map<String, String> map = new HashMap<String, String>();
map.put("name", name);
map.put("password", password);
user = session.selectOne("user.getUser", map);
} finally {
session.close();
}
return user;
}
public void update(User user) {
SqlSession session = null;
try {
session = SessionFactory.getSession();
session.update("user.update", user);
session.commit();
} finally {
session.close();
}
}
public void delete(int id) {
SqlSession session = null;
try {
session = SessionFactory.getSession();
session.delete("user.delete", id);
session.commit();
} finally {
session.close();
}
}
}
如果有事務:
之前代理的也看到了,首先呼叫的是manager.start()方法,這樣在threadLocals裡塞進去了2個東西,一個sqlSession一個狀態。
然後呼叫action中的方法,action方法中呼叫了dao的方法,dao獲取session的時候通過工廠獲取,工廠檢查事務狀態,這裡當然檢查到有事務,那就得到一個被代理過的sqlSession,他的commit與close方法失效了。
由manager來控制dao的提交、回滾、關閉。
如果沒有事務:
直接產生一個SqlSession返回去,dao怎麼寫就怎麼來。
四、測試:
action
package com.aii.struts.action;
import com.aii.mybatis.dao.UserDAO;
import com.aii.mybatis.vo.User;
public class UpdateAndDeleteAction {
private int id;
private String name;
private String password;
// @Transaction
public String execute() {
System.out.println("userName:" + name + "\npassowrd:" + password);
User user = new User();
user.setId(id);
user.setName(name);
user.setPassword(password);
UserDAO dao = new UserDAO();
dao.update(user);
if (true) {
throw new RuntimeException("丟擲異常,檢驗回滾");
}
dao.delete(id);
return null;
}
}
@Transaction加與不加不一樣的效果,親測好使,就是耦合高了一些。
但是如果真的要用的話,我可以把耦合的地方抽取到配置檔案中來設定。
工程下載地址: