mybatis懒加载与缓存

本文深入探讨MyBatis的懒加载机制,通过实例展示了如何配置和测试懒加载。同时,详细解析了MyBatis的一级缓存和二级缓存,并给出了开启和配置二级缓存的步骤,包括使用Ehcache作为二级缓存的实现。文章还提及了缓存的更新策略和生命周期,以及在高并发场景下考虑使用分布式缓存如Redis的原因和选择。
摘要由CSDN通过智能技术生成

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

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

商品信息与订单信息表

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映射

<?xml version="1.0" encoding="UTF-8"?>
<!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>
<?xml version="1.0" encoding="UTF-8"?>
<!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语句爆红的小插曲

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

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

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

在这里插入图片描述

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

//测试懒加载
  @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对缓存的支持

缓存是一种用空间换时间的一种方式,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的情况下,查询同一条数据两次
中间没有增删改操作,也没有刷新和关闭操作
在这里插入图片描述
在这里插入图片描述

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

再插入数据

在这里插入图片描述

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

如果不插入数据,在两次查询之间只增加一次commit操作,
在这里插入图片描述

下面我们在sql语句中配置----->>>
在这里插入图片描述
在不同的session中查询同一数据
在这里插入图片描述

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

一级缓存作用范围在同一个session中;
在这里插入图片描述

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

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

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

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

 <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.2.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache -->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.10.9.2</version>
        </dependency>

之后在mybatis全局配置文件中开启 二级缓存—>>>>
在这里插入图片描述
但这还没结束,还需要在想要开启二级缓存的sql语句中开启二级缓存

<!--useCache 使得二级缓存生效  将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。-->
    <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();
    }

运行结果---->>

在这里插入图片描述

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

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

例如

在这里插入图片描述

小结----
1,在全局配置文件中开启二级缓存之后,还需要在相应sql中再次开启 缓存才能生效,但是这还没完全开启,需要指定缓存的类型,即用什么类型的类取处理缓存;在这里插入图片描述

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

在这里插入图片描述

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

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

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

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

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

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

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

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

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

第一种:----->>
在这里插入图片描述
这个时候Mybatis会按照默认配置创建一个Cache对象,
此时创建的是PerpetualCache对象,这种缓存方式最多只能存1024个元素

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

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

第二种是通过引用的方式

在这里插入图片描述
这个时候两个mapper共享一个缓存空间;

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

先不管那些,大体工作原理--------Cache接口, 通过Id来获得对用的缓存对象;
在这里插入图片描述
为了保证一对一的关系,缓存底层通过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中引入依赖—

    <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.2.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache -->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.10.9.2</version>
        </dependency>

然后在核心配置文件中开启二级缓存;
在这里插入图片描述
然后在映射文件中开启该缓存框架

在这里插入图片描述

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

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

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

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false">
    <diskStore path="D:\ehcache\temporary"/>
<!--    默认缓存策略-->
<!-- name 缓存名-->
<!--    maxElementsInMemory:缓存最大数目-->
<!--    maxElementsOnDisk:硬盘最大缓存个数。-->
<!--    eternal:对象是否永久有效,一但设置了,timeout将不起作用。-->
<!--    overflowToDisk:是否保存到磁盘,当系统当机时-->

<!--    timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。
仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。-->
    
<!--    timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。
最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。-->
    
<!--    diskPersistent:是否缓存虚拟机重启期数据 -->

<!--    diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。
             每个Cache都应该有自己的一个缓冲区。-->

<!--    diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。-->
   
<!--    memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,
   Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。-->
<!--    clearOnFlush:内存数量最大时是否清除。-->
<!--    memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
    FIFO,first in first out,这个是大家最熟的,先进先出。
    LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。
    如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
    LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,
    当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。-->
    <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();
    }

在这里插入图片描述

同时我们在指定位置也可看到相应的文件
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeMartain

祝:生活蒸蒸日上!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值