MyBatisPlus

可对比:

官网:https://mp.baomidou.com/

MybatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提供效率。

特性

入门案例

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
username: root
password: root
@Mapper
public interface UserDao extends BaseMapper<User>{
}

标准数据层CRUD功能

image-20220818110251928

lombok

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!--<version>1.18.12</version>-->
</dependency>

新版本IDEA已经内置了该插件,如果删除setter和getter方法程序有报红,则需要安装插件

MP分页查询

设置分页拦截器作为Spring管理的bean

@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1 创建MybatisPlusInterceptor拦截器对象
MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
//2 添加分页拦截器
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}

执行分页查询

//1 创建IPage分页对象,设置分页参数,1为当前页码,3为每页显示的记录数
IPage<User> page=new Page<>(1,3);
//2 执行分页查询
userDao.selectPage(page,null);
//3 获取分页结果
System.out.println("当前页码值:"+page.getCurrent());
System.out.println("每页显示数:"+page.getSize());
System.out.println("一共多少页:"+page.getPages());
System.out.println("一共多少条数据:"+page.getTotal());
System.out.println("数据:"+page.getRecords());

开启日志

mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印SQL日志到控制台
global-config:
banner: off # 关闭mybatisplus启动图标

取消初始化spring日志打印,resources目录下添加logback.xml,名称固定,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
</configuration>

取消SpringBoot的log打印

spring:
main:
banner-mode: off # 关闭SpringBoot启动图标(banner)

条件查询

// 方式1:QueryWrapper
QueryWrapper qw = new QueryWrapper();
qw.lt("age",18);

// 方式2:QueryWrapper的基础上使用lambda
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.lambda().lt(User::getAge, 10);//添加条件

// 方式3:LambdaQueryWrapper
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge, 10);

List<User> userList = userDao.selectList(lqw);

多条件构建

LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
// AND
lqw.lt(User::getAge, 30).gt(User::getAge, 10);
// OR
lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
List<User> userList = userDao.selectList(lqw);

null值处理

LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(null!=uq.getAge2(),User::getAge, uq.getAge2())
.gt(null!=uq.getAge(),User::getAge, uq.getAge());
List<User> userList = userDao.selectList(lqw);

查询投影

查询指定字段

LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.select(User::getId,User::getName,User::getAge);
List<User> userList = userDao.selectList(lqw);

// 非lambda版

QueryWrapper<User> qw = new QueryWrapper<User>();
qw.select("id","name","age","tel");
List<User> userList = userDao.selectList(qw);

聚合查询

QueryWrapper<User> lqw = new QueryWrapper<User>();
//SELECT count(*) as count FROM user
lqw.select("count(*) as count");
lqw.select("max(age) as maxAge");
lqw.select("min(age) as minAge");
lqw.select("sum(age) as sumAge");
lqw.select("avg(age) as avgAge");
List<Map<String, Object>> userList = userDao.selectMaps(lqw);

分组查询

QueryWrapper<User> lqw = new QueryWrapper<User>();
lqw.select("count(*) as count,tel");
lqw.groupBy("tel");
List<Map<String, Object>> list = userDao.selectMaps(lqw);

查询条件

MP的查询条件有很多:

  • 范围匹配(> 、 = 、between)
  • 模糊匹配(like)
  • 空判定(null)
  • 包含性匹配(in)
  • 分组(group)
  • 排序(order)
  • ……

等值查询

LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.eq(User::getName, "Jerry").eq(User::getPassword, "jerry");
User loginUser = userDao.selectOne(lqw);

相当于:

SELECT id,name,password,age,tel FROM user WHERE (name = ? AND password = ?)

范围查询

LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.between(User::getAge, 10, 30);
List<User> userList = userDao.selectList(lqw);

相当于:

SELECT id,name,password,age,tel FROM user WHERE (age BETWEEN ? AND ?)
  • gt():大于(>)
  • ge():大于等于(>=)
  • lt():小于(<)
  • lte():小于等于(<=)
  • between():between ? and ?

