表与表之间存在三种关系,分别是一对一,一对多,多对多。关联查询就是指在查询某张表数据的同时也查询与该表关联表中的数据。而缓存是指将已经查询出的对象缓存在内存中,再次查询该对象时从内存中直接获取,不再从数据库中获取,从而提高程序的运行效率。
以订单数据模型为例,讲解关联查询和缓存。
订单数据模型中有 4 张表,表关系如下:
给出需求:
查询所有的订单分别是被哪些顾客订购的
需求分析: 将所有订单查询后,在视图上需要显示的列应包括订单号、订单时间、顾客、备注,如下图所示:
视图上显示的列中“顾客”来自于 userInfo 表,而其他列来自于 orders 表,也就是说视图上显示的数据来自于不同的表,因此需要为视图创建 VO 类,VO 类中应包含视图上要显示的所有列。
关联表:orders 与 userInfo 表关联,形成 1 对 1 关系,如下图 orders 表和 userinfo 表所示:
有两种方法创建用于视图的 VO 类,一种是使用扩展列的 VO 类,另一种是使用扩展实体的 POJO 类。
第一步:定义 VO 类
/*
* 用户订单 VO 类
* VO 中的 UserInfo 属性从父类继承
* VO 中的订单信息是类中的扩展列
*/
public class OrdersVO extends UserInfoModel implements Serializable {
//VO 中的扩展列
private int ordersId;
private int userId;
private Date createtime;
private String memo;
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
@Override
public String toString() {
return super.getId()+"--"+super.getUserName()
+"--"+super.getUserPass()
+"--"+sdf.format(super.getBirthday())
+"--"+super.getGender()
+"--"+super.getAddress()
+"--"+this.ordersId
+"--"+sdf.format(this.createtime)
+"--"+this.memo;
}
//省略 get/set 方法
}
第二步:Mapper 映射配置
<!-- 查询订单关联查询用户信息 -->
<select id="findOrderAndUserInfoVo" resultType="cn.itlaobing.mybatis.vo.OrdersUserInfoVO">
SELECT
orders.id,
orders.userid,
orders.createtime,
orders.memo,
userinfo.id,
userinfo.username,
userinfo.userPass,
userinfo.birthday,
userinfo.gender,
userinfo.address
FROM
userinfo,
orders
WHERE
userinfo.id = orders.userId
</select>
(1). Mapper Statement ID 命名为 findOrderAndUserInfoVo
(2). 映射文件的 resultType 设置为 cn.itlaobing.mybatis.vo. OrdersUserInfoVO 类型。
第三步:Mapper 接口定义
//查询订单关联查询用户信息(使用扩展列的 VO 类实现一对一)
public List<OrdersUserInfoVO> findOrderAndUserInfoVo() throws Exception;
第四步:单元测试
//查询订单关联查询用户信息(一对一查询 使用 resultType 实现)
@Test
public void testFindOrderAndUserInfoVo() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
IOrderMapper orderMapper = sqlSession.getMapper(IOrderMapper.class);
List<OrdersUserInfoVO> ordersUserInfoVOs = orderMapper.findOrderAndUserInfoVo();
sqlSession.close();
System.out.println(ordersUserInfoVOs);
}
运行结果
Preparing:SELECT orders.id, orders.userid, orders.createtime, orders.memo, userinfo.id,
userinfo.username, userinfo.userPass, userinfo.birthday, userinfo.gender, userinfo.address FROM
userinfo, orders WHERE userinfo.id = orders.userId
Parameters:
Total: 2
[
1--林冲--lichong--1982-11-10--男--河南开封--0--2017-09-21--要新鲜的,
2--林冲--lichong--1982-11-10--男--河南开封--0--2017-09-22--和上次的一样
]
第一步:定义 VO 类
/*
*用户订单 VO 类
* 将 UserInfoModel 类的对象作为 OrdersModel 类的属性,
* UserInfoModel 类的对象用于存储订单的用户信息。
*/
public class OrdersModel implements Serializable{
//订单属性
private int id;
private int userId;
private int orderId;
private Date createtime;
private String memo;
//userInfoModel 存储订单的用户信息
private UserInfoModel userInfoModel;
private SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public String toString() {
if(userInfoModel!=null) {
return userInfoModel.getId()+"--"+userInfoModel.getUserName()
+"--"+userInfoModel.getUserPass()
+"--"+sdf.format(userInfoModel.getBirthday())
+"--"+userInfoModel.getGender()
+"--"+userInfoModel.getAddress()
+"--"+this.orderId
+"--"+sdf.format(this.createtime)
+"--"+this.memo;
}else {
return "--"+this.orderId
+"--"+sdf.format(this.createtime)
+"--"+this.memo;
}
}
//省略 get/set 方法
}
第二步:Mapper 映射配置
<select id="findOrderAndUserInfo" resultMap="OrderAndUserInfoResultMap">
SELECT
orders.id orders_id,
orders.userid orders_userid,
orders.createtime orders_createtime,
orders.memo orders_memo,
userinfo.id userinfo_id,
userinfo.username userinfo_username,
userinfo.userPass userinfo_userPass,
userinfo.birthday userinfo_birthday,
userinfo.gender userinfo_gender,
userinfo.address userinfo_address
FROM
userinfo,
orders
WHERE
userinfo.id = orders.userId
select>
第三步:resultMap 定义
<resultMap id="OrderAndUserInfoResultMap" type="cn.itlaobing.mybatis.model.OrdersModel">
<id property="id" column="orders_id"/>
<result property="userId" column="orders_userid" />
<result property="createtime" column="orders_createtime" />
<result property="memo" column="orders_memo" />
<association property="userInfoModel" javaType="cn.itlaobing.mybatis.model.
UserInfoModel">
<id property="id" column="userinfo_id"/>
<result property="userName" column="userinfo_username"/>
<result property="userPass" column="userinfo_userPass"/>
<result property="birthday" column="userinfo_birthday"/>
<result property="gender" column="userinfo_gender"/>
<result property="address" column="userinfo_address"/>
association>
resultMap>
(1). association 标签表示关联查询,即订单关联用户
(2). association 标签的 property 属性表示 resultMap 标签中 type 指定的类的属性名称。本例中的值是 userInfoModel,即子表实体类中父表的对象名称。
(3). association 标签的 javaType 属性表示 property 属性中存储的类型。
第四步:Mapper 接口定义
//查询订单关联查询用户信息,(一对一查询 使用 resultMap 实现)
public List<OrdersModel> findOrderAndUserInfo() throws Exception;
第五步:单元测试
//查询订单关联查询用户信息,(一对一查询 使用 resultMap 实现)
@Test
public void testFindOrderAndUserInfo() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
IOrderMapper orderMapper = sqlSession.getMapper(IOrderMapper.class);
List<OrdersModel> ordersModels = orderMapper.findOrderAndUserInfo();
sqlSession.close();
System.out.println(ordersModels);
}
运行结果
Preparing: SELECT orders.id orders_id, orders.userid orders_userid, orders.createtime
orders_createtime, orders.memo orders_memo, userinfo.id userinfo_id, userinfo.username
userinfo_username, userinfo.userPass userinfo_userPass, userinfo.birthday userinfo_birthday,
userinfo.gender userinfo_gender, userinfo.address userinfo_address FROM userinfo, orders
WHERE userinfo.id = orders.userId
Parameters:
Total: 2
[
2--林冲--lichong--1982-11-10--男--河南开封--1--2017-09-21--要新鲜的,
2--林冲--lichong--1982-11-10--男--河南开封--2--2017-09-22--和上次的一样
]
给出需求:查询订单及其订单明细和订单用户
需求分析: 将所有订单查询后,在视图上需要显示的列应包括订单号、订单时间、订购的商品、价格、数量、顾客,如下图所示:
一个订单中可以购买多个商品,因此订单与商品之间是一对多的关系,例如订单号为 1的用户购买了平谷大桃和油桃,订单号为 2 的订单购买的是平谷大桃和油桃。本例中也将下单用户的用户名显示在视图上。
关联表:orders 表与 orderdetail 关联,形成一对多的关系,如下图 orders 表与 orderdetail表所示:
第一步:定义 POJO
由于一个订单中可以购买多个商品,因此在订单 POJO 中添加 List 集合来存储订单中购买的商品。
public class OrdersModel implements Serializable{
private int id;
private int userId;
private int orderId;
private Date createtime= null;
private String memo= null;
private UserInfoModel userInfoModel= null;
private List<OrderdetailModel> orderdetailModels = null;
//省略 get/set 方法
}
第二步:Mapper 映射配置
第三步:resultMap 定义
<resultMap id="OrderAndOrderDetailResultMap"
type="cn.itlaobing.mybatis.model.OrdersModel" extends="OrderAndUserInfoResultMap">
<collection property="orderdetailModels" ofType="cn.itlaobing.mybatis.model.
OrderdetailModel">
<id property="id" column="orderdetail_id"/>
<result property="orderid" column="orderid"/>
<result property="goodsid" column="goodsid"/>
<result property="itemsnum" column="itemsnum"/>
collection>
resultMap>
(1). extends:实现订单信息和用户信息从 OrderAndUserInfoResultMap 继承
(2). 一个订单关联查询出了多条明细,要使用 collection 进行映射
(3). collection:对关联查询到多条记录映射到集合对象中
(4). ofType:指定映射到 list 集合属性中 pojo 的类型
(5). property:将关联查询到的列映射到 ofType 指定的 pojo 类的哪个属性中
第四步:Mapper 接口定义
//查询订单关联订单明细和用户(一对多查询)
public List<OrdersModel> findOrderAndOrderDetail() throws Exception;
第五步:单元测试
//查询订单关联订单明细和用户(一对多查询)
@Test
public void testFindOrderAndOrderDetail() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
IOrderMapper orderMapper = sqlSession.getMapper(IOrderMapper.class);
List<OrdersModel> ordersModels = orderMapper.findOrderAndOrderDetail();
sqlSession.close();
//输出订单信息
System.out.println("订单号\t 订单时间\t 顾客");
for (int i = 0; i < ordersModels.size(); i++) {
OrdersModel ordersModel = ordersModels.get(i);
System.out.println(ordersModel.getId() +"\t" +
sdf.format(ordersModel.getCreatetime()) +"\t" +
ordersModel.getUserInfoModel().getUserName());
//输出订单明细
System.out.println("\t 商品编号\t 订购数量");
for (int j = 0; j < ordersModel.getOrderdetailModels().size(); j++) {
System.out.print("\t"+ordersModel.getOrderdetailModels().get(j).getGoodsid());
System.out.println("\t"+ordersModel.getOrderdetailModels().get(j).getItemsnum());
}
}
}
给出需求:查询用户和用户购买的商品
需求分析:查询用户和用户购买的商品时,由于用户有多个,商品有多个,一个用户可以购买多个商品,一个商品可以被多个用户购买,因此用户和商品之间形成了多对多的关系。参考显示的界面如下图,查询出了多个用户多个商品。
映射关系分析:
本业务查询的主表是用户表,用户和商品两者未直接关联,而是通过 order 和 orderdetails进行关联。因此映射思路如下
(1). 订单:一个用户对应多个订单,使用 collection 映射到用户对象的订单列表属性中
(2). 订单明细:一个订单对应多个明细,使用 collection 映射到订单对象中的明细属性中
(3). 商品信息:一个订单明细对应一个商品,使用 association 映射到订单明细对象的商品属性中
映射关系如下图所示:
第一步:定义 pojo
定义 UserInfoModel 类
public class UserInfoModel implements Serializable{
private int id;
private String userName;
private String userPass;
private Date birthday;
private String gender;
private String address;
private List<OrdersModel> ordersModels;
//省略 get/set 方法
}
定义 OrdersModel 类
public class OrdersModel implements Serializable{
private int id;
private int userId;
private Date createtime= null;
private String memo= null;
private UserInfoModel userInfoModel= null;
private List<OrderdetailModel> orderdetailModels = null;
//省略 get/set 方法
}
定义 OrderdetailModel 类
public class OrderdetailModel {
private int id;
private int orderid;
private int goodsid;
private int itemsnum;
private GoodsModel goodsModel;
//省略 get/set 方法
}
定义 GoodsModel 类
public class GoodsModel {
private int id;
private String goodsname;
private String memo;
private Date createtime;
private String pic;
private double price;
//省略 get/set 方法
}
第二步:Mapper 映射配置
<select id="findUserAndGoods" resultMap="UserAndGoodsResultMap">
SELECT
userinfo.id ,
userinfo.username ,
userinfo.userPass ,
userinfo.birthday ,
userinfo.gender ,
userinfo.address ,
orders.id orders_id,
orders.userid orders_userid,
orders.createtime ,
orders.memo ,
orderdetail.id orderdetail_id ,
orderdetail.orderid orderdetail_orderid ,
orderdetail.goodsid orderdetail_goodsid,
orderdetail.itemsnum ,
goods.id goods_id,
goods.goodsname ,
goods.memo ,
goods.createtime ,
goods.pic ,
goods.price
FROM
userinfo,orders,orderdetail,goods
WHERE
userinfo.id = orders.userId AND
orders.id= orderdetail.orderid AND
orderdetail.goodsid= goods.id
select>
第三步:resultMap 定义
<resultMap id="UserAndGoodsResultMap" type="cn.itlaobing.mybatis.model.UserInfoModel">
<id property="id" column="id"/>
<result property="userName" column="userName"/>
<result property="userPass" column="userPass"/>
<result property="birthday" column="birthday"/>
<result property="gender" column="gender"/>
<result property="address" column="address"/>
<collection property="ordersModels" ofType="cn.itlaobing.mybatis.model.OrdersModel">
<id property="id" column="orders_id"/>
<result property="createtime" column="createtime"/>
<result property="memo" column="memo"/>
<result property="userId" column="orders_userid"/>
<collection property="orderdetailModels" ofType="cn.itlaobing.mybatis.model.
OrderdetailModel">
<id property="id" column="orderdetail_id"/>
<result property="orderid" column="orderdetail_orderid"/>
<result property="goodsid" column="orderdetail_goodsid"/>
<result property="itemsnum" column="itemsnum"/>
<association property="goodsModel" javaType="cn.itlaobing.mybatis.model.
GoodsModel">
<id property="id" column="goods_id"/>
<result property="goodsname" column="goodsname"/>
<result property="memo" column="memo"/>
<result property="createtime" column="createtime"/>
<result property="pic" column="pic"/>
<result property="price" column="price"/>
association>
collection>
collection>
resultMap>
第四步:Mapper 接口定义
//查询用户和用户购买的商品
public List<UserInfoModel> findUserAndGoods() throws Exception;
第五步:单元测试
//查询用户和用户购买的商品(多对多)
@Test
public void testFindOrderAndOrderDetail() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
IUserInfoMapper userInfoMapper = sqlSession.getMapper(IUserInfoMapper.class);
List<UserInfoModel> userInfoModels = userInfoMapper.findUserAndGoods();
sqlSession.close();
System.out.println("顾客姓名\t 顾客性别\t 顾客出生日期");
for (int i = 0; i < userInfoModels.size(); i++) {
UserInfoModel userInfoModel = userInfoModels.get(i);
System.out.println(
userInfoModel.getUserName() +"\t"+
userInfoModel.getGender()+
sdf.format(userInfoModel.getBirthday()));
System.out.println("\t 订单编号\t 订购时间");
for (int j = 0; j < userInfoModel.getOrdersModels().size(); j++) {
OrdersModel ordersModel = userInfoModel.getOrdersModels().get(j);
System.out.println("\t"+
ordersModel.getId()+"\t"+
sdf.format(ordersModel.getCreatetime()));
System.out.println("\t\t 商品名称\t 购买数量");
for (int k = 0; k < ordersModel.getOrderdetailModels().size(); k++) {
OrderdetailModel orderdetailModel =
ordersModel.getOrderdetailModels().get(k);
System.out.print("\t\t"+
orderdetailModel.getGoodsModel().getGoodsname());
System.out.println("\t\t"+orderdetailModel.getItemsnum());
}
}
}
}
作用:
将查询结果按照 sql 的列名和 pojo 的属性名一致原则映射到 pojo 中。
使用场景:
常见一些明细记录的展示,例如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用 resultType 将每一条记录映射到 pojo 中,在前端页面遍历 list 即可
resutlMap 可使用 association 和 collection 完成一对一和一对多高级映射。
association:
作用:
实现一对一关联查询。
场合:
为了方便查询遍历关联信息,可以使用 collection 将关联信息映射到 list 集合中,比如查询用户权限范围模块及模块下的菜单,可使用 collection 将模块映射到模块 list 中,将菜单列表映射到模块对象的菜单 list 属性中,这样的做的目的是方便对查询结果集进行遍历。
如果使用 resultType 无法将查询结果映射到 list 集合中。
需要查询关联信息时,使用 Mybatis 懒加载特性可有效的减少数据库压力,首次查询只查询主表信息,关联表的信息在用户获取时再加载。
Mybatis 一对一关联的 association 和一对多的 collection 可以实现懒加载。懒加载时要使用 resultMap,不能使用 resultType。
Mybatis 默认没有打开懒加载配置,需要在 SqlMapperConfig.xml 中通过 settings 配置lazyLoadingEnabled、aggressiveLazyLoading 来开启懒加载。
启用懒加载
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
settings>
给出需求:查询订单信息
查询订单信息时关联查询用户信息,默认只查询订单信息,当需要查询用户信息时再去查询用户信息
第一步:定义 POJO
在 OrdersModel 类中加入 UserInfoModel 属性。
public class OrdersModel implements Serializable{
private int id;
private int userId;
private Date cr eatetime= null;
private String memo= null;
private UserInfoModel userInfoModel= null;
省略部分代码
}
第二步:在 SqlMapperConfig.xml 中开启懒加载
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties">properties>
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
settings>
省略部分配置
第三步:Mapper 映射配置
第四步:resultMap 定义
<resultMap id="OrdersLazyLoadingUserInfoResultMap"
type="cn.itlaobing.mybatis.model.OrdersModel">
<id property="id" column="id"/>
<result property="userId" column="userId" />
<result property="createtime" column="createtime" />
<result property="memo" column="memo" />
<association property="userInfoModel"
javaType="cn.itlaobing.mybatis.model.UserInfoModel"
select="cn.itlaobing.mybatis.mapper.IUserInfoMapper.findUserInfoById"
column="userid" >
association>
resultMap>
(1). Select:指定关联查询懒加载对象的 Mapper Statement ID 为 findUserById
(2). column=“userid”:关联查询时将 userid 列的值传入 findUserById,并将 findUserById 查询的结果映射到 OrdersModel 的 userInfoModel 属性中
第五步: Mapper 定义接口
//查询订单时懒加载用户信息
public List<OrdersModel> findOrdersLazyLoadingUserInfo() throws Exception;
第六步:单元测试
(1)只查询订单信息
//查询订单时懒加载用户信息
@Test
public void testFindOrdersLazyLoadingUserInfo() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
IOrderMapper orderMapper = sqlSession.getMapper(IOrderMapper.class);
List<OrdersModel> ordersModels = orderMapper.findOrdersLazyLoadingUserInfo();
//输出订单信息
System.out.println( ordersModels.get(0).getId() );
sqlSession.close();
}
只输出订单信息时,没有查询用户表,输出的 sql 语句如下
Preparing: SELECT * FROM orders
Parameters:
Total: 2
(2)查询订单信息和用户信息
//查询订单时懒加载用户信息
@Test
public void testFindOrdersLazyLoadingUserInfo() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
IOrderMapper orderMapper = sqlSession.getMapper(IOrderMapper.class);
List<OrdersModel> ordersModels = orderMapper.findOrdersLazyLoadingUserInfo();
//输出订单信息
System.out.println(ordersModels.get(0).getId());
//输出用户信息时才加载用户信息
System.out.println( ordersModels.get(0).getUserInfoModel().getUserName() );
sqlSession.close();
}
输出用户信息时才发出 select 语句查询用户信息,输出的 sql 语句如下
Preparing: SELECT * FROM orders
Parameters:
Total: 2
Preparing: SELECT * FROM userInfo WHERE id=?
Parameters: 2(Integer)
Total: 1
作用:
(1). 当需要查询关联信息时再去数据库查询,默认不去关联查询,提高数据库性能。
(2). 只有使用 resultMap 支持懒加载设置。
使用场合:
(1). 当只有部分记录需要关联查询其它表的记录时,此时可按需延迟加载,需要关联查询时再向数据库发出 sql,以提高数据库性能。
(2). 当全部需要关联查询信息时,此时不用懒加载,直接将关联查询信息全部返回即可,可使用 resultType 或 resultMap 完成映射。
配置方法:
(1). collection 和 association 都需要配置 select 和 column 属性
(2). 两者配置方法相同
缓存(也称作 cache)的作用是为了减去数据库的压力,提高数据库的性能。缓存实现的原理是从数据库中查询出来的对象在使用完后不要销毁,而是存储在内存(缓存)中,当再次需要获取该对象时,直接从内存(缓存)中直接获取,不再向数据库执行 select 语句,从而减少了对数据库的查询次数,因此提高了数据库的性能。缓存是使用 Map 集合缓存数据的。
Mybatis 有一级缓存和二级缓存。一级缓存的作用域是同一个 SqlSession,在同一个sqlSession 中两次执行相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession 结束后该 sqlSession 中的一级缓存也就不存在了。Mybatis 默认开启一级缓存。 二级缓存是多个 SqlSession 共享的,其作用域是 mapper 的同一个 namespace,不同的sqlSession 两次执行相同 namespace 下的 sql 语句且向 sql 中传递参数也相同即最终执行相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis 默认没有开启二级缓存需要在 setting 全局参数中配置开启二级缓存。
一级缓存区域是根据 SqlSession 为单位划分的。每次查询会先从缓存区域查找,如果找不到则从数据库查询,从数据库查询后将数据写入缓存。
Mybatis 内部存储缓存使用一个 HashMap 缓存数据,key 为 hashCode+sqlId+Sql 语句。value 为从查询出来映射生成的 java 对象。
sqlSession 执行 insert、update、delete 等操作 commit 提交后会清空缓存区域,防止后续查询发生脏读(脏读:查询到过期的数据)。一级缓存参考下图所示:
@Test
public void testOneLevelCache1 () throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
IUserInfoMapper userInfoMapper = sqlSession.getMapper(IUserInfoMapper.class);
//第一次查询 id=1
UserInfoModel userInfoModel1 = userInfoMapper.findUserInfoById(1);
System.out.println(userInfoModel1.getUserName());
//第二次查询 id=1
UserInfoModel userInfoModel2 = userInfoMapper.findUserInfoById(1);
System.out.println(userInfoModel2.getUserName());
sqlSession.close();
}
输出结果如下:
Preparing: SELECT * FROM userInfo WHERE id=?
Parameters: 1(Integer)
Total: 1
admin
admin
根据输出结果分析,只输出了一次 select 语句,但却输出了两次用户名 admin,说明第二次查询 id 为 1 的用户时,是从一级缓存中获取了,没有向数据库再次发送 select 语句执行查询。Mybatis 默认开启了一级缓存。
//测试一级缓存:防止脏读
@Test
public void testOneLevelCache2() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
IUserInfoMapper userInfoMapper = sqlSession.getMapper(IUserInfoMapper.class);
//第一次查询 id=1
UserInfoModel userInfoModel1 = userInfoMapper.findUserInfoById(1);
System.out.println(userInfoModel1.getUserName());
//新增了一个用户
UserInfoModel userInfoModel =new UserInfoModel();
userInfoModel.setUserName("test");
userInfoModel.setUserPass("test");
userInfoMapper.insertUserInfo(userInfoModel);
//第二次查询 id=1
UserInfoModel userInfoModel2 = userInfoMapper.findUserInfoById(1);
System.out.println(userInfoModel2.getUserName());
sqlSession.close();
}
输出结果如下:
Preparing: SELECT * FROM userInfo WHERE id=?
Parameters: 1(Integer)
Total: 1
admin
Preparing: INSERT INTO userinfo(userName,userPass,birthday,gender,address) VALUES(?,?,?,?,?);
Parameters: test(String), test(String), null, null, null
Updates: 1
Preparing: SELECT LAST_INSERT_ID()
Parameters:
Total: 1
Preparing: SELECT * FROM userInfo WHERE id=?
Parameters: 1(Integer)
Total: 1
admin
根据输出结果分析,输出了两次 select 语句,一次 insert 语句。说明当执行 insert、update、delete 语句时,Mybatis 会情况一级缓存,防止后续查询产生脏读。
二级缓存区域是根据 mapper 的 namespace 划分的,相同 namespace 的 mapper 查询的数据缓存在同一个区域,如果使用 mapper 代理方法每个 mapper 的 namespace 都不同,此时可以理解为二级缓存区域是根据 mapper 划分。
每次查询会先从缓存区域查找,如果找不到则从数据库查询,并将查询到数据写入缓存。Mybatis 内部存储缓存使用一个 HashMap,key 为 hashCode+sqlId+Sql 语句。value 为查询出来映射生成的 java 对象。
sqlSession 执行 insert、update、delete 等操作 commit 提交后会清空缓存区域,防止脏读。二级缓存参考下图所示:
第一步:启用二级缓存
在 SqlMapperConfig.xml 中启用二级缓存,如下代码所示,当 cacheEnabled 设置为 true时启用二级缓存,设置为 false 时禁用二级缓存。
第二步:POJO 序列化
将所有的 POJO 类实现序列化接口 Java.io. Serializable。
第三步:配置映射文件
在 Mapper 映射文件中添加,表示此 mapper 开启二级缓存。例如
第四步:单元测试
//测试二级缓存
@Test
public void testSecondLevelCache1() throws Exception {
//获取 session1
SqlSession session1 = sqlSessionFactory.openSession();
IUserInfoMapper userInfoMapper1 = session1.getMapper(IUserInfoMapper.class);
//使用 session1 执行第一次查询
UserInfoModel userInfoModel1 = userInfoMapper1.findUserInfoById(1);
System.out.println(userInfoModel1);
//关闭 session1
session1.close();
//获取 session2
SqlSession session2 = sqlSessionFactory.openSession();
IUserInfoMapper userInfoMapper2 = session2.getMapper(IUserInfoMapper.class);
//使用 session2 执行第二次查询,由于开启了二级缓存这里从缓存中获取数据不再向数
据库发出 sql
UserInfoModel userInfoModel2 = userInfoMapper2.findUserInfoById(1);
System.out.println(userInfoModel2);
//关闭 session2
session2.close();
}
输出结果如下:
Preparing: SELECT * FROM userInfo WHERE id=?
Parameters: 1(Integer)
Total: 1
1--admin--admin--1980-10-10--男--陕西西安
1--admin--admin--1980-10-10--男--陕西西安
根据输出结果分析,只执行了一次 select 语句,输出了两次对象,说明第二次查询时是从二级缓存中查询的数据。
在 statement 中设置 useCache=false 可以禁用当前 select 语句的二级缓存,即每次查询都会发出 sql 去查询,默认情况是 true,即该 sql 使用二级缓存。
<select id="findUserInfoById" parameterType="int" resultType="UserInfoModel"
useCache="false">
在 mapper 的同一个 namespace 中,如果有其它 insert、update、delete 操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。
设置 statement 配置中的 flushCache="true"属性,即刷新缓存,该配置默认为 true。如果改成 false 则不会刷新缓存。
<insert id="insertUserInfo" parameterType="UserInfoModel" flushCache="true">
在 Mapper 映射文件中的中还可以进行缓存的一些其他设置。如下代码所示:
flushInterval:(刷新间隔)可以被设置为任意的正整数,表示以毫秒为单位的时间段。默认情况不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
Size:(引用数目)可以被设置为任意正整数,表示被缓存对象的数量,默认值是 1024。建议该项配置要根据运行环境的可用内存大小进行设置。
readOnly:(只读)该属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改。因此提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝。运行速度慢一些,但是安全,因此默认是 false。
eviction:代表的是缓存回收策略,MyBatis 提供以下策略。
(1) LRU,最近最少使用的,移除最长时间不用的对象,默认值,
(2) FIFO,先进先出,按对象进入缓存的顺序来移除他们,
(3) SOFT,软引用,移除基于垃圾回收器状态和软引用规则的对象,
(4) WEAK,弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象。
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用 mybatis 二级缓存技术降低数据库访问量,提高访问速度,例如:耗时较高的统计分析 sql。通过设置刷新间隔时间,由 mybatis 每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为 60 分钟、24 小时等。
对于实时性要求较高的查询不能使用缓存,例如股票行情。