MybatisPlus 学习笔记

1、简介

简介

MybatisPlus时Mybatis的增加工具,在Mybatis的基础上只做增强不做改变,为简化开发、提升效率而生

特性

  • 无侵入:只做增强不做改变
  • 损耗小:内置通用Mapper、通用Service,仅仅通过少量配置即可实现大部分CRUD操作,更有强大的条件构造器,满足各类使用需求
  • 支持Lambda调用:方便编写各类查询条件
  • 支持主键自动生成:4种策略,内含分布式唯一ID生成器
  • 支持ActiveRecord模式: 实体类只需继承Mocdel类即可进行强大的CRUD操作
  • 支持自定义全局通用操作:支持全局通用方法注入(write once,use anywhere)
  • 内置代码生成器:采用代码或者Maven插件可快速生成Mapper、Model、Servcice、Controller代码
  • 内置分页插件:基于Mybatis物理分页,配置好插件后,写分页等同于list查询
  • 分页插件支持多种数据库
  • 内置性能分析插件:可输出SQL语句即执行时间,建议开发测试时启用
  • 内置全局拦截插件:提供全表delete、update操作智能分析阻断

框架结构

在这里插入图片描述

快速使用

数据准备

我们将通过一个简单的 Demo 来阐述 MyBatis-Plus 的强大功能,在此之前,我们假设您已经:

  • 拥有 Java 开发环境以及相应 IDE
  • 熟悉 Spring Boot
  • 熟悉 Maven
CREATE DATABASE `test_mpdb`;

USE test_mpdb;

DROP TABLE IF EXISTS user;

CREATE TABLE user
(
    id BIGINT(20) NOT NULL COMMENT '主键ID',
    name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    age INT(11) NULL DEFAULT NULL COMMENT '年龄',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    PRIMARY KEY (id)
);

DELETE FROM user;

INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

初始化工程

使用 Spring Initializer (opens new window)快速初始化一个 Spring Boot 工程

添加依赖

<!-- mybatis-plus启动器 -->
 <dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>mybatis-plus-boot-starter</artifactId>
     <version>3.4.2</version>
 </dependency>

 <!-- mysql 依赖 -->
 <dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
 </dependency>
 
 <!-- lombok 依赖 -->
  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
  </dependency>

配置spring的datasouce信息

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://121.4.97.105:53306/test_mpdb?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT
    username: root
    password: root

根据MybatisX插件生成基本代码
在这里插入图片描述
在这里插入图片描述
注意:点击下一步有时候插件可能会有bug,不要担心,点击上一步,然后再点一次下一步就ok了。
在这里插入图片描述

在这里插入图片描述

配置Mapper扫描
在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹:

package com.example.mptest;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.example.mptest.mapper")
public class MptestApplication {

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

}

** 测试 **
在这里插入图片描述
如果出现这种,可以继续操作,这是识别的问题。或者在Mapper上加上@Repository注解
在这里插入图片描述

package com.example.mptest;

import com.example.mptest.domain.User;
import com.example.mptest.mapper.UserMapper;

import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
class MptestApplicationTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    void contextLoads() {

        List<User> list = userMapper.selectList(null);
        Assert.assertEquals(5, list.size());
        list.forEach(System.out::println);
    }

}

小结

通过以上几个简单的步骤,我们就实现了 User 表的 CRUD 功能,甚至连 XML 文件都不用编写!

从以上步骤中,我们可以看到集成MyBatis-Plus非常的简单,只需要引入 starter 工程,并配置 mapper 扫描路径即可。

但 MyBatis-Plus 的强大远不止这些功能,想要详细了解 MyBatis-Plus 的强大功能?那就继续往下看吧!

MybatisPlus 常用注解

写在前面的:以下都只是一下最简单的使用,这些注解中的其他属性请参考官方文档
https://baomidou.com/pages/223848

@TableName

  • 描述:表名注解,标识实体类对应的表
  • 使用位置:实体类

如:

@TableName("sys_user")
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

在这里插入图片描述

@TableId

  • 描述:字段注解(非主键)
  • 使用位置:实体类主键字段
@TableName("sys_user")
public class User {
    @TableId
    private Long id;
    private String name;
    private Integer age;
    private String email;
}


在这里插入图片描述

IdType

在这里插入图片描述

TableField

  • 描述:字段注解(非主键)
@TableName("sys_user")
public class User {
    @TableId
    private Long id;
    @TableField("nickname")
    private String name;
    private Integer age;
    private String email;
}


在这里插入图片描述

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

FieldStrategy

在这里插入图片描述

FieldFill

在这里插入图片描述

@Version

  • 描述:乐观锁注解、标记 @Verison 在字段上
  • 使用:一般配合乐观锁插件 OptimisticLockerInnerInterceptor 使用

数据准备

create table t_product
(
	t_id bigint not null,
	t_desc varchar(255) null,
	price bigint default 0 not null,
	version int default 0 not null,
	constraint t_product_pk
		primary key (t_id)
)
comment '商品';


在实体类使用

使用mybatisX插件,生成相应的Service、Mapper、Domain的代码
在这里插入图片描述

在实体类相应字段加@Version注解

@TableName(value ="t_product")
@Data
public class Product implements Serializable {

    @TableId("t_id")
    private Long id;

    @TableField("t_desc")
    private String description;

    private Long price;
    private Integer version;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}

在Mybatis配置类加乐观锁拦截器

@Configuration
public class MybatisConfig {


    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //添加乐观锁
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

测试

@SpringBootTest
class OtimisticLockerInterceptorTests {

    @Autowired
    private ProductMapper productMapper;


    @Test
     void test0(){
        //新增
//        Product product = new Product();
//        product.setDesc("xiao mi phone");
//        product.setPrice(200L);
//        productMapper.insert(product);

        //模拟两个消费者同时操作一条数据
        // consumer1
        Product p1 = productMapper.selectList(null).get(0);
        // consumer 2
        Product p2 = productMapper.selectById(p1.getId());

        // price + 100
        p1.setPrice(p1.getPrice() + 100);
        productMapper.updateById(p1);
        //price - 50
        p2.setPrice(p2.getPrice() - 50);
        productMapper.updateById(p2);

        //consumer 3
        Product p3 = productMapper.selectById(p1.getId());
        // price = ? is 250 ? or 150 or 300 ?
        System.out.println(p3.getPrice()); // 300

    }
}

sql 语句如下:

==>  Preparing: SELECT t_id AS id,t_desc AS description,price,version FROM t_product WHERE t_id=?
==> Parameters: 1505798219102187522(Long)
<==    Columns: id, description, price, version
<==        Row: 1505798219102187522, xiao mi phone, 200, 0
<==      Total: 1

==>  Preparing: UPDATE t_product SET t_desc=?, price=?, version=? WHERE t_id=? AND version=?
==> Parameters: xiao mi phone(String), 300(Long), 1(Integer), 1505798219102187522(Long), 0(Integer)
<==    Updates: 1

==>  Preparing: UPDATE t_product SET t_desc=?, price=?, version=? WHERE t_id=? AND version=?
==> Parameters: xiao mi phone(String), 150(Long), 1(Integer), 1505798219102187522(Long), 0(Integer)
<==    Updates: 0

==>  Preparing: SELECT t_id AS id,t_desc AS description,price,version FROM t_product WHERE t_id=?
==> Parameters: 1505798219102187522(Long)
<==    Columns: id, description, price, version
<==        Row: 1505798219102187522, xiao mi phone, 300, 1
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@43bdaa1b]
300

小结

乐观锁配置好后,可以发现每次修改都会在条件后面加上version=?,当修改时,version条件不满足,则不会修改。由此解决了冲突。

@EnumValue

  • 描述: 普通枚举类注解(注解在枚举字段上)
  • 使用: 通常需要配合mybatisplus的 type-enums-package(扫描通用枚举类)配置

数据准备

给user新增sex字段
在这里插入图片描述

编写枚举类

@Getter
public enum SexEnum {

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

    @EnumValue  # 指明枚举类的值是sex字段
    private Integer sex;

    private String desc;

    SexEnum(Integer sex, String desc) {
        this.sex = sex;
        this.desc = desc;
    }
}

在实体中使用

@TableName(value ="user")
@Data
public class User implements Serializable {
    /**
     * 主键ID
     */
    @TableId
    private Long id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 邮箱
     */
    private String email;

    private SexEnum sex;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}

配置枚举类包扫描

mybatis-plus:
  type-enums-package: com.example.mptest.enums

测试

@SpringBootTest
class MptestApplicationTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    void contextLoads() {
        User user = new User();
        user.setName("zhangsan");
        user.setEmail("zhangsan@163.com");
        user.setAge(15);
        user.setSex(SexEnum.MALE);

        int row = userMapper.insert(user);
        System.out.println(row);

        System.out.println(user);
    }

}

// 输出
1
User(id=1505776731460476929, name=zhangsan, age=15, email=zhangsan@163.com, sex=MALE)

@TableLogic

  • 描述:表字段逻辑处理注解(逻辑删除)

在这里插入图片描述

