MyBatis高级

一.订单商品数据模型

数据模型分析思路

①分模块对每张表记录的内容进行熟悉,相当于你学习系统功能需求的过程

②每张表重要的字段设置:非空字段,外键字段

③数据库级别表与表之间的关系,外键关系

④表与表之间的业务关系,分析时一定要建立在某个业务意义基础上去分析

二、一对一查询

需求:查询订单信息关联查询创建订单的用户(一条订单信息只能由一位用户创建,所以通过一张订单也只能查到一位用户)

1. resultType实现

① 写sql 语句 步骤:先确定查询注表——订单表,再确定关联表——用户表

由于orders 表中有一个外键 user_id,通过外键关联查询用户表只能查询出一条记录,可以使用内连接

select  orders.*, user.username,user.sex,user.address from orders ,user where orders.id = user.id

②创建 pojo将上面sql 查询到的所有结果 映射到 pojo 对象中

原始的 orders,java 不能 映射查询到的全部的字段,需要新创建一个 pojo ,继承包括查询字段较多的 pojo 类。

//通过此类映射订单和用户的查询结果,让此类继承字段较多的pojo 类
		public class OrdersCust extends Orders{
			//添加用户属性
			private String userName;
			private String sex;
			private String address;
		/*省略属性的get(),set() */
}

③新建 OrdersMapperCustomer.xml文件

<mapper namespace="cn.mybatis.mapper.OrdersMpperCustom">
	<!-- 查询订单关联查询用户-->
	<select id="findOrdersUser"  resultType="cn.mybatis.po.OrderCustom"> 
		select  orders.*, user.username,user.sex,user.address from orders ,user where orders.id = user.id
	</select>
</mapper>

④建立 mapper 接口

	public interface OrdersMapperCustom{
	public List<OrdersCustom> findOrdersUser();
}
2.resultMap 实现

①sql 语句同上

②使用 resultMap 进行映射思路

使用resultMap将查询结果中的订单信息映射到orders 类中,然后在orders类中添加 user 属性(是一个类),将关联查询出的用户信息映射到 orders对象的user属性中

<!-- 定义 resutlMap
将整个查询结果映射到 cn.mybatis.po.orders
-->
<resultMap id="OrdersUserResultMap" type="cn.mybatis.po.orders">
		<!-- 配置订单信息-->
		<!--id:指定查询列中的唯一标识 ,如果有多个列组成唯一标识配置多个id,
			column:订单信息的唯一标识列
			property:订单信息的唯一标识列所映射到 orders 中那个属性值
		-->
		<id column="id" property="id" />
		<result column=" user_id" property="userid" />
		<result column=" number" property="number" />
		<result column=" createtime" property="createtime" />
		<result column=" note" property="note" />
		<!-- 配置用户关联信息-->
		<!--association:用于映射关联查询单个对象的信息 (将查询结果的部分列拿出来作为映射对象中的对象属性中的属性,这里是将列拿出来作为orders 类中的 User 属性 中的 部分属性,来形成user对象)
			property:要将关联查询的用户信息映射到Oreders中哪个属性
		 -->
		<association property="user" javaType="cn.mybatis.po.User">
			<!-- id:关联查询用户的唯一标识
				column:指定唯一标识用户信息的列
				property:映射到 user的哪个属性-->
			<id column="user_id" property="id"/>
			<result column="username" property="username"/>
			<result column="sex" property="sex"/>
			<result column="address" property="address"/>
		</association>
</resultMap>
<!-- 使用resultMap查询订单关联查询用户信息-->
	<select id="findOrdersUserResultMap" resulMap = "OrdersUserResultMap">
		select  orders.*, user.username,user.sex,user.address from orders ,user where orders.id = user.id
	</select>	
	

④建立 mapper 接口

	public interface OrdersMapperCustom{
	//查询订单关联查询用户使用 resultMap
	public List<Orders> findOrdersUseResultMapr();

}

总结实现一对一查询:

resutlType:实现较为简单,如果 pojo中没有包括查询出来的列名,增加对应的属性即可完成映射,如果没有查询结果的特殊要求,建议使用reusltType.

resultMap:需要单独定义 resultMap,实现有点麻烦,如果对查询的结果有特殊要求,使用 resultMap 可以完成将关联查询的信息映射到pojo属性中。

resultMap 还可以实现延迟加载,resultType 无法实现。

三. 一对多查询

需求:查询订单以及订单明细表(订单明细主要内容是商品的数量价格,一张订单可以有购买多种商品,所以一张订单能查到多种商品属于一对多查询)

