MyBatis

frame

# 一、MyBatis基础

# 1.1 MyBatis简介

ORM(object relational mapping)对象关系映射

POJO(Plain Old Java Objects)普通老式 Java 对象

MyBatis是一个持久层框架/半自动的ORM。

它支持自定义 SQL、存储过程以及高级映射。

是一种从SQL到POJO的模型,它需要我们提供SQL、映射管理XML、POJO。

# 1.2 Mybatis缓存机制

MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。

缓存可以极大的提升查询效率,减轻数据库压力,提高数据库性能。

MyBatis系统中默认定义了两级缓存,分别是一级缓存二级缓存

  • 默认情况下,只有一级缓存开启。
  • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
  • 为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存。

# 1)一级缓存

一级缓存SqlSession级别的缓存,也称为本地缓存,缓存的数据只在SqlSession内有效。

一级缓存一直是开启的。

它实质上就是sqlSession级别的一个Map,在操作数据库的时候需要先创建 SqlSession 会话对象,在对象中有一个 HashMap 用于存储缓存数据,此 HashMap 是当前会话对象私有的,别的 SqlSession 会话对象无法访问。

具体流程

  1. 第一次执行 select 完毕,会将查到的数据写入 SqlSession 内的 HashMap 中缓存起来;
  2. 第二次执行 select 会从缓存中查数据,如果 select 同传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率。

一级缓存失效的情况

没有使用到当前一级缓存的情况,效果就是还要再向数据库发出查询。

  1. sqlSession不同。
  2. sqlSession相同,查询条件不同(当前一级缓存中还没有这个数据)。
  3. sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响),实际上,这个是因为每个增删改查都有标签flushCache,增删改默认为flushCache=“true”,即执行完后就清除一级缓存和二级缓存。
  4. sqlSession相同,手动清除了一级缓存(缓存清空,session.clearCache(),注意,该方法只清除当前session的一级缓存)。

# 2)二级缓存

二级缓存namespace级别的缓存,也称为全局缓存,一个namespace对应一个二级缓存。

二级缓存默认是没有开启的,需要在 setting 全局参数中配置开启二级缓存。

开启二级缓存步骤

1、conf.xml 配置,全局变量开启二级缓存。

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

默认是false(关闭二级缓存)

2、在 具体某一Mapper.xml中配置(XxxMapper.xml )。

<cache></cache>
1

表示当前mapper下的所有语句开启二级缓存。

cache标签的属性说明

  • evictio:缓存的回收策略

    • LRU - 最近最少使用的,移除最长时间不被使用的对象。
    • FIFO - 先进先出,按对象进入缓存的顺序来移除他们。
    • SOFT - 软引用,移除基于垃圾回收器状态和引用规则的对象。
    • WEAK - 弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象。默认的是LRU。
  • flushInterval:缓存刷新间隔

    缓存多长时间清空一次,默认不清空,可设置一个毫秒值。

  • readOnly:是否只读

    • true:只读,MyBatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据,所以为了加快速度,mybatis直接将数据在缓存中的引用交给用户。这种方式不安全,但速度快。
    • false:非只读,MyBatis觉得获取的数据肯能会被修改,所以MyBatis会利用序列化和反序列化的技术克隆一份新的数据给你。这种方式安全,但是速度慢。因此,此时需要将所有需要在二级缓存中存放的对象都实现序列化接口。
  • size:缓存存放多少元素

  • type:指定自定义缓存的全类名

    我们一般使用默认的,也可以自定义类实现Cache接口,然后在这个属性上写上自定义实现类的全类名,就可以使用自定义的二级缓存。

缓存的相关配置

  • 每个select标签都有属性useCache,默认为true。

    true:使用缓存;false:不使用缓存。

    注意:控制的是二级缓存的使用与否,一级缓存一直可用。

  • 每个增删改查都有标签flushCache。

    • 增删改默认为flushCache=“true”,即执行完后就清除一级缓存和二级缓存;

    • 查询默认为flushCache=“false”,即执行完后不清除一级和二级缓存。

  • sqlSession.clearCache():只清除当前session的一级缓存。

# 缓存使用

当执行SQL查询时,

1、先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用;

2、如果二级缓存没有命中,再查询一级缓存;

3、如果一级缓存也没有命中,则查询数据库。

# 1.3 注解使用

注解 出处 使用场景 作用
@MapperScan 在启动类中用 是将MyBatis所需的接口(Mapper包路径下)扫描装配到Spring IoC容器中
@Reposity Spring提供 在Mapper接口中 使用该注解将对象交由Spring容器来管理,Spring会将该接口识别为Bean
@Mapper MaBatis提供 在Mapper接口中 添加了该注解的接口将被扫描为Spring的Bean装配到IoC容器中

说明:

Ant通配符的3种风格:

  • ?:匹配文件名中的一个字符; eg: com/test/entity? 匹配 com/test/entityaa
  • * : 匹配文件名中的任意字符; eg: com/*/entity 匹配 com/test/entity
  • ** : 匹配文件名中的多重路径; eg: com/**/entity 匹配 com/test/test1/entity

# 二、MyBatis-Plus

# 2.1 MyBatis-Plus简介

MyBatis-Plus(简称 MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。

官方

MyBatis-Plus教程

# 三、FQA

# update方法赋null值

问题描述

Mybatis-Plus使用updateById()将字段更新为null时,未成功赋值。

问题解读

mybatis-plus FieldStrategy 有三种策略:

  • IGNORED:0 忽略
  • NOT_NULL:1 非 NULL,默认策略
  • NOT_EMPTY:2 非空

而默认更新策略是NOT_NULL:即通过接口更新数据时,数据为NULL值时将不更新进数据库。

解决方法

1、设置全局的field-strategy

mybatis-plus:
  global-config:
      #字段策略 0:"忽略判断",1:"非 NULL 判断",2:"非空判断"
    field-strategy: 0
1
2
3
4

说明:配置文件为全局性配置,会对所有的字段都忽略判断,如果一些字段不想要修改,但是传值的时候没有传递过来,就会被更新为null,可能会影响其他业务数据的正确性。

2、对某个字段设置单独的field-strategy

根据具体情况,在需要更新为null的字段上,设置忽略策略。

@TableField(updateStrategy = FieldStrategy.IGNORED)
private String dutyJson;
1
2

然后在更新代码中,我们直接使用mybatis-plus中的updateById方法便可以更新成功。

说明:如果需要这样处理的字段较多,那么就需要涉及对各个字段上都添加该注解。

3、使用UpdateWrapper方式更新(推荐使用)

使用mybatisplus包下BaseMapper类中的update方法,即可更新null值。

package com.baomidou.mybatisplus.core.mapper;

public interface BaseMapper<T> extends Mapper<T> {
    /**
     * 根据 whereEntity 条件,更新记录
     *
     * @param entity        实体对象 (set 条件值,可以为 null)
     * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     */
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
}
1
2
3
4
5
6
7
8
9
10
11

使用案例:

public boolean delete(Long userId) {
    LambdaUpdateWrapper<SysUser> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
    lambdaUpdateWrapper.eq(SysUser::getId, userId)
            .set(SysUser::getStatus, null)
            .set(SysUser::getLevel, null)
    ;
    return sysUserMapper.update(null, lambdaUpdateWrapper) > 0;
}
1
2
3
4
5
6
7
8