MyBatis

 Mybatis官方文档:https://mybatis.org/mybatis-3/zh/index.html

启用日志

1. MyBatis支持的日志

MyBatis框架内置日志工厂。日志工厂负责自动加载项目中配置的日志。MyBatis支持以下日志,当存在多个日志工具时,严格按照从上往下顺序使用,且只会使用一个。

  • SLF4J

  • Apache Commons Logging

  • Log4j 2

  • Log4j (deprecated since 3.5.9)

  • JDK logging

其中Log4j是MyBatis中之前使用较多的一个日志实现。但是从3.5.9版本被替换了,在目前3.5.9版本中还能使用,但是在将来的版本中会被移除。下面分别演示MyBatis整合Log4j和Log4j2的实现方案。

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
复制代码
log4j.rootLogger=ERROR, stdout
# log4j.logger是固定的,a.b.c是命名空间的名字可以只写一部分。
log4j.logger.a.b.c=TRACE

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
log4j.properties
复制代码

2. log4j2

    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.17.2</version>
    </dependency>
复制代码
<?xml version="1.0" encoding="utf-8" ?>
<Configuration >
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>

    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
        <!-- namespace的值 -->
        <logger name="a.b.c" level="debug"></logger>
    </Loggers>
</Configuration>
log4j2.xml
复制代码

3. SLF4j

SLF4j是日志的接口声明,不参与日志的具体实现,需要配合其它日志具体实现工具包才能进行使用。

每次添加其它日志工具包时,不要忘记SLF4j整合这个日志的依赖。

3.1 整合log4j

复制代码
<!--slf4j整合log4j的依赖,版本要和slf4j的版本对应 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.36</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
复制代码

配置文件同log4j配置文件

3.2 整合log4j2

复制代码
<dependencies>
    <!-- SLF4j整合Log4j2的依赖 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.17.2</version>
    </dependency>
    <!-- Log4j2工具的依赖-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.17.2</version>
    </dependency>
</dependencies>
复制代码

配置文件同log4j2配置文件

一、别名

在核心配置文件中使用<typeAliases>标签配置别名。

别名可以用于映射文件中的resultType属性。

1.直接配置别名

    <typeAliases>
        <!-- type:类型全限定路径   alias:别名名称 -->
        <typeAlias type="com.gsy.pojo.People" alias="p"></typeAlias>
        <typeAlias type="com.gsy.pojo.People" alias="p2"></typeAlias>
    </typeAliases>

注意:同一个类可以有多个别名;

   且在使用时不区分大小写;

   设置了别名后,类的全限定名依然有效。

2.扫描包配置别名

    <typeAliases>
        <!-- 指定包 -->
        <package name="com.gsy.pojo"/>
    </typeAliases>

MyBatis在解析xml文件时会将指定的包中所有实体类配置别名为类名

注意:使用这种方式时严格区分大小写;

   可以与直接配置别名混用。

3.MyBatis内置的别名

别名映射的类型 别名映射的类型 别名映射的类型
_byte byte   string String   date Date
_long long   byte Byte   decimal BigDecimal
_short short   long Long   bigdecimal BigDecimal
_int int   short Short   object Object
_integer int   int Integer   map Map
_double double   integer Integer   hashmap HashMap
_float float   double Double   list List
_boolean boolean   float Float   arraylist ArrayList
      boolean Boolean   collection Collection
            iterator Iterator

 二、结果集映射的方式(面)

MyBatis会根据句映射关系把查询到的结果填充到指定结果集类型中。支持方式:

  • auto mapping:自动映射。当列名或列的别名与实体类属性名相同时不需要做额外配置。

  • resultMap:手动定义映射关系。

  • camel case:驼峰命名规则。

1.Auto Mapping(自动映射)

这要求resultType指定的类的属性与查询结果返回字段相对应,字段名和属性名不一致就无法映射。

2.resultMap

当数据库表中字段名和类的属性名不一致,就需要手动指定才能完成映射。