①编写 sql 语句

步骤:确定主查询表——订单表,关联查询表——订单明细表

在一对一查询基础之上添加订单明细表的关联即可

select  orders.*, user.username,user.sex,user.address from orders ,user,orderdetail where orders.id = user.id and orderdetail.orders_id = orders.id

分析:该语句的查询结果会有重复记录,若使用 resultType将上面的查询结果映射到pojo,就会有重复

要求:对 orders 映射不能出现重复记录

思路:在orders.java 类中 添加List<OrderDetail> orderDetails属性

最终会将订单信息映射到 orders 中,订单所对应的订单明细映射到orders 中的orderDeatils 属性中。

映射成的 orders 记录为两条,orders信息不重复

每个 orders 中的  ordersDetails 存储了订单所对应的订单明细

②orders.java 中 追加 代码:

//订单明细
private List<Orederdetail> orderDetails
/*该属性的get(),set()*/

③编写 OrdersMapperCustomer.xml文件

<!-- 查询订单并关联查询用户以及订单明细,使用resultMap-->
<mapper namespace="cn.mybatis.mapper.OrdersMpperCustom">
	<select id="findOrdersAndOrderDetailResultMap"  resultMap="OrdersUserResultMap"> 
		select  orders.*, user.username,user.sex,user.address,orderdetail.items_id,
		orderdetail.items_id,orderdetail.orders_id,ordertail.id
 from orders ,user,orderdetail where orders.id = user.id andorderdetail.orders_id = orders.id
</select></mapper>
resultMap 的定义
<!-- 查询订单以及订单明细的 resutlMap-->
<!-- 使用 extends 不用在此处重新声明订单和用户信息的映射-->
<resultMap id="OrdersAndOrederDetailResultMap" type="cn.mybatis.po.orders" extends="OrdersUserResultMap">
 <!-- 订单信息-->
<!--用户信息-->
<!-- 订单明细信息:一个订单关联查询出了多条明细,要使用Collection 进行映射
collection:对关联查询的多条记录映射到集合当中
property:将关联查询的多条记录映射到orders 中的哪个属性
ofType:指定映射到集合属性中pojo的类型
-->
<collection property="orderdetails " ofType=" cn.mybatis.po.OrderDetail">
<!--id:订单明细的唯一标识
property:要将订单明细的唯一标识映射到 OrderDetail的 哪个属性
 -->
<id column="ordertail.id" property=" id"/>
<result column="items_id " property=" itemsId" />
<result column="items_num " property="itemsNum" />
<result column="orders_id " property=" "ordersId />
</collection>
</resultMap>

④  接口的定义

//查询订单以及订单明细
public List<Orders> findOrdersAndOrderDetailResultMap();
小结:mybatis使用resultMap collection  对关联查询的多条记录映射到一个list结合属性中。
使用 resultType实现,将订单明细映射到 orders 中的 orderdetail中需要自行处理,使用双重循环遍历,去掉重复记录,将 订单明细放在orderdetails 中


四.多对多查询
  需求:查询用户以及用户购买的商品信息
分析:查询注表是用户表,由于用户和商品没有直接关联,而是通过订单表和订单明细进行关联所以 关联表 有 orders,orderldetail,items
sql 语句
select  orders.*, user.username,user.sex,user.address,orderdetail.id orderdetail_id,
orderdetail.items_id,orderdetail.items_num,orderdetail.orders_id,items.name items_name,items.detail
items_detail,items.price items_price 
from 
orders ,user,orderdetail ,items
where 
orders.id = user.id and orderdetail.orders_id = orders.id and orderdetail.items_id=items.id
实现思路:
将用户信息映射到 User 中,在 user 类中添加订单列表属性,List<Orders>ordersList,将用户创建的订单映射到 orderList 
在 orders 中添加订单列表明细属性 List<OrderDetail>orderdetails
在 orderdetail 中添加 items 属性,将订单明细所对应的商品映射到items
①编写mapper.xml
<!--查询用户以及购买的商品信息-->
<select id="findUserAndOrderDetailResultMap"  resultMap="UserAndItemsResultMap"> 		
select  orders.*, user.username,user.sex,user.address,orderdetail.id orderdetail_id,
orderdetail.items_id,orderdetail.items_num,orderdetail.orders_id,items.name items_name,items.detail
items_detail,items.price items_price 
 from 
orders ,user,orderdetail ,items
where 
orders.id = user.id and orderdetail.orders_id = orders.id and orderdetail.items_id=items.id
</select>
定义 resultMap

