一对一查询
案例:查询所有订单信息,订单信息中显示下单人信息。
注意:因为一个订单信息只会是一个人下的订单,所以从查询订单信息出发关联查询用户信息为一对一查询。如果从用户信息出发查询用户下的订单信息则为一对多查询,因为一个用户可以下多个订单。
方法一:
使用resultType,定义订单信息po类,此po类中包括了订单信息和用户信息:
Sql语句:
SELECT
orders.*,
user.username,
user.address
FROM
orders,
USER
WHERE orders.user_id = user.id
定义po类
Po类中应该包括上边sql查询出来的所有字段,如下:
public class UserOrder extends Orders { private String username;// 用户姓名
private String address;// 地址
get/set。。。。
UserOrder类继承Orders类后UserOrder类包括了Orders类的所有字段,只需要定义用户的信息字段即可。
Mapper.xml
<!-- 查询所有订单信息 -->
<select id="findOrdersList" resultType="cn.itcast.mybatis.po.UserOrder">
SELECT
orders.*,
user.id user_id,
user.username,
user.address
FROM
orders, USER
WHERE orders.user_id = user.id
</select>
Mapper接口:
public List<UserOrder> findOrdersList() throws Exception;
测试:
public void testfindOrdersList()throws Exception{
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
//查询订单信息
List<UserOrder> list = userMapper.findOrdersList();
System.out.println(list);
//关闭session
session.close();
}
总结:
定义专门的po类作为输出类型,其中定义了sql查询结果集所有的字段。此方法较为简单,企业中使用普遍。
方法二:
使用resultMap,定义专门的resultMap用于映射一对一查询结果。
Sql语句:
SELECT
orders.*,
user.username,
user.address
FROM
orders,
USER
WHERE orders.user_id = user.id
定义po类
在Orders类中加入User属性。
Mapper.xml
<select id="findOrdersList2" resultMap="userordermap">
SELECT
orders.*,
user.username,
user.address
FROM
orders, USER
WHERE orders.user_id = user.id
</select>
这里resultMap指定userordermap。
定义resultMap
<!-- 订单信息resultmap -->
<resultMap type="cn.itcast.mybatis.po.Orders" id="userordermap">
<!-- 这里的id,是mybatis在进行一对一查询时将user字段映射为user对象时要使用,必须写 -->
<id property="id" column="id" />
<result property="user_id" column="user_id" />
<result property="order_number" column="order_number" />
<association property="user" javaType="cn.itcast.mybatis.po.User">
<!-- 这里的id为user的id,如果写上表示给user的id属性赋值 -->
<id property="id" column="user_id" />
<result property="username" column="username" />
<result property="address" column="address" />
</association>
</resultMap>
association:表示进行关联查询单条记录
property:表示关联查询的结果存储在cn.itcast.mybatis.po.Orders的user属性中
javaType:表示关联查询的结果类型
<id property="id" column="user_id" />:查询结果的user_id列对应关联对象的id属性,这里是<id />表示user_id是关联查询对象的唯一标识。
<result property="username" column="username" />:查询结果的username列对应关联对象的username属性。
Mapper接口:
public List<Orders> findOrdersList2() throws Exception;
测试:
public void testfindOrdersList2()throws Exception{
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
//查询订单信息
List<Orders> list = userMapper.findOrdersList2();
System.out.println(list);
//关闭session
session.close();
}
总结:
此种方法使用了mybatis的association标签用于一对一关联查询,将查询结果映射至对象中。
一对多查询
案例:查询所有订单信息及订单下的订单明细信息。
订单信息与订单明细为一对多关系,一个订单包括多个商品信息。
使用resultMap实现如下:
ql语句:
SELECT
orders.*,
user.username,
user.address,
orderdetail.id orderdetail_id,
orderdetail.item_id,
orderdetail.item_num,
orderdetail.item_price
FROM
orders,USER ,orderdetail
WHERE orders.user_id = user.id
AND orders.id = orderdetail.orders_id
定义po类
在Orders类中加入User属性。
在Orders类中加入List<Orderdetail> orderdetails 属性
Mapper.xml
<select id="findOrdersDetailList" resultMap="userorderdetailmap">
SELECT
orders.*,
user.username,
user.address,
orderdetail.id orderdetail_id,
orderdetail.item_id,
orderdetail.item_num,
orderdetail.item_price
FROM orders,USER ,orderdetail
WHERE orders.user_id = user.id
AND orders.id = orderdetail.orders_id
</select>
定义resultMap
<!-- 订单信息resultmap -->
<resultMap type="cn.itcast.mybatis.po.Orders" id="userorderdetailmap">
<id property="id" column="id" />
<result property="user_id" column="user_id" />
<result property="order_number" column="order_number" />
<association property="user" javaType="cn.itcast.mybatis.po.User">
<id property="id" column="user_id" />
<result property="username" column="username" />
<result property="address" column="address" />
</association>
<collection property="orderdetails" ofType="cn.itcast.mybatis.po.Orderdetail">
<id property="id" column="orderdetail_id" />
<result property="item_id" column="item_id" />
<result property="item_num" column="item_num" />
<result property="item_price" column="item_price" />
</collection>
</resultMap>
collection部分定义了查询订单明细信息。
collection:表示关联查询结果集
property="orderdetails":关联查询的结果集存储在cn.itcast.mybatis.po.Orders上哪个属性。
ofType="cn.itcast.mybatis.po.Orderdetail":指定关联查询的结果集中的对象类型即List中的对象类型。
<id />及<result/>的意义同一对一查询。
Mapper接口:
public List<Orders> findOrdersDetailList () throws Exception;
测试:
public void testfindOrdersDetailList()throws Exception{
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
//查询订单信息
List<Orders> list = userMapper.findOrdersDetailList();
System.out.println(list);
//关闭session
session.close();
}
总结:
此种方法使用了mybatis的collection标签用于一对多关联查询,将查询结果映射至集合对象中。
resultMap使用继承
上边定义的resultMap中user部分和一对一查询订单信息的resultMap相同,这里使用继承可以不再填写重复的内容,如下:
<resultMap type="cn.itcast.mybatis.po.Orders" id="userorderdetailmap2" extends="userordermap">
<collection property="orderdetails" ofType="cn.itcast.mybatis.po.Orderdetail">
<id property="id" column="orderdetail_id" />
<result property="item_id" column="item_id" />
<result property="item_num" column="item_num" />
<result property="item_price" column="item_price" />
</collection>
</resultMap>
使用extends继承订单信息userordermap。
多对多查询
案例:查询所有订单信息及订单明细的商品信息。
订单信息与商品信息为多对多关系,因为一个订单包括多个商品信息,一个商品可以在多个订单中存在,订单信息与商品信息的多对多关系是通过订单明细表进行关联。
Sql语句:
SELECT
orders.*,
user.username,
user.address,
orderdetail.id orderdetail_id,
orderdetail.item_id,
orderdetail.item_num,
orderdetail.item_price,
items.item_name,
items.item_detail
FROM
orders,USER ,orderdetail,items
WHERE orders.user_id = user.id
AND orders.id = orderdetail.orders_id
AND orderdetail.item_id = items.id
定义po类
在Orders类中加入User属性。
在Orders类中加入List<Orderdetail> orderdetails 属性,存储订单明细信息
在Orderdetail类中加入Items items 属性存储商品信息
Mapper.xml
<select id="findOrdersItemsList" resultMap="userorderitemsmap">
SELECT
orders.*,
user.username,
user.address,
orderdetail.id orderdetail_id,
orderdetail.item_id,
orderdetail.item_num,
orderdetail.item_price,
items.item_name,
items.item_detail
FROM
orders,USER ,orderdetail,items WHERE orders.user_id = user.id
AND orders.id = orderdetail.orders_id
AND orderdetail.item_id = items.id
</select>
定义resultMap
<!-- 订单商品信息resultmap -->
<resultMap type="cn.itcast.mybatis.po.Orders" id="userorderitemsmap"
extends="userordermap">
<collection property="orderdetails" ofType="cn.itcast.mybatis.po.Orderdetail">
<id property="id" column="orderdetail_id" />
<result property="item_id" column="item_id" />
<result property="item_num" column="item_num" />
<result property="item_price" column="item_price" />
<!-- 商品信息 -->
<association property="items" javaType="cn.itcast.mybatis.po.Items">
<id property="id" column="item_id" />
<result property="item_name" column="item_name" />
<result property="item_detail" column="item_detail" />
</association>
</collection>
</resultMap>
在collection中加入association通过订单明细表关联查询商品信息
Mapper接口:
public List<Orders> findOrdersItemsList () throws Exception;
测试:
public void findOrdersItemsList()throws Exception{
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
//查询订单信息
List<Orders> list = userMapper.findOrdersItemsList();
System.out.println(list);
//关闭session
session.close();
}
总结:
所谓一对多查询、多对多查询都对于具体的业务分析来说,使用mybatis提交的collection和association可以完成不同的关联查询需求,通常在实际应用时association用自定义pojo方式代替,关联查询结果集使用collection完成。
延迟加载
需要查询关联信息时,使用mybatis延迟加载特性可有效的减少数据库压力,首次查询只查询主要信息,关联信息等用户获取时再加载。
打开延迟加载开关
在mybatis核心配置文件中配置:
lazyLoadingEnabled、aggressiveLazyLoading
设置项 |
描述 |
允许值 |
默认值 |
lazyLoadingEnabled |
全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。 |
true | false |
false |
aggressiveLazyLoading |
当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。 |
true | false |
true |
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
一对一查询延迟加载
Sql语句:
SELECT
orders.*
FROM
orders
定义po类
在Orders类中加入User属性。
定义resultMap
<!-- 订单信息resultmap -->
<resultMap type="cn.itcast.mybatis.po.Orders" id="userordermap2">
<id property="id" column="id" />
<result property="user_id" column="user_id" />
<result property="order_number" column="order_number" />
<association property="user" javaType="cn.itcast.mybatis.po.User" select="selectUserById" column="user_id" />
</resultMap>
association:
select="selectUserById":指定关联查询sql为selectUserById
column="user_id":关联查询时将user_id列的值传入selectUserById
最后将关联查询结果映射至cn.itcast.mybatis.po.User。
Mapper.xml
<select id="findOrdersList3" resultMap="userordermap2">
SELECT
orders.*
FROM
orders
</select>
Mapper接口:
public List<Orders> findOrdersList3() throws Exception;
测试:
public void testfindOrdersList3()throws Exception{
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
//查询订单信息
List<Orders> list = userMapper.findOrdersList3();
System.out.println(list);
//开始加载,通过orders.getUser方法进行加载
for(Orders orders:list){
System.out.println(orders.getUser());
}
//关闭session
session.close();
}
总结:
使用延迟加载提高数据库查询性能,默认不查询关联数据,按需要发出sql请求关联查询信息。
一级缓存
Mybatis一级缓存的作用域是同一个SqlSession。
第一个例子:
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
//第一次查询
User user1 = userMapper.selectUserById(1);
System.out.println(user1);
//第二次查询,由于是同一个session则不再向数据发出语句直接从缓存取出
User user2 = userMapper.selectUserById(1);
System.out.println(user2);
原理:
Mybatis首先去缓存中查询结果集,如果没有则查询数据库,如果有则从缓存取出返回结果集就不走数据库。
Mybatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象
第二个例子:
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
//第一次查询
User user1 = userMapper.selectUserById(1);
System.out.println(user1);
//在同一个session执行更新
User user_update = new User();
user_update.setId(1);
user_update.setUsername("李奎");
userMapper.updateUser(user_update);
session.commit();
//第二次查询,虽然是同一个session但是由于执行了更新操作session的缓存被清空,这里重新发出sql操作
User user2 = userMapper.selectUserById(1);
System.out.println(user2);
原理
该例子与第一个例子不同的是在两次查询中间加入了更新,更新操作执行后mybatis执行了清除缓存即清空HashMap。
二级缓存
Mybatis的二级缓存即查询缓存,它的作用域是一个mapper的namespace,即在同一个namespace中查询sql可以从缓存中获取数据。
二级缓存是可以跨SqlSession的。
启二级缓存:
- 在核心配置文件SqlMapConfig.xml中加入
<setting name="cacheEnabled" value="true"/>
描述 |
允许值 |
默认值 |
|
cacheEnabled |
对在此配置文件下的所有cache 进行全局性开/关设置。 |
true false |
true |
- 要在你的Mapper映射文件中添加一行: <cache />
- 在select语句中useCache=false可以禁用当前的语句的二级缓存,即每次查询夸session 的查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
实现序列化:
注意:将查询结果的pojo对象进行序列化实现 java.io.Serializable接口
例子:
//获取session1
SqlSession session1 = sqlSessionFactory.openSession();
UserMapper userMapper = session1.getMapper(UserMapper.class);
//使用session1执行第一次查询
User user1 = userMapper.selectUserById(1);
System.out.println(user1);
//关闭session1
session1.close();
//获取session2
SqlSession session2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
//使用session2执行第二次查询,由于开启了二级缓存这里从缓存中获取数据不再向数据库发出sql
User user2 = userMapper2.selectUserById(1);
System.out.println(user2);
//关闭session2
session2.close();
刷新缓存
在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。
sql中的 flushCache="true" 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
如下:
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">
ache 的其它参数:
flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。
readOnly(只读)属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
如下例子:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会 导致冲突。可用的收回策略有, 默认的是 LRU:
- LRU – 最近最少使用的:移除最长时间不被使用的对象。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
应用场景:
1、针对复杂的查询或统计的功能,用户不要求每次都查询到最新信息,使用二级缓存,通过刷新间隔flushInterval设置刷新间隔时间,由mybatis自动刷新。
比如:实现用户分类统计sql,该查询非常耗费时间。
将用户分类统计sql查询结果使用二级缓存,同时设置刷新间隔时间:flushInterval(一般设置时间较长,比如30分钟,60分钟,24小时,根据需求而定)
2、针对信息变化频率高,需要显示最新的信息,使用二级缓存。
将信息查询的statement与信息的增、删、改定义在一个mapper.xml中,此mapper实现二级缓存,当执行增、删、修改时,由mybatis及时刷新缓存,满足用户从缓存查询到最新的数据。
比如:新闻列表显示前10条,该查询非常快,但并发大对数据也有压力。
将新闻列表查询前10条的sql进行二级缓存,这里不用刷新间隔时间,当执行新闻添加、删除、修改时及时刷新缓存。
二级缓存使用Ehcache
Mybatis与缓存框架ehcache进行了整合,采用ehcache框架管理缓存数据。
第一步:引入缓存的依赖包
第二步:引入缓存配置文件
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>
defaultCache配置说明:
maxElementsInMemory 内存中最大缓存对象数.当超过最大对象数的时候,ehcache会按指定的策略去清理内存
eternal 缓存对象是否永久有效,一但设置了,timeout将不起作用.
timeToIdleSeconds 设置Element在失效前的允许闲置时间.仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大.
timeToLiveSeconds:设置Element在失效前允许存活时间.最大时间介于创建时间和失效时间之间.仅当element是永久有效时使用,默认是0.,也就是element存活时间无穷大.
overflowToDisk 配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中.
diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
maxElementsOnDisk 磁盘中最大缓存对象数,若是0表示无穷大.
diskPersistent 是否在重启服务的时候清楚磁盘上的缓存数据.true不清除.
diskExpiryThreadIntervalSeconds 磁盘失效线程运行时间间隔.
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存.默认策略是LRU(最近最少使用).你可以设置为FIFO(先进先出)或是LFU(较少使用).
第三步:修改mapper文件中缓存类型
在cache中指定type。
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>