Mybatis-Plus是一个以Mybatis为基础,目的是为了开发者更简单的使用Mybatis,简化开发,提高效率。意愿是成为Mybatis最好的搭档,就像魂斗罗中的1P、2P

mybatisplus generator版本和spring版本 spring mybatis plus_字段


特点(节选自官网)

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 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 操作智能分析阻断,也可自定义拦截规则,预防误操作

第一个demo
首先我们需要在MySql(也可以是其他)中创建一张user测试表,表结构如下

id

name

age

gender

1

Lil Lei

20

1

2

Han Meimei

18

0

3

Jim

20

1

4

LinTao

21

1

5

Lucy

18

0

6

Lily

18

0

DDL 如下

CREATE TABLE `user` (
  `id` bigint NOT NULL COMMENT '主键',
  `name` varchar(32) DEFAULT NULL COMMENT '姓名',
  `age` int DEFAULT NULL COMMENT '年龄',
  `gender` int DEFAULT NULL COMMENT '性别 0-女; 1-男',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

接下来需要创建springboot项目,引入以下相关maven依赖

<!-- web模块 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<!-- Mybatis-Plus starter -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.3.2</version>
</dependency>
<!-- guava-->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>29.0-jre</version>
</dependency>
<!-- springboot test -->
<dependency>
	<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
    </exclusions>
</dependency>

在entity包中创建entity

@Data
@ToString
public class User {
    private Long id;
    private String name;
    private Byte age;
    private Byte gender;
}

在mapper包中创建mapper,需要继承Mybatis-Plus的BaseMapper

@Repository
public interface IUserMapper extends BaseMapper<User> {
}

在SpringBoot启动类上标注mapper扫描路径

@SpringBootApplication
@MapperScan("com.beemo.mybatisplus.mapper")
public class MybatisplusApplication {

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

编写测试方法

@SpringBootTest
class MybatisplusApplicationTests {

    @Autowired
    private IUserMapper userMapper;

    @Test
    void testDemo() {
        List<User> users = Optional.ofNullable(userMapper.selectList(null)).orElse(Lists.newArrayList());
        users.stream().forEach(System.out::println);
    }
}

控制台输出

User(id=1, name=Li Lei, age=20, gender=1)
User(id=2, name=Han Meimei, age=18, gender=0)
User(id=3, name=Jim, age=20, gender=1)
User(id=4, name=Lin Tao, age=21, gender=1)
User(id=5, name=Lucy, age=18, gender=0)
User(id=6, name=Lily, age=18, gender=0)

我们的第一个demo就成功了

打印SQL信息

刚才的例子中,我们并不能观察到SQL信息,虽然查询结果符合预期,但是无法确认SQL是否是正确,使得我们调试非常不方便,此时可以添加以下配置,来显示SQL信息

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

在运行我们上述的demo,在控制台就可以看到执行的SQL信息了

mybatisplus generator版本和spring版本 spring mybatis plus_mybatis_02

常用注解

  • @TableName

该注解表示一个实体类对应的表名,如表名与实体名相同(或满足驼峰式命名规则对应)
举例,表名叫sys_admin,实体类叫Admin

@TableName("sys_admin")
public class Admin {
    // ...
}
  • @TableId
    标注该属性为主键,其中value值指定数据库的主键列名,type指定主键生成策略,其中有


描述

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方法)

ID_WORKER

同ASSIGN_ID

UUID

同ASSIGN_UUID

ID_WORKER_STR

同ASSIGN_ID

具体的类型还需根据程序来选择,如为微服务等,选择ASSIGN_ID;如为单机,那么选择自增就可以
举例:

@TableId(value = "u_id", type = IdType.ASSIGN_ID)
private Long id;
  • @TableField

该字段同@TableId,但是为映射非主键字段,同时属性也比较丰富
value:指定数据库的列名
exists:标识该字段是否为数据库表列
fill:自动填充策略,见后续说明
select:是否为查询项。有时候一些信息我们不想每次都进行查询,比如创建时间,创建用户等,此时我们可以标注select为false

举例:
将数据库添加新列

ALTER TABLE `user` ADD COLUMN create_time DATETIME DEFAULT NULL COMMENT '创建时间';

更新创建时间

UPDATE `user` SET create_time = now();

在实体类中添加新字段

// 表示该字段不是数据库表字段
@TableField(exist = false)
private String genderName;
// 表示查询时不查询此字段
@TableField(select = false)
private LocalDateTime createTime;

修改测试方法

@Test
void testDemo() {
     List<User> users = Optional.ofNullable(userMapper.selectList(null)).orElse(Lists.newArrayList());
     users.stream().forEach(u -> {
         u.setGenderName(convertGenderName(u.getGender()));
         System.out.println(u);
     });
 }

 private String convertGenderName(Byte gender) {
     gender = Optional.ofNullable(gender).orElse((byte)-1);
     switch (gender) {
         case 0: return "女";
         case 1: return "男";
         default: return "未知";
     }
 }

在运行方法,观察控制台

mybatisplus generator版本和spring版本 spring mybatis plus_java_03


mybatisplus generator版本和spring版本 spring mybatis plus_java_04


可以看出,createTime字段为数据库对应列名,但是查询的列中并不包含该列,证明我们的设置select = false生效;genderName不为数据库字段,程序没有报错,证明我们的设置exist = false生效

枚举映射

上述例子中,我们需要手动去转换性别,比较麻烦,Mybatis-Plus支持枚举的映射
首先定义枚举类

public enum Gender {

    MALE(1, "男"),
    FEMALE(0, "女");

    Gender(int value, String name) {
        this.value = value;
        this.name = name;
    }

    @EnumValue
    private int value;
    private String name;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public java.lang.String getName() {
        return name;
    }

    public void setName(java.lang.String name) {
        this.name = name;
    }
}

要记得与数据库对应的字段需要使用@EnumValue标注
然后将实体类的性别字段,声明为枚举类型

private Gender gender;

之后我们再进行查询

mybatisplus generator版本和spring版本 spring mybatis plus_java_05


可以看到,查询时Mybatis-Plus已经帮我们做了自动映射,我们再试一下增加和修改操作是否也会自动映射

首先修改实体类,增加主键自增方式以及构造方法

修改测试类,增加方法

@Test
void insert() {
    User user = new User();
    user.setName("Poly");
    user.setAge((byte)2);
    user.setGender(Gender.MALE);
    user.setCreateTime(LocalDateTime.now());
    userMapper.insert(user);
}

@Test
void update() {
    User user = new User();
    user.setId(1L);
    user.setGender(Gender.FEMALE);
    userMapper.updateById(user);
}

结果如下

mybatisplus generator版本和spring版本 spring mybatis plus_字段_06


mybatisplus generator版本和spring版本 spring mybatis plus_mybatis_07


验证新增和修改也能枚举映射

自动填充

有时候我们不想去在每次增加和修改的时候,去设置字段,比如创建时间,编辑时间,创建人,编辑人等,此时我们可以使用Mybatis-Plus的自动填充
在数据库增加字段update_time, create_user, update_user

ALTER TABLE `user`
ADD COLUMN create_user varchar(32) COMMENT '创建用户',
ADD COLUMN update_time datetime COMMENT '更新时间',
ADD COLUMN update_user varchar(32) COMMENT '更新用户';

在需要自动填充的字段上标注@Table(fill=xx)
其中可选值为

  • FieldFill.INSERT:新增时自动填充
  • FieldFill.UPDATE:修改时自动填充
  • FieldFill.INSERT_UPDATE:新增和修改时自动填充
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;    

@TableField(fill = FieldFill.INSERT)
private String createUser;    

@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;

@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateUser;

配置自动填充策略

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        // String user = xxUtil.getUser();
        String user = "admin";
        strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        strictInsertFill(metaObject, "createUser", String.class, user);
        strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        strictInsertFill(metaObject, "updateUser", String.class, user);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        String user = "admin2";
        strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        strictInsertFill(metaObject, "updateUser", String.class, user);
    }
}

