MyBatis-Plus框架

122 阅读12分钟

MyBatis-Plus框架

简介

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

特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响
  • 损耗小:自动注入基本 CRUD,性能基本无损耗
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

Service CRUD接口

说明

  • 通用 Service CRUD接口 封装在IService接口中,进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆。
  • 建议如果存在自定义通用 Service 方法的可能,请创建自己的 Service接口 继承 Mybatis-Plus 提供的IService接口
  • 实现接口类一定要继承MybatisPlus提供的serviceImpl类
  • 对象 Wrapper 为 条件构造器

方法介绍

save方法

// 插入一条记录(选择字段,策略插入)
boolean save(T entity);//参数:实体对象
// 插入(批量)
boolean saveBatch(Collection<T> entityList);//参数:实体集合
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);//插入批次数量

saveOrUpdate方法

//根据saveOrUpdate方法,如果参数中存在相同的id,则是修改里面的信息。反正是新增信息
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);

remove方法

// 根据 queryWrapper 设置的条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);

update方法

// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize)

get方法

// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

list方法

// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

page方法

// 无条件分页查询
IPage<T> page(IPage<T> page);
// 条件分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);

count方法

// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);

条件构建器

使用条件构建器的注意事项:

不支持以及不赞成在 RPC 调用中把 Wrapper 进行传输

  1. wrapper 很重
  2. 传输 wrapper 可以类比为你的 controller 用 map 接收值(开发一时爽,维护火葬场)
  3. 正确的 RPC 调用姿势是写一个 DTO 进行传输,被调用方再根据 DTO 执行相应的操作
  4. 我们拒绝接受任何关于 RPC 传输 Wrapper 报错相关的 issue 甚至 pr

如何创建条件构建器

条件构建器分两种,这两种都是AbstractWrapper的子类。

第一种:查询条件构建器 QueryWrapper

第二种:修改条件构建器 UpdateWrapper

以上两种都是通过new的方式进行创建。

使用条件讲解

eq:等于

ne:不等于

gt:大于

ge:大于等于

it:小于

ie:小于等于

between:两者之间

notbetwee:不在两者之间

以上参数(列名,值)

like:模糊查询(含值)

参数(列名,值)

 void findPageStudent1(){
        QueryWrapper<Student> findWrapper = new QueryWrapper<>();
        QueryWrapper<Student> like = findWrapper.like("name", "花");
         List<Student> list = studentService.list(like);
        System.out.println("结果:"+list);
    }

notLike:模糊查询(不含值)

参数(列名,值)

   void findPageStudent2(){
        QueryWrapper<Student> findWrapper = new QueryWrapper<>();
        QueryWrapper<Student> notike=findWrapper.notLike("name","三");
         List<Student> list = studentService.list(notlike);
        System.out.println("结果:"+list);
    }

likeLeft:模糊查询(第一个字符不包含值)

参数(列名,值)

notLikeLeft:模糊查询(第一个字符包含值)

参数(列名,值)

likeRight:模糊查询(最后一个字符不包含值)

参数(列名,值)

notLikeRight:模糊查询(最后一个字符包含值)

参数(列名,值)

isNull:字段为空

isNotNull:字段不为空

参数(列名)

in:字段包含的值

notin:字段不包含的值

参数(列名,可变数组)

insql:字段包含的值和sql语句

notinsql:字段不包含的值和sql语句

参数(列名,sql语句)

  void findPageStudent2(){
        QueryWrapper<Student> findWrapper = new QueryWrapper<>();
        String sql="select clazz from student";
        QueryWrapper<Student> clazz = findWrapper.inSql("clazz", sql);
        List<Student> list = studentService.list(clazz);
        System.out.println(list);
    }

groupBy:

参数(列名可变数组)

orderByAsc/Desc:排序

参数(列名)

having:

参数(sql语句)

以上是基本常用的,剩余的见官方网址:baomidou.com/pages/10c80…

常用注解

@TableName:标识实体类对应的表

属性类型必须指定默认值描述
valueString表名
schemaStringshcema 属性用来指定模式名称。如果你使用的是 mysql 数据库,则指定数据库名称。如果你使用的是 oracle,则为 schema,例如:schema="scott",其中:scott 就是 oracle 中的 schema。
keepGlobalPrefixbooleanfalse是否保持使用全局的 tablePrefix 的值(当全局 tablePrefix 生效时)
resultMapStringxml 中 resultMap 的 id(用于满足特定类型的实体类对象绑定)
autoResultMapbooleanfalse是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建与注入)
excludePropertytring[]{}需要排除的属性名 @since 3.3.1