复制代码
<mapper namespace="a.b.c">
    <!-- type:数据库中每行数据对应的实体类型,支持别名 -->
    <!-- id:自定义名称-->
    <resultMap id="myid" type="People2">
        <!-- id标签定义主键列和属性的映射关系关系 -->
        <!-- property 类中属性名称,区分大小写-->
        <!-- column 表中列名-->
        <id property="peoId" column="peo_id"/>
        <!-- result标签定义非主键列和属性的映射关系-->
        <result property="peoName" column="peo_name"/>
    </resultMap>
    <!-- resultMap的值必须和resultMap的id相同 -->
    <select id="myid" resultMap="myid">
        select * from tb_people
    </select>
</mapper>
复制代码

这时就不需要resultType了,直接用配置好的resultMap。
resultMap支持继承,

<resultMap id="myid2" type="People2" extends="myid">


</resultMap>

3.camel case

MyBatis可以自动驼峰命名转换

在MySQL中列名命名规范是xxx_yyy,多个单词之间使用下划线进行分割。在Java中属性命名规范是xxxYyy,小驼峰的方式进行命名。这两种技术的命名习惯是不一样的,这就导致每次都需要手动配置映射关系。

MyBatis发现了这个问题,提供了驼峰转换的能力。通过全局配置文件开启驼峰转换功能后,就可以让xxx_yyy自动映射到xxxYyy上。例如:列名叫做peo_id,可以自动映射到peoId的属性上。转换时去掉列中的下划线,把下划线后面单词首字母变大写。

该功能默认关闭,需要手动开启:

  在核心配置文件中开启自动驼峰命名转换

    <settings>
        <!-- 开启驼峰转换 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

三、接口绑定

MyBatis提供了一种接口绑定方案,通过SqlSession的getMapper方法产生接口的动态代理对象(该接口的实现类)。然后通过对象调用接口中提供的功能。

1.resource

    <!--配置mapper映射-->
    <mappers>
        <mapper resource="com/gsy/dao/UserMapper.xml"/>
    </mappers>
根据映射文件中的namespace找对应的接口进行绑定。

2.class

    <mappers>
        <mapper class="com.gsy.dao.UserMapper"/>
    </mappers>

注意

  这种方式要求接口和映射文件输出到target目录后都在同一个文件夹且同名;

  映射文件中的namespace命名空间为接口的全限定名,statement的id必须跟接口的方法名一致

  (底层通过反射找到对应的方法,封装了sqlSession.xxx(namespace+id)进行绑定)

  即:

 两种解决方式:

  1.将映射文件放和接口放在同一个包,然后在pom.xml中配置深度资源拷贝插件。

  2.在resource目录下建立相同的路径,注意由于是路径,所以不要用com.gsy.dao,而是com/gsy/dao

放在同一个文件夹且必须同名的原因:

MyBatis底层会直接将绑定的class中的 . 转换为 / 再拼接 .xml 去拿映射文件

 3. package

    <mappers>
        <package name="com.gsy.dao"/>
    </mappers>

使用该方式注意事项同使用class绑定。

使用package扫描包的方式 Mybatis会将指定的包中所有接口都进行注册。

4.使用

无需再通过sqlSession根据namespace+id调用方法了

通过sqlSession.getMapper(class<?>) 获取对应接口的实现类然后调用方法即可。

四、接口绑定的参数传递

以查询方法为例:

当方法带有多个参数将使用session.selectList("",Map类型参数)或session.selectOne("",Map类型参数)作为底层调用。

Map类型当做参数时,在映射文件中通过Map的key进行获取value值。

Mybatis会自动创建Map的key:

  1. 如果接口中方法没有使用注解定义名称,MyBatis使用内置名称作为key。MaBatis会在底层生成对应的参数名:arg0、arg1、param1、param2、其中arg0和param1的值都是同一个参数,以此类推。

    规则:arg0、arg1、argM(M为从0开始的数字,和方法参数顺序对应)或param1、param2、paramN(N为从1开始的数字,和方法参数顺序对应)。

  2. 也可以在接口方法参数中通过@Param("key名称")的形式进行定义key。一定使用了注解argN的这种形式就不能使用了,但是paramN的方式还是可以使用。

五、主键回填