数据准备

alter table user
	add is_delete int(2) default 0 not null;

@TableName(value ="user")
@Data
public class User implements Serializable {
    /**
     * 主键ID
     */
    @TableId
    private Long id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 邮箱
     */
    private String email;

    private SexEnum sex;
    
    @TableLogic(value = "0", delval = "1")
    private Integer is_delete;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}

测试

删除前
在这里插入图片描述
删除

   @Test
    void contextLoads() {

        int row = userMapper.deleteById(1L);

        System.out.println(row);
    }

// SQL 如下:
==>  Preparing: UPDATE user SET is_delete=1 WHERE id=? AND is_delete=0
==> Parameters: 1(Long)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@10358c32]
1

删除后
在这里插入图片描述

@OrderBy

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

在这里插入图片描述

Mybatis核心功能

代码生成器

安装

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.2</version>
</dependency>

注意
当前包未传递依赖 MP 包,需要自己引入!

使用

快速生成

FastAutoGenerator.create("url", "username", "password")
    .globalConfig(builder -> {
        builder.author("baomidou") // 设置作者
            .enableSwagger() // 开启 swagger 模式
            .fileOverride() // 覆盖已生成文件
            .outputDir("D://"); // 指定输出目录
    })
    .packageConfig(builder -> {
        builder.parent("com.baomidou.mybatisplus.samples.generator") // 设置父包名
            .moduleName("system") // 设置父包模块名
            .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://")); // 设置mapperXml生成路径
    })
    .strategyConfig(builder -> {
        builder.addInclude("t_simple") // 设置需要生成的表名
            .addTablePrefix("t_", "c_"); // 设置过滤表前缀
    })
    .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
    .execute();

交互式生成

FastAutoGenerator.create(DATA_SOURCE_CONFIG)
    // 全局配置
    .globalConfig((scanner, builder) -> builder.author(scanner.apply("请输入作者名称?")).fileOverride())
    // 包配置
    .packageConfig((scanner, builder) -> builder.parent(scanner.apply("请输入包名?")))
    // 策略配置
    .strategyConfig((scanner, builder) -> builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")))
                        .controllerBuilder().enableRestStyle().enableHyphenStyle()
                        .entityBuilder().enableLombok().addTableFills(
                                new Column("create_time", FieldFill.INSERT)
                        ).build())
    /*
        模板引擎配置,默认 Velocity 可选模板引擎 Beetl 或 Freemarker
       .templateEngine(new BeetlTemplateEngine())
       .templateEngine(new FreemarkerTemplateEngine())
     */
    .execute();


// 处理 all 情况
protected static List<String> getTables(String tables) {
    return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
}

CRUD 接口

说明:

通用 CRUD 封装BaseMapper (opens new window)接口,为 Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器
泛型 T 为任意实体对象
参数 Serializable 为任意类型主键 Mybatis-Plus 不推荐使用复合主键约定每一张表都有自己的唯一 id 主键
对象 Wrapper 为 条件构造器

Mapper CRUD

Insert
// 插入一条记录
int insert(T entity);
Delete
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 ID 删除
int deleteById(Serializable id);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

在这里插入图片描述

Update
// 根据 whereWrapper 条件,更新记录
int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper);
// 根据 ID 修改
int updateById(@Param(Constants.ENTITY) T entity);


在这里插入图片描述

Select
// 根据 ID 查询
T selectById(Serializable id);
// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);


在这里插入图片描述

分页拦截器(limit)

Mybatis自带分页插件 PaginationInnerInterceptor

在配置类中配置分页拦截器

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    //添加分页拦截
    PaginationInnerInterceptor pgInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
    pgInterceptor.setMaxLimit(2L);
    interceptor.addInnerInterceptor(pgInterceptor);
    return interceptor;
}

测试


	@Test
    void contextLoads() {
        Page<User> page = new Page<>();
        Page<User> userPage = userMapper.selectPage(page, null);

        System.out.println(userPage.getRecords());
        System.out.println(userPage.getTotal());
        System.out.println(userPage.getSize());
        System.out.println(userPage.getMaxLimit());

    }
    
// sql 如下:
==>  Preparing: SELECT COUNT(*) FROM user WHERE is_delete = 0
==> Parameters: 
<==    Columns: COUNT(*)
<==        Row: 6
<==      Total: 1
==>  Preparing: SELECT id,name,age,email,sex,is_delete FROM user WHERE is_delete=0 LIMIT ?
==> Parameters: 2(Long)
<==    Columns: id, name, age, email, sex, is_delete
<==        Row: 1, Jone, 18, test1@baomidou.com, 1, 0
<==        Row: 2, Jack, 20, test2@baomidou.com, 1, 0
<==      Total: 2
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5f96f6a2]
[User(id=1, name=Jone, age=18, email=test1@baomidou.com, sex=MALE, is_delete=null), User(id=2, name=Jack, age=20, email=test2@baomidou.com, sex=MALE, is_delete=null)]
6
2
null

