J2EE进阶学习——Mybatis(七):高级映射 查询缓存

对订单商品数据模型进行分析。

1.高级映射:

实现一对一、一对多、多对多查询
延迟加载

2.查询缓存

一级缓存
二级缓存(了解mybatis二级缓存使用场景)

3.mybatis和spring整合(掌握)
4.逆向工程(会用)

一、订单商品数据模型

1.1 数据模型分析思路

1.每张表记录的数据内容

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

2.每张表重要的字段设置

非空字段、外键字段

3.数据库级别表与表之间的关系

外键关系

4.表与表之间的业务关系

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

1.2 数据模型分析

用户表user:

记录了购买商品的用户信息

订单表orders:

记录了用户所创建的订单(购买商品的订单)

订单明细表orderdetail:

记录了订单的详细信息即订单所购买商品的信息

商品表items:

记录了商品信息

表与表之间的业务关系:

在分析表与表之间的业务关系时需要建立在某个业务意义基础上去分析
先分析数据级别之间有关系的表之间的业务关系:

  • user和orders:
    user——>orders:一个用户可以创建多个订单,一对多
    orders——>user:一个订单对应一个用户创建,一对一
  • orders和orderdetail:
    order——>orderdetail:一个订单可以包括多个订单明细,因为一个订单可以购买多个商品,每个商品的购买信息在orderdetail记录,一对多关系
    orderdetail——>orders:一个订单的明细只能包括在一个订单中,一对一
  • orderdetail和items:
    orderdetail——>items:一个订单明细只对应一个商品信息,一对一
    items——>ordeadetail:一个商品可以包括在多个订单明细中,一对多

再分析数据库级别没有关系的表之间是否有业务关系

  • orders和items:
    orders和items之间可以通过orderdetail建立关系——多对多

二、一对一查询

2.1 需求

查询订单信息,关联查询创建订单的用户信息

2.2 resultType
  • 2.2.1 sql语句
    确定查询的主表:订单表
    确定查询的关联表:用户表

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

select 
  orders.*,
  user.username,
  user.sex,
  user.address
from
  orders,
  user
where orders.user_id=user.id
  • 2.2.2 创建pojo
  • 将上边sql查询的结果映射到pojo中,pojo中必须包括所有查询列名。
  • 原始的Orders.java不能映射全部字段,需要新创建的pojo。
  • 创建一个pojo继承包括查询字段较多的pojo类
  • 2.2.3 mapper.xml
    <!-- 查询订单关联查询用户信息 -->

    <select id="findOrdersUser" resultType="com.TiHom.mybatis.po.OrdersCustom">
        select
          orders.*,
          user.username,
          user.sex,
          user.address
        from
          orders,
          user
        where orders.user_id=user.id
    </select>
2.3 resultMap
  • 2.3.1 sql语句
    同resultType实现的sql
  • 2.3.2 使用resultMap映射的思路
    使用resultMap将查询的结果中的订单信息映射到Orders对象中,在orders类中添加user属性,将关联查询出来的用户信息映射到orders对象中的user属性中。
  • 2.3.3 需要Orders类中添加user属性
public class Orders {
    private Integer id;
    private Integer user_id;
    private String number;
    private Date createtime;
    private String note;
    //用户信息
    private User user;
  • 2.3.4 mapper.xml
    定义resultMap
<resultMap id="OrdersUserResultMap" type="com.TiHom.mybatis.po.Orders">
        <!-- 配置要映射的订单信息 -->
        <!-- id:指定查询订单中的唯一标识,订单信息的唯一标识,如果有多列组成唯一标识,配置多个id
            column:订单信息的唯一标识列
            property:订单信息的唯一标识列所映射到哪个属性中
        -->
        <id column="id" property="id"/>
        <result column="user_id" property="user_id"/>
        <result column="number" property="number"/>
        <result column="createtime" property="createtime"/>
        <result column="note" property="note"/>

        <!-- 配置要映射的关联的用户信息 -->
        <!-- association:用于映射关联查询单个对象的信息
        property:要将关联查询的用户信息映射到Orders中哪个属性
        -->
        <association property="user" javaType="com.TiHom.mybatis.po.User">
            <!-- 关联查询用户的唯一标识

            -->
            <id column="user_id" property="id"/>
            <result column="username" property="username"/>
            <result column="sex" property="sex"/>
            <result column="address" property="address"/>
        </association>
    </resultMap>

statement

