在接触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>
懒加载是对于多表查询时而言的,查询一张表时也会顺带查询关联的表----如果不开启懒加载;
所以在配置订单信息表映射是要注意一下;
在配置文件中设置一下懒加载的方式
开始测试---->>>>
//测试懒加载
@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();
}
同时我们在指定位置也可看到相应的文件