如果刚开始仅仅查询一些简单的数据能够 满足用户的需求时,只需要查询简单的数据(尽量从单表中取数据),当用户需要查询关联信息时,此时我们再关联查询关联信息,叫延迟加载,对关联查询的信息进行延迟加载。
延迟加载其目的为了提高查询性能,减轻数据的压力。
查询订单关联查询用户信息,对关联查询用户进行延迟加载。
定义两个mapper:
1、查询订单列表(只从订单表查询),单表查询
2、根据用户id查询用户信息。
service调用第一个mapper查询出订单信息在页面显示出来
当用户在页面点击“查看用户”链接时通过service调用第二个mapper查询用户信息。
resultMap实现延迟加载
打开延迟加载的开关
在SqlMapConfig.xml中配置:
sql语句
查询订单的sql:
单表查询:
select * from orders;
查询用户信息:
根据用户id查询用户信息
select * from user where id=?
mapper.xml
mapper.java
测试延迟加载
resultMap提供延迟加载,通过association可以延迟加载一个对象,通过collection可以延迟加载多个对象即集合对象。
延迟加载要注意实现的方法多种多样,主要目的是提高查询性能,减轻数据库压力,先查询简单数据,按 需查询关联数据。
什么是查询缓存?
将从数据库查询的数据存储到内存中缓存起来,这样就不用从数据库中查询数据而从缓存中查询,提高查询的速度,减少对数据库的访问。
mybatis提供查询缓存包括一级缓存、和二级缓存。
一级缓存是针对每一个sqlSession进行缓存。每个sqlSession对象中使用Map存储一级缓存数据,sqlSession对象销毁其中一级缓存数据不存在了。sqlSession与SqlSession之间的一级缓存互相不影响。
map中存储了sql执行查询的结果集(java对象)。
二级缓存是针对每个mapper相同 的namespace进行缓存。每个SqlSession都要调用mapper下的sql语句,在mapper级别设置了二级缓存的数据结构map,每个mapper对应一个map数据结构,map中存储了二级缓存的数据,存储了sql执行查询的结果集(java对象)。
每个SqlSession都可以访问到二级缓存中的数据,sqlsession对象销毁mapper中的二级缓存数据仍然存在。
一级缓存运行原理:
第一次查询先去缓存中找是否有缓存数据,发现没有,查询数据库,将查询到的数据写入sqlsession的一级缓存区域。
第二次查询先去缓存中找是否有缓存数据,发现有,直接从缓存区域中取出数据返回。
如果 执行sqlsession的添加、修改、删除等操作,会执行commit,最终会清空缓存。
二级缓存原理:
不同的sqlsession都要调用mapper下的sql语句发起数据库请求。
sqlsession1执行UserMapper下的查询用户请求先从二级缓存中查找有没有数据,如果没有就从数据库中查询,并且将查询到数据存储二级缓存中。
sqlsession2执行UserMapper下的同一个查询用户请求,先从二级缓存中查找有没有数据,如果有就从二级缓存中查询数据,返回。
如果有一个sqlsession3执行UserMapper下添加、修改、删除语句,执行commit操作后,将UserMapper下的所有缓存数据全部清空。
当两次查询相同 的sql时,第二次查询要缓存中查询,而不从数据库查询。
注意:
1、相同 的sql定义:相同 mapper namespace下的相同 的statement下的sql,且sql的输入参数必须一致。
2、两次查询使用相同 的sqlsession对象。
跟踪源代码,一级缓存的区域:
当两次查询相同 的sql时,第二次查询要缓存中查询,而不从数据库查询。
注意:
1、相同 的sql定义:相同 mapper namespace下的相同 的statement下的sql,且sql的输入参数必须一致。
2、两次查询可以使用不同 的sqlsession对象。
mybatis对一级缓存是默认支持,对二级缓存需要开启。
打开总开关:
在核心配置文件SqlMapConfig.xml中加入
<setting name="cacheEnabled" value="true"/>
打开mapper的二级缓存开关:
mybatis支持使用第三方缓存框架实现二级缓存数据的存储,要求对象必须实现序列化。
每次查询都会去先查找二级缓存中是否有数据:
通过源码跟踪,二级缓存的数据结构,是一个全局的对象,不受sqlsession的影响,mybatis程序运行起来二级缓存的数据结构就创建出来,直到mybatis程序停止二级缓存数据结构销毁:
二级缓存在一个全局对象中即configruation中:
详细map数据结构如下:
测试代码:
// 二级缓存测试
@Test
public void testCache2() throws Exception {
//二级缓存是跨sqlsession的是基于mapper级别的缓存,sqlsession可以使用多个对象,多个对象必须要访问同一个mapper下的sql语句
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
SqlSession sqlSession4 = sqlSessionFactory.openSession();
// 生成代理对象
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
UserMapper mapper4 = sqlSession4.getMapper(UserMapper.class);
// 第一次查询,查询id为1的用户
// 调用mapper
User user = mapper1.findUserById(1);
System.out.println(user);
//第一次查询用户列表
List list = mapper4.findUserByName("张");
System.out.println(list);
//关闭sqlsession1,此时将数据写入二级缓存
sqlSession1.close();
sqlSession4.close();
// 中间执行commit,清空缓存,将userMapper下手所有缓存数据全部
user.setUsername("张明明");
mapper3.updateUser(user);
sqlSession3.commit();
sqlSession3.close();
// 第二次查询,查询id为1的用户,两次查询使用相同 的sqlsession
user = mapper2.findUserById(1);
System.out.println(user);
List list2 = mapper2.findUserByName("张");
System.out.println(list2);
sqlSession2.close();
sqlSession3.close();
}
可以针对每一个statement进行测试是否二级缓存,默认是进行二级缓存。
useCache=true表示要二级缓存,一般要对查询进行二级缓存。
useCache=false,查询该 statement不进行二级缓存,针对那些查询非常频繁的语句,信息的变化性较高,可以将useCache设置false不进行二级缓存。
针对insert、update、delete这些statement设置flushCache="true" 表示执行commit就清空二级缓存,如果设置flushCache="false",执行操作也不清空缓存。
一般情况下需要设置flushCache="true" ,为了避免查询出脏数据。
其它的参数
flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。
readOnly(只读)属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
如下例子:
这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。可用的收回策略有, 默认的是 LRU:
1.LRU – 最近最少使用的:移除最长时间不被使用的对象。
2.FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
3.SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
mybaits是一个持久框架,对缓存数据处理没有第三方专门做缓存框架优秀。
mybatis提供一个Cache接口供第三方框架整合。
建议使用第三方缓存框架和mybatis整合,比如:ehcache、redis、memcache。
mybatis提供二级缓存Cache接口,如下:
它的默认实现类:
第一步:引入ehcache缓存的依赖包
第二步,配置ehcache的配置文件
实现让echcache托管mybaits二级缓存数据的存取。
修改mapper.xml文件,在cache中指定EhcacheCache。
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
配置如下:
根据需求调整缓存参数:
8.4.5mybatis二级缓存应用场景
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。
mybatis二级缓存局限性
mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。