mybatis缓存以及数据什么时候更新,实时更新数据(源码详细解释)

用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 
或者通过CSDN下载(建议baiduyun下载吧,不要积分):
https://download.csdn.net/download/shenhaiyushitiaoyu/11110025

注意要自己改数据库地址
附数据库的信息:

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级别的缓存,当项目部署为集群或者分布式的时候,其实很容易就会因为一级缓存的问题导致得到的是脏数据,解决这问题其实有两点:

  1. 关闭mybatis的一级缓存:在xml文件中,全局关闭一级缓存;
  2. 使用缓存框架,如redis做缓存:
  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值