用mybatis的基本上都知道mybatis有两个级别的缓存,分别是一级缓存和二级缓存。简单的说:
一级缓存
缓存sql语句返回的数据信息,下次再访问相同的语句时,就会直接从缓存中取数据,就不需要查询数据库了,这样可以减少数据库的访问压力。
二级缓存
mapper级别的缓存,缓存每一个namespace的数据。当执行select语句时,数据会从缓存中取出,不查询数据库。当执行该namespace的非select语句时,就会清空该缓存。下次查询就会查询数据库了。
但不建议使用二级缓存,因为很多时候,一个namespace不可能只对一个表进行操作,当一个mapper.xml中操作另外的一张表,但另外的mapper.xml并没有被清空,这样会导致很多脏数据的产生~~
----------------------------------以上都是自己的理解。术语不是很官方的。
一级缓存的数据更新
我们的疑问:
当我们了解一级缓存的用途后,我们就会想,我们对数据库的操作又不是仅限于读,很多时候我们都要更新数据库的信息,而当我们更新数据之后,mybatis缓存起来的数据,会不会还是以前的数据呢?通俗来说就是查出来的是否为脏数据?
带着这些疑问,笔者花了一个下午的时间,通过研究mybatis的源码,得知,其实mybatis早就做好了相关工作了。
为了大家也能够深入了解,特意写了个demo,不过该篇文章比较适合动手能力较强的读者,因为我这里只告诉你mybatis在哪里进行了数据缓存,在哪里进行了数据更新,例子开始:
不劳烦读者自己创建项目了:
链接:https://pan.baidu.com/s/1IJCFqIAW4maoeOBMGsAeNQ
提取码:xmic
注意要自己改数据库地址
附数据库的信息:
CREATE TABLE `t_user` (
`ID` bigint(20) NOT NULL AUTO_INCREMENT,
`USERNAME` varchar(32) DEFAULT NULL,
`PASSWORD` varchar(32) DEFAULT NULL,
`TELEPHONE` varchar(32) DEFAULT NULL,
`REALNAME` varchar(32) DEFAULT NULL,
`ISADMIN` varchar(2) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8
在demo中找到主类MainTest:
import com.dao.UserMapper;
import com.entity.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MainTest {
public static void main(String[] args) throws IOException {
//的
String resource = "mybatis-config-test.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = null;
try {
session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> userList = mapper.getUserInfo("6");
System.out.println("------------------------------------------------");
//尝试update下,看看一级缓存是否有效----------事实证明: 当执行了update或者delete时,缓存失效
// int i = mapper.updateUser("6");
// if (i>0) System.out.println("更新成功!!!!");
// System.out.println("------------------------------------------------");
List<User> rrr = mapper.getUserInfo("6");
System.out.println("------------------------------------------------");
if (rrr == userList) {
System.out.println("两个对象相等。。更加证明直接使用的是缓存");
}else{
System.out.println("通过更新操作,mybatis的一级缓存被刷新了");
}
// List<User> userInfo = mapper.getUserInfo("6");
// if (userInfo==rrr) System.out.println("第三个语句也是取缓存的!!!");
} finally {
session.close();
}
System.out.println("session: " + (session == null));
}
}
第一步:注释掉更新操作,运行后出现:
只打印一条SQL语句,说明第二次查询是直接从缓存中读取的 而且连读取的对象都相同,就更加证明这个返回数据是有缓存起来的!
然后进一步调试:
实际上,mybatis在第一次查询的时候,会运行到这个方法:org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
该方法源码如下(注意看这个:localCache.putObject(key, EXECUTION_PLACEHOLDER);
):
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);//这个地方,就是进行缓存的地方~~~~
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
所以第一次查询时,mybatis把数据缓存到了localCache
这个变量中,就是这个类:PerpetualCache
可以去搜一下。
然后第二次查询,当进行第二次查询时,mybatis来到这个地方就会进行判断:
org.apache.ibatis.executor.BaseExecutor
所以简单从源码就可以看出,mybatis在两次相同的查询操作时,下一次会从缓存中取出来。
那更新后的数据,mybatis是如何更新的呢?我们去掉MainTest类中的updateUser方法的注释,然后继续调试。 可以找到,我们直接来到这里:org.apache.ibatis.executor.BaseExecutor#update
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) throw new ExecutorException("Executor was closed.");
clearLocalCache();//看见这里没,在update时,mybatis就把缓存清空了。
return doUpdate(ms, parameter);
}
注意看clearLocalCache()
方法,在进行update时,mybatis就把缓存清空了,这样等下一次查询时,没有缓存的时候,mybatis就会去查询数据库的!!!!!所以,这就是mybatis使用一级缓存时实时更新数据的技巧~~~~
如果不想使用mybatis的一级更新,那么可以在UserMapper.xml中添加flushCache="true"设置:
<select id="getUserInfo" resultType="com.entity.User" parameterType="java.lang.String" flushCache="true">
SELECT * FROM T_USER U WHERE U.`id`=#{id}
</select>
<select id="test" resultType="com.entity.User" parameterType="string" flushCache="true">
select * from t_user where id=#{id}
</select>
这个flushCache会保存在:MappedStatement类中的flushCacheRequired成员变量中。默认值为false。
使用的地方在:
抛出问题:
在集群/分布式的时候,另一台服务器对数据更新了,那另外的服务器是如何知晓数据是否更新了呢?
20190414更新:
由于一级缓存是基于sqlSession级别的缓存,当项目部署为集群或者分布式的时候,其实很容易就会因为一级缓存的问题导致得到的是脏数据,解决这问题其实有两点:
- 关闭mybatis的一级缓存:在xml文件中,全局关闭一级缓存;
- 使用缓存框架,如redis做缓存: