一、动态加载:

resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能。

需求:

如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足要求,当需要查询用户信息时再查询用户信息。把对用户信息的按需去查询就是延迟加载。

需要先说明下是按照这个sql的思路来实现延迟加载的:

mysql> select orders.*, (select user.username from user where orders.user_id = user.id) username from orders;
(对于这种查询,可以分成两部来理解,首先忽略整个select子查询,查出订单表中的数据,然后根据订单表的user_id执行子查询,对于一个orders的user_id,子查询只能返回一条数据,如果子查询返回多条数据则会出错,另外,每一条select子查询只能查询一个字段。)
 
mapper.xml:
<mapper namespace="com.cy.mapper.OrdersMapperCustom">
<!-- 延迟加载的resultMap -->
<resultMap type="com.cy.po.Orders" id="OrdersLazyLoadingUser">
<id column="id" property="id"></id>
<result column="user_id" property="userId"></result>
<result column="number" property="number"></result>
<result column="createtime" property="createtime"></result>
<result column="note" property="note"></result> <!-- 实现对用户信息进行延迟加载
select:指定延迟加载需要执行的statement的id(是根据user_id查询用户信息的statement)
要使用userMapper.xml中findUserById完成根据用户id(user_id)用户信息的查询,如果findUserById不在本mapper中需要前边加namespace
column:订单信息中关联用户信息查询的列,是user_id
关联查询的sql理解为:
SELECT orders.*,
(SELECT username FROM USER WHERE orders.user_id = user.id)username,
(SELECT sex FROM USER WHERE orders.user_id = user.id)sex
FROM orders
-->
<association property="user" javaType="com.cy.po.User" column="user_id" select="com.cy.mapper.UserMapper.findUserById"> </association>
</resultMap> <!-- 查询订单关联查询用户,用户信息需要延迟加载 -->
<select id="findOrdersLazyLoadingUser" resultMap="OrdersLazyLoadingUser">
select * from orders;
</select>
</mapper>

这边需要配置settings  延迟加载:

<settings>
<!-- 打开延迟加载 的开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 将积极加载改为消极加载即按需要加载 -->
<setting name="aggressiveLazyLoading" value="false"/
</settings>

mapper接口:

//查询订单关联查询用户,用户信息是延迟加载
public List<Orders> findOrdersLazyLoadingUser() throws Exception;

测试代码:

// 查询订单关联查询用户,用户信息使用延迟加载
@Test
public void FindOrdersLazyLoadingUser() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
OrdersMapperCustom ordersMapperCustom = sqlSession.getMapper(OrdersMapperCustom.class);
// 查询订单信息(单表)
List<Orders> orders = ordersMapperCustom.findOrdersLazyLoadingUser(); for(Orders order: orders){
// 执行getUser()去查询用户信息,这里实现按需加载
User user = order.getUser();
System.out.println(user);
}
sqlSession.close();
}

可以看到console输出的打印语句:

DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@20e6423f]
DEBUG [main] - ==> Preparing: select * from orders;
DEBUG [main] - ==> Parameters:
DEBUG [main] - <== Total: 3
DEBUG [main] - ==> Preparing: SELECT * FROM USER WHERE id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
------->> User [id=1, username=王五, sex=2, birthday=null, address=null]
------->> User [id=1, username=王五, sex=2, birthday=null, address=null]
DEBUG [main] - ==> Preparing: SELECT * FROM USER WHERE id=?
DEBUG [main] - ==> Parameters: 10(Integer)
DEBUG [main] - <== Total: 1
------->> User [id=10, username=张三, sex=1, birthday=Thu Jul 10 00:00:00 CST 2014, address=北京市]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@20e6423f]

二、1级缓存、2级缓存:

mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。

一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。

二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

1.一级缓存:

工作原理:

第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。

如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。

测试代码:

一级缓存不需要在配置文件中配置,mybatis默认支持一级缓存;

 // 一级缓存测试
@Test
public void testCache1() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 第一次发起请求,查询id为1的用户
User user1 = userMapper.findUserById(1);
System.out.println(user1); // 如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
// 更新user1的信息
// user1.setUsername("测试用户22");
// userMapper.updateUser(user1);
// //执行commit操作去清空缓存
// sqlSession.commit(); // 第二次发起请求,查询id为1的用户
User user2 = userMapper.findUserById(1);
System.out.println(user2); sqlSession.close();
}

一级缓存测试

2.二级缓存:

原理:

mybatis全局配置settings中默认开始二级缓存;

sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。

如果SqlSession3去执行相同 mapper下sql,执行commit提交,清空该 mapper下的二级缓存区域的数据。

sqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。

二级缓存与一级缓存区别:

二级缓存的范围更大,多个sqlSession可以共享一个UserMapper的二级缓存区域。

二级缓存区域(按namespace分)。两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同 的二级缓存区域中。

配置:

1)开启mybatis的二级缓存:

<settings>
<!-- 打开延迟加载 的开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 将积极加载改为消极加载即按需要加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 开启二级缓存 默认即是开启的 -->
<setting name="cacheEnabled" value="true"/>
</settings>

2)在mapper.xml中开启二级缓存:

<mapper namespace="com.cy.mapper.UserMapper">

    <!-- 开启本mapper的namespace下的二级缓存 -->
<cache/> ....
</mapper>

3) pojo类实现序列化接口:

为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定在内存。

public class User implements Serializable{
//属性名和数据库表的字段对应
private int id;
private String username;// 用户姓名
private String sex;// 性别
private Date birthday;// 生日
private String address;// 地址
.....
}

测试代码:

1.先单纯的测试跨sqlSession级别的二级缓存,查看发出的sql语句:

     // 二级缓存测试
@Test
public void testCache2() throws Exception {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession(); UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
// 第一次发起请求,查询id为1的用户
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
//这里执行关闭操作,将sqlsession中的数据写到二级缓存区域
sqlSession1.close(); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
// 第二次发起请求,查询id为1的用户
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
sqlSession2.close();
}

看console:

DEBUG [main] - Cache Hit Ratio [com.cy.mapper.UserMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 305414881.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@123442e1]
DEBUG [main] - ==> Preparing: SELECT * FROM USER WHERE id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
------->> User [id=1, username=王五, sex=2, birthday=null, address=null]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@123442e1]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@123442e1]
DEBUG [main] - Returned connection 305414881 to pool.
DEBUG [main] - Cache Hit Ratio [com.cy.mapper.UserMapper]: 0.5
------->> User [id=1, username=王五, sex=2, birthday=null, address=null]

2.sqlSession3的commit操作,清空二级缓存,再看发出的sql语句:

// 二级缓存测试
@Test
public void testCache2() throws Exception {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession(); UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
// 第一次发起请求,查询id为1的用户
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
//这里执行关闭操作,将sqlsession中的数据写到二级缓存区域
sqlSession1.close(); //使用sqlSession3执行commit()操作
UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
User user = userMapper3.findUserById(1);
user.setUsername("张明明");
userMapper3.updateUser(user);
//执行提交,清空UserMapper下边的二级缓存
sqlSession3.commit();
sqlSession3.close(); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
// 第二次发起请求,查询id为1的用户
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
sqlSession2.close();
}

看console:

DEBUG [main] - Cache Hit Ratio [com.cy.mapper.UserMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 1795834361.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6b0a41f9]
DEBUG [main] - ==> Preparing: SELECT * FROM USER WHERE id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
------->> User [id=1, username=王五, sex=2, birthday=null, address=null]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6b0a41f9]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@6b0a41f9]
DEBUG [main] - Returned connection 1795834361 to pool.
DEBUG [main] - Cache Hit Ratio [com.cy.mapper.UserMapper]: 0.5
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 1795834361 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6b0a41f9]
DEBUG [main] - ==> Preparing: update user set username=?,birthday=?,sex=?,address=? where id=?
DEBUG [main] - ==> Parameters: 张明明(String), null, 2(String), null, 1(Integer)
DEBUG [main] - <== Updates: 1
DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@6b0a41f9]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6b0a41f9]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@6b0a41f9]
DEBUG [main] - Returned connection 1795834361 to pool.
DEBUG [main] - Cache Hit Ratio [com.cy.mapper.UserMapper]: 0.3333333333333333
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 1795834361 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6b0a41f9]
DEBUG [main] - ==> Preparing: SELECT * FROM USER WHERE id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
------->> User [id=1, username=张明明, sex=2, birthday=null, address=null]

