ThreadLocal实践
2014-02-23 21:49
537 查看
无聊的话
不管是用过Hibernate还是Spring的程序员,又或者作为一名面试官,经常会提及Hibernate等框架在操作数据库时是如何做到多线程不混乱,
并且能自动提交、回滚,不需要程序员做这些麻烦的操作,极大的解放了程序员的工作量,且保持代码简洁优雅一般大家都会说,
用ThreadLocal来保存当前线程使用的SqlSession就OK了。确实是这样,但很少看到有人仔细去分析,或者自己尝试去实现这一过程
这几天一直在想实现一个简单版本的这种组件,纯粹为了实践下ThreadLocal的巧妙之处,于是有了这篇文章
关于设计的一些想法
(1)一个应用程序允许存在多个数据源,即一个或多个DataSource
(2)每个DataSource由一个数据连接工厂来处理,数据连接工厂即SqlSessionFactory
(3)正在运行的每个线程,可以操作多个数据源,那就要求实现多个数据源的一起提交与回滚
(4)针对该线程里面的每个数据源,只能分配一个SqlSession
(5)针对每个SqlSession,只能分配一个数据库连接,即一个Connection
这里面关键点就是通过【同一个线程】串联起来的,如果做到每个线程里面的每个数据源,都只有一个Sqlsession,下面看一个典型应用
那么怎么实现自动打开关闭连接、自动提交回滚事务呢,并且是涉及到多个数据源的
我的想法比较简单,在业务方法执行前,使用 Connection.setAutoCommit(false) ;成功执行后,使用 Connection.commit() 提交;执行过程中遇到异常时,使用 Connection.rollback() 回滚
那么这样一说,大家很可能就想到了使用Spring的AOP来实现,比如凡是调用 save* , update* , insert* , delete* , execute* 这种方法的
就在业务方法执行前、执行中、执行后 这3个阶段来包裹一段代码,实现自动提交与回滚
确实是这样的,不过既然是自己实现,那就不用Spring的AOP了,JDK原生的动态代理 invocationhandler 就能很好的做到
不过JDK的动态代理要求业务类必须定义接口,这不太方便,那就使用 cglib 来实现,它可以为每一个类生成动态代理,不强制要求有接口(原理就是为每个类生成一个子类)
但是我们看看上面的代码,即 userService.modifyUser(params),其实每一个业务方法里面
肯定会调用其他业务方法,或者调用其他业务类的方法,比如 employeeService.save() 方法。
如果就简单粗暴的【为每个业务类生成代理类,并为每个匹配切点的方法包裹一段代码】,那就是这种结构的代码
分析下前面提到的典型应用 userService.modifyUser(params),由于该方法又调用了 userService.updateUser()方法
那么按照上述代理类的执行过程就是这种:
===>modifyUser()
===>setAutoCommit()
===>proxy.invokeSuper(obj, args):进入了 真正的 modifyUser 方法
===>updateUser() :执行 userDao.insertUser(params) 和 userDao.updateUser(params)
===>setAutoCommit()
===>proxy.invokeSuper(obj, args)
===>commit()
===>commit()
很明显这是有问题的,出现了多次setAutoCommit和多次 commit,还能想到在异常那段也有多次rollback
特别是多次 commit 就导致整个方法调用不是事务同步的。。。因为内层方法已经提交了,外层基本出错
还怎么回滚掉内层的(不要多想Spring事务管理的传播属性,我没那么强大)
那么怎么处理呢,我的想法是把当前线程的每次方法调用,用一个栈记录下来
一进入某个方法,就push当前方法名;一调用完毕,就pop当前方法名。因为整个线程是把所有内层方法串联起来的,所以很好办了。
那么只要判断当前方法栈里面有多少个方法就行了:在第一个入口方法处调用 setAutoCommit,在第一个入口方法调用结束处调用 commit 就行了
那这个方法栈,是当前线程独有的局部变量,怎么得到呢,就是ThreadLocal来保存了
那就来看整个实现过程好了,不想啰嗦了。。。一张图+代码说明
1.创建connection包,来定义一些类,处理Connection
比如创建包:com.yli.sql.connection
2.创建session包,来定义一些类,处理Session
比如创建包:com.yli.sql.session
3.创建动态代理的包,来自动处理事务
比如创建包:com.yli.sql.transaction
好了,截止到这里,我模拟的简单自动提交、回滚就这样了,当然还是有很多bug哈
那么来测试下,我没有用Spring 的IOC,就自己模拟一个IOC好了,真的是最简单的模拟。。。
4.测试
再定义一个Dao和Servei吧
最后来一个Main方法测试了
测试表明一起提交或者某个出错就一起回滚了。。。
好吧,只是简单地应用,有人浏览到这里,看看就行了
对了我是用的mysql,在公司用db2也试了,哎,肯定是相同的结果
这个例子就依赖了 mysql5.5 和 cglib2.2的 jar包
不管是用过Hibernate还是Spring的程序员,又或者作为一名面试官,经常会提及Hibernate等框架在操作数据库时是如何做到多线程不混乱,
并且能自动提交、回滚,不需要程序员做这些麻烦的操作,极大的解放了程序员的工作量,且保持代码简洁优雅一般大家都会说,
用ThreadLocal来保存当前线程使用的SqlSession就OK了。确实是这样,但很少看到有人仔细去分析,或者自己尝试去实现这一过程
这几天一直在想实现一个简单版本的这种组件,纯粹为了实践下ThreadLocal的巧妙之处,于是有了这篇文章
关于设计的一些想法
(1)一个应用程序允许存在多个数据源,即一个或多个DataSource
(2)每个DataSource由一个数据连接工厂来处理,数据连接工厂即SqlSessionFactory
(3)正在运行的每个线程,可以操作多个数据源,那就要求实现多个数据源的一起提交与回滚
(4)针对该线程里面的每个数据源,只能分配一个SqlSession
(5)针对每个SqlSession,只能分配一个数据库连接,即一个Connection
这里面关键点就是通过【同一个线程】串联起来的,如果做到每个线程里面的每个数据源,都只有一个Sqlsession,下面看一个典型应用
package com.yli.sql.connection; import java.util.HashMap; import java.util.Map; import com.yli.sql.session.SqlSession; public class Test { public static void main(String[] args) { // 创建Dao和User的实例,通常这由Spring的IOC来完成 // 并且Dao和User在整个应用里面是单例,即singleton模式的 UserDao userDao = new UserDao(); UserService userService = new UserService(); userService.setUserDao(userDao); // 调用service的业务方法:通常在service层加事务控制 // 使用注解或者xml配置的方式:匹配粒度可以到方法名,也可以到类名等等 // 但是不需要程序员手动打开、关闭连接,也不需要手动控制事务 Map<String, Object> params = new HashMap<String, Object>(); params.put("userId", 1001); userService.modifyUser(params); } } class UserService { private UserDao userDao; public void modifyUser(Map<String, Object> params){ // ===>DB1 userDao.insertUser(params); // ===>DB2 userDao.updateUser(params); // another service method this.updateUser(params); } public void updateUser(Map<String, Object> params){ userDao.updateUser(params); } public void setUserDao(UserDao userDao) { this.userDao = userDao; } } class UserDao { // 操作数据库1 private SqlSession session1; // 操作数据库2 private SqlSession session2; public void insertUser(Map<String, Object> params){ String sql = "insert ..."; session1.insert(sql, params); } public void updateUser(Map<String, Object> params){ String sql = "update ..."; session2.insert(sql, params); } }
那么怎么实现自动打开关闭连接、自动提交回滚事务呢,并且是涉及到多个数据源的
我的想法比较简单,在业务方法执行前,使用 Connection.setAutoCommit(false) ;成功执行后,使用 Connection.commit() 提交;执行过程中遇到异常时,使用 Connection.rollback() 回滚
那么这样一说,大家很可能就想到了使用Spring的AOP来实现,比如凡是调用 save* , update* , insert* , delete* , execute* 这种方法的
就在业务方法执行前、执行中、执行后 这3个阶段来包裹一段代码,实现自动提交与回滚
确实是这样的,不过既然是自己实现,那就不用Spring的AOP了,JDK原生的动态代理 invocationhandler 就能很好的做到
不过JDK的动态代理要求业务类必须定义接口,这不太方便,那就使用 cglib 来实现,它可以为每一个类生成动态代理,不强制要求有接口(原理就是为每个类生成一个子类)
但是我们看看上面的代码,即 userService.modifyUser(params),其实每一个业务方法里面
肯定会调用其他业务方法,或者调用其他业务类的方法,比如 employeeService.save() 方法。
如果就简单粗暴的【为每个业务类生成代理类,并为每个匹配切点的方法包裹一段代码】,那就是这种结构的代码
class ProxyService implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { // 暂且不说这个 session 是如何得到的,但是前文已经提过 // 由session来获取连接 Connection // 设置要程序提交事务 session.getConnection().setAutoCommit(false); try{ Object result = proxy.invokeSuper(obj, args); }catch(SQLException e) { e.printStackTrace(); session.getConnection().rollback(); } session.getConnection().commit(); return null; } }
分析下前面提到的典型应用 userService.modifyUser(params),由于该方法又调用了 userService.updateUser()方法
那么按照上述代理类的执行过程就是这种:
===>modifyUser()
===>setAutoCommit()
===>proxy.invokeSuper(obj, args):进入了 真正的 modifyUser 方法
===>updateUser() :执行 userDao.insertUser(params) 和 userDao.updateUser(params)
===>setAutoCommit()
===>proxy.invokeSuper(obj, args)
===>commit()
===>commit()
很明显这是有问题的,出现了多次setAutoCommit和多次 commit,还能想到在异常那段也有多次rollback
特别是多次 commit 就导致整个方法调用不是事务同步的。。。因为内层方法已经提交了,外层基本出错
还怎么回滚掉内层的(不要多想Spring事务管理的传播属性,我没那么强大)
那么怎么处理呢,我的想法是把当前线程的每次方法调用,用一个栈记录下来
一进入某个方法,就push当前方法名;一调用完毕,就pop当前方法名。因为整个线程是把所有内层方法串联起来的,所以很好办了。
那么只要判断当前方法栈里面有多少个方法就行了:在第一个入口方法处调用 setAutoCommit,在第一个入口方法调用结束处调用 commit 就行了
那这个方法栈,是当前线程独有的局部变量,怎么得到呢,就是ThreadLocal来保存了
那就来看整个实现过程好了,不想啰嗦了。。。一张图+代码说明
1.创建connection包,来定义一些类,处理Connection
比如创建包:com.yli.sql.connection
package com.yli.sql.connection; import java.io.PrintWriter; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; import javax.sql.DataSource; /** * 每一个Connection都应该有DataSource来提供 * DataSource充当连接池,比如C3P0或者DBCP都是非常优秀的连接池 * 此处不想这么费事了。。。我就每次new一个Connection好了 * 所以我这就实现getConnection方法 * @author yli * */ public class MyDataSource implements DataSource { private String dirver = "com.mysql.jdbc.Driver"; private String url = "jdbc:mysql://localhost:3306/test"; private String user = "root"; private String password = "gzu_imis"; public MyDataSource(String driver, String url, String user, String password) { this.dirver = driver; this.url = url; this.user = user; this.password = password; } @Override public Connection getConnection() throws SQLException { try { Class.forName(this.dirver); return DriverManager.getConnection(this.url, this.user, this.password); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return null; } @Override public Connection getConnection(String username, String password) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PrintWriter getLogWriter() throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { // TODO Auto-generated method stub } @Override public void setLoginTimeout(int seconds) throws SQLException { // TODO Auto-generated method stub } @Override public int getLoginTimeout() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { // TODO Auto-generated method stub return null; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { // TODO Auto-generated method stub return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { // TODO Auto-generated method stub return false; } }
2.创建session包,来定义一些类,处理Session
比如创建包:com.yli.sql.session
package com.yli.sql.session; import java.sql.Connection; import java.util.List; import java.util.Map; /** * 定义SqlSession接口 * 暴露给程序员的方法,就是一些基本的query、update等等 * 当然要提供获取Connection的方法 * @author yli * */ public interface SqlSession { Connection getConnection(); boolean execute(String sql, Map<String, Object> params); int insert(String sql, Map<String, Object> params); int update(String sql, Map<String, Object> params); int delete(String sql, Map<String, Object> params); Map<String, Object> selectForOne(String sql, Map<String, Object> params); List<Map<String, Object>> selectForList(String sql, Map<String, Object> params); }
package com.yli.sql.session; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 定义一个简单的sql参数处理工具类 * 比如动态配置sql语句:select * from user where userId=:userId * 就是使用 :userId 来处理动态参数 * 这个工具类很简单,不能用于生产系统 * 看过Spring处理Sql动态参数的童鞋都明白,其实蛮复杂的一个过程 * * @author yli */ public class SqlParam { /** * 将 select * from user where userId=:userId 处理成 * select * from user where userId=? * 因为 ? 才是PreParedStatement能处理的 * 使用 :userId 完全是为了方便开发者,并且方便程序动态处理所有参数 */ private String prePareSql; /** * 动态sql语句要传递进来的参数 */ private List<Object> paramList; public SqlParam(String prePareSql, List<Object> paramList) { this.prePareSql = prePareSql; this.paramList = paramList; } public void setPreParams(PreparedStatement ps){ if(null == ps || null == paramList) { return; } for (int i = 0; i < paramList.size(); i++) { try { ps.setObject(i + 1, paramList.get(i)); } catch (SQLException e) { e.printStackTrace(); } } } /** * 用正则表达式匹配 :userId 这种结构的动态sql语句 * 比较简单的一个处理,有很多bug的,别在意。。。 * @param sql * @param params * @return */ public static SqlParam prePareSqlParam(String sql, Map<String, Object> params) { String regx = ":[a-zA-Z0-9_]+"; Pattern pattern = Pattern.compile(regx); Matcher matcher = pattern.matcher(sql); List<Object> paramList = new ArrayList<Object>(); String sqlParam; while (matcher.find()) { sqlParam = sql.substring(matcher.start(), matcher.end()); paramList.add(params.get(sqlParam.substring(1))); } if (paramList.isEmpty()) { System.out.println("==========>没有任何递参数进来:" + sql); } else { sql = matcher.replaceAll("?"); } return new SqlParam(sql, paramList); } public String getPrePareSql() { return prePareSql; } public List<Object> getParamList() { return paramList; } }
package com.yli.sql.session; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; /** * 定义一个SqlSessionFactory工厂类 * 该工厂提供openSession方法:从当前线程获取Session * 更重要的是定义一个localMap,用来保存当心线程使用到的session * 前文说过:每个线程可以操作多个数据源的,每个数据源要用一个SqlSession * 所有用了一个Map来保存 * * @author yli * */ public class SqlSessionFactory { private DataSource dataSource; private static ThreadLocal<Map<SqlSessionFactory, SqlSession>> localFactory = new ThreadLocal<Map<SqlSessionFactory, SqlSession>>(); public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public DataSource getDataSource() { return this.dataSource; } /** * 根据当前线程使用的SqlSessionFactory来判断应该使用哪个数据源 * 在创建对应的SqlSession即可 * 每次创建的SqlSession保存到Map中:可以就是当前要使用的SqlSessionFactory * @return */ public SqlSession openSession() { Map<SqlSessionFactory, SqlSession> factory = localFactory.get(); SqlSession session = null; if (null == factory) { try { factory = new HashMap<SqlSessionFactory, SqlSession>(); session = new DefaultSqlSession(this); factory.put(this, session); localFactory.set(factory); } catch (SQLException e) { e.printStackTrace(); } } else { if(null == factory.get(this)) { try { session = new DefaultSqlSession(this); factory.put(this, session); localFactory.set(factory); } catch (SQLException e) { e.printStackTrace(); } } } return factory.get(this); } // 如果使用这个方式来保存SqlSession,那么当前线程只能使用一个SqlSession了 // 对应要操作多个数据源的应用程序来说,就不适用了 // private static ThreadLocal<SqlSession> localSession = new ThreadLocal<SqlSession>(); /* 我想这段代码是很多人在分析Hibernate或者Spring时会提到的 * 但有没有像过同一线程操作对个数据源的情况了,你要亲自试试才知道 * 当然Hibernate没我说的这么简单。。。是有很多人想简单了 public SqlSession openSession1() { SqlSession session = localSession.get(); if(null == session) { try { session = new DefaultSqlSession(this); } catch (SQLException e) { e.printStackTrace(); } localSession.set(session); } return session; } */ }
package com.yli.sql.session; /** * 在定义个工具类,一般来说由程序员自己实现或者扩展它 * 因为要使用多个SqlSessionFactory对应多个数据源 * 所以此处定义两个来测试,如果你有更多的数据源,就再定义了 * * 所以这个类是客户端自己定义或者扩展的 * 客户端的Dao继承自它,就可以使用SqlSession了 * @author yli * */ public class SessionDaoSupport { private SqlSessionFactory factory1; private SqlSessionFactory factory2; public void setFactory1(SqlSessionFactory factory1) { this.factory1 = factory1; } public void setFactory2(SqlSessionFactory factory2) { this.factory2 = factory2; } public SqlSession getSession1(){ return this.factory1.openSession(); } public SqlSession getSession2(){ return this.factory2.openSession(); } }
3.创建动态代理的包,来自动处理事务
比如创建包:com.yli.sql.transaction
package com.yli.sql.transaction; /** * 简单点,我们定义一个匹配方法的切点 * * @author liyu * */ public class MethodAdvice { private String[] matches; public MethodAdvice(String[] matches) { this.matches = matches; } public String[] getMatches(){ return this.matches; } }
package com.yli.sql.transaction; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Stack; import com.yli.sql.session.SqlSessionFactory; /** * 定义一个事务管理类 * 该类关联到应用程序中所有SqlSessionFactory * 那么就可以获取到当前线程里面的 SqlSession * 获取到SqlSession就能获取Connectin * 获取到Connection就能自动提交、回滚了。。。 * 不过这很简单也很粗暴的实现啊 * * @author yli * */ public class TransactionManager { private MethodAdvice advice; private List<SqlSessionFactory> factorys = new ArrayList<SqlSessionFactory>(); private static ThreadLocal<Stack<String>> methodStack = new ThreadLocal<Stack<String>>(); public void setFactorys(List<SqlSessionFactory> factorys, MethodAdvice advice) { this.factorys = factorys; this.advice = advice; } /** * 开始事务处理 * @param methodName */ public void beginTransaction(String methodName) { // 获取当前执行线程的方法栈 Stack<String> methodStack = this.getMethodStack(); // 如果该方法匹配拦截点,则压入当前方法栈 if (isMatchMethod(methodName)) { methodStack.push(methodName); // 如果当前栈只有1个方法,说明在第一个方法入口处 if (methodStack.size() == 1) { System.out.println(methodName + "【开始】设置:conn.setAutoCommit(false)"); try { for (SqlSessionFactory factory : factorys) { factory.openSession().getConnection() .setAutoCommit(false); } } catch (SQLException e) { e.printStackTrace(); // 回滚 rollBack(); } } } } /** * 自定提交事务。。。也不是很好 * @param methodName */ public void endTransaction(String methodName) { // 获取当前执行线程的方法栈 Stack<String> methodStack = this.getMethodStack(); // 如果该方法匹配拦截点,则从栈中移除当前方法 if (isMatchMethod(methodName)) { methodStack.pop(); // 如果栈中没有方法了:说明方法链已经全部执行完 if (methodStack.isEmpty()) { try { for (SqlSessionFactory factory : factorys) { factory.openSession().getConnection().commit(); } } catch (SQLException e) { e.printStackTrace(); // 回滚 rollBack(); } System.out.println(methodName + "【结束】设置:conn.commit()"); } } } // 自动回滚:将当前所有工程获取到的Sqlsession全部回滚掉 // 不是很好,有些方法可能只使用了一个factory来创建SqlSession public void rollBack() { for (SqlSessionFactory factory : factorys) { try { factory.openSession().getConnection().rollback(); } catch (SQLException e) { e.printStackTrace(); } } } // 检查当前方法是否被匹配到了 private boolean isMatchMethod(String methodName) { for (String match : advice.getMatches()) { if (methodName.toLowerCase().startsWith(match)) { return true; } } return false; } // 获取当前线程对应的方法栈 private Stack<String> getMethodStack() { Stack<String> currentStack = methodStack.get(); if (null == currentStack) { currentStack = new Stack<String>(); methodStack.set(currentStack); } return currentStack; } }
package com.yli.sql.transaction; import java.lang.reflect.Method; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * 前文提到使用JDK的动态代理来搞自动提交和回滚 * 这里用了 cglib 为每个类生成动态代理 * @author yli * */ public class MethodTransaction implements MethodInterceptor { // 使用事务管理器来管理实务 private TransactionManager manager; public MethodTransaction(TransactionManager manager) { this.manager = manager; } @Override public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable { String methodName = method.getName(); System.out.println("=============>当前准备执行方法:" + methodName); // 开启事务 manager.beginTransaction(methodName); // 执行目标方法 Object result = null; try { result = proxy.invokeSuper(object, args); // 提交事务 manager.endTransaction(methodName); } catch (Throwable e) { System.out.println("=============>当前方法:" + methodName + "全部回滚"); manager.rollBack(); } // 返回方法执行结果 return result; } }
好了,截止到这里,我模拟的简单自动提交、回滚就这样了,当然还是有很多bug哈
那么来测试下,我没有用Spring 的IOC,就自己模拟一个IOC好了,真的是最简单的模拟。。。
4.测试
package com.yli.sql.test; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.sql.DataSource; import net.sf.cglib.proxy.Enhancer; import com.yli.sql.connection.MyDataSource; import com.yli.sql.session.SqlSessionFactory; import com.yli.sql.transaction.MethodAdvice; import com.yli.sql.transaction.MethodTransaction; import com.yli.sql.transaction.TransactionManager; /** * 定义一个Map * 把所有Bean创建好,并创建好依赖关系 * 当然DataSource要创建多个 * 那么SqlSessionFactory也是多个 * 并定义一个事务管理器、一个方法匹配器 * 然后为业务类定义代理类。。。 * 真正的Spring IOC 大家都搞过啊,就是这么干的 * * @author yli * */ public class BeanFactory { public static Map<String, Object> beanMap = new HashMap<String, Object>(); public static void initBean() { // 数据源-1:test数据库 String dirver = "com.mysql.jdbc.Driver"; String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String password = "123456"; DataSource da1 = new MyDataSource(dirver, url, user, password); // 数据源-2:test1数据库 url = "jdbc:mysql://localhost:3306/test1"; DataSource da2 = new MyDataSource(dirver, url, user, password); // Session工厂-1 SqlSessionFactory factory1 = new SqlSessionFactory(); factory1.setDataSource(da1); // Session工厂-2 SqlSessionFactory factory2 = new SqlSessionFactory(); factory2.setDataSource(da2); // DaoSupport UserDao userDao = new UserDao(); userDao.setFactory1(factory1); userDao.setFactory2(factory2); // 定义哪些方法需要处理事务 String[] methods = new String[] { "save", "update", "insert", "delete", "execute", "modify" }; MethodAdvice advice = new MethodAdvice(methods); // 事务管理类:将所有session工厂注入 TransactionManager manager = new TransactionManager(); List<SqlSessionFactory> factorys = new ArrayList<SqlSessionFactory>(); factorys.add(factory1); factorys.add(factory2); manager.setFactorys(factorys, advice); // 业务Bean:主要针对Service层,哪些方法需要自动处理事务 UserService userService = new UserService(); userService.setUserDao(userDao); // 对于要求开启事务的[类==>方法列表]:生成代理类 UserService userServiceProxy = (UserService) getProxyBean(UserService.class, manager); userServiceProxy.setUserDao(userDao); // 将所有Bean加载到BeanMap beanMap.put("da1", da1); beanMap.put("da2", da2); beanMap.put("factory1", factory1); beanMap.put("factory2", factory2); beanMap.put("userService", userServiceProxy); // 注意此处的UserService设置为代理类 beanMap.put("userService$", userService); // 原生的类则使用[相同名称+$]定位:Spring 也这么干 } public static Object getProxyBean(Class<?> classz, TransactionManager manager) { Enhancer enhancer = new Enhancer(); // 如果不能传递class进来,那就传className进来: Class.forName(className) // 也能根据类名获取class对象 ,比如Spring的bean配置文件,肯定是要配置类的全路径的 enhancer.setSuperclass(classz); enhancer.setCallback(new MethodTransaction(manager)); return enhancer.create(); } }
再定义一个Dao和Servei吧
package com.yli.sql.test; import java.util.Map; import com.yli.sql.session.SessionDaoSupport; public class UserDao extends SessionDaoSupport{ public boolean inserUserToDB1(Map<String, Object> params){ String sql = "insert into user(name,sex) values(:name,:sex)"; return getSession1().insert(sql, params) > 0; } public boolean inserUserToDB2(Map<String, Object> params){ String sql = "insert into user(name,sex) values(:name,:sex)"; return getSession2().insert(sql, params) > 0; } public boolean updateUserToDB1(Map<String, Object> params){ String sql = "update user set name=:name where id=:Id"; return getSession1().update(sql, params) > 0; } public boolean updateUserToDB2(Map<String, Object> params){ String sql = "update user set name=:name where id=:Id"; return getSession2().update(sql, params) > 0; } public boolean deleteUserToDB1(Map<String, Object> params){ String sql = "delete from user where id=:Id"; return getSession1().delete(sql, params) > 0; } public boolean deleteUserToDB2(Map<String, Object> params){ String sql = "delete from user where id=:Id"; return getSession2().delete(sql, params) > 0; } }
package com.yli.sql.test; import java.util.HashMap; import java.util.Map; public class UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void modifyUser() { Map<String, Object> params = new HashMap<String, Object>(); // params.put("Id", 1); params.put("name", "hello"); params.put("sex", "0"); // insert => DB1 this.userDao.inserUserToDB1(params); // 测试程序出错了回滚 // System.out.println(1/0); // insert => DB2 this.userDao.inserUserToDB2(params); // 有调用其他业务方法,也可以是其他业务类的方法 insertUser(); } public void insertUser() { Map<String, Object> params = new HashMap<String, Object>(); params.put("name", "yli"); params.put("sex", "1"); // insert => DB1 this.userDao.inserUserToDB1(params); // 测试程序出错了回滚 // System.out.println(1 / 0); } }
最后来一个Main方法测试了
package com.yli.sql.test; public class MainTest { public static void main(String[] args) { BeanFactory.initBean(); UserService user = (UserService)BeanFactory.beanMap.get("userService"); user.modifyUser(); } }
测试表明一起提交或者某个出错就一起回滚了。。。
好吧,只是简单地应用,有人浏览到这里,看看就行了
对了我是用的mysql,在公司用db2也试了,哎,肯定是相同的结果
这个例子就依赖了 mysql5.5 和 cglib2.2的 jar包
相关文章推荐
- Spring中属性注入详解
- struts2 spring整合fieldError问题
- spring的jdbctemplate的crud的基类dao
- java基本教程之线程休眠 java多线程教程
- 解析Java中如何获取Spring中配置的bean
- Spring的注解配置与XML配置之间的比较
- java Spring整合Freemarker的详细步骤
- Java 二维码,QR码,J4L-QRCode 的资料整理
- java当中的定时器的4种使用方式
- java中 spring 定时任务 实现代码
- Spring MVC中基于自定义Editor的表单数据处理技巧分享
- Java代码重构的几种模式详解
- 基于spring+hibernate+JQuery开发之电子相册(附源码下载)
- spring实例化javabean的三种方式分享
- Spring3.2.0和Quartz1.8.6集群配置
- spring security 3.2.0.M1 方法级别教程 基于注解——第一部分
- spring security 3.2.0.M1 方法级别教程 基于注解——第二部分
- Spring 加载 xml 文件机制
- 基于全注解的Spring3.1 mvc、myBatis3.1、Mysql的轻量级项目
- Spring的四种声明式事务的配置-Hibernate事务