以前做的一个项目,没用用框架,最近在学习框架,所以练手的时候就把以前的项目用框架写了一次。
这里用的是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";
}
dao
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("执行无事务业务");
}
}
结果:
/未检测到事务注解
执行无事务业务
//检测到事务注解
------------------开启事务--------------------
执行事务业务
------------------提交事务--------------------
因为用dao操作数据库还得看数据库数据变化,贴起来东西太多了,这里就模拟一下,但是它的确是好用的。
三、核心代码
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);
}
}
四、再看一下TransactionManager
先说下我的想法:
这里用的是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);
}
}
}
也是 FakeStruts实现@Transaction 注解事务控制中有的。
可以看到这两个类相互依赖,无法拆开。。。
到这里,service的事务就实现了,没有spring事务也能这么玩了。
测试用例、源码下载地址: