ThreadLocal实践

6 篇文章 0 订阅

无聊的话

不管是用过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包

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值