mybatis懒加载与缓存

在接触mybatis时我们会学到接触小知识,关于懒加载的一些知识点还是需要亲自去测试一下才能加深理解;

mybatis懒加载与缓存

  • mybatis对缓存的支持
    • mybatis一级缓存
    • mybatis的二级缓存---->>>
    • Mybatis整合第三方缓存框架
      • 分布式框架的使用----ehcache:

在这之前问我们都接触过关于时间和空间局部性原理,在这里不做多说;
接下来是案例的实际操作过程---->>
首先准备两个关联的表,还是熟悉的表,还是熟悉的数据—

商品信息与订单信息表

DROP TABLE IF EXISTS `product`;
CREATE TABLE `product`  (
  `pid` int(0) NOT NULL AUTO_INCREMENT,
  `pname` varchar(25) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `price` double NULL DEFAULT NULL,
  PRIMARY KEY (`pid`) USING BTREE,
  CONSTRAINT `qwe` FOREIGN KEY (`pid`) REFERENCES `ordersdetail` (`productId`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of product
-- ----------------------------
INSERT INTO `product` VALUES (1, 'JavaWeb', 128);
INSERT INTO `product` VALUES (2, 'C##', 138);
INSERT INTO `product` VALUES (3, 'Python', 132.35);





SET FOREIGN_KEY_CHECKS = 1;
DROP TABLE IF EXISTS `ordersdetail`;
CREATE TABLE `ordersdetail`  (
  `odid` int(0) NOT NULL AUTO_INCREMENT,
  `orderId` int(0) NULL DEFAULT NULL,
  `productId` int(0) NULL DEFAULT NULL,
  PRIMARY KEY (`odid`) USING BTREE,
  INDEX `WER`(`productId`) USING BTREE,
  CONSTRAINT `WER` FOREIGN KEY (`productId`) REFERENCES `product` (`pid`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of ordersdetail
-- ----------------------------
INSERT INTO `ordersdetail` VALUES (1, 1, 1);
INSERT INTO `ordersdetail` VALUES (2, 2, 2);
INSERT INTO `ordersdetail` VALUES (3, 3, 3);

SET FOREIGN_KEY_CHECKS = 1;

接口信息---->>


package com.gavin.mapper;

import com.gavin.pojo.Ordersdetail;


public interface OrdersdetailDao {

  Ordersdetail selectOrderDetail (int oidd);
}
package com.gavin.mapper;

public interface ProductDao {
   Product  selectProduct (int pid);

}

mapper映射


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gavin.mapper.ProductDao">

  <select id="selectProduct" resultType="com.gavin.pojo.Product" parameterType="int">
select * from product where pid =#{pid}
  select>
mapper>

DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gavin.mapper.OrdersdetailDao">

  <resultMap id="OrdersDetailRef" type="ordersdetail">
    <id column="odid" property="odid"/>
    <result column="orderid" property="orderid"/>
    <result column="productid" property="productid"/>
    <association property="product" javaType="product" select="com.gavin.mapper.ProductDao.selectProduct" column="productid">
      <id property="pid" column="pid"/>
      <result property="pname" column="pname"/>
      <result property="price" column="price"/>
    association>
  resultMap>


  <select id="selectOrderDetail" resultMap="OrdersDetailRef" >
    select * from ordersdetail where odid =#{oidd}

  select>
mapper>

解决sql语句爆红的小插曲

懒加载是对于多表查询时而言的,查询一张表时也会顺带查询关联的表----如果不开启懒加载;

所以在配置订单信息表映射是要注意一下;

在配置文件中设置一下懒加载的方式

mybatis懒加载与缓存_第1张图片

开始测试---->>>>

//测试懒加载
  @Test
    public void test() {
        OrdersdetailDao mapper = sqlSession.getMapper(OrdersdetailDao.class);
        Ordersdetail ordersdetail = mapper.selectOrderDetail(1);
        //System.out.println(ordersdetail);
        sqlSession.close();
//商品信息表
  /*  System.out.println("订单编号:"+ordersdetail.getOdid());
    System.out.println("订单号:"+ordersdetail.getOrderid());
    //商品表
    System.out.println("商品名---"+ordersdetail.getProduct().getPname());*/
    }

部分结果一----

lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。
aggressiveLazyLoading 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。 即只有在查询属性时积极懒加载才会执行sql;

mybatis懒加载与缓存_第2张图片

mybatis对缓存的支持

缓存是一种用空间换时间的一种方式,MyBatis 内置了一个强大的事务性查询缓存机制,默认情况下,只启用了本地的session缓存,即一级缓存,它仅仅对在一个session的数据进行缓存。 如果要启用全局的二级缓存,需要在你的mybatis配置文件以及 SQL 映射文件
中进行配置;

映射语句文件中的所有 select 语句的结果将会被缓存。

映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存

缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。

缓存不会定时进行刷新(也就是说,没有刷新间隔)。

缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。

缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

mybatis提供了对缓存的支持,在mybatis配置文件中setting标签下提供了很多关于mybatis的配置,关于缓存的一些参数配置—>>

catcheEnable----
全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。
默认false

localCacheScope----
MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。

一级缓存是基于perpetualCatche的hashMap本地缓存,其作用阈为Session,当session flush或者close后缓存就被清空;

mybatis默认开启了一级缓存 ,即在一个sqlsession中,执行相同的sql,那么第一次查询时会从数据据取数据,第二次查询时直接从缓存中取,当执行sql查询时中间发生了增删改操作,或者flush/close操作,那么sqlsession缓存会被清空

二级缓存作用域为namespace,可自定义存储源;

mybatis一级缓存

测试mybatis缓存------->>

为方便起见,测试案例用的为上面的商品表;

  public void test2() {
        ProductDao mapper = sqlSession.getMapper(ProductDao.class);
        Product product = mapper.selectProduct(1);
        System.out.println(product.getPid() + "--" + product.getPname() + "--" + product.getPrice());

        System.out.println("------分割线------------");

        Product product2 = mapper.selectProduct(1);
        System.out.println(product.getPid() + "--" + product.getPname() + "--" + product.getPrice());
       // sqlSession.close();
    }

在没有关闭session的情况下,查询同一条数据两次
中间没有增删改操作,也没有刷新和关闭操作
mybatis懒加载与缓存_第3张图片
mybatis懒加载与缓存_第4张图片

在两次之间做增加数据操作----->>>

再插入数据

mybatis懒加载与缓存_第5张图片

在操作中发现在插入数据时,如果没有提交,也会清除缓存,使得查询相同数据的sql语句执行两次,

如果不插入数据,在两次查询之间只增加一次commit操作,
mybatis懒加载与缓存_第6张图片

下面我们在sql语句中配置----->>>
mybatis懒加载与缓存_第7张图片
在不同的session中查询同一数据
mybatis懒加载与缓存_第8张图片

小结----->>
在相同的查询操作之间 刷新缓存,提交, 增删改操作都会使得一级缓存清空;

一级缓存作用范围在同一个session中;
mybatis懒加载与缓存_第9张图片

要想在多个session中共享数据,那么就需要开启二级缓存;

mybatis的二级缓存---->>>

在全局配置文件中,二级缓存默认是开启的,要使其生效需要对每个Mapper进行配置;

在配置二级缓存之前,先要下载两个jar,在maven中直接引入就可以了

 
        <dependency>
            <groupId>org.mybatis.cachesgroupId>
            <artifactId>mybatis-ehcacheartifactId>
            <version>1.2.1version>
        dependency>
        
        <dependency>
            <groupId>net.sf.ehcachegroupId>
            <artifactId>ehcacheartifactId>
            <version>2.10.9.2version>
        dependency>

之后在mybatis全局配置文件中开启 二级缓存—>>>>
mybatis懒加载与缓存_第10张图片
但这还没结束,还需要在想要开启二级缓存的sql语句中开启二级缓存


    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
    <select id="selectProduct" resultType="com.gavin.pojo.Product" parameterType="int" flushCache="false" statementType="PREPARED" useCache="false">
select * from product where pid =#{pid}
  select>

测试二级缓存-----
首先创建两个session

@Test
    public void test2() {
        SqlSession sqlSession = factory.openSession();
        SqlSession sqlSession1 = factory.openSession();
        ProductDao mapper = sqlSession.getMapper(ProductDao.class);
        ProductDao mapper1 = sqlSession1.getMapper(ProductDao.class);

        System.out.println(mapper.selectProduct(1));
        sqlSession.close();

        System.out.println(mapper1.selectProduct(1));

        sqlSession1.close();
    }

运行结果---->>

mybatis懒加载与缓存_第11张图片

注意:
如果没有在sql中开启useCache 即useCache =false,那么即使在全局配置中开了二级缓存,在sql查询时也不会开启二级缓存;

另一个,如果两此查询时在最后关闭了session,那么也会用两次sql穿语句;

例如

mybatis懒加载与缓存_第12张图片

小结----
1,在全局配置文件中开启二级缓存之后,还需要在相应sql中再次开启 缓存才能生效,但是这还没完全开启,需要指定缓存的类型,即用什么类型的类取处理缓存;mybatis懒加载与缓存_第13张图片

2,session关闭顺序会影响二级缓存的是否生效;

mybatis懒加载与缓存_第14张图片

如果不想让某条sql语句使用二级缓存—
可以在该sql配置 useCache="false"

类似问题-----.>>>如和让一级缓存失效?
可以在该sql配置 flushCache="true"

或者将本地缓存—一级缓存作用域设置为 statament

 <setting name="localCacheScope" value="STATEMENT"/>**

小结----
开启二级缓存的步骤

1,开启二级缓存----可以显示的在mybatis全局配置文件按中声明,即使不声明,也默认为true—开启二级缓存

   <setting name="cacheEnabled" value="true"/>

2,在对应的映射文件中添加配置

cache有两种配置方式------->>>

第一种:----->>
mybatis懒加载与缓存_第15张图片
这个时候Mybatis会按照默认配置创建一个Cache对象,
此时创建的是PerpetualCache对象,这种缓存方式最多只能存1024个元素

也可去指定缓存的对象类型

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

第二种是通过引用的方式

mybatis懒加载与缓存_第16张图片
这个时候两个mapper共享一个缓存空间;

既然可以指定缓存类型,那么就可以自定义缓存,先不着急自定义;有那么多已经写好的优秀插件为什么还要自定义?

先不管那些,大体工作原理--------Cache接口, 通过Id来获得对用的缓存对象;
mybatis懒加载与缓存_第17张图片
为了保证一对一的关系,缓存底层通过map来实现的;

找到其实现类-----有很多,

如果要自定义,那么首先要了解缓存的是怎样工作的;

1, XMLMappedBuilder来 解析 Mapper 中的 缓存标签

2,通过 builderAssistant 对象来调用 addMappedStatement 方法,在设置 cache 信息到 MappedStatement 对象内;
通过逆向探索可以获得缓存对象
在这里插入图片描述
3,CachingExecutor 对象的 query 方法先从 MappedStatement 对象中 getCache() 获取缓存的对象,如果没有查到则到 BaseExecutor 中查询,走本地缓存逻辑,在查不到,就要从数据库中查找了

二级缓存一般是不建议开启的,因为在高并发情况下,很容易发生数据与数据库数据不一致的情况;
另一个由于二级缓存可以存储在内存中,也可以持久化到本地,因此需要实现序列化接口;

Mybatis整合第三方缓存框架

大数据时代,要求系统在高并发下的性能要有所提高,mybatis自带缓存不适用与分布式缓存,所以要使用分布式缓存框架来对缓存实施数据管理;

上例提到的分布式框架缓存----ehcache是比较常用的一个,除此之外还有redis,memcache等;

ehcache直接在jvm虚拟机中缓存,速度快,效率高;但是缓存共享麻烦,集群分布式应用不方便。

redis是通过socket访问到缓存服务,效率比ecache低,比数据库要快很多,处理集群和分布式缓存方便,有成熟的方案。

如果是单个应用或者对缓存访问要求很高的应用,用ehcache。
如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。

分布式框架的使用----ehcache:

一种广泛使用的开源java分布式框架,主要面向缓存,
可已将缓存数据存放于内存和磁盘;

分布式框架的使用

首先在maven中引入依赖—

    
        <dependency>
            <groupId>org.mybatis.cachesgroupId>
            <artifactId>mybatis-ehcacheartifactId>
            <version>1.2.1version>
        dependency>
        
        <dependency>
            <groupId>net.sf.ehcachegroupId>
            <artifactId>ehcacheartifactId>
            <version>2.10.9.2version>
        dependency>

然后在核心配置文件中开启二级缓存;
mybatis懒加载与缓存_第18张图片
然后在映射文件中开启该缓存框架

mybatis懒加载与缓存_第19张图片

再然后----由于二级缓存可以存在内存或者持久化的存在本地,所以对应的实体类要实现序列化,这一步要看在实体类的开发过程中有有没有实现序列化,如果有,则这一步可以省略;

在然后配置ehcache的配置文件,文件名必须是ehcache.

ehcache的配置文件配置文件详解----->>


<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false">
    <diskStore path="D:\ehcache\temporary"/>








    

    





   



    <defaultCache eternal="false" maxElementsInMemory="1000"
                  overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="0"
                  timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU" />

    <cache name="FunctionCache" eternal="false"
           maxElementsInMemory="500" overflowToDisk="false" diskPersistent="false"
           timeToIdleSeconds="3600" timeToLiveSeconds="3600"
           memoryStoreEvictionPolicy="LRU">
    cache>

    <cache name="RoleFunctionCache" eternal="false"
           maxElementsInMemory="500" overflowToDisk="false" diskPersistent="false"
           timeToIdleSeconds="3600" timeToLiveSeconds="3600"
           memoryStoreEvictionPolicy="LRU">
    cache>

    <cache name="ModelDefCache" eternal="false"
           maxElementsInMemory="500" overflowToDisk="false" diskPersistent="false"
           timeToIdleSeconds="3600" timeToLiveSeconds="3600"
           memoryStoreEvictionPolicy="LRU">
    cache>

    <cache name="SysNoConfigCache" eternal="false"
           maxElementsInMemory="500" overflowToDisk="false" diskPersistent="false"
           timeToIdleSeconds="3600" timeToLiveSeconds="3600"
           memoryStoreEvictionPolicy="LRU">
    cache>

    <cache name="MesProdTimeCache" eternal="false"
           maxElementsInMemory="500" overflowToDisk="false" diskPersistent="false"
           timeToIdleSeconds="3600" timeToLiveSeconds="3600"
           memoryStoreEvictionPolicy="LRU">
    cache>

ehcache>

测试代码


    @Test
    public void test4() {
        SqlSession sqlSession = factory.openSession();
        SqlSession sqlSession1 = factory.openSession();

        ProductDao mapper = sqlSession.getMapper(ProductDao.class);
        ProductDao mapper1 = sqlSession1.getMapper(ProductDao.class);

        Product product = mapper.selectProduct(1);
        System.out.println(product.hashCode());

       sqlSession.close();

        Product product1 = mapper1.selectProduct(1);

        System.out.println(product1.hashCode());

        sqlSession1.close();
    }

mybatis懒加载与缓存_第20张图片

同时我们在指定位置也可看到相应的文件
mybatis懒加载与缓存_第21张图片

你可能感兴趣的:(mybatis,mybatis)