阶段六-Day05-MyBatis3

news2024/12/20 0:44:28

一、多表查询(面试题)

1. 介绍

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

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

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

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

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

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

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

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

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

两种方式优缺点:

  • 联合查询方式:

优点:一次查询。

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

<!--  配置SQL映射的文件 -->
<mapper namespace="com.sh.mapper.EmpMapper" >
    <!-- 联合查询 使用resultMap -->
    <resultMap id="a" type="Emp">
        <!-- 员工映射 -->
        <!-- 使用联合查询时,默认可以映射的也要进行手动映射-->
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="addr" property="addr"/>
        <result column="did" property="did"/>
        <!-- 部门映射 -->
        <!--
        property="dept" 哪个属性名
        javaType  联合查询返回的类型
        -->
        <!-- 除了集合的引用类型使用association -->
        <!-- 集合使用collection -->
        <association property="dept" javaType="Dept">
            <id  column="dept_id" property="dept_id"/>
            <result column="dname" property="dname"/>
        </association>
    </resultMap>
<select id="queryAll" resultMap="a">
    select id,name,addr,did,dept_id,dname from emp join dept where
    did = dept_id
</select>

  • 业务装配

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

    缺点:代码复杂。

一般不使用

  • N+1方式:

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

缺点:多做N次查询。

<mapper namespace="com.sh.mapper.DeptMapper">
    <!-- 先根据部门id查询该部门的所有员工 -->
    <resultMap id="b" type="Dept">
        <id column="dept_id" property="dept_id"/>
        <result column="dname" property="dname"/>
    </resultMap>
    <select id="queryById" resultMap="b">
         select * from dept where dept_id=#{id}
    </select>

</mapper>

    <!-- N+1 查询 -->
    <!-- 那边DeptMapper要设置好-->

    <!-- 再正常查询 -->
    <resultMap id="c" type="Emp">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="addr" property="addr"/>
        <result column="did" property="did"/>
        <!-- 再使用association -->
        <!--
        select 连接的mapper映射文件
        column 相当于外键
        其实就是将两个mapper练了起来,按顺序执行
        -->
        <association property="dept" javaType="Dept" select="com.sh.mapper.DeptMapper.queryById" column="did">
        </association>
    </resultMap>
    <select id="queryAll1" resultMap="c">
            select * from emp
    </select>

</mapper>

 

二、延迟加载(面试题)

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

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

1. 测试默认情况下效果

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

2. 启用延迟加载

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

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

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

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

2.1 全局配置方式

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

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

全局设置: 

 <settings>
        <!-- 开启驼峰转换 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!--
        全局方式设置延迟加载
        全局设置后所有的能延迟加载的都会延迟加载
        即所有的N+1都会使用延迟加载
        局部设置就是指定哪些不使用延迟加载
        -->
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>

局部设置:

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

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

<resultMap id="empMap2" type="Emp">
    <id column="e_id" property="id"/>
    <result column="e_name" property="name"/>
    <association property="dept" javaType="Dept"
                 select="com.sh.mapper.DeptMapper.selectById" column="e_d_id"
                 fetchType="lazy"></association>
</resultMap>
<select id="selectAllN1" resultMap="empMap2">
    select e_id,e_name,e_d_id from emp
</select>

三、缓存(面试题)

1. 缓存介绍

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

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

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

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

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

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

2. 一级缓存