关于二级缓存的一些配置参数:

1.useCache配置:

在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true

<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">

2.flushCache清空缓存:--一般执行完commit都需要刷新缓存(清空),这个默认true就行。

在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。

设置statement配置中的flushCache="true" 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。

<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">

三、mybatis整合ehcache:

ehcache是一个分布式缓存框架。

系统为了提高系统并发,性能、一般对系统进行分布式部署(集群部署方式)

分布式缓存原理:

不使用分布缓存,缓存的数据在各各服务单独存储,不方便系统 开发。所以要使用分布式缓存对缓存数据进行集中管理。

mybatis无法实现分布式缓存,需要和其它分布式缓存框架进行整合。

整合过程:

1)mybatis提供了cache接口,如果要实现自己的缓存逻辑,实现cache接口即可(mybatis自己实现的cache:public class PerpetualCache implements Cache )

加入ehcache包:ehcache-core.jar 和mybatis-ehcache.jar:

2)配置mapper.xml,使用ehcache的实现cache的实现类:

 <mapper namespace="com.cy.mapper.UserMapper">

     <!-- 开启本mapper的namespace下的二缓存
type:指定cache接口的实现类的类型,mybatis默认使用PerpetualCache
要和ehcache整合,需要配置type为ehcache实现cache接口的类型
-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
....
</mapper>

3)加入ehcache的配置文件

在config source目录下加入ehcache.xml:

 <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="F:\develop\ehcache" />
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>

测试代码:

    // 二级缓存测试
@Test
public void testCache2() throws Exception {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession(); UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
sqlSession1.close(); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
sqlSession2.close();
}

看console打印,使用了缓存:

DEBUG [main] - Cache Hit Ratio [com.cy.mapper.UserMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 1909716601.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@71d3f679]
DEBUG [main] - ==> Preparing: SELECT * FROM USER WHERE id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
------->> User [id=1, username=张明明, sex=2, birthday=null, address=null]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@71d3f679]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@71d3f679]
DEBUG [main] - Returned connection 1909716601 to pool.
DEBUG [main] - Cache Hit Ratio [com.cy.mapper.UserMapper]: 0.5
------->> User [id=1, username=张明明, sex=2, birthday=null, address=null]

四、mybatis二级缓存的应用场景和局限性:

应用场景:

对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。

实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。

局限性:

mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。