其中insertFill方法配置的是新增时需要填充的字段,updateFill方法配置的是修改时需要填充的字段

将测试创建方法的createTime赋值去掉,重新执行创建方法以及修改方法

mybatisplus generator版本和spring版本 spring mybatis plus_java_08

逻辑删除

有时候我们在删除时并不想真的将数据从数据库中删除掉,而是给一个标记,标注这条记录是被删除的。比如用户注销等,我们还需要保留用户的信息。在查询的时候,我们只需要查询未被删除的记录即可
首先需要在数据库增加删除标记

ALTER TABLE `user`
ADD COLUMN flag SMALLINT DEFAULT 0 COMMENT '删除标记 0-未删除; 1-已删除';

然后我们在实体类增加该字段,并且使用@TableLogic进行标注

@TableLogic
private Byte flag;

进行逻辑删除配置

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: flag  #全局逻辑删除字段值
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

创建删除测试类

@Test
void remove() {
    userMapper.deleteById(1L);
}

运行方法,查看控制台

mybatisplus generator版本和spring版本 spring mybatis plus_java_09


可以发现这里并不是删除,而是标记为已删除状态,实际是修改操作

我们再执行查询方法,看控制台输出

mybatisplus generator版本和spring版本 spring mybatis plus_数据库_10


可以看到在查询的时候,自动拼接了flag=0字段,即只查询未删除的

小结:

  • 标注了@TableLogic字段,那么删除时会进行修改操作而不是删除操作
  • 标注了@TableLogic字段,在查询时会自动拼接未删除条件
  • 如果公司代码比较规范,比如统一了全局都是flag为逻辑删除字段,那么在我们配置的yaml中进行flag为字段的配置,即上述的logic-delete-field: flag,那么就不需要标注@TableLogic。但是如果字段标记为@TableLogic,那么即使不是flag字段,也将作为逻辑删除字段,即@TableLogic优先级高于全局配置

CRUD

wrapper

在接触CRUD之前,我们先认识一下wrapper
wrapper是条件构造器,当我们进行查询、更新、删除操作时,会用到它
wrapper是一个抽象类,常用的子类有QueryWrapperUpdateWrapperLambdaQueryWrapperLambdaUpdateWrapper 使用方法:

  • 普通wrapper
/ 创建查询wrapper,并添加查询条件
User user = new User();
user.setName("Lucy");
Wrapper<User> wrapper1 = new QueryWrapper<>(user);
Wrapper<User> wrapper2 = Wrappers.query(user);

给wrapper传递的user对象,如果某个属性不为空,那么就会作为查询条件,上述例子中查询条件即为name=“lucy”
除了根据实体传递查询条件,我们还可以使用QueryWrapper中的方法来手动赋值查询条件

// 创建查询wrapper
QueryWrapper<User> wrapper1 = new QueryWrapper<>();
QueryWrapper<User> wrapper2 = Wrappers.query();
wrapper1.eq("name", "Lucy");
wrapper1.ge("age", 15);
wrapper1.orderByAsc("age");

其中eq,ge等常用方法含义如下

方法

描述

方法

描述

eq

相等

ge

大于

le

小于

in

在…之中

like

模糊查询

order by

排序

select

查询指定列

其中select可以指定要查询的列,例如

QueryWrapper<User> wrapper2 = Wrappers.query();
wrapper2.select("name", "age");
  • lambdaWrapper
    lambdaWrapper同普通Wrapper,只不过可以使用lambda
LambdaQueryWrapper<User> wrapper1 = Wrappers.lambdaQuery();
wrapper1.eq(User::getName, "Lily");
mapper的CURD

我们需要在我们的mapper中继承BaseMapper,然后就可以使用封装好的方法了

@Repository
public interface IUserMapper extends BaseMapper<User> {
}
  • 新增
User user = new User();
userMapper.insert(user);

调用该方法会向数据库保存一条T对象对应的数据库记录

  • 查询
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
// 查询所有
userMapper.selectList(null);
// 带有条件查询列表
userMapper.selectList(wrapper.le(User::getAge, 18).eq(User::getGender, Gender.FEMALE));
// 查询单条记录,如果结果返回多条,则会抛出异常TooManyResultsException
userMapper.selectOne(wrapper.eq(User::getName, "Lin Tao"));
// 查询数量
userMapper.selectCount(wrapper.eq(User::getName, "Lin Tao"));
// 根据主键查询
userMapper.selectById(1);
// 根据主键集合查询列表
userMapper.selectBatchIds(Arrays.asList(1, 2, 3, 4));
  • 修改
// 根据主键修改,修改ID为1的记录,赋值age为19
User user = new User();
user.setId(1L);
user.setAge((byte)19);
userMapper.updateById(user);

