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 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 orderslist。
在Orders中添加订单明细列表的属性 List 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的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表示刷新缓存,这样可以避免数据库脏读

7.4 mybatis整合ehcache

ehcache是一个分布式缓存框架。

7.4.1 分布式缓存

我们系统为了提高系统并发、性能,一般对系统进行分布式部署(集群部署方式)

不使用分布式缓存,缓存的数据在各个服务器单独存储,不方便系统开发,所以要使用分布式缓存对缓存数据进行集中管理。

mybatis无法实现分布式缓存,需要和其他分布式缓存框架进行整合。

7.4.2 整合方法(掌握)

mybatis提供了一个cache接口,如果要实现自己的缓存逻辑,实现cache接口开发即可。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.cache;

import java.util.concurrent.locks.ReadWriteLock;

public interface Cache {
    String getId();

    void putObject(Object var1, Object var2);

    Object getObject(Object var1);

    Object removeObject(Object var1);

    void clear();

    int getSize();

    ReadWriteLock getReadWriteLock();
}

mybatis和ehcache整合,mybatis和ehcache整合包中提供了一个cache接口的实现类。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.cache.impl;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;

public class PerpetualCache implements Cache {
    private final String id;
    private Map<Object, Object> cache = new HashMap();

    public PerpetualCache(String id) {
        this.id = id;
    }

    public String getId() {
        return this.id;
    }

    public int getSize() {
        return this.cache.size();
    }

    public void putObject(Object key, Object value) {
        this.cache.put(key, value);
    }

    public Object getObject(Object key) {
        return this.cache.get(key);
    }

    public Object removeObject(Object key) {
        return this.cache.remove(key);
    }

    public void clear() {
        this.cache.clear();
    }

    public ReadWriteLock getReadWriteLock() {
        return null;
    }

    public boolean equals(Object o) {
        if (this.getId() == null) {
            throw new CacheException("Cache instances require an ID.");
        } else if (this == o) {
            return true;
        } else if (!(o instanceof Cache)) {
            return false;
        } else {
            Cache otherCache = (Cache)o;
            return this.getId().equals(otherCache.getId());
        }
    }

    public int hashCode() {
        if (this.getId() == null) {
            throw new CacheException("Cache instances require an ID.");
        } else {
            return this.getId().hashCode();
        }
    }
}
7.4.3 加入ehcache包

加入自身的包和与mybatis整合的包

7.4.4 整合ehcache

配置mapper中cache中的type为ehcache对cache接口的实现类型

    <!-- 开启二级缓存
    type:指定cache接口的实现类的类型,mybatis默认使用PerpetualCache
    要和ehcache整合,需要配置type为ehcache实现cache接口的类型
    -->
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
7.4.5 加入ehcache的配置文件

classpath下:ehcache.xml
在包里面找,我手上没包 并且以后其实都是采用redis多,所以这里就不贴出来了

7.5 二级缓存的应用场景

对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。

实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。

7.6 二级缓存局限性

mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品信息,因为mybatis的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性的缓存。

猜你喜欢

转载自blog.csdn.net/tryandfight/article/details/79618003