从mybatis的缓存到ThreadLocal的一点理解

以下内容 spring已经实现了,这只是我的理解

一级缓存sqlsession,二级缓存factory

mybatis默认开启sqlsession缓存。每一个用户一个sqlsession,即使是共有的信息也会存到自己的缓存中,其他的用户查,即使是同样的方法,同一份数据,也会重新查数据库,因为sqlsession对象不一样。

//获取Mybatis对象
InputStream is = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(is);
SqlSession ss=factory.openSession();

所有的的sqlsession对象都是factory.openSession()得到的。所以是不是只要开启factory缓存即可呢?

开启factory缓存
在mapper中配置 标签catche

<cache readOnly="true"></cache>

注意,sqlsession查询公有数据的对象,只有在commit或者close的时候,才会把缓存刷新到factory中。

其他用户查数据的时候,先在自己的sqlsession中找,没有再去factory中找。都没有再查数据库,然后先自己缓存一份,提交或者关闭的时候,再刷新到factory中。

接下来是重点:

能走缓存的前提是,这些sqlsession对象都是同factory创建的。
两个线程,即使是执行相同的代码,也是不同的factory,还是没有办法走factory缓存。所以要保证,即使是不同的线程,factory也必须是一样的。并且每个线程都不是一个sqlsession对象,因为如果是一个,运行结束直接提交了。

所以必须是一个factory,不同的sqlsession。

一个线程一个sqlsession,并且每个线程只能有一个sqlsession,因为有可能调用不同的方法,所以要求任意执行位置获取sqlsession,都是唯一一个sqlsession。

问题1 :不同线程同一个factory

问题2 :不同线程,不同的sqlsession,并且每个线程只能有一个sqlsession。

解决:问题1
设置为静态的属性,只有1个factory。

解决:问题2
sqlsession不能作为静态属性,因为不同线程就是同一sqlsession了。
如果不是静态,那么即使是同一线程,调用两次,就是两个sqlsession了。怎么办呢?

解决:使用线程封闭

threadLocal 封装sqlsession对象。
threadLocal存储的数据,只能是线程内有效。

因为:
(线程1获取ThreadLocal对象,并在该对象中存储了数据B,那么在线程1中获取同一个ThreadLocal对象就可以获取到数据B,线程2获取线程1的ThreadLocal对象,但是无法获取线程1在ThreadLocal中存储的数据。)

三种封闭:

线程封闭:ad-hoc:程序控制,最糟糕,忽略。
堆栈封闭,局部变量,无并发问题。
threadLocal线程封闭:特别好。

线程封闭:

实现好的并发是一件困难的事情,所以很多时候我们都想躲避并发。避免并发最简单的方法就是线程封闭。什么是线程封闭呢?

就是把对象封装到一个线程里,只有这一个线程能看到此对象。那么这个对象就算不是线程安全的也不会出现任何安全问题。

1:ad-hoc线程封闭
这是完全靠实现者控制的线程封闭,他的线程封闭完全靠实现者实现。Ad-hoc线程封闭非常脆弱,没有任何一种语言特性能将对象封闭到目标线程上。
2:栈封闭
栈封闭是我们编程当中遇到的最多的线程封闭。什么是栈封闭呢?简单的说就是局部变量。多个线程访问一个方法,此方法中的局部变量都会被拷贝一分儿到线程栈中。所以局部变量是不被多个线程所共享的,也就不会出现并发问题。
3:ThreadLocal封闭
使用ThreadLocal是实现线程封闭的最好方法。ThreadLocal内部维护了一个Map,Map的key是每个线程的名称,而Map的值就是我们要封闭的对象。每个线程中的对象都对应着Map中一个值,也就是ThreadLocal利用Map实现了对象的线程封闭。
所以能用局部变量就别用全局的变量,全局变量容易引起并发问题。

Threadlocal

就是在用户线程到达控制层之前就先将线程的数据封存到Threadlocal中,这样在以后需要用户信息的时候,直接在Threadlocal中取出来就可以了。如果不这样做,用户的信息在request中,我们就要把用户的信息不停的从controller往下传,可能还会传到util类中去。代码看起来也不舒服。使用过滤器和Threadlocal,就可以先把数据取出来,什么时候用,什么时候从Threadlocal中取就行了。

经典的线程封闭 数据库连接对应JDBC的 connection对象,将这个对象封闭在线程里边,虽然它本身不是线程安全的,但是通过线程封闭,也做到了线程安全。

public class MyBatisUtil {
	//声明Factory对象
	private static SqlSessionFactory factory;
	//声明ThreadLocal对象封装SqlSession对象
	private static ThreadLocal<SqlSession> ts=new ThreadLocal<>();
	static{
		InputStream is;
		try {
			is = Resources.getResourceAsStream("mybatis.xml");
			 factory=new SqlSessionFactoryBuilder().build(is);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}	
	//获取SqlSession对象
	//一个线程,第一次获取,没有,生成一份,以后不管在任意位置,再去调用,
	//都是之前那一份,注意,key是当前线程,所以肯定不会出现线程安全问题。
	public static SqlSession getSqlSession(){
		SqlSession ss=ts.get();
		if(ss==null){
			ss=factory.openSession();
			ts.set(ss);
		}
		return ss;		
	}	
	//关闭SqlSession对象
	public static void closeSqlSession(){
		SqlSession ss=ts.get();
		if(ss!=null){
			ss.close();
		}
		ts.set(null);		
	}		
}

注意:一定要在线程里进行关闭,因为你线程在外边关闭,我的sqlsession对象还在,所以你再次获取的时候,他还是存在的,但是这个对象已经没有用了,你线程都关闭了,也不会再得到这个对象,这是个无用对象,所以要关闭,给他清除掉。

还有,如果在业务中调用了关闭或者提交方法,那么在调用的时候,本线程中就不是一个sqlsession对象了,所以用过滤器,进行统一的管理。在整个线程数据库操作完成后再关闭sqlsession和提交sqlsession。

不过以上spring都实现好了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值