一对一查询
案例:查询所有订单信息,订单信息中显示下单人信息。
注意:因为一个订单信息只会是一个人下的订单,所以从查询订单信息出发关联查询用户信息为一对一查询。如果从用户信息出发查询用户下的订单信息则为一对多查询,因为一个用户可以下多个订单。
方法一:
使用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:developehcache" /> <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"/>