<!-- 查询用户以及购买的商品-->
<resultMap type="cn.mybatis.po.User" id="UserAndItemsResultMap">
<!-- 用户信息-->
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
</resultMap>
<!-- 订单信息
首先在 user 类中 加属性 ,并添加get(),set()
//用户创建的订单列表
prinvate List<Orders> ordersList;
一个用户对应多个订单,使用 collection 映射
-->
<collection property="orderList" ofType="cn.mybatis.po.Orders">
		<id column="id" property="id"/>
	<result column=" user_id" property="userid" />
	<result column=" number" property="number" />
	<result column=" createtime" property="createtime" />
	<result column=" note" property="note" />
<!-- 订单明细 ,一个订单包括多个明细-->
<collecton property="ordertails" ofType="cn.mybatis.po.OrderDetail">
	<id column="ordertail_id" property="id">
	<result column="items_id " property=" itemsId" />
	<result column="items_num " property="itemsNum" />
	<result column="orders_id " property=" "ordersId />
	<--商品信息
		首先在OrderDetail.java类中增加属性并添加get(),set() 代码:
		//明细对应商品信息(一个明细对应一个商品) private items items	-->
		<association property="itms" javaType="cn.mybaits.po.Items">
			<id column="items_id" property="id"/>
			<result column="items_name" property="name"/>
			<result column="items_detail" property="detail"/>
			<result column="items_price" property="price"/>
		</association>
</collection>
</collection>

②编写 mapper.java

public List<User> findUserAndItemsResutlMap();

多对多 查询总结:

将查询用户购买的商品信息明细清单(用户名,用户地址,购买商品名称,购买商品时间,购买商品数量)

针对上面的需求就使用resultType 将查询到的记录映射到一个扩展的pojo 中,很简单的实现明细清单的功能

使用 resutlMap 是针对那些对查询结果映射有特殊要求的功能(比如映射成 list 中 还包括多个list)

resultMap 总结

resultMap 使用 association 和 collection 完成一对一和 一对多的 高级映射(应用于对结果有特殊映射要求)

五.延迟加载

1.什么是 延迟加载

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

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

延迟加载:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快,

2.使用 association 实现延迟加载

需求:查询订单并且关联查询用户信息

思路:需要定义两个 mapper对应方法的statement

只查询订单信息:select * from orders

在查询订单的statement 中使用association 实现延迟加载下边的statement(关联查询用户信息)

①编写mapper.xml 文件

	<!-- 查询订单关联查询用户,用户信息实现延迟加载-- >
		<select id="findOrdersUserLazyLoading" reslutMap="OrdersUserLazyLoadingReslutMap ">
			select * from  orders
		</select>
		<!-- 延迟加载的 resultMap -->
		<rdsultMap type="cn.mybatis.po.Orders" id="OrdersUserLazyLoadingReslutMap">
			<!-- 对订单信息进行配置映射-->		
		<id column="id" property="id" />
		<result column=" user_id" property="userid" />
		<result column=" number" property="number" />
		<result column=" createtime" property="createtime" />
		<result column=" note" property="note" />
			<!-- 实现对用户信息的延迟加载
			select:指定需要执行延迟加载的statement 的id,这里使用userMapper.xml中的 finUserById 完成根据用户id查询用户信息,如果 findUserById 不在本 mapper.xml 中,引用时前面要加 stateemnt 所在mapper.xml的 namespace
			column:订单信息中关联用户信息查询的列 :user_id
-->
			<association property="user" javaType="cn.mybatis.po.User" select="cn.myabatis.mapper.UserMapper.findUserById. " column="user_id">
				<!-- 实现了  对用户信息查询的延迟加载-->
			</association>
		</resultMap>

②编写 mapper.java

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

③延迟加载配置

mybatis 默认没有开启延迟加载,需要在 SqlMappingConfig.xml 中的setting 配置

设置项 

lazyloadingEnabled :全局性设置懒加载,如果设为 false 则所有相关联的都会被初始化加载                             默认 false

aggressiveLazyLoding:当设置为true 时,懒加载的对象可能被任何懒属性全部加载,否则每个属性都按需加载   默认 true

<settrings>
				<!-- 打开延迟加载的开关-->
				<settring  name="lazyLoadingEnambled" value="true"/>
				<!--将积极加载改为消极加载即按需加载 -->
				<settring name ="aggressiveLazyLoding" value="false"/>
			</settrings>

④测试