@TableId:标识实体类主键字段

属性类型必须指定默认值描述
valueString主键字段名
typeEnumIdType.NONE指定主键类型

关于IdType的属性解释:

描述
AUTO数据库 ID 自增
NONE无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUTinsert 前自行 set 主键值
ASSIGN_ID分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
ASSIGN_UUID分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认 default 方法)

@TableField:标识在字段上,且是非主键

属性类型必须指定默认值描述
valueString数据库字段名
existbooleantrue是否为数据库表字段
conditionString字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的
updateString字段 update set 部分注入,例如:当在version字段上注解update="%s+1" 表示更新时会 set version=version+1 (该属性优先级高于 el 属性)
insertStrategyEnumFieldStrategy.DEFAULT举例:NOT_NULL insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>)
updateStrategyEnumFieldStrategy.DEFAULT举例:IGNORED update table_a set column=#{columnProperty}
whereStrategyEnumFieldStrategy.DEFAULT举例:NOT_EMPTY
where <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if>
fillEnumFieldFill.DEFAULT字段自动填充策略
selectbooleantrue是否进行 select 查询
keepGlobalFormatbooleanfalse是否保持使用全局的 format 进行处理
jdbcTypeJdbcTypeJdbcType.UNDEFINEDJDBC 类型 (该默认值不代表会按照该值生效)
typeHandlerClass<? extends TypeHandler>UnknownTypeHandler.class类型处理器 (该默认值不代表会按照该值生效)
numericScaleString指定小数点后保留的位数

关于jdbcTypetypeHandler以及numericScale的说明:

numericScale只生效于 update 的 sql. jdbcTypetypeHandler如果不配合@TableName#autoResultMap = true一起使用,也只生效于 update 的 sql. 对于typeHandler如果你的字段类型和 set 进去的类型为equals关系,则只需要让你的typeHandler让 Mybatis 加载到即可,不需要使用注解

FieldStrategy

描述
IGNORED忽略判断
NOT_NULL非 NULL 判断
NOT_EMPTY非空判断(只对字符串类型字段,其他类型字段依然为非 NULL 判断)
DEFAULT追随全局配置
NEVER不加入SQL

FieldFill

描述
DEFAULT默认不处理
INSERT插入时填充字段
UPDATE更新时填充字段
INSERT_UPDATE插入和更新时填充字段

@Version:乐观锁注解、标记 @Version 在字段上

@EnumValue:普通枚举类注解(注解在枚举字段上)

@TableLogic:表字段逻辑处理注解(逻辑删除)

属性类型必须指定默认值描述
valueString逻辑未删除值
delvalString逻辑删除值

@OrderBy:内置 SQL 默认指定排序,优先级低于 wrapper 条件查询

属性类型必须指定默认值描述
isDescbooleantrue是否倒序查询
sortshortShort.MAX_VALUE数字越小越靠前

安装

在springBoot的pom文件中注入依赖即可使用

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>最新版本</version>
</dependency

案例:简单操作MyBatis-Plus

创建springBoot的应用程序见SpringBoot教案,此处略过

Pom文件

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.15</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.hh</groupId>
    <artifactId>Deom2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Deom2</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.0.33</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
<!--        引入mybatis-plus依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.2</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

配置文件

#数据源
    //此处的Mysql数据库用的是8版本的数据库
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql:///shop
spring.datasource.username=root
spring.datasource.password=123456

pojo层

package com.hh.pojo;

import lombok.Data;

@Data
public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private String  Clazz;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", Clazz='" + Clazz + '\'' +
                '}';
    }
}

mapper层

package com.hh.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hh.pojo.Student;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface StudentMapper extends BaseMapper<Student> {
}

config层

package com.hh.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan(basePackages = "com.hh.mapper")
public class MybatisPlusConfig {
}

启动项

package com.hh;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AppApplication {

    public static void main(String[] args) {
        SpringApplication.run(AppApplication.class, args);
    }

}

测试

package com.hh.mapper;

import com.hh.pojo.Student;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.fasterxml.jackson.databind.type.LogicalType.Map;

