1 关联查询映射
1.1 分析数据模型
1.1.1 思路
1、 每张表记录的数据内容
分模块对每张表记录的内容进行熟悉,相当于你学习系统需求(功能)的过程。
2、 每张表重要的字段
主键、外键、非空字段
3、 数据库级别表与表的关系
外键关系
4、 表与表之间的业务关系
在分析表与表之间的业务关系时一定要建立 在某个业务意义基础上去分析。
1.1.2 图形分析
1.1.3 数据库表之间有外键关系的业务关系
user和orders:
user---->orders:一个用户可以创建多个订单,一对多
orders--->user:一个订单只由一个用户创建,一对一
orders和orderdetail:
orders-àorderdetail:一个订单可以包括 多个订单明细,因为一个订单可以购买多个商品,每个商品的购买信息在orderdetail记录,一对多关系
orderdetail--> orders:一个订单明细只能包括在一个订单中,一对一
orderdetail和itesm:
orderdetail---》itesms:一个订单明细只对应一个商品信息,一对一
items--> orderdetail:一个商品可以包括在多个订单明细 ,一对多
1.1.4 数据库表之间没有外键关系的业务关系
Orders和items:
这两张表没有直接的外键关系,通过业务及数据库的间接关系分析出它们是多对多的关系。
Ordersà orderdetail –>items:一个订单可以有多个订单明细,一个订单明细对应一个商品,所以一个订单对应多个商品
Items-àorderdetailàorders:一个商品可以对应多个订单明细,一个订单明细对应一个订单,所以一个商品对应多个订单
User和items:
这两张表没有直接的外键关系,通过业务及数据库的间接关系分析出它们是多对多的关系。
Useràordersàorderdetailàitems:一个用户有多个订单,一个订单有多个订单明细、一个订单明细对应一个商品,所以一个用户对应多个商品
Itemsàorderdetailàordersàuser:一个商品对应多个订单明细,一个订单明细对应一个订单,一个订单对应一个用户,所以一个商品对应多个用户
1.2 一对一查询
1.2.1 需求
查询订单信息,关联查询创建订单的用户信息
1.2.2 SQL语句
确定查询的主表:订单表
确定查询的关联表:用户表
确定关联查询表,确定关联方式(内连接、外连接)
如果通过外键关联查询子表,使用内连接决对没有问题。
Select
Orders.id,
Orders.user_id,
orders.number,
orders.createtime,
orders.note,
user.username,
user.address
from orders,user
where orders.user_id = user.id
1.2.3 resultType
复杂查询时,单表对应的po类已不能满足输出结果集的映射。
所以要根据需求建立一个扩展类来作为resultType的类型。
1.2.3.1 创建po类
//通过此类映射订单和用户查询的结果,让此类继承包括 字段较多的pojo类
public class OrdersExt extends Orders{
//添加用户属性
/*USER.username,
USER.address */
private String username;
private String address;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
1.2.3.2 编写mapper接口
创建OrdersMapper接口类,在类中添加以下内容:
// 进行订单信息查询,包括用户的名称和地址信息
public List<OrdersExt> findOrdersUser();
1.2.3.3 编写映射文件
<mapper namespace="cn.itcast.mybatis.mapper.OrdersMapper">
<!-- 定义查询订单表列名的SQL片段 -->
<sql id="select_orders">
Orders.id,
Orders.user_id,
orders.number,
orders.createtime,
orders.note
</sql>
<!-- 定义查询用户表列名的SQL片段 -->
<sql id="select_user">
user.username,
user.address
</sql>
<!-- 进行订单信息查询,包括用户的名称和地址信息 -->
<select id="findOrdersUser" resultType="OrdersExt">
Select
<include refid="select_orders" />
<include refid="select_user"></include>
from orders,user
where orders.user_id = user.id
</select>
</mapper>
1.2.3.4 加载映射文件
<!-- 批量加载mapper文件,需要mapper接口文件和mapper映射文件名称相同且在同一个包下 -->
<package name="cn.itcast.mybatis.mapper"/>
1.2.3.5 编写测试代码
@Test
public void testFindOrdersUser() {
// 创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession构造usermapper的代理对象
OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
// 调用usermapper的方法
List<OrdersExt> list = ordersMapper.findOrdersUser();
System.out.println(list);
// 释放SqlSession
sqlSession.close();
}
1.2.4 resultMap
1.2.4.1 修改po类
在Orders类中,添加User对象
1.2.4.2 编写mapper接口
// 进行订单信息查询,包括用户的名称和地址信息(resultMap)
public List<OrdersExt> findOrdersUserRstMap();
1.2.4.3 编写映射文件
<!-- 进行订单信息查询,包括用户的名称和地址信息 (ResultMap) -->
<select id="findOrdersUserRstMap" resultMap="OrdersUserRstMap">
Select
<include refid="select_orders" />
,
<include refid="select_user"></include>
from orders,user
where orders.user_id = user.id
</select>
<!-- 定义orderUserResultMap -->
<resultMap type=" cn.itcast.mybatis.po.Orders" id="OrdersUserRstMap">
<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" />
<!-- 映射一对一关联关系的用户对象-->
<!--
property:指定关联对象要映射到Orders的哪个属性上
javaType:指定关联对象所要映射的java类型
-->
<!-- id标签:指定关联对象结果集的唯一标识,很重要,不写不会报错,但是会影响性能 -->
<association property="user" javaType="cn.itcast.mybatis.po.User">
<id column="user_id" property="id" />
<result column="username" property="username" />
<result column="address" property="address" />
</association>
</resultMap>
1.2.4.4 编写测试代码
@Test
public void testFindOrdersUserRstMap() {
// 创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession构造usermapper的代理对象
OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
// 调用usermapper的方法
List<Orders> list = ordersMapper.findOrdersUserRstMap();
//此处我们采用debug模式来跟踪代码,然后验证结果集是否正确
System.out.println(list);
// 释放SqlSession
sqlSession.close();
}
1.2.5 一对一小结
实现一对一查询:
resultType:使用resultType实现较为简单,如果pojo中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。
如果没有查询结果的特殊要求建议使用resultType。
resultMap:需要单独定义resultMap,实现有点麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射pojo的对象属性中。
resultMap可以实现延迟加载,resultType无法实现延迟加载。
1.3 一对多查询
一对多查询和一对一查询的配置基本类似。只是如果使用resultMap的话,映射一对多关联关系要使用collection标签。
1.3.1 需求
查询订单信息及订单明细信息
1.3.2 SQL语句
确定主查询表:订单表
确定关联查询表:订单明细表
在一对一查询基础上添加订单明细表关联即可。
Select
Orders.id,
Orders.user_id,
orders.number,
orders.createtime,
orders.note,
user.username,
user.address,
orderdetail.id detail_id,
orderdetail.items_id,
orderdetail.items_num
from orders,user,orderdetail
where orders.user_id = user.id
and orders.id = orderdetail.orders_id
1.3.3 分析
使用resultType将上边的 查询结果映射到pojo中,订单信息将会重复。
要求:
对orders映射不能出现重复记录。
在orders.java类中添加List<Orderdetail> detailList属性。
最终会将订单信息映射到orders中,订单所对应的订单明细映射到orders中的detailList属性中。
映射成的orders记录数为两条(orders信息不重复)
每个orders中的detailList属性存储了该订单所对应的订单明细集合。
1.3.4 修改PO类
在Orders类中添加List<Orderdetail> detailList属性,并提供get/set方法:
1.3.5 编写mapper接口
// 查询订单信息及订单明细信息(一对多映射之使用resultMap)
public List<Orders> findOrdersAndOrderdetailRstMap();
1.3.6 编写映射文件
<!-- 定义OrdersAndOrderdetailRstMap -->
<!-- extends:继承已有的ResultMap,值为继承的ResultMap的唯一标示 -->
<resultMap type="Orders" id="OrdersAndOrderdetailRstMap"
extends="OrdersUserRstMap">
<!-- 映射关联关系(一对多) -->
<!-- collection标签:定义一个一对多关系
ofType:指定该集合参数所映射的类型
-->
<collection property="detailList" ofType="Orderdetail">
<id column="detail_id" property="id" />
<result column="items_id" property="itemsId" />
<result column="items_num" property="itemsNum" />
</collection>
</resultMap>
<!-- 查询订单信息,包括用户名称、用户地址,订单商品信息(嵌套结果) -->
<select id="findOrdersAndOrderdetailRstMap" resultMap="OrdersAndOrderdetailRstMap">
Select
<include refid="select_orders" />
,
<include refid="select_user"/>
,
orderdetail.id detail_id,
orderdetail.items_id,
orderdetail.items_num
from orders,user,orderdetail
where orders.user_id = user.id
and
orders.id = orderdetail.orders_id
</select>
resultMap的extends属性:可以用此属性来继承一个已有的resultmap。但是它继承的resultMap的type和它本身的type要保持一致。
1.3.7 编写测试代码
1.3.8 一对多小结
mybatis使用resultMap的collection对关联查询的多条记录映射到一个list集合属性中。
使用resultType实现:
需要对结果集进行二次处理。
将订单明细映射到orders中的orderdetails中,需要自己处理,使用双重循环遍历,去掉重复记录,将订单明细放在orderdetails中。
1.4 多对多查询
1.4.1 需求
查询用户信息及用户购买的商品信息,要求将关联信息映射到主pojo的pojo属性中
1.4.2 SQL语句
查询主表:user
查询关联表:orders、orderdetail、items
Select
Orders.id,
Orders.user_id,
orders.number,
orders.createtime,
orders.note,
user.username,
user.address,
orderdetail.id detail_id,
orderdetail.items_id,
orderdetail.items_num
items.name items_name,
items.detail items_detail
FROM
orders,
USER,
orderdetail,
items
WHERE user.`id` = orders.`user_id`
AND orders.`id` = orderdetail.`orders_id`
AND orderdetail.`items_id` = items.`id`
1.4.3 映射思路
将用户信息映射到user中。
在user类中添加订单列表属性List<Orders>orderslist,将用户创建的订单映射到orderslist
在Orders中添加订单明细列表属性List<Orderdetail>detailList,将订单的明细映射到detailList
在Orderdetail中添加Items属性,将订单明细所对应的商品映射到Items
1.4.4 修改PO类
在user类中添加List<Orders>ordersList 属性
在Orders类中添加List<Orderdetail>属性
在Orderdetail类中添加Items属性
1.4.5 编写mapper接口
//查询用户及用户购买商品信息(多对多映射之使用resultMap)
public List<User> findUserAndItemsRstMap();
1.4.6 编写映射文件
<!-- 定义UserAndItemsRstMap -->
<resultMap type="User" id="UserAndItemsRstMap">
<!-- 用户信息 -->
<!-- id:关联查询用户的唯一标示 -->
<id column="user_id" property="id" />
<result column="username" property="username" />
<result column="address" property="address" />
<!-- 订单信息 (一个用户有多个订单) -->
<collection property="ordersList" ofType="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" />
<!-- 订单明细信息(一个订单有多个订单明细) -->
<collection property="detailList" ofType="orderdetail">
<id column="detail_id" property="id" />
<result column="items_id" property="itemsId" />
<result column="items_num" property="itemsNum" />
<!-- 商品信息 (一个订单明细对应一个商品) -->
<association property="items" javaType="cn.itcast.mybatis.po.Items">
<id column="items_id" property="id" />
<result column="items_name" property="name" />
<result column="items_detail" property="detail" />
</association>
</collection>
</collection>
</resultMap>
<!-- 查询用户及用户购买商品信息(多对多映射之使用resultMap) -->
<select id="findUserAndItemsRstMap" resultMap="UserAndItemsRstMap">
Select
<include refid="select_orders" />
,
<include refid="select_user" />
,
<include refid="select_orderdetail"></include>
,
items.name items_name,
items.detail items_detail
from
orders,user,orderdetail,items
where orders.user_id = user.id
and
orders.id = orderdetail.orders_id
and orderdetail.items_id = items.id
</select>
1.4.7 编写测试代码
1.4.8 多对多查询小结
将查询用户购买的商品信息明细清单,(用户名、用户地址、购买商品名称、购买商品时间、购买商品数量)
针对上边的需求就使用resultType将查询到的记录映射到一个扩展的pojo中,很简单实现明细清单的功能。
一对多是多对多的特例,如下需求:
查询用户购买的商品信息,用户和商品的关系是多对多关系。
需求1:
查询字段:用户账号、用户名称、用户性别、商品名称、商品价格(最常见)
企业开发中常见明细列表,用户购买商品明细列表,
使用resultType将上边查询列映射到pojo输出。
需求2:
查询字段:用户账号、用户名称、购买商品数量、商品明细(鼠标移上显示明细)
使用resultMap将用户购买的商品明细列表映射到user对象中。
总结:
使用resultMap是针对那些对查询结果映射有特殊要求的功能,,比如特殊要求映射成list中包括 多个list。
1.5 高级映射总结
resultType:
作用:
将查询结果按照sql列名pojo属性名一致性映射到pojo中。
场合:
常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到pojo中,在前端页面遍历list(list中是pojo)即可。
resultMap:
使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)。
association:
作用:
将关联查询信息映射到一个pojo对象中。
场合:
为了方便查询关联信息可以使用association将关联订单信息映射为用户对象的pojo属性中,比如:查询订单及关联用户信息。
使用resultType无法将查询结果映射到pojo对象的pojo属性中,根据对结果集查询遍历的需要选择使用resultType还是resultMap。
collection:
作用:
将关联查询信息映射到一个list集合中。
场合:
为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中,比如:查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块list中,将菜单列表映射到模块对象的菜单list属性中,这样的作的目的也是方便对查询结果集进行遍历查询。
如果使用resultType无法将查询结果映射到list集合中。
2 延迟加载
2.1 什么是延迟加载
resultMap中的association和collection标签具有延迟加载的功能。
延迟加载的意思是说,在关联查询时,利用延迟加载,先加载主信息。需要关联信息时再去按需加载关联信息。这样会大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
2.2 设置延迟加载
Mybatis默认是不开启延迟加载功能的,我们需要手动开启。
需要在SqlMapConfig.xml文件中,在<settings>标签中开启延迟加载功能。
lazyLoadingEnabled、aggressiveLazyLoading
设置项 | 描述 | 允许值 | 默认值 |
lazyLoadingEnabled | 全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。 | true | false | true |
aggressiveLazyLoading | 当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载 | true | false | true |
<settings>
<!-- 打开延迟加载的开关 ,默认是true-->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 积极的懒加载 ,默认是true,设置为false时,懒加载生效-->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 开启二级缓存总开关 -->
<setting name="cacheEnabled" value="true"/>
</settings>
2.3 使用association进行延迟加载
2.3.1 需求
查询订单并且关联查询用户信息(对用户信息的加载要求是按需加载)
2.3.2 编写映射文件
需要定义两个mapper的方法对应的statement。
1、只查询订单信息
SELECT * FROM orders
在查询订单的statement中使用association去延迟加载(执行)下边的satatement(关联查询用户信息)
<!-- 定义OrdersUserLazyLoadingRstMap -->
<resultMap type="cn.itcast.mybatis.po.Orders" id="OrdersUserLazyLoadingRstMap">
<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(是根据user_id查询用户信息的statement)
我们使用UserMapper.xml中的findUserById完成根据用户ID(user_id)查询用户信息
如果findUserById不在本mapper中,前边需要加namespace
-->
<!-- column:主信息表中需要关联查询的列,此处是user_id -->
<association property="user" select="cn.itcast.mybatis.mapper.UserMapper.findUserById" column="user_id"></association>
</resultMap>
<!-- 查询订单信息,延迟加载关联查询的用户信息 -->
<select id="findOrdersUserLazyLoading" resultMap="OrdersUserLazyLoadingRstMap">
SELECT * FROM orders
</select>
2、关联查询用户信息
通过上边查询到的订单信息中user_id去关联查询用户信息
使用UserMapper.xml中的findUserById
<select id="findUserById" parameterType="int"
resultType="cn.itcast.mybatis.po.User">
SELECT * FROM user WHERE id = #{id}
</select>
上边先去执行findOrdersUserLazyLoading,当需要去查询用户的时候再去执行findUserById,通过resultMap的定义将延迟加载执行配置起来。
2.3.3 加载映射文件
2.3.4 编写mapper接口
2.3.5 编写测试代码
思路:
1、执行上边mapper方法(findOrdersUserLazyLoading),内部去调用cn.itcast.mybatis.mapper.OrdersMapper中的findOrdersUserLazyLoading只查询orders信息(单表)。
2、在程序中去遍历上一步骤查询出的List<Orders>,当我们调用Orders中的getUser方法时,开始进行延迟加载。
3、执行延迟加载,去调用UserMapper.xml中findUserbyId这个方法获取用户信息。
@Test
public void testFindOrdersUserLazyLoading() {
// 创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession构造usermapper的代理对象
OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
// 调用usermapper的方法
List<Orders> list = ordersMapper.findOrdersUserLazyLoading();
for(Orders orders : list){
System.out.println(orders.getUser());
}
// 释放SqlSession
sqlSession.close();
}
2.4延迟加载思考
不使用mybatis提供的association及collection中的延迟加载功能,如何实现延迟加载??
实现方法如下:
定义两个mapper方法:
1、查询订单列表
2、根据用户id查询用户信息
实现思路:
先去查询第一个mapper方法,获取订单信息列表
在程序中(service),按需去调用第二个mapper方法去查询用户信息。
总之:
使用延迟加载方法,先去查询简单的sql(最好单表,也可以关联查询),再去按需要加载关联查询的其它信息。
3 查询缓存
3.1 mybatis缓存分析
mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
一级缓存的map的key就是hashcode+statement的id+sql语句+输入参数。。作为一个唯一标识sql的key
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
3.2 一级缓存
3.2.1 原理
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
Mybatis默认支持一级缓存。
3.2.2 应用
正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
一个service方法中包括 很多mapper方法调用。
service{
//开始执行时,开启事务,创建SqlSession对象
//第一次调用mapper的方法findUserById(1)
//第二次调用mapper的方法findUserById(1),从一级缓存中取数据
//方法结束,sqlSession关闭
}
如果是执行两次service调用查询相同 的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空。
3.3 二级缓存
3.3.1 原理
下图是多个sqlSession请求UserMapper的二级缓存图解。
二级缓存是mapper级别的。
第一次调用mapper下的SQL去查询用户信息。查询到的信息会存到该mapper对应的二级缓存区域内。
第二次调用相同namespace下的mapper映射文件中相同的SQL去查询用户信息。会去对应的二级缓存内取结果。
如果调用相同namespace下的mapper映射文件中的增删改SQL,并执行了commit操作。此时会清空该namespace下的二级缓存。
3.3.2 开启二级缓存
Mybatis默认是没有开启二级缓存
1、 在核心配置文件SqlMapConfig.xml中加入以下内容(开启二级缓存总开关):
在settings标签中添加以下内容:
<!-- 开启二级缓存总开关 -->
<setting name="cacheEnabled" value="true"/>
2、 在UserMapper映射文件中,加入以下内容,开启二级缓存:
<!-- 开启本mapper下的namespace的二级缓存,默认使用的是mybatis提供的PerpetualCache -->
<cache></cache>
3.3.3 实现序列化
由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,所以需要给缓存的对象执行序列化。
如果该类存在父类,那么父类也要实现序列化。
public class User implements Serializable{
3.3.4 禁用二级缓存
该statement中设置userCache=false,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。
<select id="findUserById" parameterType="int"
resultType="cn.itcast.mybatis.po.User" useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
3.3.5 刷新二级缓存
该statement中设置flushCache=true可以刷新当前的二级缓存,默认情况下如果是select语句,那么flushCache是false。如果是insert、update、delete语句,那么flushCache是true。
如果查询语句设置成true,那么每次查询都是去数据库查询,即意味着该查询的二级缓存失效。
如果查询语句设置成false,即使用二级缓存,那么如果在数据库中修改了数据,而缓存数据还是原来的,这个时候就会出现脏读。
flushCache设置如下:
<select id="findUserById" parameterType="int"
resultType="cn.itcast.mybatis.po.User" useCache="true" flushCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
3.3.6 整合ehcache(了解)
Ehcache是一个分布式缓存。
3.3.6.1 分布式缓存
系统为了提高性能,通常会对系统采用分布式部署(集群部署方式)
不使用分布式缓存,缓存的数据在各个服务单独存储,不方便开发。所以要使用分布式缓存对缓存数据进行集中式管理。
Mybatis自身无法实现分布式缓存,需要和其它分布式缓存框架进行整合。
3.3.6.2 整合思路(重点)
Mybatis提供了一个cache接口,同时它自己有一个默认的实现类PerpetualCache。
通过实现cache接口可以实现mybatis缓存数据通过其他缓存数据库整合,mybatis的特长是sql,缓存数据管理不是mybatis的特长,为了提高mybatis的性能,所以需要mybatis和第三方缓存数据库整合,比如ehcache、memcache、redis等
Mybatis提供接口如下:
Mybatis的默认实现类:
3.3.6.3 整合ehcache的步骤
1、 引入ehcache的jar包;
2、 在mapper映射文件中,配置cache标签的type为ehcache对cache接口的实现类类型。
3、 加入ehcache的配置文件
3.3.6.4 第一步:引入ehcache的jar包
3.3.6.5 第二步:配置cache的type属性
<!-- 使用默认二级缓存 -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
3.3.6.6 第三步:添加ehcache的配置文件
在classpath下添加ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 缓存数据要存放的磁盘地址 -->
<diskStore path="F:\develop\ehcache" />
<!-- diskStore:指定数据在磁盘中的存储位置。 defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略
以下属性是必须的: maxElementsInMemory - 在内存中缓存的element的最大数目 maxElementsOnDisk
- 在磁盘上缓存的element的最大数目,若是0表示无穷大 eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上 以下属性是可选的: timeToIdleSeconds
- 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大 diskSpoolBufferSizeMB
这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区. diskPersistent
- 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。 diskExpiryThreadIntervalSeconds
- 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作 memoryStoreEvictionPolicy
- 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出) -->
<defaultCache maxElementsInMemory="1000"
maxElementsOnDisk="10000000" eternal="false" overflowToDisk="false"
timeToIdleSeconds="120" timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
3.3.7 应用场景
使用场景:对于访问响应速度要求高,但是实时性不高的查询,可以采用二级缓存技术。
注意:在使用二级缓存的时候,要设置一下刷新间隔(flashInterval)来定时刷新二级缓存,这个刷新间隔根据具体需求来设置,比如设置30分钟、60分钟等,单位为毫秒。
3.3.8 局限性
Mybatis二级缓存对细粒度的数据级别的缓存实现不好。
场景:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次查询都是最新的商品信息,此时如果使用二级缓存,就无法实现当一个商品发生变化只刷新该商品的缓存信息而不刷新其他商品缓存信息,因为二级缓存是mapper级别的,当一个商品的信息发送更新,所有的商品信息缓存数据都会清空。
解决此类问题,需要在业务层根据需要对数据有针对性的缓存。
比如可以对经常变化的数据操作单独放到另一个namespace的mapper中。
4 mybatis与spring集成
4.1 集成思路
Ø 需要spring来管理数据源信息。
Ø 需要spring通过单例方式管理SqlSessionFactory。
Ø 使用SqlSessionFactory创建SqlSession。(spring和mybatis整合自动完成)
Ø 持久层的mapper都需要由spring进行管理,spring和mybatis整合生成mapper代理对象。
4.2 集成步骤
1、 jar包集成;
2、 配置文件集成(数据源);
3、 SqlSessionFactory集成;
4、 Mapper接口集成;
4.3 开始集成
4.3.1 搭建工程结构
4.3.2 Jar包集成
Jar包如下:
Mybatis3.2.7的jar包(mybatis核心包、依赖包)
Spring3.2.0的jar包
Spring与mybatis的集成包
数据库驱动包
Junit包
Dbcp连接池包
4.3.3 配置文件集成
注意:Mybatis的配置文件中的数据源配置去掉,由spring进行管理配置。
4.3.3.1 Mybatis的SqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 设置全局参数 -->
<settings>
<!-- lazyLoadingEnabled:延迟加载的开关,默认是false -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- aggressiveLazyLoading:默认为true,一旦为true上面的懒加载开关失效 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- cacheEnabled:二级缓存的总开关 默认是false-->
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 定义别名 -->
<typeAliases>
<!-- 批量定义别名 -->
<!-- name:指定需要别名定义的包的名称 它的别名就是类名(类名的首字母大小写都可)-->
<package name="cn.itcast.ssm.po"></package>
</typeAliases>
<!-- 注意:与spring集成后,数据源和事务交给spring来管理 -->
<!-- 加载mapper文件 -->
<mappers>
<mapper resource="mybatis/sqlmap/User.xml"></mapper>
<!-- 批量加载mapper
注意:mapper接口文件和mapper映射文件,名称相同,在同一个包下
-->
<package name="cn.itcast.mybatis.mapper"/>
</mappers>
</configuration>
4.3.3.2 Spring的applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 引用java配置文件 -->
<context:property-placeholder location="db.properties"/>
<!-- 配置数据源,使用dbcp连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${db.driver}" />
<property name="url" value="${db.url}" />
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}" />
<property name="maxActive" value="10" />
<property name="maxIdle" value="5" />
</bean>
</beans>
4.3.4 Spring对SqlSessionFactory进行管理配置
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- mybatis的配置文件路径 -->
<property name="configLocation" value="sqlMapConfig.xml"></property>
<!-- SqlSessionFactory需要数据源信息,之前是写在sqlmapconfig.xml,现在需要重新指定 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
4.3.5 Mybatis程序编写
4.3.5.1 原始dao方式
4.3.5.1.1 编写dao接口
public interface UserDao {
// 1、 根据用户ID来查询用户信息;
public User findUserById(int id);
// 2、 根据用户名称来模糊查询用户信息列表;
public List<User> findUsersByName(String name);
// 3、 添加用户;
public void insertUser(User user);
}
4.3.5.1.2 编写dao实现类(继承SqlSessionDaoSupport)
通过this.getSqlSession()获取sqlsession。
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
@Override
public User findUserById(int id) {
return this.getSqlSession().selectOne("test.findUserById", id);
}
}
4.3.5.1.3 编写Mapper映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test">
<select id="findUserById" parameterType="int" resultType="cn.feibai.pojo.User">
SELECT * FROM USER WHERE id = #{id}
</select>
</mapper>
4.3.5.1.4 Spring定义bean
<!-- 由spring管理原始dao的实现 -->
<bean id="userDao" class="cn.itcast.mybatis.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
4.3.5.1.5 编写测试代码
public class UserDaoTest {
//spring上下文
private ApplicationContext ctx;
@Before
public void setUp() throws Exception {
//读取spring的上下文,然后封装到ctx
ctx = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
}
@Test
public void testFindUserById() {
//创建userdao对象
UserDao userDao = (UserDao) ctx.getBean("userDao");
//调用userdao对象的方法
User user = userDao.findUserById(1);
System.out.println(user);
}
}
4.3.5.2 Mapper代理方式
4.3.5.2.1 编写mapper接口
public interface UserMapper {
// 1、 根据用户ID来查询用户信息
public User findUserById(int id);
}
4.3.5.2.2 编写mapper映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.itcast.ms.mapper.UserMapper">
<!-- 1、 根据用户id来查询用户信息 -->
<select id="findUserById" parameterType="int" resultType="user">
SELECT
* FROM USER WHERE id = #{id}
</select>
</mapper>
4.3.5.2.3 Spring定义bean
Mapper代理开发方式有两种bean的定义方法,一种是MapperFactoryBean,一种是MapperScannerConfigurer(推荐)。
4.3.5.2.3.1 通过MapperFactoryBean创建代理对象(了解)
<!-- mapper代理开发方式之单个mapper配置 -->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="cn.itcast.mybatis.mapper.UserMapper"></property>
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
如果需要创建多个mapper对象,需要在spring中配置多个,比较繁琐,不推荐使用
4.3.5.2.3.2 通过MapperScannerConfigurer批量扫描创建代理对象(掌握)
<!-- 使用MapperScannerConfigurer扫描mapper
扫描器将mapper扫描出来自动 注册到spring容器,bean的id是类名(首字母小写)
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定扫描的包
如果扫描多个包中间使用半角逗号分隔
如果使用扫描器,不用在sqlmapconfig.xml中去配置mapper的扫描了,如果使用mapper代理的开发,在SqlMapConfig.xml中不用配置mapper项了
-->
<property name="basePackage" value="cn.itcast.mybatis.mapper"/>
<!-- 使用sqlSessionFactoryBeanName注入sqlSessionFactory -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--
注意: 这里使用sqlSessionFactoryBeanName而不使用sqlSessionFactory原因如下:
MapperScannerConigurer在扫描mapper时需要注入 sqlSessionFactory,如果使用
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
会存在PropertyPlaceholderConfigurer还没来得及替换dataSource定义中的${jdbc.driver}等数据源变量就注入到了MapperScannerConigurer中,将导致数据库连接不上,如果改为如下方式可以解决问题:
-->
</bean>
4.3.5.2.4 编写测试代码
private ApplicationContext ctx;
@Before
public void setUp() throws Exception {
ctx = new ClassPathXmlApplicationContext(
"spring/applicationContext.xml");
}
@Test
public void testFindUserById() {
// 创建mapper对象
UserMapper userMapper = (UserMapper) ctx.getBean("userMapper");
// 调用mapper对象的方法
User user = userMapper.findUserById(1);
System.out.println(user);
}
5 Mybatis的逆向工程(会用)
5.1 什么是逆向工程
简单点说,就是通过数据库中的单表,自动生成java代码。
Mybatis官方提供了逆向工程,可以针对单表自动生成mybatis代码(mapper.java\mapper.xml\po类)
企业开发中,逆向工程是个很常用的工具。
5.2 下载逆向工程
https://github.com/mybatis/generator/releases/tag/mybatis-generator-1.3.2
5.3 使用方法
1、 创建generator配置文件;
2、 使用java类来执行逆向工程;
3、 把生成的代码拷贝到项目中。
4、 在正式项目中使用逆向工程生成的代码
5.3.1 第一步:创建generator配置文件
在classpath下,创建generator.xml配置文件:
(文件内容可以从逆向工程的jar包中docs目录下的index.html中找到相关代码)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="testTables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root"
password="mysql">
</jdbcConnection>
<!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver"
connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg"
userId="yycg"
password="yycg">
</jdbcConnection> -->
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和
NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成PO类的位置 -->
<javaModelGenerator targetPackage="cn.itcast.ssm.po"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="cn.itcast.ssm.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage:mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="cn.itcast.ssm.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定数据库表 -->
<table tableName="items"></table>
<table tableName="orders"></table>
<table tableName="orderdetail"></table>
<table tableName="user"></table>
</context>
</generatorConfiguration>
5.3.2 第二步:使用java类来执行逆向工程
public class Generator {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("config/generator.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);
}
}
5.3.3 第三步:把生成的代码拷贝到项目中
如果正式项目中已经有po类所在的包了,那么就只需要拷贝po类到指定包下就可以。
如果正式项目中没有po包,那么就把逆向工程中整个po类的包拷贝过去。
Mapper.xml和mapper.java的拷贝与po类一样。
注意:mapper xml文件和mapper.java文件在一个目录内且文件名相同。
5.3.4 第四步:使用生成的代码
public class ItemsMapperTest {
// spring上下文
private ApplicationContext ctx;
@Before
public void setUp() throws Exception {
// 读取spring的上下文,然后封装到ctx
ctx = new ClassPathXmlApplicationContext(
"spring/applicationContext.xml");
}
@Test
public void testSelectByExample() {
ItemsMapper mapper = (ItemsMapper) ctx.getBean("itemsMapper");
ItemsExample example = new ItemsExample();
//使用它进行参数封装传递
Criteria criteria = example.createCriteria();
//设置参数
criteria.andNameEqualTo("背包");
List<Items> list = mapper.selectByExample(example);
System.out.println(list);
}
}
学会使用mapper自动生成的增、删、改、查方法
//删除符合条件的记录
int deleteByExample(UserExample example);
//根据主键删除
int deleteByPrimaryKey(String id);
//插入对象所有字段
int insert(User record);
//插入对象不为空的字段
int insertSelective(User record);
//自定义查询条件查询结果集
List<User> selectByExample(UserExample example);
//根据主键查询
UserselectByPrimaryKey(String id);
//根据主键将对象中不为空的值更新至数据库
int updateByPrimaryKeySelective(User record);
//根据主键将对象中所有字段的值更新至数据库
int updateByPrimaryKey(User record);
5.4 注意事项
Mapper.xml文件已经存在时,如果进行重新生成则mapper.xml文件时,内容不被覆盖而是进行内容追加,结果导致mybatis解析失败。
解决方法:删除原来已经生成的mapper xml文件再进行生成。
Mybatis自动生成的po及mapper.java文件不是内容而是直接覆盖没有此问题。
5.5 Table schema问题
下边是关于针对oracle数据库表生成代码的schema问题:
Schma即数据库模式,oracle中一个用户对应一个schema,可以理解为用户就是schema。
当Oralce数据库存在多个schema可以访问相同的表名时,使用mybatis生成该表的mapper.xml将会出现mapper.xml内容重复的问题,结果导致mybatis解析错误。
解决方法:在table中填写schema,如下:
<table schema="XXXX"tableName=" " >
XXXX即为一个schema的名称,生成后将mapper.xml的schema前缀批量去掉,如果不去掉当oracle用户变更了sql语句将查询失败。
快捷操作方式:mapper.xml文件中批量替换:“from XXXX.”为空
Oracle查询对象的schema可从dba_objects中查询,如下:
select *from dba_objects