以前做的一个项目,没用用框架,最近在学习框架,所以练手的时候就把以前的项目用框架写了一次。
这里用的是mybatis与struts2框架。没用到spring(因为不会)。
在处理事务的时候出现了一些问题:
action
public class PendingDocAction { private List<CheckedAccount> checkedAccounts = new ArrayList<CheckedAccount>(); public String submitCheckDoc() { PendingDocDAO dao = DAOFactory.getDAO(PendingDocDAOImpl.class); dao.updateCheckedAccounts(checkedAccounts); dao.updatePendingDoc(pendingDocId); return "index"; }
public class PendingDocDAOImpl implements PendingDocDAO { public void updateCheckedAccounts(List<CheckedAccount> checkedAccounts) { SqlSession session = SessionFactory.getSession(); session.update("PendingDoc.submitCheckedAccount", checkedAccounts); session.commit(); session.close(); } public void updatePendingDoc(Integer pendingDocId) { SqlSession session = SessionFactory.getSession(); session.update("PendingDoc.updatePendingDoc", pendingDocId); session.commit(); session.close(); }所以这里就出现了问题,dao写好了,两步独立的操作,事务无法控制了。
主要原因还是这个action不归我管 ,所以要做事务就得改dao,这当然不是我想要的。
我的目标是dao不动,一个操作对应一个方法。
然后再另外想办法。。。。。
真是因为这里碰到的问题,加上对struts实现原理的好奇,所以有了在前几篇中提到的自己写个简单功能的struts。
的确,简单的struts功能完成了,action的事务也实现了。
现在要做的就是将action中的事务移到service中,这样在不是自己写的struts2中使用service来控制事务。
一、方法:
把之前产生action对象的方法套在产生service方法上。
检测service中的方法:
如果无transaction注解:正常执行
如果有……………:代理改方法,将原先的方法移到TransactionManager的控制之下。
因为service和action一样都是没有接口的,所以还是用cglib
二、先看效果:
action
public class TestAction { public String execute() { TestService service = ServiceManager.getService(TestService.class); service.doSth1(); service.doSth2(); return null; } }
service
public class TestService { @Transaction public void doSth1() { System.out.println("执行事务业务"); } public void doSth2() { System.out.println("执行无事务业务"); } }
/////////未检测到事务注解 执行无事务业务 //////////检测到事务注解 ------------------开启事务-------------------- 执行事务业务 ------------------提交事务--------------------
三、核心代码
service获取源:serviceManager
public class ServiceManager { /** * 通过该方法得到写好的service对象。 * */ @SuppressWarnings("unchecked") public static <T> T getService(Class<T> clazz) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallback(new ServiceTransactionInterceptor()); return (T) enhancer.create(); } }核心方法代理类:ServiceTransactionInterceptor
public class ServiceTransactionInterceptor implements MethodInterceptor { public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { if (method.isAnnotationPresent(Transaction.class)) { System.out.println("//////////检测到事务注解"); TransactionManager manager = TransactionManager.getInstance(); manager.startTranscation(); Object result = null; try { result = methodProxy.invokeSuper(obj, args); manager.commit(); } catch (Exception e) { manager.rollback(); } return result; } System.out.println("/////////未检测到事务注解"); return methodProxy.invokeSuper(obj, args); } }
先说下我的想法:
这里用的是mybatis数据库,dao就与mybatis相关了,要实现mybatis的事务,我就写了mybatis的事务管理器MyBatisTransactionManager,mybatis用的是sqlSession,这里我也写好了SessionFactory,里面的sqlSession也是被做过手脚的。
总之就是MyBatisTransactionManager与SessionFactory的耦合性有点高,所以就放一起写好了。
等以后要是用其他持久层框架的话,那就再写一个XXXTransactionManager以及SessionFactory。
所以在工程的classPath上就有一个配置文件,里面设置TransactionManager的类名,可以自定义TransactionManager,然后去修改配置文件。
serviceSupport.properties
TransactionManager=com.aii.struts.service.transaction.mybatis.MyBatisTransactionManager
因为考虑到通用性,所以这里就用TransactionManager抽象类,具体的实例去配置中读(没读到就默认我写好的这个)。
在jdk8中接口中就能使用带static或者default的已实现方法,但是为了通用,就不用接口了。
TransactionManager
public abstract class TransactionManager { public abstract void startTranscation(); public abstract void commit(); public abstract void rollback(); @SuppressWarnings("rawtypes") public static TransactionManager getInstance() { String className = PropertiesReader.read("TransactionManager"); if (className == null) { className = "com.aii.struts.service.transaction.mybatis.MyBatisTransactionManager"; } try { Class clazz = Class.forName(className); Field instance = clazz.getDeclaredField("manager"); instance.setAccessible(true); return (TransactionManager) instance.get(null); } catch (NoSuchFieldException e) { throw new RuntimeException( "there should a manager filed in manager"); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } }对了,这个transaction中得有一个属性叫做manager,不然我去new实例不好把。
具体的实现类:
public class MyBatisTransactionManager extends TransactionManager{ private static MyBatisTransactionManager manager = new MyBatisTransactionManager(); private MyBatisTransactionManager() { }; private ThreadLocal<SqlSession> sessions = new ThreadLocal<SqlSession>(); private ThreadLocal<Boolean> states = new ThreadLocal<Boolean>(); static MyBatisTransactionManager get(){ return manager; } 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(); } }这在 FakeStruts实现@Transaction 注解事务控制中也有,基本没变。
五、SessionFactory
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.get(); 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.get().getState(); if (method.getName().equals("commit") && state) { return null; } if (method.getName().equals("close") && state) { return null; } return method.invoke(sqlSession, args); } } }
可以看到这两个类相互依赖,无法拆开。。。
到这里,service的事务就实现了,没有spring事务也能这么玩了。
测试用例、源码下载地址:
点击打开链接