一级缓存是SqlSession级缓存。只要是同一个SqlSession对象(必须是同一个)调用同一个<select>标签相同参数值时(不同<select>完全相同的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. 同一个方法(<select>)。

  3. SQL完全相同。

重要提示:

二级缓存默认不开启,需要手动开启。

只有当SqlSession执行commit或close时才会存储到二级缓存中。

下面演示配置二级缓存生效的步骤:

  1. 全局开关:在mybatis.xml文件中的<settings>标签配置开启二级缓存

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

    2. 分开关:在要开启二级缓存的mapper文件中开启缓存

使用<cache/>配置时,注解的查询无法缓存

<mapper namespace="com.sh.mapper.EmpMapper">
    <cache/>
</mapper>

     3.二级缓存未必完全使用内存,有可能占用硬盘存储,缓存中存储的JavaBean对象必须实现序列化接口

二级缓存执行流程: 需要手动开启
    全局设置:
    在在mybatis.xml文件中的<settings>标签配置开启二级缓存
    <settings>
    <setting name="cacheEnabled" value="true"/>
    </settings>
    局部配置:
    在映射文件中通过<cache/>开启    
    标签中设置useCache="false",设置指定select标签不使用二级缓存
    每一个映射文件对应着一个映射文件的二级缓存
    有效范围:同一个映射文件中的sql
    1.根据调用的接口中的方法 + select语句 + ... 建立了缓存的 key
    2.先从当前映射文件的二级缓存(真正存储二级缓存数据的集合:Cache接口实现类PerpetualCache的cache属性)
    [本质为map集合]中根据key获取对应数据
    如果
        没有:
            1. 从一级缓存(localCache)中获取key对应的数据
                没有:从数据库查询,将查询结果放入一级缓存中,
                再添加到二级缓存的临时存储缓存数据的集合:
                Cache接口实现类TransactionalCache的entriesToAddOnCommit属性[本质为map集合]中,返回数据
                有:返回一级缓存中的数据,并再二级缓存中添加,和上面一样

        有:直接返回二级缓存中的数据        
 
        只有在执行了commit(),close() 才会将临时缓存map集合(entriesToAddOnCommit中数据存储到真正存储缓存map集合(cache)中

     注意:
         清除二级缓存的操作
         防止出现脏读,幻读,可重复读的问题
        添加,修改,删除       

四、四大核心接口介绍及执行流程(面试题)

1. 四大核心接口介绍

MyBatis执行过程中涉及到非常重要的四个接口,这四个接口为MyBatis的四大核心接口:

  1. Executor执行器,执行器负责整个SQL执行过程的总体控制。默认SimpleExecutor执行器。

  2. StatementHandler语句处理器,语句处理器负责和JDBC层具体交互,包括prepare语句,执行语句,以及调用ParameterHandler.parameterize()。默认是PreparedStatementHandler。

  3. ParameterHandler参数处理器,参数处理器,负责PreparedStatement入参的具体设置。默认使用DefaultParameterHandler。

  4. ResultSetHandler结果集处理器,结果处理器负责将JDBC查询结果映射到java对象。默认使用DefaultResultSetHandler。

2. 四大核心接口对应的JDBC代码

对应的JDBC代码

3. 四大核心接口执行顺序按照下图进行执行

 

4. 通过断点测试执行流程

可以通过对四大核心接口的实现类中核心方法添加断点。

SimpleExecutor -> doQuery() 方法(必须以查询作为测试,其他类型SQL使用不同方法)

DefaultParameterHandler -> setParameters

PreparedStatementHandler -> query

DefaultResultHandler -> handleResult

里面需要注意的是会在SimpleExecutor先实例化Statement对象,然后调用DefaultParameterHandler 的setParameters,再然后调用PreparedStatementHandler的query。

5. 完整执行流程文字说明

(1)使用执行器Executor控制整个执行流程

(2)实例化StatementHandler,进行SQL预处理

(3)使用ParameterHandler设置参数

(4)使用StatementHandler执行SQL

(5)使用ResultSetHandler处理结果集

五、执行器类型(面试题)

MyBatis的执行器都实现了Executor接口。作用是控制SQL执行的流程。

  1. BaseExecutor:主要是使用了模板设计模式,共性被封装在 BaseExecutor 中,容易变化的内容被分离到了子类中 。

    1. SimpleExecutor:默认的执行器类型。每次执行query和update(DML)都会重新创建Statement对象。

    2. ReuseExecutor:执行器会重用预处理语句。不会每一次调用都去创建一个新的 Statement 对象,而是会重复利用以前创建好的(如果SQL相同的话)。

    3. BatchExecutor:用在update(DML)操作中。所有SQL一次性提交。适用于批量操作。

  2. CachingExecutor:处理缓存的执行器。无论使用上面三种执行器中的哪个。都是会执行CachingExecutor。

 在项目可以通过factory.openSession()方法参数设置执行器类型。通过枚举类型ExecutorType进行设置。

执行器主要控制的就是Statement对SQL如何进行操作。

有效范围:同一个SqlSession对象。

1. SimpleExecutor

SimpleExecutor 是MyBatis默认的执行器类型。

2. ReuseExecutor

ReuseExecutor主要用在执行时,重用预编译SQL。在同一个SqlSession对象中下次调用已经预编译的SQL直接设置参数。

3. BatchExecutor

BatchExecutor底层使用JDBC的批量操作。每一条SQL都不会立即执行,而是放到了List<Statement>中,最终统一提交。

由于底层的批量操作只支持DML操作,所以BatchExecutor也主要用在批量新增、批量删除、批量修改中。

六、MyBatis执行原理详解(较常见面试题)

对于MyBatis执行原理来说,不同的情况有不同的执行过程,大致可以分下面几种情况:

(1)接口绑定方式、使用SqlSession执行方法。

(2)是否有插件。

(3)不同的执行器。

为了演示一个较为详细的执行流程。整个讲解过程中以SimpleExecutor作为执行器,包含接口和映射文件的接口绑定方案,同时带有自定义插件。其实就是上面自定义插件的代码。

MyBatis项目不能自动运行,测试代码如下,每一行都进入源码进行观察。

public class Test {
    public static void main(String[] args) throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis.cfg.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession session = factory.openSession();
        EmpMapper empMapper = session.getMapper(EmpMapper.class);
        MyPageHelper.startPage(0,2);
        List<Emp> list = empMapper.selectAllpage();
        System.out.println(list);
        //session.commit();
        session.close();
    }
}

8. MyBatis执行原理文字说明

首先加载全局配置文件为输入流,交给XPathParser解析器解析为Document文档对象,然后使用DOM解析Document文档对象,把解析结果存放在Configuration配置类中。

通过DefaultSqlSessionFactory实例化工厂,实例SqlSession的对象。创建了SqlSession接口的实现类DefaultSqlSession对象,在创建过程中,会同时创建Transaction事务对象、Executor执行器对象。如果当前项目有Interceptor拦截器,创建执行器时会执行拦截器。

通过JDK提供的Proxy创建接口的动态代理对象。

可以通过接口的代理对象调用方法。在调用方法时MyBatis会根据方法的类型判断调用SqlSession的哪个方法。例如:selectList、selectOne、update、insert等。

确定好具体调用SqlSession的哪个方法后,会按照执行器类型执行MyBatis四大核心接口,执行时也会触发拦截器Interceptor。最终会返回SQL的执行结果。

执行完方法后需要提交事务,提交时清空缓存、清除存储的Statement对象。

最后关闭SqlSession对象,释放资源。

以上就是MyBatis执行原理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1121560.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

String、StringBuffer、StringBuilder 适合的应用场景

文章目录 String适用场景示例代码 StringBuffer适用场景示例代码 StringBuilder适用场景示例代码 性能比较总结 &#x1f389;欢迎来到Java学习路线专栏~String、StringBuffer、StringBuilder 适合的应用场景 ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f379;✨博客主页&#xff…

《视觉SLAM十四讲》公式推导(二)

CH3-5 四元数表示旋转 三维空间中任意点均可用一个纯虚四元数表示即 p [ 0 , v ] T \boldsymbol{p}[0,\boldsymbol{v}]^T p[0,v]T&#xff0c;经一个单位四元数 q \boldsymbol{q} q的旋转后&#xff0c;得到 p ′ \boldsymbol{p} p′&#xff0c;则 p ′ q p q − 1 (3-5-1)…

STM32F4_中英文显示

目录 1. 液晶显示逻辑 2. 汉字显示原理 3. 实验程序 3.1 main.c 3.2 text.c 3.3 text.h 3.4 fontupd.c 3.5 fontupd.h 1. 液晶显示逻辑 字符编码&#xff1a; 由于计算机只能识别 0 和 1&#xff0c;文字也只能以 0 和 1 的形式在计算机里存储&#xff0c;所以我们需要…

算法通过村第十六关-滑动窗口|青铜笔记|滑动很简单

文章目录 前言滑动窗口的基本思想入门题目练习子数组最大平均数最长连续递增序列 总结 前言 提示&#xff1a;我宁愿做自己&#xff0c;做卑微的自己&#xff0c;也不愿做别人&#xff0c;无论那会多么快乐。 --《美丽新世界》 我们在数组和链表的部分就已经接触到了双指针的思…

IMX6ULL开发——第一个驱动程序

实现第一个应用程序&#xff1a;在IMX6ULL开发板上运行驱动程序hello_drv_test hello_drv_test #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h>/** ./hel…

生产者消费者模式(c++实现)

生产者消费者模式思路就是&#xff1a;一批专门生产资源的线程 和一批专门处理资源的线程以及一个线程安全的任务队列组成的 。并且当任务队列满的时候阻塞生产线程&#xff0c;任务队列为空的时候阻塞消费线程。 要实现一个生产者消费者队列 1。需要实现线程同步&#xff0c;…

react的setState做了什么

1、为什么需要setState setState的作用是帮助我们更改数据的同时并且通知视图进行渲染。因为React并不会绑定视图和state&#xff0c;需要我们手动去更新视图。 2、setState什么时候是同步的&#xff0c;什么时候是异步的 setState这个方法在调用的时候是同步的&#xff0c;…

如果面试问到你redis的常用数据类型,你怎么和面试官聊上十分钟?

最近组长把一些简历推到我这边让我帮他面试几份&#xff0c;问到这种基础题目时收到的回答总是不太理想 1、最简单的回答&#xff1a; Redis存储的是key-value结构的数据&#xff0c;其中key是字符串类型&#xff0c;value有5种常用的数据类型&#xff1a; 字符串 string哈希 …

为什么spring默认采用单例bean

概 述 熟悉 Spring开发的朋友都知道 Spring 提供了 5种 scope&#xff0c;分别是&#xff1a; singleton: 单例模式&#xff0c;当spring创建applicationContext容器的时候&#xff0c;spring会欲初始化所有的该作用域实例&#xff0c;加上lazy-init就可以避免预处理&#xf…

磁盘分区如何分? 电脑磁盘分区免费软件指南!

列出并比较顶级免费磁盘分区管理器软件&#xff0c;以选择适用于 Windows 的最佳分区软件&#xff1a; 系统分区在现代计算机设备中起着非常重要的作用。它们可以存储数据&#xff0c;使系统文件远离用户数据&#xff0c;并在同一台设备上安装多个操作系统。但是&#xff0c;这…

MyBatis-Plus 实战教程一

这里写目录标题 简介快速上手数据库建立创建实体类修改参数引入依赖测试常见注解介绍TableNameTableIdTableField 常见配置仓库地址 简介 MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;…

qt-C++笔记之信号与槽

qt-C笔记之信号与槽 code review! 本文抄自公众号&#xff1a;嵌入式小生 文章目录 qt-C笔记之信号与槽一.信号2.1.信号的发出2.2.信号的处理 二.槽函数2.1.带有默认参数的信号和槽函数2.2.使用QObject::connect()将信号连接到槽函数的三种方法2.2.1.第一种方法&#xff1a;使…

整理MongoDB文档:身份验证

整理MongoDB文档:身份验证 个人博客&#xff0c;求关注。 文章概叙 本文主要讲MongoDB在单机状态下的账户配置。理解了MongoDB的语法&#xff0c;对于如何配置用户权限会知道怎么配置&#xff0c;但是请注意给谁配置什么权限才是最重要的。 最小权限原则 系统的每个程序或者…

【C++编程语言】STL常用算法 算术生成和集合算法

1.算术生成算法概念 算法简介&#xff1a; accumlate 计算容器元素累计总和fill 向容器中添加元素 注意&#xff1a;算术生成算法属于小型算法 使用时包含头文件为#include<numeric> 2.accumulate /*函数原型&#xff1a;int accumulate(iterator beg ,iterator end…

热点不热!如何修复笔记本电脑未连接到移动热点的问题

当你远离常规Wi-Fi时,移动热点是让你的笔记本电脑上网的关键,但当它没有按计划运行时,你会怎么办?以下是Windows笔记本电脑无法连接到移动热点时的几种修复方法。 为什么我的笔记本电脑没有连接到我的热点 由于你的笔记本电脑正试图连接到另一个有限制和可能存在问题的设…

前端学成在线项目详细解析二

12-banner区域-课程表布局 HTML布局 <div class"right"><h3>我的课程表</h3><div class"content">1</div> </div> CSS样式 /* 课程表 */ .banner .right {margin-top: 60px;width: 218px;height: 305px;background-…

STM32F4_照相机

目录 前言 1. BMP编码 2. JPEG编码 前言 我们所要实现的照相机&#xff0c;支持BMP图片格式的照片和JPEG图片格式的照片。 1. BMP编码 BMP文件是由文件头、位图信息头、颜色信息和图形数据四部分构成。 1. BMP文件头&#xff08;14个字节&#xff09;&#xff1a;BMP文件…

在ESP32-Arduino开发中添加其它Arduino库

目录 前言 原理说明 操作步骤 下载Bounce 安装Bounce 将下载的文件夹(压缩包需要解压)移动到components/arduino/libraries路径下&#xff0c;并重命名为Bounce2 查看添加库里所有的源文件位置 在arduino的CMakeList.txt里添加库源文件 使用Bounce 前言 乐鑫官方的es…

HTTP介绍 原理 消息结构 客户端请求 服务器响应 HTTP状态码

一、HTTP介绍二、HTTP工作原理HTTP三点注意事项 三、HTTP消息结构四、客户端请求消息五、服务器响应消息HTTP请求方法 七、HTTP响应头信息八、HTTP状态码&#xff08;HTTP Status Code&#xff09;下面是常见的HTTP状态码&#xff1a;HTTP状态码分类HTTP状态码列表 一、HTTP介绍…

旁注、目录越权、跨库查询、cdn绕过

原理&#xff1a; 搭建网站多IP多端口&#xff0c;更多一个域名多网站&#xff0c;IIS的在属性-高级里面设置主机头设置域名&#xff0c;域名是收费的需要自己买一个 旁注&#xff1a;在同一服务器上有多个站点&#xff0c;要攻击的这个站点假设没有漏洞&#xff0c;可以攻击…