@SpringBootTest
public class StudentMapperTest {
    @Autowired
    private StudentMapper studentMapper;
    @Test
    //新增学生
    public void addStudent(){
        Student Lily = new Student();
        Lily.setName("花无常");
        Lily.setAge(21);
        Lily.setClazz("2班");
        studentMapper.insert(Lily);
        System.out.println("=========================================");
        //我们发现,在我们没给id时,MyBatisPlus框架会帮我们自动生产一个id
    }
    //查询学生
    //通过List集合的方式查询学生
    @Test
   public void findStudent1(){
        ArrayList<Integer> idsList = new ArrayList<>();
         idsList.add(321585154);
         idsList.add(1641336830);
        List<Student> students = studentMapper.selectBatchIds(idsList);
        System.out.println(students);
        System.out.println("===============================================");
    }
    @Test
    //通过Map集合的方式查询学生
    public void findStudent2(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("id",321585154);
        List<Student> students = studentMapper.selectByMap(map);
        System.out.println(students);
        System.out.println("===============================================");
    }
    @Test
    //通过id查询学生
    public void findStudent3(){
        Student student = studentMapper.selectById(321585154);
        System.out.println(student);
    }
    //根据id删除学生
    @Test
    public void deleteStudent(){
        int i = studentMapper.deleteById(321585154);
        if (i>0){
            System.out.println("删除成功");
        }else {
            System.out.println("删除失败");
        }
    }
    @Test
    //此时我们发现,运用update的方法,传参是pojo类型的话,只修改需要修改的部分。如果pojo类型里面没有设置值,那么数据库里面对应的值不作修改
    public void updateStudent(){
        Student student = new Student();
        student.setId(1641336830);
        student.setClazz("1班");
        studentMapper.updateById(student);
        System.out.println("=============================");
    }
}

以上是Mybatis-Plus框架的基本用法

插件

插件主体

MybatisPlusInterceptor:

该插件是核心插件,目前代理了 Executor#queryExecutor#updateStatementHandler#prepare 方法

目前已有的功能:

  • 自动分页: PaginationInnerInterceptor
  • 多租户: TenantLineInnerInterceptor
  • 动态表名: DynamicTableNameInnerInterceptor
  • 乐观锁: OptimisticLockerInnerInterceptor
  • sql 性能规范: IllegalSQLInnerInterceptor
  • 防止全表更新与删除: BlockAttackInnerInterceptor

注意:

使用多个功能需要注意顺序关系,建议使用如下顺序

  • 多租户,动态表名
  • 分页,乐观锁
  • sql 性能规范,防止全表更新与删除

总结: 对 sql 进行单次改造的优先放入,不对 sql 进行改造的最后放入

乐观锁插件

什么是乐观锁

当要更新一条记录的时候,希望这条记录没有被别人更新

乐观锁的实现方式

  • 取出记录时,获取当前 version
  • 更新时,带上这个 version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果 version 不对,就更新失败

如何配置乐观锁(OptimisticLockerInnerInterceptor)

第一步配置插件

第一种:

spring.XML方式:

<bean class="com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor" id="optimisticLockerInnerInterceptor"/>

<bean id="mybatisPlusInterceptor" class="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor">
    <property name="interceptors">
        <list>
            <ref bean="optimisticLockerInnerInterceptor"/>
        </list>
    </property>
</bean>

springboot注解方式:

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}

第二步:在实体类的字段上加上@Version注解(该实体类要有version字段)

@Version
private Integer version;

分页插件(PaginationInnerInterceptor)

支持的数据库

  • mysql,oracle,db2,h2,hsql,sqlite,postgresql,sqlserver,Phoenix,Gauss ,clickhouse,Sybase,OceanBase,Firebird,cubrid,goldilocks,csiidb,informix,TDengine,redshift
  • 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库,优炫数据库,星瑞格数据库

属性介绍

属性名类型默认值描述
overflowbooleanfalse溢出总页数后是否进行处理(默认不处理)
maxLimitLong单页分页条数限制(默认无限制)
dbTypeDbType数据库类型(根据类型获取应使用的分页方言)
dialectIDialect方言实现类

建议单一数据库类型的均设置 dbType

如何配置分页插件

springBoot注解方式:

//注册拦截器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//    创建MybatisPlus核心插件(拦截器)
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    //添加分页插件(拦截器)
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
    return interceptor;
}

通过分页插件可以获取以下数据:

 @Test
    void findpageStudent(){
        IPage page=new Page(1,1);
        page = studentService.page(page);
        System.out.println("获取总页数"+page.getPages());
        System.out.println("获取数据信息"+page.getRecords());
        System.out.println("获取分页大小"+page.getSize());
        System.out.println("获取当前页数:"+page.getCurrent());
        System.out.println("获取总条数:"+page.getTotal());
    }