模糊查询

LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.likeLeft(User::getName, "J");
List<User> userList = userDao.selectList(lqw);

相当于:

SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?)
  • like():前后加百分号,如 %J%
  • likeLeft():前面加百分号,如 %J
  • likeRight():后面加百分号,如 J%

排序查询

LambdaQueryWrapper<User> lwq = new LambdaQueryWrapper<>();
/**
* condition :条件,返回boolean,
当condition为true,进行排序,如果为false,则不排序
* isAsc:是否为升序,true为升序,false为降序
* columns:需要操作的列
*/
lwq.orderBy(true,false, User::getId);

image-20220818154424656

  • orderBy排序
    • condition:条件,true则添加排序,false则不添加排序
    • isAsc:是否为升序,true升序,false降序
    • columns:排序字段,可以有多个
  • orderByAsc/Desc(单个column):按照指定字段进行升序/降序
  • orderByAsc/Desc(多个column):按照多个字段进行升序/降序
  • orderByAsc/Desc
    • condition:条件,true添加排序,false不添加排序
    • 多个columns:按照多个字段进行排序

除此之外,还有isNull,isNotNull,in,notIn等等方法可供选择,具体参考官方文档的条件构造器

映射匹配兼容性

@Data
// 使用@TableName设置当前类与数据库表对应关系
@TableName("tbl_user")
public class User {
private Long id;
private String name;
// 查询时将pwd隐藏(使用@TableField映射关系)
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
// 使用@TableField排除字段
@TableField(exist=false)
private Integer online;
}

id生成策略控制

@TableId(type = IdType.AUTO)
private Long id;
  • AUTO: 使用数据库ID自增
  • NONE: 不设置id生成策略
  • INPUT: 用户手工输入id
    这种ID生成策略,需要将表的自增策略删除掉
  • ASSIGN_ID: 雪花算法生成id(可兼容数值型与字符串型),
    这种生成策略,不需要手动设置ID,如果手动设置ID,则会使用自己设置的值。
  • ASSIGN_UUID: 以UUID生成算法作为id生成策略,
    使用uuid需要注意的是,主键的类型不能是Long,而应该改成String类型
  • 其他的几个策略均已过时,都将被ASSIGN_ID和ASSIGN_UUID代替掉。

ID生成策略对比

介绍了这些主键ID的生成策略,我们以后该用哪个呢?

  • NONE: 不设置id生成策略,MP不自动生成,约等于INPUT,所以这两种方式都需要用户手动设置,但是手动设置第一个问题是容易出现相同的ID造成主键冲突,为了保证主键不冲突就需要做很多判定,实现起来比较复杂
  • AUTO:数据库ID自增,这种策略适合在数据库服务器只有1台的情况下使用,不可作为分布式ID使用
  • ASSIGN_UUID:可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是32位的字符串,长度过长占用空间而且还不能排序,查询性能也慢
  • ASSIGN_ID:可以在分布式的情况下使用,生成的是Long类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键
  • 综上所述,每一种主键策略都有自己的优缺点,根据自己项目业务的实际情况来选择使用才是最明智的选择。

简化配置

模型类主键策略设置:

mybatis-plus:
global-config:
db-config:
id-type: assign_id

数据库表与模型类的映射关系:

mybatis-plus:
global-config:
db-config:
table-prefix: tbl_

雪花算法

雪花算法(SnowFlake),是Twitter官方给出的算法实现 是用Scala写的。其生成的结果是一个64bit大小整数,它的结构如下图:

1631243987800

  1. 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。
  2. 41bit-时间戳,用来记录时间戳,毫秒级
  3. 10bit-工作机器id,用来记录工作机器id,其中高位5bit是数据中心ID其取值范围0-31,低位5bit是工作节点ID其取值范围0-31,两个组合起来最多可以容纳1024个节点
  4. 序列号占用12bit,每个节点每毫秒0开始不断累加,最多可以累加到4095,一共可以产生4096个ID