Service CRUD

说明:

通用 Service CRUD 封装IService (opens new window)接口,进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆,
泛型 T 为任意实体对象
建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类
对象 Wrapper 为 条件构造器

Save
// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);

SaveOrUpdate
// TableId 注解存在更新记录,否插入一条记录
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
// 根据 entity 条件,删除记录
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);

在这里插入图片描述

Chain

** query **

// 链式查询 普通
QueryChainWrapper<T> query();
// 链式查询 lambda 式。注意:不支持 Kotlin
LambdaQueryChainWrapper<T> lambdaQuery();

// 示例:
query().eq("column", value).one();
lambdaQuery().eq(Entity::getId, value).list();


** update **

// 链式更改 普通
UpdateChainWrapper<T> update();
// 链式更改 lambda 式。注意:不支持 Kotlin
LambdaUpdateChainWrapper<T> lambdaUpdate();

// 示例:
update().eq("column", value).remove();
lambdaUpdate().eq(Entity::getId, value).update(entity);

条件构造器

Wrapper

  • QueryWrapper: 查询条件封装

  • UpdateWrapper: Update条件封装

  • LambdaQueryWrapper:Lambda语法使用的查询QueryWrapper

  • LambdaUpdateWrapper:Lambda语法更新封装UpdateWrapper

QueryWrapper

查询用户名中包含a,年龄在20-30之间,邮箱信息不为null的用户信息,并将结果按照年龄降序排列,若年龄相同,则按照id升序

 @Test
void contextLoads() {
    //查询用户名中包含a,年龄在20-30之间,邮箱信息不为null的用户信息
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.like("name", "a")
            .between("age", 20, 30)
            .isNotNull("email")
            .orderByDesc("age")
            .orderByAsc("id");
    List<User> list = userMapper.selectList(wrapper);
    list.forEach(System.out::println);
    //SELECT id,name,age,email,sex,is_delete FROM user WHERE is_delete=0 AND (name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL) ORDER BY age DESC,id ASC
}

删除邮箱地址为null的用户信息

@Test
void testD() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.isNull("email");

    int row = userMapper.delete(wrapper);

    System.out.println(row);
    //UPDATE user SET is_delete=1 WHERE is_delete=0 AND (email IS NULL) 注意:有@TabLogic
}

将(年龄大于20并且用户名包含a)或者邮箱为null的用户信息修改 and() | or()

 @Test
void testU1(){
    // 将(年龄大于20并且用户名包含a)或者邮箱为null的用户信息修改
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.gt("age", 20)
            .like("name","a")
            .or()
            .isNull("email");
    User u = new User();
    u.setEmail("aaa@aaa.aaa");
    int row = userMapper.update(u, wrapper);
    System.out.println(row);
    //UPDATE user SET email=? WHERE is_delete=0 AND (age > ? AND name LIKE ? OR email IS NULL)
}

将用户名包含a并且(年龄大于20或者邮箱为null)的用户信息修改(and|or(consumer->{…}))

@Test
void testU2(){
    // 将用户名包含a并且(年龄大于20或者邮箱为null)的用户信息修改
    // and 或 or 方法括号内的内容会优先执行
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.like("name","a")
            .and(w->w.gt("age", 20)
                    .or()
                    .isNull("email"));

    User u = new User();
    u.setEmail("bbb@bbb.bbb");
    int row = userMapper.update(u, wrapper);
    System.out.println(row);
    //UPDATE user SET email=? WHERE is_delete=0 AND (name LIKE ? AND (age > ? OR email IS NULL))
}

查询用户的用户名、年龄、邮箱(select(…))

@Test
void testS(){
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.select("name","age","email");

    List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);

    maps.forEach(System.out::println);
    //SELECT name,age,email FROM user WHERE is_delete=0
}

ID小于等于100 (使用子查询)

@Test
void testS1(){
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.inSql("id", "select id from user where id <= 100");
    List<User> list = userMapper.selectList(wrapper);
    list.forEach(System.out::println);
    //SELECT id,name,age,email,sex,is_delete FROM user WHERE is_delete=0 AND (id IN (select id from user where id <= 100))
}

condition 条件组装

