1. 程式人生 > >自定義struts(二)--FakeStruts實現@Transaction 註解事務控制

自定義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加與不加不一樣的效果,親測好使,就是耦合高了一些。

但是如果真的要用的話,我可以把耦合的地方抽取到配置檔案中來設定。

工程下載地址: