一.订单商品数据模型
数据模型分析思路
①分模块对每张表记录的内容进行熟悉,相当于你学习系统功能需求的过程
②每张表重要的字段设置:非空字段,外键字段
③数据库级别表与表之间的关系,外键关系
④表与表之间的业务关系,分析时一定要建立在某个业务意义基础上去分析
二、一对一查询
需求:查询订单信息关联查询创建订单的用户(一条订单信息只能由一位用户创建,所以通过一张订单也只能查到一位用户)
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