@Test
void testQ3(){
    //模拟组装条件
    String name = "";
    Integer ageStart = 20;
    Integer ageEnd = 26;

    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.like(StringUtils.isNotBlank(name), "name", name)
            .between(ageStart != null && ageEnd != null && ageStart <= ageEnd, "age", ageStart ,ageEnd);
    List<User> list = userMapper.selectList(wrapper);
    list.forEach(System.out::println);
    //SELECT id,name,age,email,sex,is_delete FROM user WHERE is_delete=0 AND (age BETWEEN ? AND ?)
}
UpdateWrapper

将用户名包含a并且(年龄大于20或者邮箱为null)的用户信息修改(and|or(consumer->{…}))

 @Test
void testU3(){
    //将用户名包含a并且(年龄大于20或者邮箱为null)的用户信息修改(and\|or(consumer->{...}))
    UpdateWrapper<User> wrapper = new UpdateWrapper<>();
    wrapper.like("name", "a")
            .and(w->{w.gt("age", 20).or().isNull("email");})
            .set("name","wangermazi")
            .set("email", "ccc@ccc.ccc");


    int row = userMapper.update(null, wrapper);
    System.out.println(row);
    //UPDATE user SET name=?,email=? WHERE is_delete=0 AND (name LIKE ? AND (age > ? OR email IS NULL))
}
LambdaQueryWrapper
@Test
    void testLQ1(){
        //模拟组装条件
        String name = "";
        Integer ageStart = 20;
        Integer ageEnd = 26;

        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StringUtils.isNotBlank(name), User::getName, name)
                .between(ageStart != null && ageEnd != null && ageStart <= ageEnd,  User::getAge, ageStart, ageEnd);
        List<User> list = userMapper.selectList(wrapper);
        list.forEach(System.out::println);
        //SELECT id,name,age,email,sex,is_delete FROM user WHERE is_delete=0 AND (age BETWEEN ? AND ?)
    }

LambdaUpdateWrapper
@Test
 void testLU1(){
     //将用户名包含a并且(年龄大于20或者邮箱为null)的用户信息修改(and\|or(consumer->{...}))
     LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
     wrapper.like(User::getName,"a")
             .and(w->{w.gt(User::getAge, 20).or().isNull(User::getEmail);})
             .set(User::getName,"wangermazi")
             .set(User::getEmail, "ccc@ccc.ccc");

     int row = userMapper.update(null, wrapper);
     System.out.println(row);
     //UPDATE user SET name=?,email=? WHERE is_delete=0 AND (name LIKE ? AND (age > ? OR email IS NULL))
 }

主键策略

提示
主键生成策略必须使用 INPUT
支持父类定义 @KeySequence 子类继承使用

如:


@KeySequence(value = "SEQ_ORACLE_STRING_KEY", clazz = String.class)
public class YourEntity {

    @TableId(value = "ID_STR", type = IdType.INPUT)
    private String idStr;

}

SpringBoot 配置

方式一:使用配置类

@Bean
public IKeyGenerator keyGenerator() {
    return new H2KeyGenerator();
}

方式二:通过MybatisPlusPropertiesCustomizer 自定义

@Bean
public MybatisPlusPropertiesCustomizer plusPropertiesCustomizer() {
    return plusProperties -> plusProperties.getGlobalConfig().getDbConfig().setKeyGenerator(new H2KeyGenerator());
}

自定义ID生成器

方式一:使用Bean扫描注入

@Component
public class CustomIdGenerator implements IdentifierGenerator {
    @Override
    public Long nextId(Object entity) {
      	//可以将当前传入的class全类名来作为bizKey,或者提取参数来生成bizKey进行分布式Id调用生成.
      	String bizKey = entity.getClass().getName();
        //根据bizKey调用分布式ID生成
        long id = ....;
      	//返回生成的id值即可.
        return id;
    }
}

方式二:使用配置类

@Bean
public IdentifierGenerator idGenerator() {
    return new CustomIdGenerator();
}

方式三:通过 MybatisPlusPropertiesCustomizer 自定义

@Bean
public MybatisPlusPropertiesCustomizer plusPropertiesCustomizer() {
    return plusProperties -> plusProperties.getGlobalConfig().setIdentifierGenerator(new CustomIdGenerator());
}


扩展功能

多数据源

特性

  • 支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
  • 支持数据库敏感配置信息 加密 ENC()。
  • 支持每个数据库独立初始化表结构schema和数据库database。
  • 支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。
  • 支持 自定义注解 ,需继承DS(3.2.0+)。
  • 提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成。
  • 提供对Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等组件的集成方案。
  • 提供 自定义数据源来源 方案(如全从数据库加载)。
  • 提供项目启动后 动态增加移除数据源 方案。
  • 提供Mybatis环境下的 纯读写分离 方案。
  • 提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。
  • 支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。
  • 提供 **基于seata的分布式事务方案。
  • 提供 本地多数据源事务方案。

