【框架学习 | 第一篇】一篇文章快速入门MyBatis

文章目录

  • 1.Mybatis介绍
    • 1.1Mybatis历史
    • 1.2Mybatis特点
    • 1.3与其他持久化框架对比
    • 1.4对象关系映射——ORM
  • 2.搭建Mybatis
    • 2.1引入依赖
    • 2.2创建核心配置文件
    • 2.3创建表、实体类、mapper接口
    • 2.4创建映射文件
      • 2.4.1映射文件命名位置规则
      • 2.4.2编写映射文件
      • 2.4.3修改核心配置文件中映射文件路径
    • 2.5Junit测试
    • 2.6修改、删除、查询功能测试
  • 3.核心配置文件详讲(了解)
    • 3.1environment、properties讲解
    • 3.2typeAliases讲解
    • 3.3mappers
      • 3.3.1将UserMapper.xml转移到新目录下
      • 3.3.2在核心配置文件中引入映射文件
  • 4.MyBatis获取参数值的两种方式(重要)
    • 4.1区分${}和#{}
      • 4.4.1区分点
      • 4.4.2预编译过程
      • 4.4.3 ${}的sql注入问题
    • 4.2获取参数值的各种情况
      • 4.2.1情况一:参数为单个的字面量类型
      • 4.2.2情况二:参数为多个时
        • (1)使用原生的map
        • (2)使用自己的map(${}、#{})
      • 4.2.3参数是实体类类型的参数
      • 4.2.4使用@Param注解命名参数(以这个和实体类类型两种情况为标准)
    • 4.3举例
      • 4.3.1mapper接口
      • 4.3.2映射文件
      • 4.3.3测试
    • 4.4小结
    • 4.5@Param标识的源码解析
  • 5.MyBatis的各种查询功能
    • 5.1查询的数据只有一条
      • 5.1.1接收情况
      • 5.1.2举例
    • 5.2查询的数据有多条
    • 5.3查询单个数据(聚合函数)
  • 6.特殊SQL的执行
    • 6.1模糊查询
    • 6.2批量删除
    • 6.3动态设置表名(会出现sql注入问题)
    • 6.4添加功能获取自增的主键
  • 7.自定义映射resultMap
    • 7.1搭建mybatis框架
    • 7.2字段名和属性名不一致
      • 7.2.1给字段名起别名
      • 7.2.2全局配置mapUnderscoreToCamelCase
      • 7.2.3resultMap(建议使用)
    • 7.3多对一(从员工到部门)映射关系
      • 7.3.1通过级联属性(连接查询)赋值
      • 7.3.2通过association标签
      • 7.3.3通过分步查询(推荐)
        • (1)通过id查询员工信息、并查出did
        • (2)通过did查询员工所对应的部门
        • (3)测试
      • 7.3.4分步查询的好处:延迟加载
        • (1)开启延迟加载之前
        • (2)开启全局延迟加载之后
        • (3)可以局部关闭延迟加载
    • 7.4.一对多映射关系
      • 7.4.1通过collection解决一对多映射关系
  • 8.动态SQL
    • 8.1if-where-trim标签
      • 8.1.1if标签
      • 8.1.2where标签
      • 8.1.3trim标签
    • 8.2choose-when-ohterwise标签
    • 8.3.foreach标签
      • 8.3.1实现批量删除方式一
      • 8.3.2实现批量删除方式二
      • 8.3.3实现批量添加
    • 8.4SQL标签
  • 9.MyBatis缓存
    • 9.1介绍
      • 9.1.1一级缓存
      • 9.1.2二级缓存
    • 9.2一级缓存
      • 9.2.1一级缓存生效(默认生效)
      • 9.2.2一级缓存失效
    • 9.3二级缓存
      • 9.3.1二级缓存开启
      • 9.3.2二级缓存失效
      • 9.3.3二级缓存的相关配置
    • 9.4缓存查询的顺序
  • 10_MyBaits的逆向工程
    • 10.1创建逆向工程的步骤
    • 10.2豪华尊享版两种函数的区别
    • 10.3QBC查询
    • 10.4QBC修改
  • 11_MyBatis分页
    • 11.1使用步骤
    • 11.2使用

在这里插入图片描述

框架系列第一篇主要围绕MyBatis展开,从MyBatis介绍、搭建学习环境,再到探讨映射文件获取参数值(${}、#{})@param注解命名参数、各种查询功能(1条、多条、单个数据)、再到特殊SQL的执行(模糊查询、批量删除)、自定义resultMap(重点讲述了多对一、一对多的解决方案)、动态SQL(if、where、foreach标签)MyBatis的一二级缓存

1.Mybatis介绍

1.1Mybatis历史

  1. 原是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation 迁移到了Google Code,随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis ,代码于2013年11月迁移到Github(下载地址见后)
  2. iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 iBatis提供的持久层框架包括SQL Maps和Data Access Obje

1.2Mybatis特点

  1. Mybatis是一个半自动的ORM(Object Relation Mapping)框架
  2. 面向接口编程思想:可以使用简单的XML或注解用于配置和原始映射,将接口和Java的实体类对象映射成数据库中的记录
  3. 支持定制化SQL、存储过程以及==高级映射(一对多的映射关系)==的持久化框架
  4. 封装了几乎所有的JDBC代码和手动设置参数以及获取结果集

1.3与其他持久化框架对比

  • JDBC
    • SQL夹杂在Java代码中耦合度高,导致硬编码内伤。
    • 维护不易且实际开发需求中 SQL有变化,频繁修改的情况多见。
    • 代码冗长,开发效率低。
  • Hibernate和IPA
    • 操作简便,开发效率高
    • 程序中的长难复杂 SQL需要绕过框架
    • 内部自动生产的 SQL,不容易做特殊优化。
    • 基于全映射的全自动框架,大量字段的 POJ0 进行部分映射时比较困难
    • 反射操作太多,导致数据库性能下降
  • MyBatis
    • 轻量级,性能出色
    • SQL和Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据。
    • 开发效率稍逊于HIbernate,但是完全能够接受

1.4对象关系映射——ORM

ORM即Object Relationship Mapping,对象关系映射!

解释/含义
对象java的实体类对象
关系关系型数据库
映射二者之间的对应关系

在java概念和数据库概念中的对应关系如下:

java概念数据库概念
属性字段/列
对象记录/行

2.搭建Mybatis

搭建一个基于Maven的project

2.1引入依赖

<dependencies>
    <!-- Mybatis核心 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.10</version>
    </dependency>
    <!-- junit测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.29</version>
    </dependency>

    <!-- log4j日志 -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>

2.2创建核心配置文件

  1. 习惯上命名为mybatis-config.xml,这个文件名仅仅只是建议,并非强制要求。
  2. 将来整合Spring之后,这个配置文件可以省略
  3. 核心配置文件主要用于配置连接数据库的环境以及MyBatis的全局配置信息核心配置文件存放的位置是src/main/resources目录下

image-20240304232348464

2.3创建表、实体类、mapper接口

image-20240304232548553

MyBatis 中的 mapper 接口相当于以前的 DAO 接口,但是区别在于, mapper 仅仅是接口,我们不需要提供实现类(面向接口编程);

image-20240304232617262

2.4创建映射文件

2.4.1映射文件命名位置规则

  1. 表所对应的 实体类的类名+Mapper.xml ,例如:表 t_user,映射的实体类为 User,所对应的映射文件为 UserMapper.xml放到resources目录下
  2. 因此一个映射文件对应一个实体类,对应一张表的操作
  3. MyBatis 映射文件用于编写 SQL,访问以及操作表中的数据

2.4.2编写映射文件

MyBatis中面向接口编程的两个一致:

  •  ==映射文件的namespace要和mapper接口的全类名保持一致==
    
  •  ==映射文件中SQL语句的id要和mapper接口中的方法名一致==
    

image-20240304232819888

首先先根据核心配置文件中的来引入映射文件,再根据方法名匹配到SQL语句

2.4.3修改核心配置文件中映射文件路径

若修改核心配置文件中的映射文件路径,则需将UserMapper.xml文件放置在resources/mapper文件夹下

image-20240304232954726

2.5Junit测试

image-20240304233129822

2.6修改、删除、查询功能测试

  • UserMapper.java

image-20240304233507867

  • UserMapper.xml

image-20240304233546616

3.核心配置文件详讲(了解)

MyBatis核心配置文件中,标签的顺序
    properties?,settings?,typeAliases?,typeHandlers?,
    objectFactory?,objectWrapperFactory?,reflectorFactory?,
    lugins?,environments?,databaseIdProvider?,mappers?

3.1environment、properties讲解

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--
        MyBatis核心配置文件中,标签的顺序:
        properties?,settings?,typeAliases?,typeHandlers?,
        objectFactory?,objectWrapperFactory?,reflectorFactory?,
        plugins?,environments?,databaseIdProvider?,mappers?
    -->

    <!--引入properties文件,此时就可以${属性名}的方式访问属性值-->
    <properties resource="jdbc.properties" />

    <!--设置类型别名-->
    <typeAliases>
        <!--
            typeAlias:设置某个类型的别名
            属性:
                type:设置需要设置别名的类型
                alias:设置某个类型的别名,若不设置该属性,那么该类型拥有默认的别名,即类名且不区分大小写
        -->
        <!--<typeAlias type="com.atguigu.User"></typeAlias>-->
        <!--以包为单位,将包下所有的类型设置默认的类型别名,即类名且不区分大小写-->
        <package name="com.atguigu.mybatis.pojo"/>
    </typeAliases>

    <!--
        environments:配置多个连接数据库的环境
        属性:
            default:设置默认使用的环境的id
    -->
    <environments default="development">
        <!--
            environment:配置某个具体的环境
            属性:
                id:表示连接数据库的环境的唯一标识,不能重复
        -->
        <environment id="development">
            <!--
                transactionManager:设置事务管理方式
                属性:
                    type="JDBC|MANAGED"
                    JDBC:表示当前环境中,执行SQL时,使用的是JDBC中原生的事务管理方式,事务的提交或回滚需要手动处理
                    MANAGED:被管理,例如被Spring管理
            -->
            <transactionManager type="JDBC"/>
            <!--
                dataSource:配置数据源,与spring整合后不用再设置dataSource
                属性:
                    type:设置数据源的类型
                    type="POOLED|UNPOOLED|JNDI"
                    POOLED:表示使用数据库连接池缓存数据库连接,即会将创建的连接进行缓存,下次使用可以从缓存中直接获取,不需要重新创建
                    UNPOOLED:表示不使用数据库连接池,即每次使用连接都需要重新创建
                    JNDI:表示使用上下文中的数据源
            -->
            <dataSource type="POOLED">
                <!--设置连接数据库的驱动-->
                <property name="driver" value="${jdbc.driver}"/>
                <!--设置连接数据库的连接地址-->
                <property name="url" value="${jdbc.url}"/>
                <!--设置连接数据库的用户名-->
                <property name="username" value="${jdbc.username}"/>
                <!--设置连接数据库的密码-->
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--引入映射文件-->
    <mappers>
        <!--<mapper resource="mappers/UserMapper.xml"/>-->
        <!--
            以包为单位引入映射文件
            要求:
            1、mapper接口所在的包要和映射文件所在的包一致
            2、mapper接口要和映射文件的名字一致
        -->
        <package name="com.atguigu.mybatis.mapper"/>
    </mappers>
</configuration>

3.2typeAliases讲解

作用:设置类型别名

<!-- 设置类型别名
        方式一:以某一个类为单位(不常用)
        方式二:以包为单位,将包下所有的类型设置为默认类型别名,即类型且不区分大小写(常用)
-->
<typeAliases>
    <!--
        方式一:
        typeAlias:设置某个类型的别名
        属性:
            type:设置需要设置别名的类型
            alias:设置某个类型的别名,如果不设置该属性,那么该类型拥有默认的别名,即类名而且不区分大小写
    -->
    <typeAlias type="com.peng.mybatis.pojo.User" alias="User"></typeAlias>

    <!--
        方式二:
    -->
    <package name="com.peng.mybatis.pojo.User"></package>

</typeAliases>

3.3mappers

3.3.1将UserMapper.xml转移到新目录下

image-20240304234039332

在 resources 包下面创建 directory(目录)创建 package(包) 是不一样的

不能用com.peng.mybatis.mapper 这种方式创建多个文件夹,这样创建出来的是一个包名;应该用 com/peng/mybatis/mapper 这种方式。

3.3.2在核心配置文件中引入映射文件

<!-- 引入映射文件
        以包为单位引入映射文件
            要求:mapper接口所在的包要和映射文件所在的包一致
                 mapper接口要和映射文件的名字一致
-->
<mappers>
    <package name="com.peng.mybatis.mapper"/>
</mappers>

4.MyBatis获取参数值的两种方式(重要)

4.1区分${}和#{}

4.4.1区分点

  1. MyBatis获取参数值的两种方式:${}和#{}

    ${}本质为字符串替换

    #{}本质为占位符赋值

    ${}和#{}的相同点为都能获取参数变量的值,在预编译语法上则不同

${}#{}
预编译不行将#{变量}编译成?,在执行时后再取值(自动加上引号),防止sql注入
应用场景传入的参数是sql片段的场景在sql映射文件中动态拼接sql时的开发场景(获取变量)

传入的参数是sql片段的场景

<select id="getUserPage" resultType="com.ymxx.oa.bean.User">
        ${sql片段}
</select>
  • 对于这样外部传入的sql,就不能使用#{},上面也说了,#{}会进行预编译,检测到该sql片段是个字符串,就会加上引号,即’sql片段’,这样就是字符串了而不是sql,执行会报错。

4.4.2预编译过程

mybatis在预编译过程大体上可以分为 数据类型检查安全检查 两部分

数据类型检查若检测到为数值类型,就不加引号,即?若检测到位字符串类型,就加上引号,即’?'

安全检查:若变量的值带有引号,会对引号进行转义处理,这样可以防止sql注入

4.4.3 ${}的sql注入问题

用${}时要特别注意sql注入的风险,如果该sql片段是根据用户的输入拼接的,要注意检查sql注入的问题,防止数据的泄露与丢失!

案例:

//sql语句
select * from ${tableName} where name = ${name}

如果tableName的值为 user; delete user; –,该sql最终解析成

select * from user; delete user; -- where name = xxx

结果查询了整张表,然后把user表给删了

4.2获取参数值的各种情况

获取参数值的情况可以分为以下四种情况:

  • 参数为单个的字面量类型
  • 参数为多个
    • 使用原生的map
    • 使用自己的map(${}、#{})
  • 参数是实体类类型的参数
  • 使用@Param注解命名参数

4.2.1情况一:参数为单个的字面量类型

  • 可以通过KaTeX parse error: Expected 'EOF', got '#' at position 4: {}和#̲{}以任意的字符串获取参数值 …{}需要额外加上单引号

4.2.2情况二:参数为多个时

(1)使用原生的map
  • mybatis会把形参的值放在一个map集合中,以两种方式进行存储
  • arg0,arg1为键或者以param0,param1为键
  • 以参数值为值,可以混着用
(2)使用自己的map(${}、#{})
  • 使用自己的map可以通过KaTeX parse error: Expected 'EOF', got '#' at position 4: {}和#̲{}以键名的方式获取参数值==…{}需要额外加上单引号
  • 可以==通过KaTeX parse error: Expected 'EOF', got '#' at position 4: {}和#̲{}以自己设置的键名的方式获取…{}需要额外加上单引号

4.2.3参数是实体类类型的参数

  • 通过${}和#{}以自己设置的键名实体类里面的属性)的方式获取参数值
  • 使用${}需要额外加上单引号

4.2.4使用@Param注解命名参数(以这个和实体类类型两种情况为标准)

  • mybatis会把形参的值放在一个map集合中,以两种方式进行存储
  • 以自己使用的==@Param注解的参数名为键或者以param1,param2为键==,
  • 以参数值为值,可以混着用

4.3举例

4.3.1mapper接口

image-20240305084222152

4.3.2映射文件

image-20240305085039896

4.3.3测试

image-20240305090616441

4.4小结

  1. 有实体类的时候直接用属性值来访问

    例如,如果有一个 User 实体类,其中包含了 idname 两个属性,那么在 SQL 语句中就可以直接引用这些属性:

    <select id="getUserById" resultType="User">
        SELECT * FROM users WHERE id = #{id}
    </select>
    
  2. 没有实体类的时候用 @Param 注解命名参数,不管字面量单个还是多个,是不是 map 集合都可以使用自己用注解定义的参数来访问

    当没有实体类或需要额外命名参数时,可以使用 @Param 注解来命名参数。例如,假设我们需要在 SQL 中引用多个参数,可以这样做:

    • mapper接口:
    public interface UserMapper {
        List<User> getUsersByAgeRange(@Param("minAge") int minAge, @Param("maxAge") int maxAge);
    }
    
    • 映射文件:

      <select id="getUsersByAgeRange" resultType="User">
          SELECT * FROM users WHERE age BETWEEN #{minAge} AND #{maxAge}
      </select>
      

4.5@Param标识的源码解析

为啥我们能将我们想要的 key 值,以及对应的 value 值设置进去呢?

(1)首先看这个方法的第三步的内部是如何调用的?

在这里插入图片描述

(2) mapper 映射的底层使用了代理模式,通过反射执行当前命令对应的方法

  • MapperProxy.java

在这里插入图片描述

在这里插入图片描述

(3)当前命令中,name 对应的是要执行的 sql 语句唯一标识:mapper 映射文件中 namespace + id),方法对应的是 select 方法,所以 switch - case 直接跳到 select 方法去执行对应的方法:

  • MapperMethod.java

在这里插入图片描述在这里插入图片描述

在这里插入图片描述

这边在 else 模块中,第一个方法是将 args 参数转换成 sql 要求的参数,点进这个方法看一下里面怎么写的?

在这里插入图片描述

再进入方法内部,这个方法是一个将注解设定的命名规则的名称设置成 map 容器的 key 值的方法

  • ParamNameResolver.java

在这里插入图片描述

第一步:name 是一个排序的 map 容器,其中 0 号位置放了我们通过注解命名的第一个参数名称1 号位置放置了我们通过注解命名的第二个参数名称:

在这里插入图片描述第二步:又因为我们有参数注解而且参数注解的个数不为1,所以跳到 else 中执行,新建一个 map 容器用来放置 (键值,参数值)

第三步遍历 names 这个 map 容器,取出其中第一个键值对

第四步:将它的 value 值即放置的自定义的 username 注解名称当作键值,将 args[当前键值对的键值] 即 args[0] 当作 value 值放进 params 这个 map 容器中

在这里插入图片描述

第五步定义一个字符串 param1,这里的 1 表示的是遍历第一个键值对即 i = 0;

第六步如果当前 names 这个 map 容器中没有包含当前的这个 param1 这个值(防止我们自己定义了,重复放置),那么我们就将(param1 ,args[0])这一个键值对放进 param 这个 map 容器中,也就是我们既可以用我们自己设定的 username 来访问对应的参数值,也可以用 param1 来访问对应的参数值。

第七步:i++,遍历 names 容器中的下一个键值对,重复执行 4 - 6;

第八步:最后将生成的 param 这个 map 容器返回,我们可以从中任意选择一个键值来访问对应的参数。

5.MyBatis的各种查询功能

5.1查询的数据只有一条

5.1.1接收情况

若查询的数据只有一条,那可以分为以下三种情况:

  1. 通过实体类对象接收
  2. 通过list集合接收(建议使用list集合接收
  3. 通过map集合接收

5.1.2举例

  • mapper 接口中方法的定义:

image-20240305091458001

  • mapper 映射文件中 sql 语句的编写:

    注意:通过map集合接收的sql语句的 resultType值 必须修改为map

image-20240305091816596

  • 测试类

image-20240305091824270

5.2查询的数据有多条

若查询的数据有多条,那可以分为以下三种情况:

  1. 通过实体类类型的list集合接收

  2. 通过map类型的list集合接收

  3. 通过注解@MapKey注解实现注入

    可以在mapper接口的方法上添加@MapKey注解,此时就可以将每条数据转换为map集合作为值,以某个字段的值作为键,放在同一个map集合中

  • mapper接口中方法的定义

image-20240305092035641

  • mapper 映射文件中 sql 语句的编写:

image-20240305092056982

  • 测试

image-20240305092312490

5.3查询单个数据(聚合函数)

  • mapper 接口中方法的定义:

image-20240305092355706

  • mapper 映射文件中 sql 语句的编写:

image-20240305092424526

  • 测试

image-20240305092501138

补充:MyBatis 中设置了默认的类型别名,可以在resultType使用别名

java.lang.Integer --> int,integer
int --> _int,_integer
Map --> map
String --> string

6.特殊SQL的执行

6.1模糊查询

模糊查询语句:

select * from tb_user where username like '%鹏%';

重点在如何解析出鹏字,由前面可知,${}本质为字符串替换、#{}本质为占位符(会进行预编译,若在数据类型检查中,检查为字符串类型,则会自动添加’‘引号)

mapper 映射文件中 sql 语句的编写,一共有三种方法可以实现:

  1. 使用${}
  2. 使用 concat#{} 进行字符串拼接(推荐)
  3. 使用"%#{}%"
  • mapper 接口中方法的定义:

image-20240305092616279

  • 映射文件:

image-20240305092943837

  1. 这里不可以使用 select * from t_user where username like '%#{username}%',即将 #{} 这种占位符赋值获取参数值的放在 单引号 里面;

  2. 这样直接将单引号里面的三个字符都当作字符串的一部分,而不会解析成占位符,看报错信息可以看出来,?直接放在单引号内部,当成一个==字符?==了,而不会被当成一个占位符

  3. 解析结果:

    image-20240305093102586

  • 测试

image-20240305095726057

6.2批量删除

  • mapper 接口中方法的定义:

image-20240305095817002

  • mapper 映射文件中 sql 语句的编写,这里只能用 ${} 这种字符串替换的方法来实现:

image-20240305095922782

  • 不能使用 #{}这种方式:#{} 这种占位符赋值的方式,会自动给参数加上单引号
delete from t_user where id in (#{ids});
# in (1,2,3,4)——>in('1,2,3,4'),结果必定报错
  • 测试

image-20240305100210905

6.3动态设置表名(会出现sql注入问题)

  • mapper 接口中方法的定义:

image-20240305100300248

  • mapper 映射文件中 sql 语句的编写

image-20240305100317945

  • 测试

image-20240305100344989

6.4添加功能获取自增的主键

  • mapper 接口中方法的定义:

image-20240305100738436

  • mapper 映射文件中 sql 语句的编写

image-20240305100801535

  • 测试

image-20240305100826298

7.自定义映射resultMap

7.1搭建mybatis框架

  • 数据库新建两张表t_emp和t_dept,并存入测试数据

    • t_emp表

      image-20240305100947447

    • t_dept表和数据

      image-20240305101049361

  • 新建两个类,其中emp_name对应的属性名为empName

    image-20240305101100400

7.2字段名和属性名不一致

解决字段名和属性名不一致的情况的三种方式

  1. 为字段名起别名
  2. 全局配置mapUnderscoreToCamelCase(驼峰映射)
  3. resultMap设置自定义映射关系(推荐)

7.2.1给字段名起别名

image-20240305101327430

7.2.2全局配置mapUnderscoreToCamelCase

  • mybatis-config.xml

image-20240305101342388

7.2.3resultMap(建议使用)

image-20240305101442659

7.3多对一(从员工到部门)映射关系

定义:多对一的映射关系指的是一个对象(或实体)在关联另一个对象时,该对象拥有多个对应关联对象的情况。具体来说,多对一关系表示多个对象共同指向同一个对象

举例:假设有两个实体类 DepartmentEmployee,一个部门可以有多名员工,而一名员工只能属于一个部门。在这种情况下,EmployeeDepartment 的关系就是多对一的映射关系,因为多名员工可以指向同一个部门

处理多对一的映射关系可以有以下几种方法:

  1. 通过 级联属性 赋值

  2. 通过 association标签

  3. 通过 分步查询

7.3.1通过级联属性(连接查询)赋值

  • 员工对象

image-20240305155201552

  • 部门对象

image-20240305155230176

  • mapper接口中方法定义

image-20240305155041400

  • mapper映射文件

image-20240305155116762

7.3.2通过association标签

  • mapper映射文件

image-20240305155704639

  • 测试

image-20240305155715928

7.3.3通过分步查询(推荐)

(1)通过id查询员工信息、并查出did
  • EmpMapper接口方法定义

image-20240305155729119

  • EmpMapper.xml映射文件

image-20240305155838408

解释:

  • association 标签中的 column 属性将两个查询到的信息关联了起来
  • select 属性中写的是 mapper 接口的全类名(唯一标识)
(2)通过did查询员工所对应的部门
  • DeptMapper接口方法定义

image-20240305160029531

  • DeptMapper.xml映射配置文件,注意字段名和属性名并不相同

image-20240305160047325

(3)测试

image-20240305160117304

7.3.4分步查询的好处:延迟加载

  1. 可以实现 延迟加载,但是必须在核心配置文件中设置全局配置信息(配置如下两个信息):没有全局设置的话,默认的是立即加载;

  2. lazyLoadingEnabled延迟加载的全局开关

    当开启时,所有关联对象都会延迟加载;

    需要 设置为true,默认为false。

  3. aggressiveLazyLoading设置为false,默认为false。当开启时,任何方法的调用都会加载该对象的所有属性 ,需要关闭他。关闭后每个属性会按需加载。

(1)开启延迟加载之前

image-20240305160614749

(2)开启全局延迟加载之后
  • mybatis-config.xml

image-20240305160630439

image-20240305160652120

(3)可以局部关闭延迟加载

条件:全局延迟加载开启

image-20240305160845187

7.4.一对多映射关系

  1. 定义:一对多关系表示一个对象指向多个相关联的对象
  2. 假设有两个实体类 DepartmentEmployee,一个部门可以有多名员工,而一名员工只能属于一个部门。在这种情况下,DepartmentEmployee 的关系就是一对多的映射关系,因为一个部门可以指向多名员工

7.4.1通过collection解决一对多映射关系

  • DeptMapper接口方法定义

image-20240305161128118

  • DeptMapper.xml映射配置文件

image-20240305161135178

  • 测试

image-20240305161209832

8.动态SQL

在 MyBatis 中,可以使用动态 SQL 来根据条件生成不同的 SQL 语句

  1. if:根据标签中test属性所对应的表达式决定标签中的内容是否需要拼接到 SQL 语句

  2. where

    • 当where标签中有内容时,会自动生成where关键字,并且将内容前多余的and或者or去掉

    • 当where标签中没有内容时,此时where标签没有任何效果

    • 注意:where标签不能将其中内容后面多余的and或者or去掉,只能将内容前多余的and或者or去掉

  3. trim

    • prefix|suffix:将trim标签中内容前面或者后面添加指定内容
    • prefixOverrides|suffixOverrides:将trim标签中内容前面或者后面去掉指定内容
    • 若标签中没有任何内容时,trim标签也没有任何效果
  4. choose、when、otherwise

    • 相当于if…else if…else if…otherwise
    • 其中when至少要有一个,otherwise最多只能有一个
  5. foreach

  • collection:设置需要循环的数组或者集合
  • item:表示数组或者集合中的每一个数据
  • separator:循环体之间的分隔符\
  • open:foreach标签所循环的所有内容的开始符
  • close:foreach标签所循环的所有内容的结束符

8.1if-where-trim标签

8.1.1if标签

image-20240305162857172

8.1.2where标签

image-20240305162915829

8.1.3trim标签

image-20240305163028054

image-20240305163038846

8.2choose-when-ohterwise标签

choose、when、otherwise,相当于if…else if…else

image-20240305163118041

8.3.foreach标签

8.3.1实现批量删除方式一

image-20240305163209567

8.3.2实现批量删除方式二

image-20240305163238958

8.3.3实现批量添加

image-20240305163251101

image-20240305163315943

8.4SQL标签

  • 设置SQL片段:

    <sql id="empColumns">eid,emp_name,age,sex,email</sql>
    
  • 引用SQL片段:

    <include refid="empColumns"></include>
    

image-20240305163350471

9.MyBatis缓存

9.1介绍

MyBatis 中的缓存主要分为一级缓存和二级缓存,它们的作用是提高数据库访问效率,减少不必要的数据库查询操作。

9.1.1一级缓存

  • 一级缓存是指在同一个 SqlSession 中执行相同的查询操作时,第二次及以后的查询会直接从缓存中获取结果,而不会再次查询数据库。
  • 一级缓存是默认开启的,且无法关闭。它的范围是 SqlSession 级别的,即在同一个 SqlSession 中共享缓存。
  • 一级缓存可以通过清除缓存的方式来刷新数据,比如调用 clearCache() 方法。

9.1.2二级缓存

  • 二级缓存是指多个 SqlSession 共享相同的缓存,可以跨 SqlSession 进行数据共享。
  • 使用二级缓存需要在映射文件中配置 <cache> 标签,并在 MyBatis 配置文件中开启二级缓存功能
  • 二级缓存的范围是 Mapper 级别的,即同一个 Mapper 接口下的多个 SqlSession 可以共享缓存。
  • 二级缓存对于经常被访问且不经常改变的数据非常有效,但在数据频繁更新的情况下,需要考虑缓存的有效性和数据一致性。

9.2一级缓存

9.2.1一级缓存生效(默认生效)

一级缓存是 SqlSession 级别 的,通过 同一个 SqlSession 查询 的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问。

  • mapper接口方法定义

image-20240305164155208

  • mapper接口映射文件

image-20240305164204852

  • 测试,同一个sqlSession是去缓存中取数据

image-20240305164226716

9.2.2一级缓存失效

使得一级缓存失效的四种情况

  • 不同的SqlSession对应不同的一级缓存
  • 同一个SqlSession但是查询条件不同
  • 同一个SqlSession两次查询期间执行了任何一次增删改操作,数据发生了变化
  • 同一个SqlSession两次查询期间手动清空了缓存

image-20240305164256605

9.3二级缓存

二级缓存是 SqlSessionFactory 级别,通过 同一个 SqlSessionFactory 创建的 SqlSession 查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取。

9.3.1二级缓存开启

(1)在核心配置文件中,设置全局配置属性 cacheEnabled=“true”,默认为 true,不需要设置;

(2)在映射文件中设置标签 <cache />

image-20240305164322284

(3)二级缓存必须在 SqlSession 关闭或提交之后有效;

(4)查询的数据所转换的实体类类型必须实现序列化的接口。

image-20240305164351626

  • 测试

image-20240305164420343

9.3.2二级缓存失效

失效情况:两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效

9.3.3二级缓存的相关配置

image-20240305164510303

9.4缓存查询的顺序

  1. 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
  2. 如果二级缓存没有命中,再查询一级缓存
  3. 如果一级缓存也没有命中,则查询数据库
  4. SqlSession关闭之后,一级缓存中的数据会写入二级缓存

10_MyBaits的逆向工程

10.1创建逆向工程的步骤

(1)添加依赖和插件,这里的 mybatis 和 JDBC 换成你自己对应的版本:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.peng.mybatis</groupId>
    <artifactId>MyBatis_demo04</artifactId>
    <version>1.0-SNAPSHOT</version>

    <packaging>jar</packaging>

    <!-- 依赖MyBatis核心包 -->
    <dependencies>
        <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.4</version>
        </dependency>

        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.29</version>
        </dependency>

        <!-- log4j日志 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>


        <dependency>
            <groupId>org.apache.ibatis</groupId>
            <artifactId>ibatis-core</artifactId>
            <version>3.0</version>
        </dependency>

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.2.0</version>
        </dependency>

    </dependencies>

    <!-- 控制Maven在构建过程中相关配置 -->
    <build>
        <!-- 构建过程中用到的插件 -->
        <plugins>
            <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.0</version>
                <!-- 插件的依赖 -->
                <dependencies>
                    <!-- 逆向工程的核心依赖 -->
                    <dependency>
                        <groupId>org.mybatis.generator</groupId>
                        <artifactId>mybatis-generator-core</artifactId>
                        <version>1.3.2</version>
                    </dependency>
                    <!-- 数据库连接池 -->
                    <dependency>
                        <groupId>com.mchange</groupId>
                        <artifactId>c3p0</artifactId>
                        <version>0.9.2</version>
                    </dependency>
                    <!-- MySQL驱动 -->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.29</version>
                    </dependency>

                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>


(2)创建 MyBatis 的核心配置文件 mybatis-config.xml

(3)创建逆向工程的配置文件,文件名必须是:generatorConfig.xml(修改数据库名称和密码、修改包名、修改对应的数据库表名)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--
        targetRuntime: 执行生成的逆向工程的版本
        MyBatis3Simple: 生成基本的CRUD(清新简洁版),只有增删改查(查所有和根据 Id 查)5个方法
        MyBatis3: 生成带条件的CRUD(奢华尊享版)
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3">
        <!-- 数据库的连接信息 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mybatis"
                        userId="root"
                        password="123456">
        </jdbcConnection>
        <!-- javaBean的生成策略-->
        <javaModelGenerator targetPackage="com.peng.mybatis.pojo"
                            targetProject=".\src\main\java">
            <!--true生成的是子包,false生成的是一个文件夹名-->
            <property name="enableSubPackages" value="true"/>
            <!-- 去掉字符串前后的空格-->
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!-- SQL映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="com.peng.mybatis.mapper"
                         targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!-- Mapper接口的生成策略 -->
        <javaClientGenerator type="XMLMAPPER"
                             targetPackage="com.peng.mybatis.mapper" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!-- 逆向分析的表 -->
        <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
        <!-- domainObjectName属性指定生成出来的实体类的类名,会自动在后面补上Mapper -->
        <table tableName="t_emp" domainObjectName="Emp"/>
        <table tableName="t_dept" domainObjectName="Dept"/>
    </context>
</generatorConfiguration>
  • MyBatis3Simple: 生成基本的CRUD(清新简洁版),只有增删改查(查所有和根据 Id 查)5个方法
  • MyBatis3: 生成带条件的CRUD(奢华尊享版)

(5)执行MBG插件的generate目标

在这里插入图片描述

10.2豪华尊享版两种函数的区别

在这里插入图片描述

10.3QBC查询

image-20240305164823124

  • 报了错误Error:(6, 37) java: 程序包org.apache.ibatis.annotations不存在,网上找不到解决方法,怀疑是配置文件里面版本问题

10.4QBC修改

image-20240305164840117

  • and 条件都是链式添加的;
  • or 条件需要换行,调用 or() 方法之后再调用 and() 方法。

11_MyBatis分页

11.1使用步骤

  • 核心配置文件中配置分页插件
<plugins>
	<!--设置分页插件-->
	<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
  • 在pom.xml中添加依赖
<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper</artifactId>
	<version>5.2.0</version>
</dependency>

11.2使用

public class PageHelperTest {

    /**
     * limit index,pageSize
     * index:当前页的起始索引
     * pageSize:每页显示的条数
     * pageNum:当前页的页码
     *
     * index=(pageNum-1)*pageSize
     *
     * 使用MyBaits的分页插件实现分页功能
     * 1.需要在查询功能之前开启分页,2代表了当前页码,4代表当前页显示的数据
     * PageHelper.startPage(2,4);
     * 2.在查询功能之后获取分页相关信息
     * PageInfo<Emp> page=new PageInfo<>(list,5);
     * list代表分页数据
     * 5代表当前导航分页的数量
     */
    @Test
    public void testPageHelper(){
        try {
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
            SqlSession sqlSession = sqlSessionFactory.openSession(true);
            EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);

            //开启分页功能

            //查询详细分页信息方式一:
//            Page<Object> page = PageHelper.startPage(2, 4);
//            System.out.println(page);

            PageHelper.startPage(2,4);

            //查询所有数据
            List<Emp> list = mapper.selectByExample(null);
            list.forEach(emp-> System.out.println(emp));

            //查询详细分页信息方式二,其中5代表导航数
            PageInfo<Emp> page=new PageInfo<>(list,5);
            System.out.println(page);


        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
  • Page<Object> page = PageHelper.startPage(2, 4); ,这个函数返回的 page 是包含少量信息的 page数据;
  • PageInfo<Emp> page = new PageInfo<>(list, 5);,展示非常详细的 pageInfo 信息,里面包含 page 信息。

在这里插入图片描述

常用数据:

  • pageNum:当前页的页码
  • pageSize:每页显示的条数
  • size:当前页显示的真实条数(有可能每页显示 5 条,但是最后一页只剩 2 条数据这样子)
  • total:总记录数
  • pages:总页数
  • prePage:上一页的页码
  • nextPage:下一页的页码
  • isFirstPage/isLastPage:是否为第一页/最后一页
  • hasPreviousPage/hasNextPage:是否存在上一页/下一页
  • navigatePages:导航分页的页码数
  • navigatepageNums:导航分页的页码,[1,2,3,4,5]

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于springboot+vue的医疗报销系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

培训机构如何通过小魔推做高效短视频矩阵?

随着智能手机的普及和移动互联网的高速发展&#xff0c;短视频作为一种全新的媒介形式&#xff0c;迅速崛起并占领了大量用户的碎片化时间。从野蛮生长到全面流行&#xff0c;逐渐成为各行业引流获客的主战场之一。 各行各业都意识到了短视频平台的潜力&#xff0c;今天给大家…

【JAVA】Tomcat集成到IDEA

目录 1.在IDEA中安装插件&#xff1a;Smart Tomcat。 2.配置smart tomcat 浏览器显示中文出现乱码 我们可以借助IDEA的插件&#xff0c;把tomcat集成IDEA中&#xff0c;然后我们就可以通过IDEA一键式的重新打包部署了。 1.在IDEA中安装插件&#xff1a;Smart Tomcat。 1&a…

建立网络防御时需要重点考虑的10个因素

互联网安全中心&#xff08;CIS&#xff09;建议企业可以从以下10个因素入手&#xff1a;资产管理、数据管理、安全配置、账户和访问控制管理、漏洞管理、日志管理、恶意软件防御、数据恢复、安全培训和事件响应。 1、资产管理 建立网络防御的第一步是制定企业资产和软件资产的…

day12_oop_抽象和接口

今日内容 零、 复习昨日 一、作业 二、抽象 三、接口 零、 复习昨日 final的作用 修饰类,类不能被继承修饰方法,方法不能重写[重点]修饰变量/属性,变成常量,不能更改 static修饰方法的特点 static修饰的方法,可以通过类名调用 static修饰的属性特点 在内存只有一份,被该类的所有…

msvcp140.dll丢失的解决方法的全面分析,msvcp140.dll文件的应用范围

在我们使用计算机的时候&#xff0c;偶尔会遭遇一些技术问题&#xff0c;其中一个比较常见的问题就是出现了"丢失msvcp140.dll文件"的提示。当我们的电脑告诉我们缺少了msvcp140.dll文件时&#xff0c;常常是因为某些程序无法找到这个文件而导致了程序的运行异常。那…

lightGBM的学习整理

执行步骤 1、初始化&#xff0c;选择一个初始模型&#xff0c;通常是一个常数&#xff0c;比如分类问题中内的类别概率的先验值&#xff0c;回归问题中的目标变量的平均值。 2、训练决策树&#xff0c;对于每一轮迭代&#xff0c;计算当前模型的梯度&#xff08;损失函数的负…

程序员如何选择职业赛道?看这宝典就够了

文章目录 程序员如何选择职业赛道&#xff1f;方向一&#xff1a;自我评估与兴趣探索方向二&#xff1a;提升技能水平方向三&#xff1a;考虑个人职业规划方向四&#xff1a;寻求职业咨询方向五&#xff1a;市场需求与趋势分析 总结 程序员如何选择职业赛道&#xff1f; 程序员…

LLM(十一)| Claude 3:Anthropic发布最新超越GPT-4大模型

2024年3月4日&#xff0c;Anthropic发布最新多模态大模型&#xff1a;Claude 3系列&#xff0c;共有Haiku、Sonnet和Opus三个版本。 Opus在研究生水平专家推理、基础数学、本科水平专家知识、代码等10个维度&#xff0c;超过OpenAI的GPT-4。 Haiku模型更注重效率&#xff0c;能…

智能排班系统 【聚合服务开发】

文章目录 聚合服务创建聚合服务添加依赖启动类问题整合所有微服务的配置文件到聚合服务中文件结构 其他微服务修改网关服务修改启动 聚合服务 为什么需要开发聚合服务&#xff1f; 答&#xff1a;微服务项目中&#xff0c;往往会将系统的功能进行分析&#xff0c;然后进行服务…

【Python】进阶学习:pandas--describe()函数的使用介绍

&#x1f40d;【Python】进阶学习&#xff1a;pandas——describe()函数的使用介绍 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&am…

JDK收费的各个版本(记录一下)

JDK收费的各个版本&#xff08;记录一下&#xff09; Java收费的安装包使用的时候要闭坑 从2019年1月份开始&#xff0c;Oracle JDK 开始对 Java SE 8 之后的版本开始进行商用收费&#xff0c;确切的说是 8u201/202 之后的版本。如果你用 Java 开发的功能如果是用作商业用途的…

uniapp iOS 真机调试

一、下载爱思助手 二、打开爱思助手&#xff0c;把你的 苹果手机 用原装数据线连接至电脑&#xff1a; 找到 工具箱 > 搜索IPA > 打开IAP签名 三、添加 IPA 文件 mac&#xff1a;finder 》应用程序 》右键 HbuilderX 》显示包内容 》HbuilderX / plugins/ lau…

【vue.js】文档解读【day 1】 | 模板语法2

如果阅读有疑问的话&#xff0c;欢迎评论或私信&#xff01;&#xff01; 本人会很热心的阐述自己的想法&#xff01;谢谢&#xff01;&#xff01;&#xff01; 文章目录 模板语法JavaScript表达式仅支持表达式调用函数&#xff1f;受限的全局访问 指令参数动态参数动态参数中…

LeetCode Python - 31.下一个排列

目录 题目答案运行结果 题目 整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。 例如&#xff0c;arr [1,2,3] &#xff0c;以下这些都可以视作 arr 的排列&#xff1a;[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。 整数数组的 下一个排列 是指其整数的下一个字典序更…

C语言-两数组元素互换

#include <stdio.h> #include <string.h>//两数组元素互换 void swap(int ch1[],int ch2[],int sz) {int i 0;char ch 0;for(i 0;i < sz;i){ch ch1[i];ch1[i] ch2[i];ch2[i] ch;} } //打印数组元素 void print(int ch[],int sz) {int i 0;for(i 0;i <…

04. Nginx入门-Nginx WEB模块

测试环境 此处使用的yum安装的Nginx路径。 此处域名均在本地配置hosts。 主配置文件 路径&#xff1a;/etc/nginx/nginx.conf user nginx; worker_processes auto;error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid;events {worker_connection…

Linux——自写一个简易的shell

目录 前言 一、打印提示信息 二、分割字符串 三、替换程序 前言 之前学习了很多进程相关的知识&#xff0c;包括环境变量、进程的创建与退出、进程等待、进程替换。现在可以用所学的作一个小总结&#xff0c;手撕一个shell解释器&#xff0c;大致的思路是先通过环境变量获…

【会议征稿通知】第二届数字化经济与管理科学国际学术会议(CDEMS 2024)

第二届数字化经济与管理科学国际学术会议&#xff08;CDEMS 2024&#xff09; 2024 2nd International Conference on Digital Economy and Management Science&#xff08;CDEMS 2024&#xff09; 2024年第二届数字经济与管理科学国际会议(CDEMS 2024) 定于2023年4月26-28日…

【Sql Server】存储过程的创建和使用事务,常见运用场景,以及目前现状

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《Sql Server》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对…