0
点赞
收藏
分享

微信扫一扫

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

<?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语句爆红的小插曲​​

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

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

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

mybatis懒加载与缓存_mybatis

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

//测试懒加载
@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懒加载与缓存_sql_02

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懒加载与缓存_缓存_03


mybatis懒加载与缓存_缓存_04

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

再插入数据

mybatis懒加载与缓存_缓存_05

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

如果不插入数据,在两次查询之间只增加一次commit操作,

mybatis懒加载与缓存_sql_06

下面我们在sql语句中配置----->>>

mybatis懒加载与缓存_二级缓存_07


在不同的session中查询同一数据

mybatis懒加载与缓存_sql_08

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

一级缓存作用范围在同一个session中;

mybatis懒加载与缓存_缓存_09

要想在多个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全局配置文件中开启 二级缓存—>>>>

mybatis懒加载与缓存_二级缓存_10


但这还没结束,还需要在想要开启二级缓存的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();
}

运行结果---->>

mybatis懒加载与缓存_sql_11

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

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

例如

mybatis懒加载与缓存_mybatis_12

小结----

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

mybatis懒加载与缓存_sql_13

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

mybatis懒加载与缓存_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来实现的;找到其实现类-----有很多,

mybatis懒加载与缓存_二级缓存_18

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

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

2,通过 builderAssistant 对象来调用 addMappedStatement 方法,在设置 cache 信息到 MappedStatement 对象内;

通过逆向探索可以获得缓存对象

mybatis懒加载与缓存_二级缓存_19


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>

然后在核心配置文件中开启二级缓存;

mybatis懒加载与缓存_二级缓存_20


然后在映射文件中开启该缓存框架

mybatis懒加载与缓存_二级缓存_21

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

在然后配置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();
}

mybatis懒加载与缓存_mybatis_22

同时我们在指定位置也可看到相应的文件

mybatis懒加载与缓存_二级缓存_23


举报

相关推荐

0 条评论