约定

  • 本框架只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何CRUD。
  • 配置文件所有以下划线 _ 分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。
  • 切换数据源可以是组名,也可以是具体数据源名称。组名则切换时采用负载均衡算法切换。
  • 默认的数据源名称为 master ,你可以通过 spring.datasource.dynamic.primary 修改。
  • 方法上的注解优先于类上注解
  • DS支持继承抽象类上的DS,暂不支持继承接口上的DS

使用方法

引入依赖:dynamic-datasource-spring-boot-starter
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  <version>${version}</version>
</dependency>

配置数据源
spring:
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        master:
          url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
        slave_1:
          url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
        slave_2:
          url: ENC(xxxxx) # 内置加密,使用请查看详细文档
          username: ENC(xxxxx)
          password: ENC(xxxxx)
          driver-class-name: com.mysql.jdbc.Driver
       #......省略
       #以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2

使用 @DS 切换数据源

@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解
在这里插入图片描述

测试

准备
再启动一个mysql服务,端口号为53307,再把53306服务的user表复制一份。
在这里插入图片描述

<!-- pom.xml -->
<!-- 动态数据源 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.5.0</version>
        </dependency>
spring:
  datasource:
    dynamic:
      primary: master
      strict: false
      datasource:
        master:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://121.4.97.105:53306/test_mpdb?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT
          username: root
          password: root
        slave_1:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://121.4.97.105:53307/test_mpdb?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT
          username: root
          password: root

再ServiceImpl或Mapper中的方法中使用@DS注解

@Repository
public interface UserMapper extends BaseMapper<User> {

    @DS("master")
    User selectByIdFromMysql01(Long id);
    @DS("slave_1")
    User selectByIdFromMysql02(Long id);
}


@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
    implements UserService{

    @Autowired
    private UserMapper userMapper;

    @DS("master")
    @Override
    public User getUserByIdFromMysql01(Long id) {
        return userMapper.selectById(id);
    }

    @DS("slave_1")
    @Override
    public User getUserByIdFromMysql02(Long id) {
        return userMapper.selectById(id);
    }
}

测试

    @Test
    void contextLoads() {

//        User u1 = userMapper.selectByIdFromMysql01(1L);
//        System.out.println(u1);
//
//        User u2 = userMapper.selectByIdFromMysql02(1l);
//        System.out.println(u2);

        User u1 = userService.getUserByIdFromMysql01(1l);
        System.out.println(u1);
        User u2 = userService.getUserByIdFromMysql02(1l);
        System.out.println(u2);

    }

}

字段类型处理器

类型处理器,用于 JavaType 与 JdbcType 之间的转换,用于 PreparedStatement 设置参数值和从 ResultSet 或 CallableStatement 中取出一个值,本文讲解 mybaits-plus 内置常用类型处理器如何通过TableField注解快速注入到 mybatis 容器中


@Data
@Accessors(chain = true)
@TableName(autoResultMap = true)
public class User {
    private Long id;

    ...


    /**
     * 注意!! 必须开启映射注解
     *
     * @TableName(autoResultMap = true)
     *
     * 以下两种类型处理器,二选一 也可以同时存在
     *
     * 注意!!选择对应的 JSON 处理器也必须存在对应 JSON 解析依赖包
     */
    @TableField(typeHandler = JacksonTypeHandler.class)
    // @TableField(typeHandler = FastjsonTypeHandler.class)
    private OtherInfo otherInfo;

}

SQL 打印分析

该功能依赖 p6spy 组件,完美的输出打印 SQL 及执行时长 3.1.0 以上版本

操作步骤

依赖导入


<dependency>
  <groupId>p6spy</groupId>
  <artifactId>p6spy</artifactId>
  <version>最新版本</version>
</dependency>

application.yml配置

spring:
  datasource:
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:mysql://127.0.0.1:3360/test?userSSL=false
    username: root
    password: 123456
  profiles:
    active: dev

spy.properties 配置:

module.log=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
 
 
#日志输出到控制台,解开注释就行了
# appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
 
# 指定输出文件位置
logfile=sql.log
 
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,batch,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2

Mybatis 代码生成器

依赖