    <select id="findOrdersUserResultMap" resultMap="OrdersUserResultMap">
        select
          orders.*,
          user.username,
          user.sex,
          user.address
        from
          orders,
          user
        where orders.user_id=user.id
    </select>
  • 2.3.5 mapper.java
    public List<Orders> findOrdersUserResultMap() throws Exception;

2.4 resultType和resultMap实现一对一查询小结

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

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

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

三、一对多查询

3.1 需求

查询订单及订单明细的信息

3.2 sql语句

确定主查询表:订单表
确定关联查询表:订单明细表
在一对一查询基础上添加订单明细表关联即可

3.3 分析

使用resultType将上边的查询结果集映射到pojo中,订单信息的pojo就会重复。
要求:对order信息的映射不能出现重复记录

在orders.java类中添加List<orderDetail> orderDetails属性。
最终会将订单信息映射到orders中,订单所对应的订单明细映射到orders中的orderDetails属性中。
映射成的orders记录数为两条(orders信息不重复)
每个orders中的orderDetails属性存储了该订单所对应的订单明细。

3.4 在orders中添加订单明细属性

private List<Orderdetail> orderdetails;

3.5 mapper.xml
    <select id="findOrdersAndOrderDetailResultMap" resultMap="OrdersAndOrderDetailResultMap">
        select
          orders.*,
          user.username,
          user.sex,
          user.address,
          orderdetail.id orderdetail_id,
          orderdetail.items_id,
          orderdetail.items_num,
          orderdetail.orders_id
        from
          orders,
          user,
          orderdetail
        where orders.user_id=user.id and orderdetail.orders_id=orders.id
    </select>
<resultMap id="OrdersAndOrderDetailResultMap" type="com.TiHom.mybatis.po.Orders" extends="OrdersUserResultMap">
        <!-- 订单信息 -->
        <!-- 用户信息 -->
        <!-- 订单明细
        一个订单关联查询出了多条明细,要使用collection进行映射
        collection:对关联查询到多条记录映射到集合对象中
        -->
        <collection property="orderdetails" ofType="com.TiHom.mybatis.po.Orderdetail">
            <id column="orderdetail_id" property="id"/>
            <result column="items_id" property="items_id"/>
            <result column="orders_id" property="orders_id"/>
            <result column="items_num" property="items_num"/>
        </collection>

    </resultMap>
3.6 mapper.java
    public List<Orders> findOrdersAndOrderDetailResultMap() throws Exception;

3.7 小结

mybatis使用resultMap的collection对关联查询的多条记录映射到一个list集合属性中。

使用resultType实现:
将订单明细映射到orders中的orderdetails中,需要进行自己处理,使用双重循环遍历,去掉重复记录,将订单明细放在orderdetails中。

四、多对多查询

4.1 需求

查询用户及用户购买商品信息

4.2 分析

查询主表是:用户表
关联表:由于用户和商品没有直接关联,通过订单和订单明细进行关联,所以关联表:orders、orderdetails、items

4.3 映射思路

将用户信息映射到user中。
在user类中添加订单列表属性List<Orders> orderslist。
在Orders中添加订单明细列表的属性 List<OrderDetail> orderdetails,将订单的明细映射到orderdetails
在OrderDetail中添加Items属性,将订单明细所对应的的商品映射到Items中

4.4 mapper.xml
    <!-- 查询用户及购买的商品信息 -->
    <select id="findUserAndItemsResultMap" 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.user_id=user.id and orderdetail.orders_id=orders.id and orderdetail.items_id=items.id
    </select>
4.5 resultMap定义
<!-- 查询用户及购买的商品 -->
    <resultMap id="UserAndItemsResultMap" type="com.TiHom.mybatis.po.User">
        <!-- 用户信息 -->
        <id column="user_id" property="id"/>
        <result column="username" property="username"/>
        <result column="sex" property="sex"/>
        <result column="address" property="address"/>

        <!-- 订单信息
        一个用户对应多个订单
        -->
        <collection property="ordersList" ofType="com.TiHom.mybatis.po.Orders">
            <id column="id" property="id"/>
            <result column="user_id" property="user_id"/>
            <result column="number" property="number"/>
            <result column="createtime" property="createtime"/>
            <result column="note" property="note"/>
            <!-- 订单明细 -->
            <collection property="orderdetails" ofType="com.TiHom.mybatis.po.Orderdetail">
                <id column="orderdetail_id" property="id"/>
                <result column="items_id" property="items_id"/>
                <result column="orders_id" property="orders_id"/>
                <result column="items_num" property="items_num"/>
                <!-- 一个订单明细对应一个商品 -->
                <association property="items" javaType="com.TiHom.mybatis.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="pic"/>
                </association>
            </collection>
        </collection>
    </resultMap>
4.6 mapper.java

public List<Orders> findUserAndItemsResultMap() throws Exception;

4.7 多对多查询总结

将查询用户购买的商品信息明细清单,(用户名、用户地址、购买物品名称、购买商品时间、购买商品数量)
针对上边的需求就使用resultType将查询到的记录映射到一个扩展的pojo中,很简单实现明细清单的功能。

五、resultMap小结

六、延迟加载

6.1 什么是延迟加载

resultMap可以实现高级映射(使用association、collection实现一对一及一对多的映射),

association、collection都具备延迟加载的功能。

需求:

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

延迟加载:

先从单表查询、需要时再从关联表去关联查询,大大提高数据库的性能,因为查询单表要比关联查询多表的速度要快。

6.2 使用association来实现延迟加载
6.2.1 需求

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

6.2.2 mapper.xml

需要定义两个mapper方法对应的statement。