多记录操作

List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
//查询指定多条数据
List<Long> list = new ArrayList<>();
list.add(1L);
list.add(3L);
list.add(4L);
userDao.selectBatchIds(list);

// 根据主键删除多条记录
List<Long> list = Arrays.asList(new Long[]{2,3});
userDao.deleteBatchIds(ids);

逻辑删除

步骤1:修改数据库表添加deleted

步骤2:实体类添加属性

@TableLogic(value="0",delval="1")
// value为正常数据的值,delval为删除数据的值
private Integer deleted;

也可在yaml中配置:

mybatis-plus:
global-config:
db-config:
# 逻辑删除字段名
logic-delete-field: deleted
# 逻辑删除字面值:未删除为0
logic-not-delete-value: 0
# 逻辑删除字面值:删除为1
logic-delete-value: 1

逻辑删除的本质为:

逻辑删除的本质其实是修改操作。如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段。

底层执行的SQL语句为:

UPDATE tbl_user SET deleted=1 where id = ? AND deleted=0

乐观锁

步骤1:数据库中添加锁标记字段

image-20220819142340644

步骤2:实体类中添加对应的属性

@Version
private Integer version;

步骤3:添加乐观锁的拦截器

@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
//1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加乐观锁拦截器
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}

携带version字段更新

User user = new User();
user.setId(3L);
user.setName("Jock666");
user.setVersion(1);
userDao.updateById(user);

也可以根据主键查询出数据,这样自带version

User user = userDao.selectById(3L);
user.setName("Jock888");
userDao.updateById(user);

底层执行的SQL语句为:

UPDATE tbl_user SET name =?, version =? where id = ? AND version=?

MP官网乐观锁配置步骤

代码生成器

  • 模板: MyBatisPlus提供,可以自己提供,但是麻烦,不建议
  • 数据库相关配置:读取数据库获取表和字段信息
  • 开发者自定义配置:手工配置,比如ID生成策略
<!--代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>

<!--velocity模板引擎-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
public class CodeGenerator {
public static void main(String[] args) {
//1.获取代码生成器的对象
AutoGenerator autoGenerator = new AutoGenerator();

//设置数据库相关配置
DataSourceConfig dataSource = new DataSourceConfig();
dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("root");
autoGenerator.setDataSource(dataSource);

//设置全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java");
//设置代码生成位置
globalConfig.setOpen(false); //设置生成完毕后是否打开生成代码所在的目录
globalConfig.setAuthor("黑马程序员"); //设置作者
globalConfig.setFileOverride(true); //设置是否覆盖原始生成的文件
globalConfig.setMapperName("%sDao"); //设置数据层接口名,%s为占位符,指代模块名称
globalConfig.setIdType(IdType.ASSIGN_ID); //设置Id生成策略
autoGenerator.setGlobalConfig(globalConfig);

//设置包名相关配置
PackageConfig packageInfo = new PackageConfig();
packageInfo.setParent("com.aaa"); //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径
packageInfo.setEntity("domain"); //设置实体类包名
packageInfo.setMapper("dao"); //设置数据层包名
autoGenerator.setPackageInfo(packageInfo);

//策略设置
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setInclude("tbl_user"); //设置当前参与生成的表名,参数为可变参数
strategyConfig.setTablePrefix("tbl_"); //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名 例如: User = tbl_user - tbl_
strategyConfig.setRestControllerStyle(true); //设置是否启用Rest风格
strategyConfig.setVersionFieldName("version"); //设置乐观锁字段名
strategyConfig.setLogicDeleteFieldName("deleted"); //设置逻辑删除字段名
strategyConfig.setEntityLombokModel(true); //设置是否启用lombok
autoGenerator.setStrategy(strategyConfig);
//2.执行生成操作
autoGenerator.execute();
}
}

官方文档