<!-- myvatisPlus依赖 -->
 <dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>mybatis-plus-boot-starter</artifactId>
     <version>3.5.1</version>
 </dependency>

 <!-- 代码生成器依赖  -->
 <dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>mybatis-plus-generator</artifactId>
     <version>3.5.2</version>
 </dependency>

 <!-- freemarker 模板引擎依赖 -->
 <dependency>
     <groupId>org.freemarker</groupId>
     <artifactId>freemarker</artifactId>
     <version>2.3.30</version>
 </dependency>
 
 <!-- freemarker模板和velocity模板引擎根据选择配一个就好了 -->
 <!-- velocity 模板引擎依赖 -->
 <dependency>
     <groupId>org.apache.velocity</groupId>
     <artifactId>velocity</artifactId>
     <version>1.7</version>
 </dependency>

快速使用

FastAutoGenerator.create("url", "username", "password")
    .globalConfig(builder -> {		//全局配置
        builder.author("baomidou") // 设置作者
            .enableSwagger() // 开启 swagger 模式
            .fileOverride() // 覆盖已生成文件
            .outputDir("D://"); // 指定输出目录
    })
    .packageConfig(builder -> {		//包配置
        builder.parent("com.baomidou.mybatisplus.samples.generator") // 设置父包名
            .moduleName("system") // 设置父包模块名
            .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://")); // 设置mapperXml生成路径
    })
    .strategyConfig(builder -> {		//策略配置
        builder.addInclude("t_simple") // 设置需要生成的表名
            .addTablePrefix("t_", "c_"); // 设置过滤表前缀
    })
    .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
    .execute();


自定义

 /**
     * 数据源配置
     */
    private static final DataSourceConfig DATA_SOURCE_CONFIG = new DataSourceConfig
            .Builder("jdbc:mysql://ip:port/bmos_cms?useSSL=false", "root", "root")
            .build();

    public static void execute() {
        new AutoGenerator(DATA_SOURCE_CONFIG)
                // 全局配置
                .global(new GlobalConfig.Builder()
                        .fileOverride()
                        .outputDir("/Users/guowenjia/Desktop/code/mptest/")
                        .author("wguo")
                        .enableSwagger()
                        .dateType(DateType.TIME_PACK)
                        .commentDate("yyyy-MM-dd")
                        .build())
                // 包配置
                .packageInfo(new PackageConfig.Builder()
                        .parent("com.wguo.mybatisplusgenertortemplate") // 设置父包名
                        .moduleName("test") // 设置父包模块名
                        .entity("domain")
                        .controller("controller")
                        .service("service")
                        .serviceImpl("service.impl")
                        .mapper("mapper")
                        .xml("mapper.xml")
                        .pathInfo(Collections.singletonMap(OutputFile.xml ,"/Users/guowenjia/Desktop/code/mptest/mapper/"))//设置mapperXml生成路径
                        .build())
                // 策略配置
                .strategy(new StrategyConfig.Builder()
                        .enableCapitalMode()
                        .enableSkipView()
                        .disableSqlFilter()
//                        .likeTable(new LikeTable("USER"))
                        .addInclude("t_news")
                        .addTablePrefix("t_", "c_")
//                        .addFieldSuffix("_flag")

                        //entity策略
                        .entityBuilder()
//                        .superClass(BaseEntity.class)
//                        .disableSerialVersionUID()
//                        .enableChainModel()
                        .enableLombok()
//                        .enableRemoveIsPrefix()
                        .enableTableFieldAnnotation()
//                        .enableActiveRecord()
                        .versionColumnName("version")
                        .versionPropertyName("version")
                        .logicDeleteColumnName("is_deleted")
                        .logicDeletePropertyName("is_deleted")
                        .naming(NamingStrategy.underline_to_camel)
                        .columnNaming(NamingStrategy.underline_to_camel)
//                        .addSuperEntityColumns("created", "created_time", "updated", "last_updated_time")
//                        .addIgnoreColumns("password")
//                        .addTableFills(new Column("create_time", FieldFill.INSERT))
//                        .addTableFills(new Property("updateTime", FieldFill.INSERT_UPDATE))
//                        .idType(IdType.AUTO)
                        .formatFileName("%s")

                        //Controller 策略配置
                        .controllerBuilder()
//                        .superClass(BaseController.class)
                        .enableHyphenStyle()
                        .enableRestStyle()
                        .formatFileName("%sController")

                        //Service 策略配置
                        .serviceBuilder()
//                        .superServiceClass(BaseService.class)
//                        .superServiceImplClass(BaseServiceImpl.class)
                        .formatServiceFileName("%sService")
                        .formatServiceImplFileName("%sServiceImp")

                        //Mapper 策略配置
                        .mapperBuilder()
                        .superClass(BaseMapper.class)
                        .enableMapperAnnotation()
                        .enableBaseResultMap()
                        .enableBaseColumnList()
//                        .cache(MyMapperCache.class)
                        .formatMapperFileName("%sMapper")
                        .formatXmlFileName("%sXml")
                        .build())
                // 注入配置
                .injection(new InjectionConfig.Builder()
//                        .beforeOutputFile((tableInfo, objectMap) -> {
//                            System.out.println("tableInfo: " + tableInfo.getEntityName() + " objectMap: " + objectMap.size());
//                        })
//                        .customMap(Collections.singletonMap("test", "baomidou"))
//                        .customFile(Collections.singletonMap("cus-controller.java.ftl", "/templates/cus-controller.java.ftl"))
                        .build())
                // 模板配置,注意在mybatis-plus-generator-3.5.2包下的templates下有一些定义好的模板
                //如果我们使用的是Freemarker模板,就是以ftl结尾的;使用Velocity,以vm结尾;使用Beetl,以btl结尾
                .template(new TemplateConfig.Builder()
//                        .disable(TemplateType.ENTITY)
                        .entity("/templates/entity.java")
                        .service("/templates/service.java")
                        .serviceImpl("/templates/serviceImpl.java")
                        .mapper("/templates/mapper.java")
                        .xml("/templates/mapper.xml")
                        .controller("/templates/cus-controller.java") //controller 使用我们自定义的模板
                        .build())
                // 执行
                .execute(new FreemarkerTemplateEngine());
    }

