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 进行传输
- wrapper 很重
- 传输 wrapper 可以类比为你的 controller 用 map 接收值(开发一时爽,维护火葬场)
- 正确的 RPC 调用姿势是写一个 DTO 进行传输,被调用方再根据 DTO 执行相应的操作
- 我们拒绝接受任何关于 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:标识实体类对应的表
| 属性 | 类型 | 必须指定 | 默认值 | 描述 |
|---|---|---|---|---|
| value | String | 否 | 表名 | |
| schema | String | 否 | shcema 属性用来指定模式名称。如果你使用的是 mysql 数据库,则指定数据库名称。如果你使用的是 oracle,则为 schema,例如:schema="scott",其中:scott 就是 oracle 中的 schema。 | |
| keepGlobalPrefix | boolean | 否 | false | 是否保持使用全局的 tablePrefix 的值(当全局 tablePrefix 生效时) |
| resultMap | String | 否 | xml 中 resultMap 的 id(用于满足特定类型的实体类对象绑定) | |
| autoResultMap | boolean | 否 | false | 是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建与注入) |
| excludeProperty | tring[] | 否 | {} | 需要排除的属性名 @since 3.3.1 |
@TableId:标识实体类主键字段
| 属性 | 类型 | 必须指定 | 默认值 | 描述 |
|---|---|---|---|---|
| value | String | 否 | 主键字段名 | |
| type | Enum | 否 | IdType.NONE | 指定主键类型 |
关于IdType的属性解释:
| 值 | 描述 |
|---|---|
| AUTO | 数据库 ID 自增 |
| NONE | 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) |
| INPUT | insert 前自行 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:标识在字段上,且是非主键
| 属性 | 类型 | 必须指定 | 默认值 | 描述 |
|---|---|---|---|---|
| value | String | 否 | 数据库字段名 | |
| exist | boolean | 否 | true | 是否为数据库表字段 |
| condition | String | 否 | 字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的 | |
| update | String | 否 | 字段 update set 部分注入,例如:当在version字段上注解update="%s+1" 表示更新时会 set version=version+1 (该属性优先级高于 el 属性) | |
| insertStrategy | Enum | 否 | FieldStrategy.DEFAULT | 举例:NOT_NULL insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>) |
| updateStrategy | Enum | 否 | FieldStrategy.DEFAULT | 举例:IGNORED update table_a set column=#{columnProperty} |
| whereStrategy | Enum | 否 | FieldStrategy.DEFAULT | 举例:NOT_EMPTYwhere <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if> |
| fill | Enum | 否 | FieldFill.DEFAULT | 字段自动填充策略 |
| select | boolean | 否 | true | 是否进行 select 查询 |
| keepGlobalFormat | boolean | 否 | false | 是否保持使用全局的 format 进行处理 |
| jdbcType | JdbcType | 否 | JdbcType.UNDEFINED | JDBC 类型 (该默认值不代表会按照该值生效) |
| typeHandler | Class<? extends TypeHandler> | 否 | UnknownTypeHandler.class | 类型处理器 (该默认值不代表会按照该值生效) |
| numericScale | String | 否 | 指定小数点后保留的位数 |
关于jdbcType和typeHandler以及numericScale的说明:
numericScale只生效于 update 的 sql. jdbcType和typeHandler如果不配合@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:表字段逻辑处理注解(逻辑删除)
| 属性 | 类型 | 必须指定 | 默认值 | 描述 |
|---|---|---|---|---|
| value | String | 否 | 逻辑未删除值 | |
| delval | String | 否 | 逻辑删除值 |
@OrderBy:内置 SQL 默认指定排序,优先级低于 wrapper 条件查询
| 属性 | 类型 | 必须指定 | 默认值 | 描述 |
|---|---|---|---|---|
| isDesc | boolean | 否 | true | 是否倒序查询 |
| sort | short | 否 | Short.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#query 和 Executor#update 和 StatementHandler#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
- 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库,优炫数据库,星瑞格数据库
属性介绍
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| overflow | boolean | false | 溢出总页数后是否进行处理(默认不处理) |
| maxLimit | Long | 单页分页条数限制(默认无限制) | |
| dbType | DbType | 数据库类型(根据类型获取应使用的分页方言) | |
| dialect | IDialect | 方言实现类 |
建议单一数据库类型的均设置 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());
}