步骤:执行上面mapper 中的方法 findOrdersUserLazyLoading(),内部去调用mapper.xml 中的findOrdersUserLazyLoading ,只查询orders 信息(单表)

在程序中去遍历上一步骤查询出的 List<orders>, 当我们调用 orders 中的 getUser() 时开始进行延迟加载。

延迟加载时去调用UserMapper.xml 中的 findUserById 来获取用户信息。 

代码:

public void testFindOrdersUserLazyLoading(){
					SqlSession ss = ssf.openSession();//创建代理对象
					OrdersMapperCustom omc = ss.getMapper(OrdersMapperCustom.class);
					//查询订单信息(单表)
					List<Orders>list = omc.findOrdersUsersLazyLoading();
					//遍历上面的订单列表
					for(Orders order:list){
						//执行getUser()去查询用户信息,这里实现按需加载
						User user = order.getUser();
						system.out.println(user);
					}		
}

注意:collection 实现延迟加载方式与 associaton 相同


3.延迟加载思考

不适用mybatis 提供的 collection 和 association 如何实现 延迟加载

实现方法:

定义两个mapper 方法

①查询订单列表

②根据用户ID查询用户信息

实现思路:先去查询第一个mapper 方法获取 订单列表,在程序中(service)按需调用第二个mapper 方法查询用户信息

总之使用延迟加载的方法先去查询简单的sql(最好单表,也可以是关联查询),再去按需关联查询其它的表


六.查询缓存

1.什么是查询缓存

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

②mybatis 提供一级缓存和 二级缓存

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

④二级缓存 是 mapper 级别的缓存,多个SqlSession 去操作同一个Mapper 的 sql语句,多个SqlSession 共享二级缓存,二级缓存是·跨SqlSession

⑤为什么要用缓存

如果缓存中有数据就不用从数据库中获取,大大提高系统性能

2.一级缓存

①案例原理:

第一发起查询用户ID 为1 的用户信息请求,会先去缓存中找是否有 ID为1 的 用户信息,如果没有,则会从数据库中获取该用户信息,并将信息存入一级缓存

如果SqlSession 执行 commit操作(增删改查),清空sqlsession 中的一级缓存,目的是让缓存中存储的是最新信息,避免脏读

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

②一级缓存测试

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

按上面一级缓存原理步骤去测试

代码:

	public void testCash1(){			
				SqlSession ss = ssf.openSession();//创建代理对象
				UserMapper um = ss.getMapper(UserMapper.class);
				//下面查询使用一个 sqlsession
				//第一次发起请求,查询 id 为1 的用户
				User u1 = um.findUserById(1);
				system.out.pirnltn(u1)
				//如果SqlSession 执行 commit操作(增删改查),清空sqlsession 中的一级缓存,目的是让缓存中存储的是最新信息,避免脏读
				// 更新 u1 的信息去清空缓存
				u1.setUsername("新测试用户22");
				um.updateUser(u1);	
				ss.commit()//清空缓存
				//第二次发起请求查询id 为1 的用户
				User u2 = um.findUserById(1);
				system.out.pirnltn(u1)
				ss.close();
}
③一级缓存应用

正式开发时将 mybatis 和sping 进行整合开发,事务控制在service 方法中,一个 service 方法包括很多 mapper 方法调用

service{

开启执行时,开启事务创建 sqlsession 对象

第一次调用 mapper 方法的 findUserById(1);

第一次调用 mapper 方法的 findUserById(1);会从 一级缓存中获取

方法结束 sqlsession 关闭

}
注意:如果是执行两次 service 方法进行查询同一个Id,第二次 将不会从 缓存中获取,因为 Service方法结束,sqlsession就会关闭,一级缓存随之清空


3.二级缓存

①执行原理:

首先开启mybatis 的二级缓存

sqlsession1 去查询id 为1 的用户信息,并将查到的信息存入二级缓存

sqlsession2再去查询id为1的用户信息,先去缓存中寻找对应数据,如果存在就直接获取。

如果sqlsession3 去 执行相同 mapper 下的 sql 并执行 commit 提交将会 清空 该mapper 的二级缓存

②与一级缓存的区别:二级缓存的范围更大,多个sqlsession 可以共享同一个UserMapper的二级缓存

UserMapper 有一个二级缓存区域(按照 namespace 区分),其它的Mapper 也有自己的 二级缓存区域

每一个 namespace 的 mapper  都有一个 独自的 二级缓存区域,两个 mapper 的 namespace 如果相同,那么这两个 mapper 执行sql 查询到的数据将存储在相同的二级缓存区域中。