Mybatis学习(6)动态加载、一二级缓存的更多相关文章

  1. js的动态加载、缓存、更新以及复用(四)

    本来想一气呵成,把加载的过程都写了,但是卡着呢,所以只好在分成两份了. 1.页面里使用<script>来加载 boot.js . 2.然后在boot.js里面动态加载 bootLoad.j ...

  2. js的动态加载、缓存、更新以及复用(二)

    上一篇发出来后得到了很多回复,在此首先感谢大家的热情捧场!有的推荐第三方框架,比如 In.js.requrieJS.sea.js.lab.js等.这个开阔了眼界,以前只知道sea.js,省去了自己搜索 ...

  3. js的动态加载、缓存、更新以及复用(一)

    使用范围: OA.MIS.ERP等信息管理类的项目,暂时不考虑网站. 遇到的问题: 完成一个项目,往往需要引用很多js文件,比如jQuery.js.easyUI等.还有自己写的一些列js文件,那么这些 ...

  4. js的动态加载、缓存、更新以及复用

    使用范围: OA.MIS.ERP等信息管理类的项目,暂时不考虑网站. 遇到的问题: 完成一个项目,往往需要引用很多js文件,比如jQuery.js.easyUI等.还有自己写的一些列js文件,那么这些 ...

  5. [WPF学习笔记]动态加载XAML

    好久没写Blogs了,现在在看[WPF编程宝典],决定开始重新写博客,和大家一起分享技术. 在编程时我们常希望界面是动态的,可以随时变换而不需要重新编译自己的代码. 以下是动态加载XAML的一个事例代 ...

  6. 从高德 SDK 学习 Android 动态加载资源

    前不久跑去折腾高德 SDK 中的 HUD 功能,相信用过该功能的用户都知道 HUD 界面上的导航转向图标是动态变化的.从高德官方导航 API 文档中 AMapNaviGuide 类的描述可知,导航转向 ...

  7. JS学习之动态加载script和style样式

    前提:我们可以把一个网页里面的内容理解为一个XML或者说网页本身也就是一个XML文档,XML文档都有很特殊的象征:"标签"也叫"节点".我们都知道一个基本的网页 ...

  8. Android学习——Fragment动态加载

    动态加载原理 利用FragmentManager来添加一套Fragment事务,最后通过commit提交该事务来执行对Fragment的相关操作. FragmentManager fragmentma ...

  9. 学习Tomcat动态加载JSP的Class类

    今天在修改项目一个JSP文件时,突然想到Tomat是怎么实现动态实时加载JSP编译后的class类的? 查了半天资料,看了很多文章,终于明白是怎么回事了:ClassLoader,当tomcat发现js ...

  10. js的动态加载、缓存、更新以及复用(三)

    总体思路 1.  建立一个js服务,该服务实现通用js文件的加载.依赖.缓存.更新以及复用. 2.  各个项目如果使用通用js,可(bi)以(xu)使用js服务实现加载. 3.  Js服务只提供通用的 ...

随机推荐

  1. 项目中常用js方法整理common.js

    抽空把项目中常用js方法整理成了common.js,都是网上搜集而来的,大家一起分享吧. var h = {}; h.get = function (url, data, ok, error) { $ ...

  2. iptables配置vsftp访问

    一.FTP服务简介    FTP服务器有两种工作模式:主动模式和被动模式.这两种方式的特点如下:  (1)主动模式下:  tcp, 20(20号端口用于数据传输),21(21号端口用于控制连接) (2 ...

  3. 【Android】获取手机中已安装apk文件信息(PackageInfo、ResolveInfo)(应用图片、应用名、包名等)

    众所周知,通过PackageManager可以获取手机端已安装的apk文件的信息,具体代码如下 PackageManager packageManager = this.getPackageManag ...

  4. i2c的时钟延展问题(转)

    源:http://blog.csdn.net/zyboy2000/article/details/7636769 结论: (即在模拟i2c主:在主设置SCL为高后,要超时判断SCL是否为高,再发后面的 ...

  5. ASP.NET 的内置对象

    ASP.NET的内置对象介绍 1.Response 2.Request 3.Server 4.Application 5.Session 6.Cookie Request对象主要是让服务器取得客户端浏 ...

  6. call_grant_exec.sql

    set echo off feedback off verify off pagesize 0 linesize 120 define v_grantee                 = & ...

  7. WeakSelf和StrongSelf

    转载自:http://sherlockyao.com/blog/2015/08/08/weakself-and-strongself-in-blocks/ 现在我们用 Objective-C 写代码时 ...

  8. 在 WinCe 平台读写 ini 文件

    在上篇文章开发 windows mobile 上的今日插件时,我发现 wince 平台上不支持例如 GetPrivateProfileString 等相关 API 函数.在网络上我并没有找到令我满意的 ...

  9. ftok函数的使用

    ftok函数的定义:系统建立IPC通讯 (消息队列.信号量和共享内存) 时必须指定一个ID值.通常情况下,该id值通过ftok函数得到. 头文件 #include <sys/types.h> ...

  10. Permission denied: user=xxj, access=WRITE, inode="user":hadoop:supergroup:rwxr-xr-x

    在windows中运行eclipse时报错Permission denied: user=xxj, access=WRITE, inode="user":hadoop:superg ...