关闭

Spring事务处理-ThreadLocal的使用

883人阅读 评论(0) 收藏 举报
分类:
经历了几天的研究,终于是明白了ThreadLocal在Spring事务管理过程中发挥的用途。下面就以图文的形式和大家分享,如有错误,欢迎指正。

大家都知道,Spring允许以声明的方式进行事务管理。通过声明的方式,程序员可以仅仅专注于业务代码,事务管理由Spring框架代为进行。

以JDBC为例,正常的事务代码可能如下:
dbc = new DataBaseConnection();//第1行
Connection con = dbc.getConnection();//第2行
con.setAutoCommit(false);// //第3行
con.executeUpdate(...);//第4行
con.executeUpdate(...);//第5行
con.executeUpdate(...);//第6行
con.commit();////第7行

上述代码,可以分成三个部分:


事务准备阶段:第1~3行
业务处理阶段:第4~6行
事务提交阶段:第7行


在Spring框架中,程序员专注于设计业务处理阶段,事务准备阶段和事务提交阶段由Spring来完成。在实际开发过程中,我们仅仅编写了业务处理阶段,事务准备阶段和事务提交阶段会由Spring框架根据我们的事务相关配置文件动态生成--利用AOP。关于AOP,这里就不说了,网上有很多资料。

但是大家需要注意一个问题,在利用AOP动态生成的代码中,如何才能让三个阶段使用同一个数据源连接呢?这是很重要的。如果三个阶段使用不同的数据源连接,自然是错误的。

现在需要办到的是 让软件结构中纵向的三个阶段 使用同样的一个参数,而这三个阶段之间不可以进行参数传递。解决方案是---线程绑定。

Web容器中,每个完整的请求周期会由一个线程来处理。因此,如果我们能将一些参数绑定到线程的话,就可以实现在软件架构中跨层次的参数共享(是隐式的共享)。这是一件很牛逼的事情,在框架中被经常使用。而JAVA中恰好提供了绑定的方法--使用ThreadLocal。

ThreadLocal是一种线程本地变量,使用ThreadLocal的形式声明一个变量,该变量就会在每个线程中创建一个变量的副本。

public class Demo {
	public static ThreadLocal<String> threadLocalString = new ThreadLocal<String>(){
		protected String initialValue() {
			return "";
		}
	};
	public static ThreadLocal<Long> threadLocalLong =new ThreadLocal<Long>(){
		protected Long initialValue() {
			return 0L;
		}
	};
	public static void main(String [] args){
		threadLocalLong.set(100L);
		threadLocalString.set("test");
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				threadLocalString.set("thread");
				System.out.println(threadLocalLong.get());
				System.out.println(threadLocalString.get());
			}
		}).start();
		
		System.out.println(threadLocalLong.get());
		System.out.println(threadLocalString.get());
	}
}


从上面的代码可看出,在不同的线程中调用同一个类对象的get()方法,输出依据线程的不同而不同。
再来看一个关于ThreadLocal的例子:
import java.util.HashMap;
import java.util.Map;

public class Demo {
	public static void main(String [] args){
		ResourceHolder.putResource("conn",new Conn("connection1"));
		new Thread(new Runnable() {
			@Override
			public void run() {
				// 该线程不会得到主线程绑定的变量
				System.out.println(ResourceHolder.getResource("conn"));
			}
		}).start();
		
		System.out.println(ResourceHolder.getResource("conn"));
		new Demo().function1();
		new Demo().function2();
		System.out.println(ResourceHolder.getResource("conn"));
	}
	public void function1(){
		System.out.println(ResourceHolder.getResource("conn"));
	}
	public void function2(){
		System.out.println(ResourceHolder.getResource("conn"));
	}
}

class ResourceHolder{
	
	public static ThreadLocal<Map<Object,Object>> threadLocalMap=new ThreadLocal<Map<Object,Object>>();
	public static void putResource(Object key,Object value){
		if(threadLocalMap.get()==null)
			threadLocalMap.set(new HashMap<Object,Object>());
		threadLocalMap.get().put(key, value);
	}
	public static Object getResource(Object key){
		if(threadLocalMap.get()==null)
			threadLocalMap.set(new HashMap<Object,Object>());
		return threadLocalMap.get().get(key);
	}
	public static void clearResource(Object key,Object value){
		if(threadLocalMap.get()!=null)
			threadLocalMap.remove();
	}
}
class Conn{
	private String name;
	