LambdaUpdateWrapper<User> wrapper = Wrappers.lambdaUpdate();
// 此种写法会修改age为21,但是不会触发自动填充updateTime以及updateUser
// userMapper.update(null, wrapper.eq(User::getId, 1).set(User::getAge, 21));
userMapper.update(new User(), wrapper.eq(User::getId, 1).set(User::getAge, 21));
  • 删除
// 如果有@TableLogic或者全局配置逻辑删除,则会改为update方法
// 根据主键删除,逻辑删除时,不会触发自动填充
userMapper.deleteById(1);
// 根据主键集合删除,逻辑删除时,不会触发自动填充
userMapper.deleteBatchIds(Arrays.asList(1, 2, 3, 4));
// 根据wrapper删除,逻辑删除时,不会触发自动填充
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
userMapper.delete(wrapper.eq(User::getName, "Jim"));
// 根据map删除,逻辑删除时,不会触发自动填充
Map<String, Object> params = Maps.newHashMap();
params.put("name", "Han meimei");
userMapper.deleteByMap(params);
service中的CURD

Mybatis-Plus在service中又为我们封装了一层CRUD
首先我们需要创建service以及实现类,并继承相应的接口/类IServiceServiceImpl

public interface IUserService extends IService<User> {
}
@Service
@AllArgsConstructor
public class UserServiceImpl extends ServiceImpl<IUserMapper, User> implements IUserService {
}

在测试类注入service

@Autowired
private IUserService userService;
  • 增加
User user = new User();
// 保存单条
// 这里赋值ID不会生效
user.setId(1L);
userService.save(user);
// 批量保存
userService.saveBatch(Lists.newArrayList(user));
// 批量保存,一次insert插入n条
userService.saveBatch(Lists.newArrayList(new User(), new User(), new User(), new User()), 2);
// 如何设置id,则为修改,会首先查询一次;如果不设置id,则为新增
// user.setId(1L);
userService.saveOrUpdate(user);
  • 查询
// 查询所有
userService.list();
// 根据wrapper条件查询
userService.list(Wrappers.<User>lambdaQuery().eq(User::getName, "Li Lei"));
// 根据主键查询
userService.listByIds(Arrays.asList(1, 2, 3, 4));
  • 修改
// 根据主键修改
User user = new User();
user.setId(1L);
user.setAge((byte)17);
userService.updateById(user);
// 根据wrapper修改,不会触发自动填充
userService.update(Wrappers.<User>lambdaUpdate()
       .eq(User::getId, 1).set(User::getAge, 16));
// 根据wrapper修改,user中的设置无效,触发自动填充
userService.update(user, Wrappers.<User>lambdaUpdate().set(User::getAge, 15));
  • 删除
// 根据wrapper删除,如果为逻辑删除,则会update,不会触发自动填充
userMapper.delete(Wrappers.<User>lambdaQuery().eq(User::getId, 1));
// 根据主键删除,如果为逻辑删除,则会update,不会触发自动填充
userMapper.deleteById(1);
// 根据主键删除,如果为逻辑删除,则会update,不会触发自动填充
userMapper.deleteBatchIds(Arrays.asList(1, 2, 3, 4));
// 根据map删除,如果为逻辑删除,则会update,不会触发自动填充
Map<String, Object> params = Maps.newHashMap();
params.put("id", 1);
userMapper.deleteByMap(params);

分页

我们平时开发中使用最多的就是分页了,MyBatis-Plus为我们实现了分页组件,即在SQL中拼装LIMIT 使用分页首先必须要进行配置

@Component
public class MyConfig {
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
}

之后我们就可以使用mapper或service中的分页API了
分页API中会使用到IPage接口,我们会使用到他的实现类Page

// 常用构造方法
new Page();	// 默认一页显示10条,查询第一页
new Page(2, 20); // 查询第二页,一页显示20条

如需要不分页(比如导出EXCEL查询list需要调用分页查询),可以有以下两种方法

new Page<>(1, -1); // 不会进行LIMIT拼接
new Page<>(1, Long.MAX_VALUE); // 会拼接LIMIT,只不过LIMIT一个Long的最大值

mapper

IPage page = new Page<>(1, 20);
userMapper.selectPage(page, null);

mybatisplus generator版本和spring版本 spring mybatis plus_数据库_11


service

IPage page = new Page<>(1, 20);
userService.page(page);
userService.page(page, Wrappers.<User>lambdaQuery().le(User::getAge, 18));