MyBatis中有两种方式可以获取到自增主键的值:

  • 使用<selectKey>子标签编写SQL进行回填属性值。

  • 使用<select>的useGeneratedKeys属性值进行自动回填。

回填的主键值会放在作为参数的对象的属性中。

1. selectKey

<insert id="insert1">
    <selectKey keyProperty="id" resultType="int">
        select @@identity
    </selectKey>
    insert into people values(default,#{name},#{address})
</insert>

<selectKey>标签是<insert>的子标签,作用:把查询到的结果填充进行回填。

  • keyProperty:接口方法参数中,对象的哪个属性需要进行回填。
  • resultType:SQL查询到的结果类型。
  • select @@identity:是MySQL内置的全局变量表示获取到自增主键值。
  • order:selectKey中SQL是在添加SQL执行之前还是之后执行。可取值:AFTER和BEFORE。

2. 自动主键回填

MyBatis的映射文件的<insert>标签带有自动主键回填功能,只需要设置useGeneratedKeys进行开启自动主键回填功能,同时设置keyProperty的值需要回填到对象的哪个属性

<insert id="insert2" useGeneratedKeys="true" keyProperty="id">
    insert into people values(default,#{name},#{address})
</insert>

六、动态SQL

1. if标签

mapper映射文件通过if进行判断参数的属性是否为null。

<if>标签的test属性值为OGNL(对象导航图语言)表达式,通过对象属性名可以快速获取到对象属性值。

复制代码
<select id="selectIf" resultType="People">
    select * from people where 1=1
    <if test="name!=null">
        and name=#{name}
    </if>
    <if test="address!=null">
        and address=#{address}
    </if>
</select>
复制代码

代码解释说明:

  name!=null : OGNL 表达式,直接写属性名可以获取到属性值。不需要添加${}或#{}。

  name=#{name} 中name是表中的列名。#{name}是MyBatis获取参数对象属性值的写法(之前学习的)。

  where 1=1 中1=1是为了保证SQL语法的正确性。如果if成立没有1=1最后的SQL就是where and name=xxx 这种写法是不对的。

 2. choose标签

choose标签相当于Java中的switch...case....default。(if..else...if+default)

在choose标签里面可以有多个when标签和一个otherwise(可以省略)标签。只要里面有一个when成立了后面的when和otherwise就不执行了。

复制代码
<select id="selectIf" resultType="People">
    select * from people where 1=1
    <choose>
        <when test="name!=null">
            and name=#{name}
        </when>
        <when test="address!=null">
            and address=#{address}
        </when>
        <otherwise>
            // do something
        </otherwise>
    </choose>
</select>
复制代码

3. trim标签

trim作为很多其它标签的底层。

无论是开头操作还是结尾的操作,都是先去掉容,后添加。

trim只会对里面的子内容进行操作。如果子内容为空则不进行任何操作。

后添加的内容会有空格。

特例:

  如果内部字符串为要去掉的字符串,去掉后认为内容不为空,prefix依然添加。

trim标签包含四个属性:

  • prefix:只要子内容不是空字符串(""),就在子内容前面添加特定字符串。
  • prefixOverrides:如果子内容是以某个内容开头,去掉这个内容。
  • suffix:只要内容不是空字符串(""),就在子内容后面添加特定字符串。
  • suffixOverrides:如果里面内容以某个内容结尾,就去掉这个内容。
复制代码
<select id="selectIf" resultType="People">
    select * from people
    <trim prefix="where" prefixOverrides="and">
        <if test="name!=null">
            and name=#{name}
        </if>
        <if test="address!=null">
            and address=#{address}
        </if>
    </trim>
</select>
复制代码

4. where标签

where标签属于trim标签的简化版,被where标签包含的内容具备:

  • 如果里面内容不为空串,在里面内容最前面添加where。
  • 如果里面内容是以and开头,去掉最前面的and。
复制代码
<select id="selectIf" resultType="People">
    select * from people
    <where>
        <if test="name!=null">
            and name=#{name}
        </if>
        <if test="address!=null">
            and address=#{address}
        </if>
    </where>
</select>
复制代码

5. set标签

set标签是专门用在修改SQL中的,属于trim的简化版,带有下面功能:

  • 如果子内容不为空串,在最前面添加set。
  • 去掉最后一个逗号。
复制代码
<update id="update">
    update people
    <set>
        <if test="name!=null">
            name=#{name},
        </if>
        <if test="address!=null">
            address=#{address},
        </if>
        id=#{id}
    </set>
    where id = #{id}
</update>
复制代码

注意:set结束标签的后面的id=#{id}是非常重要的,不能不写。因为set在解析时,如果里面为空串,是不会在前面添加set的,对于SQL的修改来说,没有set关键字是不正确的语法。

6. foreach标签

foreach标签表示循环,主要用在in查询或批量新增的情况。

<select id="selectByIds" resultType="People">
    select * from people where id in
    <foreach collection="array" open="(" close=")" item="id" separator=",">
        #{id}
    </foreach>
</select>

foreach标签的属性解释说明:

  collection:要遍历的数组或集合对象。

    1. 如果参数没有使用@Param注解:arg0或array或list。

    2. 如果使用@Param注解,使用注解的名称或param1。

  open:遍历结束在前面添加的字符串。

  close:遍历结束在后面添加的字符串。

  item:迭代变量。在foreach标签里面#{迭代变量}获取到循环过程中迭代变量的值。

  separator:分隔符。在每次循环中间添加的分割字符串。

  index:迭代的索引。从0开始的数字。

  nullable:是否允许数组或集合对象为null。如果设置为true,表示集合或数组允许为null。如果设置为false表示不允许数组或集合对象为null,一旦为null会出现:  org.apache.ibatis.builder.BuilderException: The expression 'array' evaluated to a null value。

7. bind标签

bind标签表示对传递进来的参数重新赋值。最多的使用场景为模糊查询。通过bind可以不用在Java代码中对属性添加%。

<select id="selectLike" resultType="People">
    <bind name="name" value="'%'+name+'%'"/>
    select * from people where name like #{name}
</select>

PS:

  模糊查询另一种写法为使用SQL中的CONCAT函数,将%与参数拼接进行查询

例如:

复制代码
    <select id="selectPeopleByLike" resultType="people">
        select * from people
        <where>
            <if test="name!=null">
                and name like concat('%',#{name},'%')
            </if>
            <if test="address!=null">
                and address like concat('%',#{address},'%')
            </if>
        </where>
    </select>
复制代码

8. sql和include标签

对于多次重复使用且比较冗余的sql代码可以使用sql标签作为SQL片段

使用时用include标签引入即可

<sql id="mysqlpart">
    id,name,address
</sql>
<select id="selectSQL" resultType="People">
    select <include refid="mysqlpart"></include> from people
</select>

include引入的内容和sql片段中的完全一致。

七、MyBatis常用注解

在MyBatis中对于特别简单的SQL、尤其不需要定义resultMap的SQL可以使用注解进行实现。通过注解能简化映射文件的编写。

MyBatis的注解通过全局配置文件<mappers>进行加载。

  • 如果一个Mapper接口中所有方法都使用注解定义SQL,可以在全局文件中配置。

<mappers>
    <mapper class="com.gsy.mapper.PeopleMapper"></mapper>
</mappers>
  • 如果一个Mapper接口中既有注解又有mapper.xml定义SQL。可以在全局配置文件中通过<package>进行加载。这种方式和之前的接口绑定方案的配置是一样的。也就是说MyBatis在扫描这个包的时候就可以加载到注解。

<mappers>
    <package name="com.bjsxt.mapper"/>
</mappers>

在MyBatis中注解都是写在Mapper接口的方法上中,所有的注解都在org.apache.ibatis.annotations包中,常见注解:

注解解释
@Select 查询
@Insert 新增
@Delete 删除
@Update 修改
@SelectKey 主键回填
@SelectProvider 调用SQL构建器。查询专用
@DeleteProvider 调用SQL构建器。删除专用
@UpdateProvider 调用SQL构建器。修改专用
@INSERTProvider 调用SQL构建器。删除专用
@Param 定义参数的名称

1. CRUD

复制代码
public interface PeopleMapper {
    @Select("select * from people")
    List<People> selectAll();
    @Insert("insert into people values(default,#{name},#{address})")
    int insert(People peo);
    @Delete("delete from people where id=#{id}")
    int deleteById(int id);
    @Update("update people set name=#{name},address=#{address} where id=#{id}")
    int updateById(People peo);
}
复制代码

2. 主键回填

使用注解时,主键回填需要通过@SelectKey注解。

该注解中:必有属性

  keyProperty:表示回填属性名。

  statement:执行的sql。

  before:表示是否在@Insert的SQL之前执行。

  resultType:表示statement对应SQL执行结果。

@Insert("insert into people values(default,#{name},#{address})")
@SelectKey(keyProperty = "id",statement = "select @@identity",before = false,resultType = Integer.class)
int insert(People people);

3. SQL构建器(Provider)

如果SQL较长还想使用注解,可以使用SQL构建器

MyBatis的SQL构建器赋予了程序员在Java类中编写SQL的方式。把以前写在注解参数中的复杂SQL转移到了类的方法中进行书写。

3.1 直接写SQL

在接口中提供方法。并在方法上面添加@SelectProvider注解,注解中属性含义:

  • type:编写SQL的类。

  • method:类中哪个方法返回SQL。

@SelectProvider(type = MySQLProvider.class,method = "selectprovider")
List<People> select(People peo);

新建参数type中的MySQLProvider类,并在类中提供selectprovider方法。

下面代码并不是必须写成多行,只是为了演示当SQL比较复杂时都是分多行写的效果。

返回SQL的方法必须是public String的。

public class MySQLProvider {
    public String selectprovider(){
        return "select *" +
                " from people" +
                " where name=#{name} and address like #{address}" +
                " order by id desc";
    }
}

看起来写的不是特别费劲,但是一定要注意关键字前后都有空格。上面代码每行前面都有空格,其实这点非常不友好。

3.2 使用SQL类

MyBatis提供了SQL类,该类中封装了很多方法,方法名称和SQL的关键字名称正好对应。

里面需要注意的点:

  • 如果多个条件可以放在一个where中,也可以放在多个连续where中(放在多个where方法中不需要and关键字)。
  • 最终需要调用toString()转换为字符串。
复制代码
import org.apache.ibatis.jdbc.SQL;

public class MySQLProvider {
    public String selectprovider2(){
        return "select *" +
                "from people" +
                "where name=#{name} and address like #{address}" +
                "order by id desc";
    }
    public String selectprovider(){
        return new SQL()
                .SELECT("*")
                .FROM("people")
                .WHERE("name=#{name}")
                // 没有and,多个并列条件使用WHERE方法
                .WHERE("address like #{address}")
                .ORDER_BY("id desc")
                .toString();
    }
}
复制代码

4. 使用注解进行结果映射

@Results的value属性类型Result[]。

@Result注解:

  column:数据库列

  property:属性名

  id:是否为主键,默认false

    @Results(value = {
            @Result(column = "peo_id",property = "id",id = true),
            @Result(column = "peo_name",property = "name")
    })
    @Select("select * from tb_people where peo_name=#{name}")
    List<People> select2(People peo);

八、多表查询(面)

表之间关系分为:一对一、一对多、多对多。这三种关系又细分为单向和双向。

在MyBatis框架中只有两种情况:当前表对应另外表是一行数据还是多行数据。转换到实体类上:当前实体类包含其它实体类一个对象还是多个对象。

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

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

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

    复制代码
        <resultMap id="empMap" type="com.gsy.pojo.Emp">
            <id property="eid" column="eid"/>
            <result property="name" column="ename"/>
            <result property="deptId" column="dept_id"/>
            <association property="dept" javaType="com.gsy.pojo.Dept">
                <id property="did" column="did"/>
                <result property="name" column="dname"/>
                <result property="addr" column="addr"/>
            </association>
        </resultMap>
    
        <select id="allEmpAndDept" resultMap="empMap">
            select eid,ename,dept_id,did,dname,addr from emp , dept where dept_id=did
        </select>
    复制代码
  2. 如果一个实体类关联一个实体类的List集合对象,需要使用<collection>

    复制代码
        <resultMap id="deptMap" type="Dept">
            <id column="did" property="did"/>
            <result column="dname" property="name"/>
            <result column="addr" property="addr"/>
            <collection property="list" ofType="Map">
                <id column="eid" property="eid"/>
                <result column="ename" property="name"/>
            </collection>
        </resultMap>
        <select id="allDeptAndEmp" resultMap="deptMap">
            select eid,ename,dept_id,did,dname,addr from emp , dept where dept_id=did
        </select>
    复制代码

    一对多就用collection ,也可以这样写。oftype用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来。

    <collection property="list"  javaType="java.util.List" ofType="Dept">
  3. 所以分析的思路是:先分析需求->分析数据库设计对应关系->创建实体类->根据实体类关联属性类型决定使用哪个标签。

根据编写的SQL分为联合查询和N+1查询

两种方式优缺点:

  • 联合查询方式:

    优点:一次查询。

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

  • 业务装配(N+1的一种)

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

        缺点:代码复杂。

  • N+1方式:

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

    缺点:多做N次查询。

1. 联合查询

联合查询方式中,对SQL编写有一定的能力要求,只要把SQL能编写出来,知道哪些列的值需要放在哪些属性中就可以了。

编写联合查询SQL例如:

select e_id,e_name,e_d_id,d_id,d_name from dept,emp where d_id=e_d_id

2. 业务装配

所谓的业务装配是不使用MyBatis进行装配。而是使用Java代码进行装配。具体体现在Web项目中,是在service里面通过Java代码实现结果的填充。

这种方式理解起来简单,写持久层时也简单。但是service的代码写起来更多了。属于N+1方式的另一种写法。

比如查所有部门后再遍历部门根据部门id查对应所有员工。

3. N+1

N+1查询方式命名由来:当查询Emp表中N条数据时,需要编写1条查询全部的SQL和N条根据外键值作为另一张表主键查询条件的N条SQL语句。

N+1查询方式,在进行操作时需要先分析出最终想要的结果需要包含对于两张表的单表查询语句是什么。

注意:条件的字段必须作为查询的字段,否则不生效。

<resultMap id="empMap2" type="Emp">
    <id column="e_id" property="id"/>
    <result column="e_name" property="name"/>
    <!-- 此处依然使用association填充单个对象属性值.property和javaTye依然需要写 -->
    <!-- select 调用另一个查询的路径,同一个映射文件中,前面namespace可以省略-->
    <!-- column: 当前SQL查询结果哪个列值当做参数传递过去。如果是多个参数{"key":column,"key2":column2}-->
    <association property="dept" javaType="Dept" select="com.gsy.mapper.DeptMapper.selectById" column="e_d_id"></association>
</resultMap>

复制代码
<resultMap id="deptMap3" type="Dept">
    <id column="d_id" property="id"></id>
    <result column="d_name" property="name"></result>
    <!-- 加载集合类型属性时依然使用collection标签,property和ofType依然存在 -->
    <!-- select 调用另一个查询的路径,同一个映射文件中,前面namespace可以省略-->
    <!-- column 当前查询哪个列的值作为参数传递给另一个参数-->
    <collection property="list" ofType="Emp" select="com.bjsxt.mapper.EmpMapper.selectByEid" column="d_id"/>
</resultMap>
<select id="selectAllN1" resultMap="deptMap3">
    select * from dept
</select>
复制代码

九、延迟加载(面)

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

只能使用在多表联合查询的N+1方式中。

1. 启用延迟加载

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

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

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

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

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

全局配置方式

从3.4.1版本开始需要在MyBatis全局配置文件里面配置lazyLoadingEnabled=true即可在当前项目所有N+1的位置开启延迟加载。

<settings>
    <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.bjsxt.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>
复制代码

十一、缓存(面)

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

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

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

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

1. 一级缓存

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

重要提示:

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

  1. 同一个SqlSession对象。

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

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

清除缓存:写操作、SqlSession事务提交、回滚、释放SqlSession

insert()、delete()、update()

close(),commit(),rollback()。

1.1 一级缓存流程图

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

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

1.2 一级缓存流程

一级缓存执行流程:默认开启
  1.根据调用的接口中的方法 + SQL语句 + ... 建立了缓存的 key
  2.从一级缓存(localCache)中获取key对应的数据
    无:从数据库查询,将查询结果存储到一级缓存中,(key,数据),返回查询到的数据
    有:直接返回一级缓存中获取到的数据

  3.一级缓存基于SqlSession,使用同一个SqlSession一级缓存生效

  4.执行了写操作会先清除缓存再执行写入操作。

注意:清除一级缓存
1.commit() rollback()
2.insert() update() delete()
3.close()

2. 二级缓存

二级缓存是以namespace为标记的缓存,可能要借助磁盘,磁盘上的缓存,可以由一个SqlSessionFactory创建的SqlSession之间共享缓存数据,默认并不开启。

二级缓存生效条件:

  1. 同一个SqlSessionFactory对象。

  2. 同一个方法(<select>)。

  3. SQL完全相同。

重要提示:

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

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

2.1 开启二级缓存

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

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

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

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

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

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

public class Dept implements  Serializable {  }

否则会提示该错误:

注意:

  1. MyBatis的二级缓存的缓存介质有多种多样,而并不一定是在内存中,所以需要对JavaBean对象实现序列化接口。

  2. 二级缓存是以 namespace 为单位的,不同 namespace 下的操作互不影响。

  3. 查询数据顺序 二级-->一级--->数据库--->把数据保存到一级,当sqlsession关闭或者提交的时候,把一级缓存中数据刷新到二级缓存中。

  4. 执行了DML操作,会清空一级缓存,所以数据变更不可能到达二级缓存中。

  5. cache 有一些可选的属性 type, eviction, flushInterval, size, readOnly, blocking。

<cache type="" readOnly="" eviction=""flushInterval=""size=""blocking=""/>
属性含义默认值
type 自定义缓存类,要求实现org.apache.ibatis.cache.Cache接口 null
readOnly 是否只读true:给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。false:会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全 false
eviction 缓存策略LRU(默认) – 最近最少使用的:移除最长时间不被使用的对象。FIFO – 先进先出:按对象进入缓存的顺序来移除它们。SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。 LRU
flushInterval 刷新间隔,毫秒为单位。 null
size 缓存对象个数。 1024
如果在加入Cache元素的前提下让个别select 元素不使用缓存,可以使用useCache属性,设置为false。useCache控制当前sql语句是否启用缓存, flushCache控制当前sql执行一次后是否刷新缓存  
<select id="findByEmpno" resultType="emp" useCache="true" flushCache="false">

 2.2 二级缓存执行流程

二级缓存执行流程:手动开启(映射文件中通过 <cache/>开启)
select标签中设置useCache="false"表示select标签不使用二级缓存
每一个映射文件对应着一个映射文件的二级缓存
有效范围:同一个映射文件中的SQL
  1.根据调用的接口中的方法 + select语句 + ... 建立了缓存的 key
  2.从当前映射文件的二级缓存(真正存储二级缓存数据的集合:Cache接口实现类PerpetualCache的cache属性[本质为map集合])中根据key获取对应数据
    无:
      1.从一级缓存(localCache)中获取key对应的数据
        无:从数据库查询,将查询结果存储到一级缓存中,(key,数据),返回查询到的数据
        有:直接返回一级缓存中获取到的数据
      2.添加到二级缓(临时存储缓存数据的集合:Cache接口实现类TransactionalCache的entriesToAddOnCommit属性[本质为map集合])存中,返回数据
      3.执行了,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。

执行顺序图

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

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

(3)使用ParameterHandler设置参数

(4)使用StatementHandler执行SQL

(5)使用ResultSetHandler处理结果集

对应JDBC代码

 十三、执行器类型(面)

 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进行设置。

也可以在全局配置文件中通过<settings>中defaultExecutorType 进行全局设置(不推荐)。

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

有效范围:同一个SqlSession对象

1. SimpleExecutor

SimpleExecutor 是MyBatis默认的执行器类型。在没有明确设置执行器类型时,默认就是这个类型。

2. ReuseExecutor

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

3. BatchExecutor

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

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

posted @   ygdgg  阅读(55)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示