mybatis04-mybatis源码-一级缓存、二级缓存

文章目录

  • 拓展阅读
  • 一级缓存
    • 具体流程
    • 注意事项
    • 开启日志打印
    • 源码分析
      • 缓存key创建
      • 最终查询方法
    • 执行update、inset方法
    • 一级缓存问题
    • 关闭一级缓存
  • 二级缓存
    • mybatis缓存执行流程
    • redis集成mybatis二级缓存
      • mybatis.xml
      • 指定缓存
        • 二级缓存回收策略
          • 软引用与弱引用的区别:
      • MybatisRedisCache
      • UserEntity
      • 测试
      • 效果
    • 源码分析
      • TransactionalCache
      • TransactionalCacheManager
      • StatementHandler
      • ResultSetHandler
  • 一级缓存与二级缓存区别

拓展阅读

  • mybatis核心应用配置与原理剖析一级二级缓存

一级缓存

mybatis的一级缓存是SqlSession级别的缓存,在操作数据库的时候需要先创建SqlSession会话对象,在对象中有一个HashMap用于存储缓存数据,此HashMap是当前会话对象私有的,别的SqlSession会话对象无法访问。

具体流程

  1. 第一次执行select完毕会将查到的数据写入SqlSession内的HashMap中缓存起来
  2. 第二次执行select会从缓存中查数据,如果select相同切传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率

注意事项

  1. 如果SqlSession执行了DML操作(insert、update、delete),并commit了,那么mybatis就会清空当前SqlSession缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现脏读
  2. 当一个SqlSession结束后那么他里面的一级缓存也就不存在了,mybatis默认是开启一级缓存,不需要配置
  3. mybatis的缓存是基于[namespace:sql语句:参数]来进行缓存的,意思就是,SqlSession的HashMap存储缓存数据时,是使用[namespace:sql:参数]作为key

开启日志打印


    

源码分析

缓存key创建

org.apache.ibatis.executor.CachingExecutor#createCacheKey

@Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    //简单执行器创建缓存key实现代码复用,因为一级二级缓存都会用此key
    //mybatis缓存控制先查找二级缓存(硬盘、redis)、二级缓存没有的情况在查找一级缓存。
    //一级缓存绝对是有的 ,但是二缓存可以没有。
    return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
  }

在这里插入图片描述
mybatis04-mybatis源码-一级缓存、二级缓存_第1张图片

最终查询方法

org.apache.ibatis.executor.BaseExecutor#query()

@Override
  public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List list;
    try {
      //计数器 
      queryStack++;
      list = resultHandler == null ? (List) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List list;
    //localCache为PerpetualCache类型 指的就是我们的一级 一级缓存属于本地缓存 存放在内存中 使用map集合存放
    //占位符
    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;
  }

执行update、inset方法

底层都时执行update操作
mybatis04-mybatis源码-一级缓存、二级缓存_第2张图片
会清理缓存

一级缓存问题

mybatis04-mybatis源码-一级缓存、二级缓存_第3张图片

注意:服务器集群的时候,每个sqlSession有自己独立的缓存相互之间不存在共享,所以在服务器集群的时候容易产生数据冲突问题。

关闭一级缓存

  • 在sql语句上 随机生成 不同的参数 存在缺点:map集合可能报内存溢出
select * from user where #{radom}=#{radom}
  • 开启二级缓存
  • 使用sqlSession强制清除缓存
sqlSession.clearCache();
  • 创建新的sqlSession连接。

二级缓存

二级缓存是mapper级别的缓存,也就是同一个namespace的mappe.xml,当多个SqlSession使用同一个Mapper操作数据库的时候,得到的数据会缓存在同一个二级缓存区域,二级缓存默认是没有开启的。

mybatis缓存执行流程

mybatis04-mybatis源码-一级缓存、二级缓存_第4张图片

redis集成mybatis二级缓存

mybatis.xml


    
    

指定缓存

  • xml方式




  • 注解方式
@CacheNamespace(implementation = MybatisRedisCache.class)
public interface OrderMapper {
	@Insert("insert order_info values (null,#{orderName},#{orderDes})")
	public int addOrder(OrderEntity OrderEntity);

	@Select("SELECT * FROM order_info;")
	public List findByOrder();
}

二级缓存回收策略

  • LRU:最近最少使用的策略,移除最长时间不被使用的对象。
  • FIFO:先进先出策略,按对象进入缓存的顺序来移除它们。
  • SOFT:软引用策略,移除基于垃圾回收器状态和软引用规则的对象。
  • WEAK:弱引用策略,更积极地移除基于垃圾收集器状态和弱引用规则的对象。
软引用与弱引用的区别:
  • 软引用: 软引用是用来描述一些有用但并不是必需的对象, 对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象
  • 弱引用: 弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象

MybatisRedisCache