  • 只查询订单信息
    select * from orders
    在查询订单的statement中使用association去延迟加载(执行)下表的statement(关联查询用户信息)
<!-- 查询订单关联查询用户,用户信息需要延迟加载 -->
    <select id="findOrdersUserLazyLoading" resultMap="OrdersUserLazyLoadingResultMap">
        select * from orders
    </select>
  • 查询用户信息
    通过上边查询到的订单信息中的user_id去关联查询用户信息
    使用UserMapper.xml中的findUserById
select orders.*,
    (select username form user where orders.user_id = user.id)username,
    (select sex from user where orders,user_id=user.id)sex
from orders

这里sql语句是表示关联查询同时添加两列username、sex

上边先去执行findOrdersUserLazyLoading,当需要去查询用户的时候再去执行findUserById,通过resultMap的定义将延迟加载执行配置起来

6.2.3 延迟加载resultMap

使用association中的select指定延迟加载去执行的statement的id

<resultMap id="OrdersUserLazyLoadingResultMap" type="com.TiHom.mybatis.po.Orders">
        <!-- 对订单信息进行映射配置 -->
        <id column="id" property="id"/>
        <result column="user_id" property="user_id"/>
        <result column="number" property="number"/>
        <result column="createtime" property="createtime"/>
        <result column="note" property="note"/>
        <!-- 实现对用户的延迟加载
        select:指定延迟加载需要执行的statement的id(是根据user_id查询用户信息的statement)
        column:订单信息中关联用户信息查询的列,是user_id
        -->
        <association property="user" javaType="com.TiHom.mybatis.po.User"
                     select="com.TiHom.mybatis.mapper.UserMapper.findUserById" column="user_id">

        </association>
    </resultMap>
6.2.4 mapper.java
/**
     * 查询订单关联查询用户,用户信息是延迟加载
     */
    public List<Orders> findOrdersUserLazyLoading() throws Exception;
6.2.5 测试

1.执行上面的mapper方法(findOrdersUserLazyLoading),内部去调用com.TiHom.mybatis.mapper.OrdersMapperCustom中的findOrdersUserLazyLoading只查询orders信息(单表)。
2.在程序中去遍历上一步骤查询出的List<Orders>,当我们调用Orders的getUser方法时,开始进行延迟加载
3.延迟加载,去调用UserMapper.xml中findUserById这个方法获取用户信息

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

<settings>
        <!-- 打开延迟加载开关 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 将积极加载改为消极加载即按需加载 -->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

测试代码

    @Test
    public void testFindOrdersUserLazyLoading() throws Exception{
        SqlSession sqlSession = sqlSessionFactory.openSession();
        OrdersMapperCustom ordersMapperCustom = sqlSession.getMapper(OrdersMapperCustom.class);
        //查询订单信息(单表)
        List<Orders> list = ordersMapperCustom.findOrdersUserLazyLoading();

        //遍历上边的订单列表
        for(Orders orders:list){
            //执行getUser()去查询用户信息,这里实现按需加载
            User user = orders.getUser();

        }
    }
6.2.6 延迟加载思考

不使用mybatis提供的association及collection中的延迟加载功能,如何实现延迟加载?

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

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

七、查询缓存

7.1 定义

mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。
mybatis提供一级缓存,和二级缓存。


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

二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,二级缓存是跨SqlSession的。

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

7.2 一级缓存
7.2.1 工作原理
一级缓存

第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。

如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中永远存储最新信息,避免脏读

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

7.2.2 一级缓存测试

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

7.2.3 一级缓存应用

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

service{
//第一次调用mapper方法findUserById(1)
//第二次调用mapper方法,从一级缓存中取数据
//方法结束,sqlSession关闭
}

如果是执行两次service调用查询相同的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空。所以如果想完成上述操作,可以使用二级缓存。

7.3 二级缓存
二级缓存

首先开启mybatis的二级缓存。

  • sqlSession1去查询用户id为1的用户信息,查询到的用户信息会将查询数据存储到二级缓存中。
  • 如果SqlSession3去执行相同mapper下sql,执行commit提交,清空该mapper下的二级缓存区域的数据
  • sqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。

二级缓存与一级缓存区别:二级缓存的分为更大,多个sqlSession可以共享一个UserMapper的二级缓存区,UserMapper有一个二级缓存区(按namespace分),其他mapper也有自己二级缓存区,每一个namespace的mapper有一个二级缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。

7.3.2 开启二级缓存

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

SqlMapConfig.xml

<!-- 开启二级缓存 -->
        <setting name="cacheEnabled" value="true"/>

UserMapper.xml中开启二级缓存,UserMapper.xml下的sql执行完会存储到它的缓存区域(HashMap)

<!-- 开启二级缓存 -->
    <cache/>
7.3.3 调用pojo类实现序列化接口

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

7.3.4 测试方法

这里是调用提交操作


7.3.5 useCache配置

在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql查询,默认情况是true,即该sql使用二级缓存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
总结:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。

7.3.6 刷新缓存(就是清空缓存)

在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存出现脏读。

设置statement配置中的flushCache=“true”属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。

如下:
<insert id="insertUser" parameterType="com.TiHom.mybatis.po.User" flushCache="true">
总结:一般执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容