以下内容 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都实现好了。