// mybatis二级缓存整合Redis
public class MybatisRedisCache implements Cache {
    private static Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);

    private Jedis redisClient = createReids();

    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    private String id;

    public MybatisRedisCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        logger.debug(">>>>>>>>>>>>>>>>>>>>>>>>MybatisRedisCache:id=" + id);
        this.id = id;
    }


    public String getId() {
        return this.id;
    }


    public int getSize() {

        return Integer.valueOf(redisClient.dbSize().toString());
    }


    public void putObject(Object key, Object value) {
        logger.debug(">>>>>>>>>>>>>>>>>>>>>>>>putObject:" + key + "=" + value);
        redisClient.set(SerializeUtil.serialize(key.toString()), SerializeUtil.serialize(value));
    }

    public Object getObject(Object key) {
        Object value = SerializeUtil.unserialize(redisClient.get(SerializeUtil.serialize(key.toString())));
        logger.debug(">>>>>>>>>>>>>>>>>>>>>>>>getObject:" + key + "=" + value);
        return value;
    }


    public Object removeObject(Object key) {
        return redisClient.expire(SerializeUtil.serialize(key.toString()), 0);
    }


    public void clear() {
        redisClient.flushDB();
    }


    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }

    protected static Jedis createReids() {
        JedisPool pool = new JedisPool("127.0.0.1", 6379);
        return pool.getResource();
    }
}

UserEntity

实现Serializable接口

@Data
public class UserEntity implements Serializable {
    private Long id;
    private String name;
    private Integer age;

    public UserEntity(Long id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
}

测试

public class HelloMybatisTest {
    public static void main(String[] args) {
        try {
            // 1.mybatis配置文件
            String resources = "mybatis.xml";
            // 2.获取Reader对象
            Reader resourceAsReader = Resources.getResourceAsReader(resources);
            // 3.获取SqlSessionFactoryBuilder
            SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsReader);
            // 4.创建对应的session
            SqlSession sqlSession = build.openSession();
            // 5.获取对应的mapper
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            // 6.执行方法
            //UserEntity user = userMapper.getUser(1);
            UserEntity user1= sqlSession.selectOne("com.grape.demo.mybatis.hello.mapper.UserMapper.getUser", 1);
            //提交之后才会更新到redis里
            sqlSession.commit();
            SqlSession sqlSession1 = build.openSession();
            UserEntity user2= sqlSession1.selectOne("com.grape.demo.mybatis.hello.mapper.UserMapper.getUser", 1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

效果

mybatis04-mybatis源码-一级缓存、二级缓存_第5张图片

mybatis04-mybatis源码-一级缓存、二级缓存_第6张图片

源码分析

  • CachingExecutor.query
@Override
  public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, parameterObject, boundSql);
        @SuppressWarnings("unchecked")
        List list = (List) tcm.getObject(cache, key);
        if (list == null) {
          //简单执行器查询数据
          list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          //放入缓存TransactionalCache里面  list数据放在entriesToAddOnCommit里面
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

注意到执行完毕后并没有把缓存更新到redis,此时我们查看org.apache.ibatis.cache.decorators.TransactionalCache#commit方法可知,当我们提交时才会更新到我们缓存里面。

private void flushPendingEntries() {
    for (Map.Entry entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }

TransactionalCache

TransactionalCache:继承自Cache接口,主要作用是保存SqlSession在事务中需要向某个二级缓存提交的缓存数据(因为事务过程中的数据可能会回滚,所以不能直接把数据就提交二级缓存,而是暂存在TransactionalCache中,在事务提交后再将过程中存放在其中的数据提交到二级缓存,如果事务回滚,则将数据清除掉)

TransactionalCacheManager

TransactionalCacheManager:用于管理CachingExecutor使用的二级缓存对象,只定义了一个transactionalCaches字段

private final Cache delegate;	//对应的二级缓存对象
private boolean clearOnCommit;	//是否在commit时清除二级缓存的标记
// 需要在commit时提交到二级缓存的数据
private final Map entriesToAddOnCommit;
// 缓存未命中的数据,事务commit时,也会放入二级缓存(key,null)
private final Set entriesMissedInCache;
 
  

StatementHandler

StatementHandler接口的实现大致有四个,其中三个实现类都是和JDBC中的Statement响对应的:

  • SimpleStatementHandler,这个很简单了,就是对应我们JDBC中常用的Statement接口,用于简单SQL的处理; 存在sql注入攻击问题
  • PreparedStatementHandler,这个对应JDBC中的PreparedStatement,预编译SQL的接口;
    防止sql注入
  • CallableStatementHandler,这个对应JDBC中CallableStatement,用于执行存储过程相关的接口;
  • RoutingStatementHandler,这个接口是以上三个接口的路由,没有实际操作,只是负责上面三个StatementHandler的创建及调用。

ResultSetHandler

就是将Statement实例执行之后返回的ResultSet结果集转换成我们需要的List结果集

一级缓存与二级缓存区别

  • 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(sqlHashMap)是互相不影响的。
  • 二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
  • 注意:sqlSession缓存底层存在线程安全问题。

你可能感兴趣的:(mybatis,mybatis,一级缓存,二级缓存,源码)