自定义controller模板如下

${entity?uncap_first} uncap_first 首字母小写

package ${package.Controller};

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import ${package.Entity}.${entity};
import ${package.Service}.${table.serviceName};
import com.bmos.cms.support.JsonData;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * <p>
 * ${table.comment!} 前端控制器
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */
@Api(tags = "${table.comment!}管理控制器")
@RestController
@RequestMapping("/api/v1/${entity?uncap_first}")
public class ${table.controllerName} {

    @Autowired
    private ${table.serviceName} ${entity?uncap_first}Service;


    @ApiOperation("根据ID获取唯一的${table.comment!}")
    @GetMapping("/{key:[0-9]+}")
    public JsonData get(@ApiParam(name = "ID,路径参数", example = "0") @PathVariable Integer key){
        LambdaQueryWrapper<${entity}> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(key != null, ${entity}::getId, key);
        return JsonData.buildSuccess(${entity?uncap_first}Service.getOne(queryWrapper));
     }

    @ApiOperation("分页查询${table.comment!}")
    @GetMapping("/page")
    public JsonData page(@ApiParam(name = "当前页", required = true, example = "1") @RequestParam(name = "pageNumber",required = true, defaultValue = "1") Integer pageNumber,
                         @ApiParam(name = "每页显示的条数", required = true, example = "50") @RequestParam(name = "pageSize",required = true, defaultValue = "50") Integer pageSize
    ){

        Page<${entity}> page = new Page<>();
        page.setSize(pageSize);
        page.setCurrent(pageNumber);
        LambdaQueryWrapper<${entity}> queryWrapper = new LambdaQueryWrapper<>();

        ${entity?uncap_first}Service.page(page, queryWrapper);
        return JsonData.buildSuccess(page);
    }



   @ApiOperation("新增或修改一个${table.comment!},ID存在则修改,不存在则新增")
   @PostMapping("/")
   public JsonData save(@ApiParam(name = "${table.comment!}信息")@RequestBody ${entity} entity){
        LambdaUpdateWrapper<${entity}> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(${entity}::getId, entity.getId() != null ?entity.getId():0);
        ${entity?uncap_first}Service.saveOrUpdate(entity, wrapper);
        return JsonData.buildSuccess(entity);
    }


    @ApiOperation("删除${table.comment!}")
    @DeleteMapping("/{id:[0-9]+}")
    public JsonData delete(@ApiParam(name = "${table.comment!}编码", example = "0") @PathVariable("id") Integer id){
    LambdaQueryWrapper<${entity}> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(id != null, ${entity}::getId, id);
        boolean b = ${entity?uncap_first}Service.remove(wrapper);
        return b?JsonData.buildSuccess("删除成功!"):JsonData.buildError(-1, "删除失败");
     }

     @ApiOperation("批量删除")
     @PostMapping("/delBatch")
     public JsonData delBatch(@ApiParam(name = "id集合") @RequestBody List<Integer> ids){

        ${entity?uncap_first}Service.removeBatchByIds(ids);
        return JsonData.buildSuccess("删除成功");
      }


}


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值