• 阶段六-Day05-MyBatis3


    一、多表查询(面试题)

    1. 介绍

    多表查询是在企业中必不可少的,无论多么简单的项目里通常会出现多表查询的操作。因为只要是关系型数据库,在设计表时都需要按照范式进行设计,为了减少数据冗余,都会拆成多个表。当需要多张表中数据时,需要进行联合查询

    在MySQL学习时,知道表之间关系分为:一对一、一对多、多对多。这三种关系又细分为单向和双向。

    如果学习的是Hibernate框架,必须要严格区分开表之间的关系,然后才能使用Hibernate框架。但是在MyBatis框架中只有两种情况:当前表对应另外表是一行数据还是多行数据。转换到实体类上:当前实体类包含其它实体类一个对象还是多个对象。

    转换到MyBatis的映射文件上:在标签里面使用还是标签就可以。

    所以:在学习MyBatis多表查询时其实就是在学习标签和标签。

    1. 如果一个实体类关联另一个实体类的一个对象使用

    2. 如果一个实体类关联一个实体类的List集合对象,需要使用

    3. 所以分析的思路是:先分析需求->分析数据库设计对应关系->创建实体类->根据实体类关联属性类型决定使用哪个标签。

    这两个标签根据编写的SQL,分为N+1查询和联合查询两种方式。

    两种方式优缺点:

    • 联合查询方式:

    优点:一次查询。

    缺点:SQL相对复杂。不支持延迟加载。

    1. <mapper namespace="com.sh.mapper.EmpMapper" >
    2. <resultMap id="a" type="Emp">
    3. <id column="id" property="id"/>
    4. <result column="name" property="name"/>
    5. <result column="addr" property="addr"/>
    6. <result column="did" property="did"/>
    7. <association property="dept" javaType="Dept">
    8. <id column="dept_id" property="dept_id"/>
    9. <result column="dname" property="dname"/>
    10. association>
    11. resultMap>
    12. <select id="queryAll" resultMap="a">
    13. select id,name,addr,did,dept_id,dname from emp join dept where
    14. did = dept_id
    15. select>
    • 业务装配

      优点:手动实现,灵活度高。

      缺点:代码复杂。

    一般不使用

    • N+1方式:

    优点:SQL简单。支持延迟加载。

    缺点:多做N次查询。

    1. <mapper namespace="com.sh.mapper.DeptMapper">
    2. <resultMap id="b" type="Dept">
    3. <id column="dept_id" property="dept_id"/>
    4. <result column="dname" property="dname"/>
    5. resultMap>
    6. <select id="queryById" resultMap="b">
    7. select * from dept where dept_id=#{id}
    8. select>
    9. mapper>
    1. <resultMap id="c" type="Emp">
    2. <id column="id" property="id"/>
    3. <result column="name" property="name"/>
    4. <result column="addr" property="addr"/>
    5. <result column="did" property="did"/>
    6. <association property="dept" javaType="Dept" select="com.sh.mapper.DeptMapper.queryById" column="did">
    7. association>
    8. resultMap>
    9. <select id="queryAll1" resultMap="c">
    10. select * from emp
    11. select>
    12. mapper>

     

    二、延迟加载(面试题)

    延迟加载只能出现在多表联合查询的N+1方式

    表示当执行当前方法时,是否立即执行关联方法的SQL。

    1. 测试默认情况下效果

    以EmpMapper接口的queryAllN1()方法进行举例:当前方法的作用是查询全部Emp信息,并且调用DeptMapper的queryById方法,同时查询Dept的内容。

    2. 启用延迟加载

    配置延迟加载有两种方式:

    全局配置。整个项目所有N+1位置都生效。

    局部配置。只配置某个N+1位置。

    两种方式需要选择其中一种,如果两种方式都使用了,局部配置方式生效。

    2.1 全局配置方式

    官方文档全局设置属性说明:

    属性名解释说明可取值默认值
    lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。true | falsefalse

    全局设置: 

    1. <settings>
    2. <setting name="mapUnderscoreToCamelCase" value="true"/>
    3. <setting name="lazyLoadingEnabled" value="true"/>
    4. settings>

    局部设置:

    局部配置方式需要在collection或association标签中配置fetchType属性。fetchType可取值:lazy(延迟加载)和earge(立即加载)。

    当配置了fetchType属性后,全局settings的配置被覆盖,对于当前标签以fetchType属性值为准。

    1. <resultMap id="empMap2" type="Emp">
    2. <id column="e_id" property="id"/>
    3. <result column="e_name" property="name"/>
    4. <association property="dept" javaType="Dept"
    5. select="com.sh.mapper.DeptMapper.selectById" column="e_d_id"
    6. fetchType="lazy">association>
    7. resultMap>
    8. <select id="selectAllN1" resultMap="empMap2">
    9. select e_id,e_name,e_d_id from emp
    10. select>

    三、缓存(面试题)

    1. 缓存介绍

    缓存是一种临时存储少量数据至内存或者是磁盘的一种技术。减少数据的加载次数,可以降低工作量,提高程序响应速度,缓存的重要性是不言而喻的。

    MyBatis的缓存将相同查询条件的SQL语句执行一遍后所得到的结果存在内存或者某种缓存介质当中,当下次遇到一模一样的查询SQL时候不在执行SQL与数据库交互,而是直接从缓存中获取结果,不再查询数据库,提升了性能;尤其是在查询多、缓存命中率越高的情况下,使用缓存对性能的提高更明显。

    MyBatis分为一级缓存和二级缓存,同时也可配置关于缓存设置。

    1. 一级存储是SqlSession上的缓存。

    2. 二级缓存是在SqlSessionFactory(namespace)上的缓存。

    3. 默认情况下,MyBatis开启一级缓存,没有开启二级缓存。当数据量大的时候可以借助一些第三方缓存框架或Redis缓存来协助保存Mybatis的二级缓存数据。

    2. 一级缓存

    一级缓存是SqlSession级缓存。只要是同一个SqlSession对象(必须是同一个)调用同一个完全相同的SQL不会走同一个缓存),将直接使用缓存数据,而不会访问数据库。

    重要提示:

    一级缓存想要生效,必须同时满足3个条件:

    1. 同一个SqlSession对象

    2. 同一个select标签。本质为底层同一个JDBC的Statemen对象。

    3. 完全相同的SQL,包含SQL的参数值也必须相同。

    4. insert、delete、update操作会清空一级缓存数据。

    5. close(),commit也会清空一级缓存

    2.1 一级缓存流程图

    命中缓存:从Map中查询是否存在指定key。如果存在表示命中缓存,如果不存在这个key,需要访问数据库。

    更新到缓存:把查询结果put到map中。

    MyBatis中的缓存机制:
    一级缓存执行流程: 默认开启
        1.根据调用的接口中的方法 + select语句 + ... + 建立了缓存的key
        2.从一级缓存(localCache的集合)中获取key对应的数据
            如果
            没有:从数据库中查询,将查询结果存储到一级缓存中
            (key,数据),返回查询到的数据
            有:直接返回一级缓存中获取到的数据
        3.一级缓存基于SqlSession,使用同一个SqlSession一级缓存生效
        也就是说多个用户访问时就不好使了

        注意:
            哪些操作可以清除一级缓存
            防止出现脏读,幻读,可重复读的问题
            1.commit() rollback()
            2.insert() update() delete()
            3.close()

    3. 二级缓存

    二级缓存是以namespace为标记的缓存,可能要借助磁盘,磁盘上的缓存,可以由一个SqlSessionFactory(单例设计模式,多个用户可以使用缓存)创建的SqlSession之间共享缓存数据,默认并不开启。下面的代码中创建了两个SqlSession,执行相同的SQL语句,尝试让第二个SqlSession使用第一个SqlSession查询后缓存的数据。

    二级缓存生效条件:

    1. 同一个SqlSessionFactory对象。

    2. 同一个方法(