简介

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

特点

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑

  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求

  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错

  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题

  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询

    …………

基本使用步骤

(1)导入起始依赖

1
2
3
4
5
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>

(2)在Mapper接口上加上@Mapper注释,并继承自BaseMapper接口

(3)创建pojo类,并加上**@TableName**注解,告诉MP应该查询的表名。若pojo类名和表名一样则无需填写。

(4)现在就已经可以调用BaseMapper里的方法了。

分页查询

(1)调用继承来的SelectPage()方法,传入IPage对象

(2)设置分页查询的拦截器

1
2
3
4
5
6
7
8
9
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor =new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}

(3)查询结束后分页信息就封装在IPage对象中。

1
2
3
4
5
6
7
8
9
10
@Test
public void getByPageTest(){
IPage<Book> page = new Page<>(1,10);
bookMapper.selectPage(page,null);
System.out.println("当前页:"+page.getCurrent());
System.out.println("总页数:"+page.getPages());
System.out.println("每页数据数:"+page.getSize());
System.out.println("总数据数:"+page.getTotal());
System.out.println("查询结果"+page.getRecords());
}

条件查询

基本操作

方案一:在查询的方法中传入QueryWrapper对象,该对象用于条件查询。

  • 并通过lt(<=)、le**(<)、**gt**(>=)、ge(>)、eq(==)、like(like)、between**,方法设置判断条件

多条件的 或 运算符可以在两个条件中加入or()方法来实现

方法入参为 字段名、比较值

eg:

1
2
3
4
5
QueryWrapper<Book> wrapper = new QueryWrapper<>();
wrapper.lt("price",8)
.or()
.ge("price",100);
List<Book> books = bookMapper.selectList(wrapper);

ps:这种方式字段名通过字符串的形式输入,不利于debug

方案二:通过lambda表达式引用实体类的Getter方法来定位字段名,

首先把传入对象换为LambdaQueryWrapper(或者调用QueryWrapper的lambda方法)

之后通过 ClassName::MethodName 来引用Getter方法,从而定位指定字段

eg:

1
2
3
4
5
LambdaQueryWrapper<Book> wrapper = new LambdaQueryWrapper<>();
wrapper.lt(Book::getPrice,8)
.or()
.ge(Book::getPrice,100);
List<Book> books = bookMapper.selectList(wrapper2);

null值处理

  1. 用If进行条件判断,太low了。

  2. le、ge、eq、select、groupBy等方法中的第一个入参可以接受一个Boolean值(condition),用来决定这个方法是否生效。

查询投影

  • 可以调用QueryWrapper#select()方法来规定查询的字段。入参同上,可以为字符串也可以为lambda表达式

    • 但是若查询的字段不是实体类中的属性(如count),则不能用lambda表达式,只能用字符串形式。
    • 若使用了聚合函数,那么查询使用的方法就应该为selectMaps。
  • 可以调用QueryWrapper#groupBy()方法来规定分组的字段,入参同上。

  • 可以调用QueryWrapper#orderByAsc/Desc()方法来规定排序的字段,入参同上。

eg:

1
2
3
4
5
6
7
8
public void testGetByCondition(){
QueryWrapper<Book> wrapper = new QueryWrapper<>();
wrapper.select("author,count(*) as count");
wrapper.lambda().groupBy(Book::getAuthor);
wrapper.orderByDesc("count");
List<Map<String, Object>> countList = bookMapper.selectMaps(wrapper);
System.out.println(countList);
}

映射问题

表名映射

实体类上,使用**@TableName**注解来规定查询的表名,不填则默认为实体类名。

字段映射

成员变量上使用**@TableField**注解

  • 注解中的value属性来映射字段名
  • 注解中的exist属性用来表示表中是否有此字段
  • 注解中的select属性用来设置该字段是否参与查询(敏感数据,比如密码)

主键生成策略

  1. 在主键的成员变量上,使用**@TableId**注解。并在type属性上传入一个 IdType枚举类中常量。

  2. 在yml中设置 mybatis.global-config.dbconfig.id-type 属性,传入这些常量

  • IdType.AUTO:使用数据库自带的生成策略

  • IdType.NONE:不设置id自动生成策略

  • IdType.INPUT:使用用户手动输入的主键

  • IdType.ASSIGN_ID:用雪花算法生成id(可以兼容数值型Long和字符串型)

    雪花算法:生成一个64位的二进制串,生成策略如下:

    占位符,固定为0 时间戳(41) 机器码+服务码(10) 序列号(12)
    0 10110000011100011001001110000011100011000 0000000101 000000000010
  • IdType.ASSIGN_UUID:(以UUID生成算法作为id生成策略)

逻辑删除

简介

  • 删除操作存在着业务问题:业务数据从数据库中直接丢失了

  • 逻辑删除:为数据设置是否可用状态字段:删除时设置状态字段为不可用状态,数据依旧保留在数据库中

在MybatisPlus中的实现方法:

数据库表中和实体类中加入 业务删除的状态字段

在实体类中对应逻辑删除的成员变量上设置@TableLogic注解

在注解中

  • 设置value属性代表未删除的状态值,

  • 设置delval代表已删除的状态值

eg:

1
2
@TableLogic(value = "0",delval = "1")
private Short deleted;
  • 当逻辑删除的需求较大时,可在yml中统一配置以下属性:

    1
    2
    3
    4
    5
    6
    mybatis-plus:
    global-config:
    db-config:
    logic-delete-field: deleted
    logic-delete-value: 1
    logic-not-delete-value: 0

实现原理

设置逻辑删除之后,将会把delete方法所映射的Sql语句改为

1
UPDATE table_name SET deleted=1 WHERE id = ? and deleted = 0

把select方法所映射的Sql语句改为

1
SELECT * FROM table_name WHERE deleted=1

乐观锁

简介

**悲观锁(Pessimistic Locking)**:系统认为如果不严格的同步线程调用,那么一定会产生异常。所以悲观锁会锁定资源,只让一个线程调用。而阻塞其他的线程。

乐观锁(Optimistic Locking):基于CAS操作的一种无锁同步机制。当线程需要修改共享资源的对象时总是会乐观的认为对象的状态值没有被其他对象修改过。

  • CAS:全称compare and swap,比较然后修改状态值。即先比较共享资源是否为可用状态,若为可用则先把状态值变成不可用之后再进行操作。

    若比较为为不可用则开始进行自旋,直到成功等到状态值为可用状态为止。一般也会设置最大自旋次数来防止死循环。

    该操作为原子性的

MybatisPlus中乐观锁的实现

  1. 数据库表中和实体类中 加入version字段

  2. 在实体类中对应的version成员变量上设置**@Version**注解

  3. 在MybatisPlus的设置类中加入乐观锁需要的拦截器 OptimisticLockerInnerInterceptor

  4. 执行修改操作时,先查询version值,再根据查询到的version值来进行修改。

eg:

1
2
3
4
5
6
7
8
9
10
public void testVersion(){
Book book1 = bookMapper.selectById(1);
Book book2 = bookMapper.selectById(1);

book1.setName("aaa");
book2.setName("bbb");

bookMapper.updateById(book1);
bookMapper.updateById(book2);
}

实现原理

  • 各个线程在修改数据之前需要先获得版本号。获得同样一个版本号的线程存在线程安全问题

  • 之后的update操作会被替换成:

    1
    UPDATE table_name SET version= VERSION +1,name= ? WHERE id= ? AND version= VERSION

    我们乐观地认为这个update语句是原子性的,即:

    • A操作:通过where语句判断 表中的version值和线程获得的version值相等。
    • B操作:修改version的值,修改业务数据。

    B操作进行时,version及业务数据还没有被别的线程修改过。

通过乐观锁,可以较为便捷的解决小型的并发问题

基于MybatisPlus快速开发业务层

简介

和BaseSql接口类似,MP发现Service层的基本开发也有很多重复的操作。于是推出了:

  • IService接口:封装了基本的业务层抽象方法
  • ServiceImpl<DAO,T>类:封装了基本的业务层方法的实现。需要传入Dao和实体类。

使用步骤

让BookService接口继承自**Iservice**接口

让BookServiceImpl实现类继承自ServiceImpl,并实现BookService接口

eg:

1
2
3
4
5
6
public interface BookService extends IService<Book> {
}

@Service
public class BookServiceImpl extends ServiceImpl<BookMapper, Book> implements BookService {
}

这样BookService就已经有基本的业务方法了。

注意事项

  • 继承自IService的方法有时不能完成我们的业务需求,届时需要自己再重新加入新的方法或重写方法
  • 学习阶段尽量自己写