	public Conn(String name) {
		super();
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	@Override
	public String toString() {
		return "Conn [name=" + name + "]";
	}
}



现在我们可以考虑使用ThreadLocal来将 事务准备阶段使用的连接 绑定到当前线程 以便在之后的 业务处理阶段 和 事务提交阶段使用了。不过问题来了,这个ThreadLocal放在哪里呢?一种方案是写到DataSource中,但DataSource是策略模式动态配置的,况且都是第三方的,不那么容易改。

我们再一次想到AOP,为DataSource创建一个代理类,每次调用DataSource的getConn方法的时候,都由拦截器拦截并转换为对DataSource代理类的调用,在代理类中加一些猫腻;

看代码: (代理类)
package com.xyz.transaction;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DataSourceHandler implements InvocationHandler {
	
	private Object originalDataDource;
	public Object bind(Object obj) {
		this.originalDataDource=obj;
		return Proxy.newProxyInstance(this.originalDataDource.getClass().getClassLoader(),
				originalDataDource.getClass().getInterfaces(), this);
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		// TODO Auto-generated method stub
		if("getConn".equals(method.getName())){//默认数据源的获取连接方法为getConn()
			if(ResourceHolder.getResource(proxy)==null){
				Object obj=method.invoke(originalDataDource, args);
				ResourceHolder.addResource(proxy, obj);
			}
			return ResourceHolder.getResource(proxy);
		}else{
			return method.invoke(originalDataDource, args);
		}
	}
	
}

package com.xyz.transaction;

import java.util.HashMap;
import java.util.Map;

public class ResourceHolder {
	private static ThreadLocal<Map<Object,Object>> threadLocalMap=new ThreadLocal<Map<Object,Object>>();
	public static void addResource(Object key,Object value){
		if(threadLocalMap.get()==null)
			threadLocalMap.set(new HashMap<Object, Object>());
		threadLocalMap.get().put(key, value);
	}
	public static Object getResource(Object key){
		if(threadLocalMap.get()==null)
			threadLocalMap.set(new HashMap<Object, Object>());
		return threadLocalMap.get().get(key);
	}
	public static void clear(){
		threadLocalMap.remove();
	}
}


来看以上代码,每次访问getConn方法的时候,都查看是否在当前线程绑定的Map中有对应的连接,如果有直接返回。如果没有,再向真实的getConn请求获得一个连接并放到当前线程绑定的Map中。

流程如图所示,图片代码见附件。



                                                                    

  • 大小: 1.2 MB
0
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

【Java基础】采用ThreadLocal封装Connection控制事务,保证线程安全

前言: 上篇博客介绍了ThreadLocal的原理和功能,这篇博客会做一个ThreadLocal的具体示例:采用ThreadLocal封装Connection,控制事务,保证线程安全。 原理:采用...
  • u010028869
  • u010028869
  • 2016-01-06 21:30
  • 1489

【Java技术点滴】——ThreadLocal封装JDBC事务操作

背景     在Java程序实现中,我们往往应用到事务的机制,在业务层进行事务开启,创建数据库连接,调用Dao层方法进行数据库访问,过程中需要将数据库连接Connection作为参数传递给Dao层方法...
  • lyg673770712
  • lyg673770712
  • 2015-02-28 20:49
  • 1640

ThreadLocal 内部实现和应用场景

很多人都知道java中有ThreadLocal这个类,但是知道ThreadLocal这个类具体有什么作用,然后适用什么样的业务场景还是很少的。今天我就尝试以自己的理解,来讲解下ThreadLocal类...
  • z69183787
  • z69183787
  • 2016-05-24 14:41
  • 6867

Spring 对hibernate事务处理的实现过程(辅助) ThreadLocal

spring在事物处理的时候,会把ConnectionHolder和SessionHolder与线程绑定,使用的是ThreadLocal,下面看看ThreadLocal的实现是怎样做到线程间隔离的 ...
  • Admiral_dota
  • Admiral_dota
  • 2016-04-09 13:22
  • 394

spring声明式事务处理(使用jdbc操作数据库)

声明式:针对编程人员,声明spring容器遇到哪些目标方法时需要开启事务,哪些不用开启事务。 事务处理:把事务处理交给spring容器来完成。 spring声明式事务处理的目标: 让程序...
  • andy_px
  • andy_px
  • 2015-09-23 17:10
  • 476

Java事务处理全解析(七)—— 像Spring一样使用Transactional注解(Annotation)

在本系列的上一篇文章中,我们讲到了使用动态代理的方式完成事务处理,这种方式将service层的所有public方法都加入到事务中,这显然不是我们需要的,需要代理的只是那些需要操作数据库的方法。在本篇中...
  • qq_33824312
  • qq_33824312
  • 2017-05-07 09:57
  • 231

Spring中编程式事务处理(使用TransactionTemplate)之一

Spring的编程式事务处理,需要使用Hibernate事务回调接口,事务回调接口可以管理Hibernate的事务:  TransactionCallbackWithoutResult —— 执...
  • Cherry_tly
  • Cherry_tly
  • 2016-04-28 14:47
  • 472

Java事务处理全解析(七)—— 像Spring一样使用Transactional注解(Annotation)

在本系列的上一篇文章中,我们讲到了使用动态代理的方式完成事务处理,这种方式将service层的所有public方法都加入到事务中,这显然不是我们需要的,需要代理的只是那些需要操作数据库的方法。在本篇中...
  • huilangeliuxin
  • huilangeliuxin
  • 2015-02-03 11:10
  • 7828

spring 使用AbstractRoutingDataSource自定义动态数据源时的事务处理问题

最近在网上看到了一篇博客,继承spring的AbstractRoutingDataSource
  • ld2007081055
  • ld2007081055
  • 2014-09-23 10:55
  • 1126

Spring事务处理案例总结 rollback-for使用

spring只是控制数据库的事务提交和回滚,借助于java的反射机制,在事务控制的方法(通常是service层的方法)前后获取事务开启session,然后执行你的数据操作,如果你的方法内有异常被抛出,...
  • lan12334321234
  • lan12334321234
  • 2017-04-11 10:50
  • 190
    个人资料
    • 访问:142306次
    • 积分:1687
    • 等级:
    • 排名:千里之外
    • 原创:139篇
    • 转载:10篇
    • 译文:0篇
    • 评论:36条
    最新评论