③开启 二级缓存

mybatis 的二级缓存是 mapper 级别的,除了要在 sqlmapcofig.xml 中设置二级缓存的总开关,还要在具体的mapper.xml 中开启二级缓存

设置项:cashEnambled  对此配置文件下的所有cash 进行 全局性开关设置,默认 true

在 SqlMapConfig.xml 中 加入如下代码,方便代码维护

<settings>
			<setting name="cashEnambled" value="true"/>
			</settings>

在 UserMapper.xml 中开启二级缓存,该文件下的sql执行完会存储到它的二级缓存区域(hashmap机构)

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

④设置 pojo 类实现序列化接口

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

⑤测试代码:

public void testCash1(){		
				SqlSession ss1 = ssf.openSession();				
				SqlSession ss2 = ssf.openSession();
				SqlSession ss3 = ssf.openSession();
				//创建代理对象
				UserMapper um1 = ss.getMapper(UserMapper.class);
				//第一次发起请求,查询 id 为1 的用户
				User u1 = um.findUserById(1);
				system.out.pirnltn(u1)	
				//这里执行 关闭操作将 sqlsession 中的数据写入二级缓存区域
				ss1.colse();
				//使用 ss3 执行 commit(),执行完 ss1,ss2后再执行这步
				UserMapper um3 = ss.getMapper(UserMapper.class);
				User u3 = um3.findUserById(1);
				um3.setUsername("明明");
				//执行提交,清空UserMapper下的二级缓存
				ss3.commit();
				ss3.close();

				UserMapper um2 = ss.getMapper(UserMapper.class);
				//第二次发起请求查询id 为1 的用户
				User u2 = um2.findUserById(1);
				system.out.pirnltn(u2)
				ss.close();
}

⑥.禁用二级缓存(就是情况缓存)

在statement 设置 useCashe=false可以禁用当前select 语句的二级缓存,即每次查询都会发出sql 进行查询 ,默认值true 即该Sql 使用二级缓存

禁用配置:<select id="findJserById " resultType="cn.mybatis.po.User " useCashe="false"/>

总结:针对每次查询都需要更新数据的sql

⑦刷新缓存

在 mapper 的同一个namespace中,如果有其它的增删改查操作后需要刷新缓存,否则会出现脏读

设置 statement 中的 flushCashe="true" 属性,默认值为 true,即刷新缓存,改成false 则不会刷新

配置:<insert id="insertUser" parameterType="cn.mybatis.po.User" flushCashe="true"/>

注意:使用缓存时如果手动修改数据库表中的数据将会出现脏读,一般情况下执行完 commit() 都需要刷新缓存,fulshCashe=“true” 表示刷新缓存,这样可以避免数据库的脏读

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

  实现方法:通过设置刷新间隔时间,由mybatis 每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新时间间隔 fluseinterval,比如设置30min,1h,24h...

根据需求来定

⑨局限性:

mybatis 二级缓存对细粒度的数据级别缓存实现不好,比如缓存中有很多的商品信息,而一旦其中的一个商品信息发生改变,执行commit()后,二级缓存中的全部商品信息都会清空,导致访问其它商品时又要通过数据库访问。解决此类问题需要再业务层根据需求对数据有针对性缓存。

3.mybatis 整合 ehcache

 encache是一个分布式缓存框架

①分布缓存

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

若不使用分布式缓存,那么缓存的数据在各个服务器单独存储,不方便进行开发,

要是实现对缓存数据的集中管理需要使用分布式缓存框架 如 redis,memached,ehcache

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

②整合方法

mybatis 提供了一个 cache 接口,如果要实现自己的缓存逻辑,实现 cache 接口开发即可。

mybatis 和 ehcashe 整合包中提供了一个  cache 接口的实现类。

③加入 ehcache 包

ehcache-core-XXX.jar

mybatis-ehcache-XXX.jar

④整合 ehcache

配置 mapper.xml 中 cache 的 type 为 ehcache 对 cache 接口的实现类型

<! -- 开启本mapper的 namespace下的二级缓存
				type:指定cache接口实现类的类型,如果要和 encache 整合需要配置type 为 ehcache 实现cache 接口的类型  -->
				<cache type="org.mybatis.caches.ehcache.EhcacheCache"  />
⑤加入 ehcache 配置文件

在 classpath配置 ehache.xml

猜你喜欢

转载自blog.csdn.net/qq_30162219/article/details/76992760