MyBatis-plus使用

mybatis-plus

开篇:

简介:
MyBatis Plus (简称 MP) 是一款持久层框架,说白话就是一款操作数据库的框架。它是一个 MyBatis 的增强工具,就像 iPhone手机一般都有个 plus 版本一样,它在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

Mybatis Plus 特性:
**1、无侵入:**只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
**2、损耗小:**启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
**3、强大的 CRUD 操作:**内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
**4、支持 Lambda 形式调用:**通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
**5、支持主键自动生成:**支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
**6、支持 ActiveRecord 模式:**支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
**7、支持自定义全局通用操作:**支持全局通用方法注入( Write once, use anywhere )
**8、内置代码生成器:**采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
**9、内置分页插件:**基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
**10、分页插件支持多种数据库:**支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
**11、内置性能分析插件:**可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
**12、内置全局拦截插件:**提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

框架结构:

1、快速开始

2、新建测试库与表

新建一个名为 test 的测试数据库,并创建一张用户表,Schema 建表脚本如下:

DROP TABLE IF EXISTS t_user;

CREATE TABLE `t_user` (
  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(30) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` int(11) NULL DEFAULT NULL COMMENT '年龄',
  `gender` tinyint(2) NOT NULL DEFAULT 0 COMMENT '性别,0:女 1:男',
  PRIMARY KEY (`id`)
) COMMENT = '用户表';

3、新建 Spring Boot 示例项目

数据库准备好了,我们来新建一个 Spring Boot 示例项目,用来讲解如何使用 Mybatis Plus,先放一张示例项目目录结构截图:

4、添加 Mybatis Plus 依赖

在 pom.xml 文件中添加以下依赖:

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

<!-- 单元测试依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

<!-- lombok 依赖(免写 setXXX/getXXX 方法) -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.10</version>
</dependency>

<!-- mysql 依赖 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

5、添加配置

# 数据库配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf-8
    username: root
    password: xxxx

然后,在项目根目录下新建一个 config 包,并创建 MybatisPlusConfig 配置类:

@Configuration
@MapperScan("com.yichat.mybatisplusdemo.mapper")
public class MybatisPlusConfig {
}

6、添加实体类

@Data
@Builder
@TableName("t_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
	@TableField(value = "name")
    private String name;
	@TableField(value = "age")
    private Integer age;
	@TableField(value = "gender")
    private Integer gender;
}

@TableName(“t_user”) 注解用于指定表名;
@TableId(type = IdType.AUTO) 注解指定了字段 id 为表的主键,同时指定主键为自增类型。
@TableField(value = “name”)该实体类的属性与表中value字段符合的相对应

7、添加 Mapper 类

在项目根目录下创建 mapper 包,并新建 UserMapper 接口,同时继承自 BaseMapper, 代码如下:

public interface UserMapper extends BaseMapper<User> {
    
}

BaseMapper 接口由 Mybatis Plus 提供,封装了一些常用的 CRUD 操作,使得我们无需像 Mybatis 那样编写 xml 文件,就拥有了基本的 CRUD 功能,点击 BaseMapper 接口,源码如下:

public interface BaseMapper<T> extends Mapper<T> {
    // 新增数据
    int insert(T entity);
	// 根据 ID 删除
    int deleteById(Serializable id);
	// 删除数据
    int deleteByMap(@Param("cm") Map<String, Object> columnMap);
	// 删除数据
    int delete(@Param("ew") Wrapper<T> queryWrapper);
	// 根据 ID 批量删除数据
    int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);
	// 根据 ID 更新
    int updateById(@Param("et") T entity);
	// 更新数据
    int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
	// 根据 ID 查询
    T selectById(Serializable id);
	// 根据 ID 批量查询
    List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
	// 查询数据
    List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);
	// 查询一条数据
    T selectOne(@Param("ew") Wrapper<T> queryWrapper);
	// 查询记录总数
    Integer selectCount(@Param("ew") Wrapper<T> queryWrapper);
	// 查询多条数据
    List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
	// 查询多条数据
    List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);
	// 查询多条数据
    List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);
	// 分页查询
    <E extends IPage<T>> E selectPage(E page, @Param("ew") Wrapper<T> queryWrapper);
	// 分页查询
    <E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param("ew") Wrapper<T> queryWrapper);
}

8、简单的 CRUD

@SpringBootTest
class MybatisPlusBaseMapperTests {

    @Autowired
    private UserMapper userMapper;

    /**
     * 查询数据
     */
    @Test
    public void testSelectUser() {
        System.out.println(("----- 开始测试 mybatis-plus 查询数据 ------"));
        //  selectList() 方法的参数为 mybatis-plus 内置的条件封装器 Wrapper,这里不填写表示无任何条件,全量查询
        List<User> userList = userMapper.selectList(null);

        userList.forEach(System.out::println);
    }

    /**
     * 新增一条数据
     */
    @Test
    public void testInsertUser() {
        System.out.println(("----- 开始测试 mybatis-plus 插入数据 ------"));
        User user = User.builder()
                .name("测试")
                .age(30)
                .gender(1)
                .build();

        userMapper.insert(user);
    }

    /**
     * 删除数据
     */
    @Test
    public void testDeleteUser() {
        System.out.println(("----- 开始测试 mybatis-plus 删除数据 ------"));
        // 根据主键删除记录
        userMapper.deleteById(1);

        // 根据主键批量删除记录
        userMapper.deleteBatchIds(Arrays.asList(1, 2));
    }

    /**
     * 更新数据
     */
    @Test
    public void testUpdateUser() {
        System.out.println(("----- 开始测试 mybatis-plus 更新数据 ------"));
        User user = User.builder()
                .id(1L)
                .name("测试")
                .build();

        userMapper.updateById(user);
    }
}

Mybatis Plus 打印 SQL 语句(包含执行耗时)

我们已经使用 Mybatis Plus 对数据库进行了最简单的 CRUD 操作,但是在实际项目中,增删改查操作会更加复杂,接下来,我们将更加深入的学习 Mybatis Plus 的增删改查。

在这之前呢,我们先配置一下 Mybatis Plus 打印 SQL 功能(包括执行耗时),以方便我们更直观的学习 CRUD, 一方面可以了解到每个操作都具体执行的什么 SQL 语句, 另一方面通过打印执行耗时,也可以规避一些慢 SQL,提前做好优化。

1、引入依赖

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

2、添加配置

2.1 第一步:修改 application.yml 配置文件:
spring:
  datasource:
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:mysql://127.0.0.1:3306/test?characterEncoding=utf-8

注意:
driver-class-name 用 p6spy 提供的驱动类;
url 前缀为 jdbc:p6spy 跟着冒号,后面对应数据库连接地址;

2.2 第二步:添加 p6spy 配置文件

然后在 resources 目录下添加 spy.properties 配置文件:

配置文件内容如下:

#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 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,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2

3、看看打印效果

配置添加完成后,单元测试执行一条插入语句:

@Test
    void testInsertUser() {
        System.out.println(("----- 开始测试 mybatis-plus 插入数据 ------"));
        User user = User.builder()
                .name("测试")
                .age(30)
                .gender(1)
                .build();

        userMapper.insert(user);
    }

执行之后可以看到完整的打印了执行语句

增删改查

1、新增数据

1.1 表结构
DROP TABLE IF EXISTS t_user;

CREATE TABLE `t_user` (
  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(30) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` int(11) NULL DEFAULT NULL COMMENT '年龄',
  `gender` tinyint(2) NOT NULL DEFAULT 0 COMMENT '性别,0:女 1:男',
  PRIMARY KEY (`id`)
) COMMENT = '用户表';
1.2 定义实体类
@Data
@TableName("t_user")
public class User {
    /**
     * 主键 ID, @TableId 注解定义字段为表的主键,type 表示主键类型,IdType.AUTO 表示随着数据库 ID 自增
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 性别
     */
    private Integer gender;
}

@TableName 表名注解
作用:标识实体类对应的表。

TIP :

当实体类名称和实际表名一致时,如实体名为 User, 表名为 user ,可不用添加该注解,Mybatis Plus 会自动识别并映射到该表。
当实体类名称和实际表名不一致时,如实体名为 User, 表名为 t_user,需手动添加该注解,并填写实际表名称。

@TableId 主键注解

作用:声明实体类中的主键对应的字段。

属性类型必须指定默认值描述
valueString“”主键字段名
typeEnumIdType.NONE指定主键类型

IdType 主键类型

描述
AUTO数据库 ID 自增
NONE无状态,该类型为未设置主键类型(默认)
INPUT插入数据前,需自行设置主键的值
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分布式全局唯一 ID 长整型类型 (推荐使用 ASSIGN_ID)
UUID32 位 UUID 字符串 (推荐使用 ASSIGN_UUID)
ID_WORKER_STR分布式全局唯一 ID 字符串类型 (推荐使用 ASSIGN_ID)
新增数据

测试表准备好后,我们准备开始演示新增数据。实际上,Mybatis Plus 对 Mapper 层和 Service 层都将常见的增删改查操作都封装好了,只需简单的继承,即可轻松搞定对数据的增删改查,本文重点讲解新增数据这块。

Mapper 层:

public interface UserMapper extends BaseMapper<User> {
}

然后,注入 Mapper :

@Autowired
private UserMapper userMapper;

BaseMapper 提供的新增方法仅一个 insert() 方法:

我们通过它测试一下添加数据,并获取主键 ID :

User user = new User();
user.setName(“测试”);
user.setAge(30);
user.setGender(1);

userMapper.insert(user);

// 获取插入数据的主键 ID
Long id = user.getId();

System.out.println(“id:” + id);

Service 层:

Mybatis Plus 同样也封装了通用的 Service 层 CRUD 操作,并且提供了更丰富的方法。接下来,我们上手看 Service 层的代码结构,如下图:

先定义 UserService 接口 ,让其继承自 IService:

public interface UserService extends IService<User> {
}

再定义实现类 UserServiceImpl,让其继承自 ServiceImpl, 同时实现 UserService 接口,这样就可以让 UserService 拥有了基础通用的 CRUD 功能,当然,实际开发中,业务会更加复杂,就需要向 IService 接口自定义方法并实现:

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

注入 UserService :

@Autowired
private UserService userService;

与 Mapper 层不同的是,Service 层的新增方法均以 save 开头,并且功能更丰富,来看看都提供了哪些方法:

简单解释下每个方法的作用,以作了解:

// 新增数据
sava(T) : boolean
// 伪批量插入,实际上是通过 for 循环一条一条的插入
savaBatch(Collection<T>) : boolean
// 伪批量插入,int 表示批量提交数,默认为 1000
savaBatch(Collection<T>, int) : boolean
// 新增或更新(单条数据)
saveOrUpdate(T) : boolean
// 批量新增或更新
saveOrUpdateBatch(Collection<T>) : boolean
// 批量新增或更新(可指定批量提交数)
saveOrUpdateBatch(Collection<T>, int) : boolean

sava(T)

// 新增数据
// 实际执行 SQL : INSERT INTO user ( name, age, gender ) VALUES ( '测试', 30, 1 )
User user = new User();
user.setName("测试");
user.setAge(30);
user.setGender(1);

boolean isSuccess = userService.save(user);

// 返回主键ID
Long id = user.getId();
System.out.println("isSuccess:" + isSuccess);
System.out.println("主键 ID: " + id);

savaBatch(Collection)

伪批量插入,注意,命名虽然包含了批量的意思,但这不是真的批量插入,不信的话,我们来实际测试一下:

// 批量插入
List<User> users = new ArrayList<>();
for (int i = 0; i < 5; i++) {
    User user = new User();
    user.setName("测试" + i);
    user.setAge(i);
    user.setGender(1);
    users.add(user);
}
boolean isSuccess = userService.saveBatch(users);
System.out.println("isSuccess:" + isSuccess);

这里的批量插入并不是 insert into user (xxx) values (xxx),(xxx),(xxx) 这种批量形式,还是一条一条插入的。

批量新增源码分析

我们来看下源码, 内部的saveBatch() 方法默认的批量提交阀值参数,数值为 1000, 即达到 1000 条批量提交一次,继续点进去看:

public boolean saveBatch(Collection<T> entityList, int batchSize) {
        // 获取预编译的插入 SQL
        String sqlStatement = this.getSqlStatement(SqlMethod.INSERT_ONE);
        // for 循环执行 insert
        return this.executeBatch(entityList, batchSize, (sqlSession, entity) -> {
            sqlSession.insert(sqlStatement, entity);
        });
    }

再看下 SqlMethod.INSERT_ONE 这个枚举,描述信息为插入一条数据:

继续往 executeBatch() 方法里看,瞅瞅它这个批量到底是怎么处理的,具体每行代码的意思,小哈都加了注释:

    public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
    	// 断言需要批处理数据集大小不等于1
        Assert.isFalse(batchSize < 1, "batchSize must not be less than one", new Object[0]);
        // 判空数据集,若不为空,则开始执行批量处理
        return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, (sqlSession) -> {
        	
            int size = list.size();
            // 将批处理大小与传入的操作集合大小进行比较,取最小的那个
            int idxLimit = Math.min(batchSize, size);
            int i = 1;
            // 迭代器循环
            for(Iterator var7 = list.iterator(); var7.hasNext(); ++i) {
            	// 获取当前需要执行的数据库操作
                E element = var7.next();
                // 回调 sqlSession.insert() 方法
                consumer.accept(sqlSession, element);
                // 判断是否达到需要批处理的阀值
                if (i == idxLimit) {
                	// 开始批处理,此方法执行并清除缓存在 JDBC 驱动类中的执行语句
                    sqlSession.flushStatements();
                    idxLimit = Math.min(idxLimit + batchSize, size);
                }
            }
        });
    }

相比较自己手动 for 循环执行插入,Mybatis Plus 这个伪批量插入性能会更好些,内部会将每次的插入语句缓存起来,等到达到 1000 条的时候,才会统一推给数据库,虽然最终在数据库那边还是一条一条的执行 INSERT,但还是在和数据库交互的 IO 上做了优化。

savaBatch(Collection, int)

多了个 batchSize 参数,可以手动指定批处理的大小,即多少 SQL 操作执行一次,默认为 1000。

saveOrUpdate(T)

保存或者更新。即当你需要执行的数据,数据库中不存在时,就执行插入操作:

// 实际执行 SQL : INSERT INTO user ( name, age, gender ) VALUES ( '测试', 60, 1 )
User user = new User();
user.setName("测试");
user.setAge(60);
user.setGender(1);
userService.saveOrUpdate(user);

当你需要执行的数据,数据库中已存在时,就执行更新操作。框架是如何判断该记录是否存在呢? 如设置了主键 ID,因为主键 ID 必须是唯一的,Mybatis Plus 会先执行查询操作,判断数据是否存在,存在即执行更新,否则,执行插入操作:

User user = new User();
// 设置了主键字段
user.setId(21L);
user.setName("测试");
user.setAge(60);
user.setGender(1);
userService.saveOrUpdate(user);

saveOrUpdateBatch(Collection)

批量保存或者更新,示例代码如下:

List<User> users = new ArrayList<>();
for (int i = 0; i < 5; i++) {
    User user = new User();
    user.setId(Long.valueOf(i));
    user.setName("测试" + i);
    user.setAge(i+1);
    user.setGender(1);
    users.add(user);
}
userService.saveOrUpdateBatch(users);

saveOrUpdateBatch(Collection, int)

批量保存或者更新(可手动指定批量大小),示例代码如下:

List<User> users = new ArrayList<>();
for (int i = 0; i < 5; i++) {
    User user = new User();
    user.setId(Long.valueOf(i));
    user.setName("测试" + i);
    user.setAge(i+1);
    user.setGender(1);
    users.add(user);
}
userService.saveOrUpdateBatch(users, 100);

2.删除数据

表结构和实体类就沿用之前的

删除数据

Mybatis Plus 对 Mapper 层和 Service 层都将常见的增删改查操作封装好了,只需简单的继承,即可轻松搞定对数据的增删改查。

Mapper 层

定义一个 UserMapper , 让其继承 BaseMapper :

public interface UserMapper extends BaseMapper<User> {
}
@Autowired
private UserMapper userMapper;

删除相关的方法均以 delete 开头,方法如下:

解释一下每个方法的作用:

// 根据主键 ID 删除 (直接传入 ID)
int deleteById(Serializable id);
// 根据主键 ID 删除 (传入实体类)
int deleteById(T entity);
// 根据主键 ID 批量删除
int deleteBatchIds(Collection<?> idList)
// 通过 Wrapper 条件构造器删除
int delete(Wrapper<T> queryWrapper);
// 通过 Map 设置条件来删除
int deleteByMap(Map<String, Object> columnMap);

示例代码

根据主键 ID 删除 (直接传入 ID):

// 实际执行的 SQL : DELETE FROM user WHERE id=9
int count = userMapper.deleteById(9L);
System.out.println("受影响的行数:" + count);

根据主键 ID 删除 (传入实体类):

User user = new User();
user.setId(9L);
// 实际执行的 SQL : DELETE FROM user WHERE id=9
int count = userMapper.deleteById(user);
System.out.println("受影响的行数:" + count);

根据主键 ID 批量删除:

// 根据 ID 批量删除
List ids = new ArrayList<>();
ids.add(1L);
ids.add(2L);

// 实际执行 SQL 为 :DELETE FROM user WHERE id IN ( 1 , 2 )
userMapper.deleteBatchIds(ids);

通过 Wrapper 条件构造器删除:

// 构造删除条件
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("name", "测试");
wrapper.eq("age", 1);

// 实际执行 SQL 为 :DELETE FROM user WHERE (name = '测试' AND age = 1)
userMapper.delete(wrapper);

// Lambda 表达式形式
userMapper.delete(new QueryWrapper<User>()
            .lambda()
            .eq(User::getName, "测试")
            .eq(User::getAge, 1));

通过 Map 设置条件来删除:

// 通过 Map 设置条件来删除
Map<String, Object> columnMap = new HashMap<>();
columnMap.put("name", "测试");
columnMap.put("age", 1);

int count = userMapper.deleteByMap(columnMap);
System.out.println("受影响的行数:" + count);

Service 层

Mybatis Plus 同样也封装了通用的 Service 层 CRUD 操作,并且提供了更丰富的方法。接下来,我们上手看 Service 层的代码结构,如下图:

先定义 UserService 接口 ,让其继承自 IService:

public interface UserService extends IService<User> {
}

再定义实现类 UserServiceImpl,让其继承自 ServiceImpl, 同时实现 UserService 接口,这样就可以让 UserService 拥有了基础通用的 CRUD 功能,当然,实际开发中,业务会更加复杂,就需要向 IService 接口自定义方法并实现:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
@Autowired
private UserService userService;

Service 层封装的删除方法均以 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);

3.更新数据

表结构和实体类就沿用之前的

更新数据

Mybatis Plus 对 Mapper 层和 Service 层都将常见的增删改查操作封装好了,只需简单的继承,即可轻松搞定对数据的增删改查,本文重点讲解修改数据部分。

Mapper 层

定义一个 UserMapper , 让其继承 BaseMapper :

public interface UserMapper extends BaseMapper<User> {
}
@Autowired
private UserMapper userMapper;

BaseMapper 提供的修改方法以 update 开头,方法如下:

解释一下每个方法的作用:

# 根据主键 ID 来更新
int updateById(T entity);
# entity 用于设置更新的数据,wrapper 用于组装更新条件
int update(T entity, Wrapper<T> updateWrapper);

示例代码

根据主键 ID 来更新:

User user = new User();
user.setId(1L);
user.setName("修改后的测试");
user.setGender(0);

// 实际执行的 SQL : UPDATE user SET name='修改后的测试', gender=0 WHERE id=1
int count = userMapper.updateById(user);
System.out.println("受影响的行数:" + count);

entity 用于设置更新的数据,wrapper 用于组装更新条件

User user = new User();
user.setName("修改后的测试");
user.setGender(0);
// 组装更新条件,更新 age = 20 的数据
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("age", 20);

// 实际执行的 SQL : UPDATE user SET name='修改后的测试', gender=0 WHERE (age = 20)
int count = userMapper.update(user, updateWrapper);
System.out.println("受影响的行数:" + count);

Service 层

Mybatis Plus 同样也封装了通用的 Service 层 CRUD 操作,并且提供了更丰富的方法。接下来,我们上手看 Service 层的代码结构,如下图:

先定义 UserService 接口 ,让其继承自 IService:

public interface UserService extends IService<User> {
}

再定义实现类 UserServiceImpl,让其继承自 ServiceImpl, 同时实现 UserService 接口,这样就可以让 UserService 拥有了基础通用的 CRUD 功能,当然,实际开发中,业务会更加复杂,就需要向 IService 接口自定义方法并实现:

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

注入 UserService :

@Autowired
private UserService userService;

Service 层封装的更新方法均以 update 开头,方法如下:

这里把常用的一些删除方法摘出来:

// 根据 ID 来更新,entity 用于设置 ID 以及其他更新条件
boolean updateById(T entity);
// wrapper 用于设置更新数据以及条件
boolean update(Wrapper<T> updateWrapper);
// entity 用于设置更新的数据,wrapper 用于组装更新条件
boolean update(T entity, Wrapper<T> updateWrapper);
// 批量更新
boolean updateBatchById(Collection<T> entityList);
// 批量更新,可手动设置批量提交阀值
boolean updateBatchById(Collection<T> entityList, int batchSize);
// 保存或者更新
boolean saveOrUpdate(T entity);

示例代码

Service 层和上面 Mapper 的使用方法差不多,这里演示一下 boolean update(Wrapper updateWrapper) 使用示例:

UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
// set name = '更新后的测试'
updateWrapper.set("name", "更新后的测试");
// where id = 1 AND age = 30
updateWrapper.eq("id", 1L).eq("age", 30);

// 实际执行 SQL : UPDATE user SET name='更新后的测试' WHERE (id = 1 AND age = 30)
boolean isSuccess = userService.update(updateWrapper);
System.out.println("更新是否成功:" + isSuccess);

4.查询数据

表结构和实体类就沿用之前的

查询数据

Mybatis Plus 对 Mapper 层和 Service 层都将常见的增删改查操作封装好了,只需简单的继承,即可轻松搞定对数据的增删改查,本文重点讲解查询相关的部分。

Mapper 层

定义一个 UserMapper , 让其继承自 BaseMapper :

public interface UserMapper extends BaseMapper<User> {
}

然后,注入 Mapper :

@Autowired
private UserMapper userMapper;

BaseMapper 提供的查询相关的方法如下:

解释一下每个方法的作用:

// 根据 ID 查询
T selectById(Serializable id);
// 通过 Wrapper 组装查询条件,查询一条记录
T selectOne(Wrapper<T> queryWrapper);

// 查询(根据ID 批量查询)
List<T> selectBatchIds(Collection<? extends Serializable> idList);
// 通过 Wrapper 组装查询条件,查询全部记录
List<T> selectList(Wrapper<T> queryWrapper);
// 查询(根据 columnMap 来设置条件)
List<T> selectByMap(Map<String, Object> columnMap);
// 根据 Wrapper 组装查询条件,查询全部记录,并以 map 的形式返回
List<Map<String, Object>> selectMaps(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(Wrapper<T> queryWrapper);

// =========================== 分页相关 ===========================
// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询总记录数
Integer selectCount(Wrapper<T> queryWrapper);

参数说明:

类型参数名描述
Serializableid主键 ID
WrapperqueryWrapper实体对象封装操作类(可以为 null)
Collection<? extends Serializable>idList主键 ID 列表(不能为 null 以及 empty)
Map<String, Object>columnMap表字段 map 对象
IPagepage分页查询条件(可以为 RowBounds.DEFAULT)

示例代码

1、根据 ID 查询:

// 实际执行 SQL : SELECT id,name,age,gender FROM user WHERE id=1 
User user = userMapper.selectById(1L);

2、通过 Wrapper 组装查询条件,查询一条记录:

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 仅查询 id, name 字段
queryWrapper.select("id", "name");
// where id = 1
queryWrapper.eq("id", 1L);

// 实际执行 SQL : SELECT id,name,age,gender FROM user WHERE (id = 1)
User user = userMapper.selectOne(queryWrapper);

注意: selectOne 方法期望仅返回一条数据,若实际查询到多条数据,会主动抛出异常,内部源码如下:

2、查询(根据ID 批量查询):

// 实际执行 SQL : SELECT id,name,age,gender FROM user WHERE id IN ( 1 , 2 , 3 )
List users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L));

3、通过 Wrapper 组装查询条件,查询全部记录:

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 仅查询 id, name 字段
queryWrapper.select("id", "name");
// where age = 30
queryWrapper.eq("age", 30);

// 实际执行 SQL : SELECT id,name FROM user WHERE (age = 30)
List<User> users = userMapper.selectList(queryWrapper);

4、查询(根据 columnMap 来设置条件):

// 通过 map 来设置查询条件
Map<String, Object> columnMap = new HashMap<>();
columnMap.put("name", "测试");
columnMap.put("age", 30);

// 实际执行 SQL : SELECT id,name,age,gender FROM user WHERE name = '测试' AND age = 30
List<User> users = userMapper.selectByMap(columnMap);

5、根据 Wrapper 组装查询条件,查询全部记录,并以 map 的形式返回:

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 仅查询 id, name 字段
queryWrapper.select("id", "name");
// where age = 30
queryWrapper.eq("age", 30);

// 实际执行 SQL : SELECT id,name FROM user WHERE (age = 30)
List<Map<String,Object>> users = userMapper.selectMaps(queryWrapper);

6、根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值:

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// where age = 30
queryWrapper.eq("age", 30);

// 实际执行 SQL : SELECT id,name FROM user WHERE (age = 30)
List<Object> ids = userMapper.selectObjs(queryWrapper);

7、根据 Wrapper 条件,查询总记录数:

// 组装查询条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "测试").eq("age", 30);

// 实际执行 SQL : SELECT COUNT( * ) FROM user WHERE (name = '测试' AND age = 30)
long count = userMapper.selectCount(queryWrapper);

System.out.println("总记录数:" + count);

Service 层

Mybatis Plus 同样也封装了通用的 Service 层 CRUD 操作,并且提供了更丰富的方法。接下来,我们上手看 Service 层的代码结构,如下图:

先定义 UserService 接口 ,让其继承自 IService:

public interface UserService extends IService<User> {
}

再定义实现类 UserServiceImpl,让其继承自 ServiceImpl, 同时实现 UserService 接口,这样就可以让 UserService 拥有了基础通用的 CRUD 功能,当然,实际开发中,业务会更加复杂,就需要向 IService 接口自定义方法并实现:

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

注入 UserService :

@Autowired
private UserService userService;

Service 层封装的查询方法注意分为 4 块:

  • getXXX : get 开头的方法;
  • listXXX : list 开头的方法,用于查询多条数据;
  • pageXXX : page 开头的方法,用于分页查询;
  • count : 用于查询总记录数;

get 相关方法
get 开头的相关方法用于查询一条记录,方法如下:

// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。如果结果集是多个会抛出异常
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录,以 map 的形式返回数据
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

参数说明:

类型参数名描述
Serializableid主键 ID
WrapperqueryWrapper实体对象封装操作类 QueryWrapper
booleanthrowEx有多个 result 是否抛出异常
Tentity实体对象
Function<? super Object, V>mapper转换函数

示例代码

这里挑两个具有代表性的方法:

1.根据 ID 查询

// 实际执行 SQL : SELECT id,name,age,gender FROM user WHERE id=1        
User user = userService.getById(1L);

2.根据 Wrapper,查询一条记录。如果结果集是多个会抛出异常:

// 组装查询条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("id", 1L).eq("name", "测试");

// 实际执行 SQL : SELECT id,name,age,gender FROM user WHERE (id = 1 AND name = '测试')
User user = userService.getOne(queryWrapper);

list 相关方法

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);
// 查询所有列表, 以 map 的形式返回
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);

参数说明:

类型参数名描述
WrapperqueryWrapper实体对象封装操作类 QueryWrapper
Collection<? extends Serializable>idList主键 ID 列表
Map<String, Object>columnMap表字段 map 对象
Function<? super Object, V>mapper转换函数

示例代码

这里挑几个具有代表性的方法:

// 组装查询条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// where name = '测试' and age >= 20
queryWrapper.eq("name", "测试").ge("age", 20);

// 实际执行 SQL : SELECT id,name,age,gender FROM user WHERE (name = '测试' AND age >= 20)
List<User> users = userService.list(queryWrapper);
// 实际执行 SQL : SELECT id,name,age,gender FROM user WHERE id IN ( 1 , 2 , 3 )
List<User> users = userService.listByIds(Arrays.asList(1L, 2L, 3L));
Map<String , Object> columnMap = new HashMap<>();
columnMap.put("name", "测试");
columnMap.put("age", 30L);

// 实际执行 SQL : SELECT id,name,age,gender FROM user WHERE name = '测试' AND age = 30
List<User> users = userService.listByMap(columnMap);

page 分页相关方法

count 查询总记录数
查询总记录数 count 相关方法如下:

// 查询总记录数(不带查询条件)
count();
// 查询总记录数(可以带查询条件)
count(Wrapper<T>)

示例代码

// 组装查询条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// where age = 30
queryWrapper.eq("age", 30);

// 实际执行 SQL : SELECT COUNT( * ) FROM user WHERE (age = 30)
long count = userService.count(queryWrapper);
System.out.println("总记录数:" + count);

5.分页查询数据

为什么需要分页查询?

  1. 前端页面能够展示的内容有限;
  2. 当数据库中数据量太多,比如 100W 条,一次性全部返回,查询速度慢,而且内存也顶不住;

表结构和实体类就沿用之前的

新增测试数据

// 循环插入 100 条测试数据
for (int i = 0; i < 100; i++) {
    User user = new User();
    user.setName("测试" + i);
    user.setAge(i);
    user.setGender(1);

    userMapper.insert(user);
}

添加分页插件

接着,在 MybatisPlusConfig 配置类中,添加分页插件 PaginationInnerInterceptor:

@Configuration
@MapperScan("com.quanxiaoha.mybatisplusdemo.mapper")
public class MybatisPlusConfig {

    /**
     * 分页插件
     * @return
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
  
}
分页查询数据

Mybatis Plus 对 Mapper 层和 Service 层都将常见的增删改查操作封装好了,只需简单的继承,即可轻松搞定对数据的增删改查,本文重点讲解分页查询相关的部分。

Mapper 层

定义一个 UserMapper , 让其继承自 BaseMapper :

public interface UserMapper extends BaseMapper<User> {
}

然后,注入 Mapper :

@Autowired
private UserMapper userMapper;

BaseMapper 提供的分页查询相关的方法如下:

解释一下每个方法的作用:

// 分页查询,page 用于设置需要查询的页数,以及每页展示数据量,wrapper 用于组装查询条件
IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper);
// 同上,区别是用 map 来接受查询的数据
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, Wrapper<T> queryWrapper);

参数说明:

类型参数名描述
WrapperqueryWrapper实体对象封装操作类(可以为 null)
IPagepage分页查询条件(可以为 RowBounds.DEFAULT)

示例代码

// 组装查询条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// where age = 30
queryWrapper.eq("age", 30);

// 查询第 2 页数据,每页 10 条
Page<User> page = new Page<>(2, 10);

page = userMapper.selectPage(page, queryWrapper);
System.out.println("总记录数:" + page.getTotal());
System.out.println("总共多少页:" + page.getPages());
System.out.println("当前页码:" + page.getCurrent());
// 当前页数据
List<User> users = page.getRecords();

Page 类说明:

该类继承了 IPage 类,实现了 简单分页模型 ,如果你要实现自己的分页模型可以继承 Page 类或者实现 IPage 类

属性名类型默认值描述
recordsListemptyList查询数据列表
totalLong0查询列表总记录数
sizeLong10每页显示条数,默认 10
currentLong1当前页
ordersListemptyList排序字段信息,允许前端传入的时候,注意 SQL 注入问题,可以使用 SqlInjectionUtils.check(…) 检查文本
optimizeCountSqlbooleantrue自动优化 COUNT SQL 如果遇到 jSqlParser 无法解析情况,设置该参数为 false
optimizeJoinOfCountSqlbooleantrue自动优化 COUNT SQL 是否把 join 查询部分移除
searchCountbooleantrue是否进行 count 查询,如果指向查询到列表不要查询总记录数,设置该参数为 false
maxLimitLong单页分页条数限制
countIdStringxml 自定义 count 查询的 statementId

Service 层

Mybatis Plus 同样也封装了通用的 Service 层 CRUD 操作,并且提供了更丰富的方法。接下来,我们上手看 Service 层的代码结构,如下图:

先定义 UserService 接口 ,让其继承自 IService:

public interface UserService extends IService<User> {
}

再定义实现类 UserServiceImpl,让其继承自 ServiceImpl, 同时实现 UserService 接口,这样就可以让 UserService 拥有了基础通用的 CRUD 功能,当然,实际开发中,业务会更加复杂,就需要向 IService 接口自定义方法并实现:

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

注入 UserService :

@Autowired
private UserService userService;

Service 层封装的分页相关方法如下:

// 无条件分页查询
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);

示例代码

Service 层的分页方法入参和 Mapper 差不多:

// 组装查询条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// where age = 30
queryWrapper.eq("age", 30);

// 查询第 2 页数据,每页 10 条
Page<User> page = new Page<>(2, 10);

page = userService.page(page, queryWrapper);
System.out.println("总记录数:" + page.getTotal());
System.out.println("总共多少页:" + page.getPages());
System.out.println("当前页码:" + page.getCurrent());
// 当前页数据
List<User> users = page.getRecords();

6.多表联查

表结构

DROP TABLE IF EXISTS user;

CREATE TABLE `t_user` (
  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(30) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` int(11) NULL DEFAULT NULL COMMENT '年龄',
  `gender` tinyint(2) NOT NULL DEFAULT 0 COMMENT '性别,0:女 1:男',
  PRIMARY KEY (`id`)
) COMMENT = '用户表';

INSERT INTO `t_user` (`id`, `name`, `age`, `gender`) VALUES (1, '犬小哈', 30, 1);
INSERT INTO `t_user` (`id`, `name`, `age`, `gender`) VALUES (2, '关羽', 46, 1);
INSERT INTO `t_user` (`id`, `name`, `age`, `gender`) VALUES (3, '诸葛亮', 26, 1);


CREATE TABLE `t_order` (
  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `order_id` bigint(20) UNSIGNED NOT NULL COMMENT '订单ID',
  `user_id` bigint(20) UNSIGNED NOT NULL COMMENT '下单用户ID',
  `goods_name` varchar(30) NOT NULL COMMENT '商品名称',
  `goods_price` decimal(10,2) NOT NULL COMMENT '商品价格',
  PRIMARY KEY (`id`),
  INDEX idx_order_id(`order_id`)
) COMMENT = '订单表';

INSERT INTO `t_order` (`id`, `order_id`, `user_id`, `goods_name`, `goods_price`) VALUES (1, 805646264648356, 1, 'Switch 游戏机', 1400.00);
INSERT INTO `t_order` (`id`, `order_id`, `user_id`, `goods_name`, `goods_price`) VALUES (2, 551787441310504, 1, '小米手机', 2000.00);
INSERT INTO `t_order` (`id`, `order_id`, `user_id`, `goods_name`, `goods_price`) VALUES (3, 938562101633493, 2, '《三国演义》', 66.00);
INSERT INTO `t_order` (`id`, `order_id`, `user_id`, `goods_name`, `goods_price`) VALUES (4, 791129917310894, 3, '华为手机', 1200.00);
INSERT INTO `t_order` (`id`, `order_id`, `user_id`, `goods_name`, `goods_price`) VALUES (5, 208722395587361, 3, '《西游记》', 56.00);

需求分析

假设前端需要展示数据有如下几个字段:订单号、商品名称、商品价格、下单用户姓名、下单用户年龄、下单用户性别

则对应的关联 SQL 语句如下:

select o.order_id, o.goods_name, o.goods_price, u.name, u.age, u.gender
        from t_order as o left join  t_user as u on  o.user_id = u.id

实体类

接下来,我们定义实体类。创建一个 OrderVO 视图类,用于传输给前端展示:

@Data
public class OrderVO {

    /**
     * 订单ID
     */
    private Long orderId;
    /**
     * 下单用户ID
     */
    private Long userId;
    /**
     * 商品名称
     */
    private String goodsName;

    /**
     * 商品价格
     */
    private BigDecimal goodsPrice;

    /**
     * 用户名
     */
    private String userName;
    /**
     * 年龄
     */
    private Integer userAge;
    /**
     * 性别
     */
    private Integer userGender;
}

关联查询

简单的关联查询

创建 UserMapper , 让其继承自 BaseMapper , 并自定义一个查询订单列表的方法:

public interface UserMapper extends BaseMapper<User> {
	// 查询订单列表
	List<OrderVO> selectOrders();
}

在项目的 resource 目录下新建 mapper 文件夹,并在 mapper 文件夹中创建 UserMapper.xml 文件:

UserMapper.xml 中编写关联语句,以及需要映射的对象,内容如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.quanxiaoha.mybatisplusdemo.mapper.UserMapper">

    <resultMap id="orderMap" type="com.quanxiaoha.mybatisplusdemo.model.OrderVO">
        <result property="userName" column="name"/>
        <result property="userAge" column="age"/>
        <result property="userGender" column="gender"/>
        <result property="orderId" column="order_id"/>
        <result property="userId" column="user_id"/>
        <result property="goodsName" column="goods_name"/>
        <result property="goodsPrice" column="goods_price"/>
    </resultMap>

    <select id="selectOrders" resultMap="orderMap">
        select o.order_id, o.user_id, o.goods_name, o.goods_price, u.name, u.age, u.gender
        from t_order as o left join  t_user as u on  o.user_id = u.id
    </select>

</mapper>

定义关联查询分页方法

在 UserMapper 接口中再定义支持分页的关联查询方法:

public interface UserMapper extends BaseMapper<User> {

	//...
    IPage<OrderVO> selectOrderPage(IPage<OrderVO> page, @Param(Constants.WRAPPER) QueryWrapper<OrderVO> wrapper);
    //...
}

然后在 UserMapper.xml 中创建该方法对应的关联查询:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.quanxiaoha.mybatisplusdemo.mapper.UserMapper">

    <resultMap id="orderMap" type="com.quanxiaoha.mybatisplusdemo.model.OrderVO">
        <result property="userName" column="name"/>
        <result property="userAge" column="age"/>
        <result property="userGender" column="gender"/>
        <result property="orderId" column="order_id"/>
        <result property="userId" column="user_id"/>
        <result property="goodsName" column="goods_name"/>
        <result property="goodsPrice" column="goods_price"/>
    </resultMap>

	//...
	
    <select id="selectOrderPage" resultMap="orderMap">
        select u.name, u.age, u.gender, o.order_id, o.goods_name, o.goods_price
        from t_user as u left join t_order as o on u.id = o.user_id
        ${ew.customSqlSegment}
    </select>

	//...

</mapper>

再创建一个单元测试:

@Autowired
private UserMapper userMapper;

@Test
void testSelectOrdersPage() {
    // 查询第一页,每页显示 10 条
    Page<OrderVO> page = new Page<>(1, 10);
    // 注意:一定要手动关闭 SQL 优化,不然查询总数的时候只会查询主表
    page.setOptimizeCountSql(false);
    // 组装查询条件 where age = 20
    QueryWrapper<OrderVO> queryWrapper = new QueryWrapper<>();
    queryWrapper.ge("age", 20);

    IPage<OrderVO> page1 = userMapper.selectOrderPage(page, queryWrapper);

    System.out.println("总记录数:" + page1.getTotal());
    System.out.println("总共多少页:" + page1.getPages());
    System.out.println("当前页码:" + page1.getCurrent());
    System.out.println("查询数据:" + page1.getRecords());
}

7.批量 Insert_新增数据

我们向 MySQL 中新增一条记录,SQL 语句类似如下:

INSERT INTO `t_user` (`name`, `age`, `gender`) VALUES ('测试', 0, 1);

如果你需要添加 100 万条数据,就需要多次执行此语句,这就意味着频繁地 IO 操作(网络 IO、磁盘 IO),并且每一次数据库执行 SQL 都需要进行解析、优化等操作,都会导致非常耗时。

幸运的是,MySQL 支持一条 SQL 语句可以批量插入多条记录,格式如下:

INSERT INTO `t_user` (`name`, `age`, `gender`) VALUES ('测试1', 0, 1), ('测试2', 0, 1), ('测试3', 0, 1);

和常规的 INSERT 语句不同的是,VALUES 支持多条记录,通过 , 逗号隔开。这样,可以实现一次性插入多条记录。

表与实体类

先创建一个测试表 t_user, 执行脚本如下:

DROP TABLE IF EXISTS user;

CREATE TABLE `t_user` (
  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(30) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` int(11) NULL DEFAULT NULL COMMENT '年龄',
  `gender` tinyint(2) NOT NULL DEFAULT 0 COMMENT '性别,0:女 1:男',
  PRIMARY KEY (`id`)
) COMMENT = '用户表';

再定义一个名为 User 实体类:

@Data
@TableName("t_user")
public class User {
    /**
     * 主键 ID, @TableId 注解定义字段为表的主键,type 表示主键类型,IdType.AUTO 表示随着数据库 ID 自增
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 性别
     */
    private Integer gender;
}

利用 SQL 注入器实现真的批量插入

新建批量插入 SQL 注入器

在工程 config 目录下创建一个 SQL 注入器 InsertBatchSqlInjector :

public class InsertBatchSqlInjector extends DefaultSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
    	// super.getMethodList() 保留 Mybatis Plus 自带的方法
        List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
        // 添加自定义方法:批量插入,方法名为 insertBatchSomeColumn
        methodList.add(new InsertBatchSomeColumn());
        return methodList;
    }
}

InsertBatchSomeColumn

InsertBatchSomeColumn 是 Mybatis Plus 内部提供的默认批量插入,只不过这个方法作者只在 MySQL 数据测试过,所以没有将它作为通用方法供外部调用,注意看注释:

源码复制出来,如下:

/**
 * 批量新增数据,自选字段 insert
 * <p> 不同的数据库支持度不一样!!!  只在 mysql 下测试过!!!  只在 mysql 下测试过!!!  只在 mysql 下测试过!!! </p>
 * <p> 除了主键是 <strong> 数据库自增的未测试 </strong> 外理论上都可以使用!!! </p>
 * <p> 如果你使用自增有报错或主键值无法回写到entity,就不要跑来问为什么了,因为我也不知道!!! </p>
 * <p>
 * 自己的通用 mapper 如下使用:
 * <pre>
 * int insertBatchSomeColumn(List<T> entityList);
 * </pre>
 * </p>
 *
 * <li> 注意: 这是自选字段 insert !!,如果个别字段在 entity 里为 null 但是数据库中有配置默认值, insert 后数据库字段是为 null 而不是默认值 </li>
 *
 * <p>
 * 常用的 {@link Predicate}:
 * </p>
 *
 * <li> 例1: t -> !t.isLogicDelete() , 表示不要逻辑删除字段 </li>
 * <li> 例2: t -> !t.getProperty().equals("version") , 表示不要字段名为 version 的字段 </li>
 * <li> 例3: t -> t.getFieldFill() != FieldFill.UPDATE) , 表示不要填充策略为 UPDATE 的字段 </li>
 *
 * @author miemie
 * @since 2018-11-29
 */

@SuppressWarnings("serial")
public class InsertBatchSomeColumn extends AbstractMethod {

    /**
     * 字段筛选条件
     */
    @Setter
    @Accessors(chain = true)
    private Predicate<TableFieldInfo> predicate;

    /**
     * 默认方法名
     */
    public InsertBatchSomeColumn() {
    	// 方法名
        super("insertBatchSomeColumn");
    }

    /**
     * 默认方法名
     *
     * @param predicate 字段筛选条件
     */
    public InsertBatchSomeColumn(Predicate<TableFieldInfo> predicate) {
        super("insertBatchSomeColumn");
        this.predicate = predicate;
    }

    /**
     * @param name      方法名
     * @param predicate 字段筛选条件
     * @since 3.5.0
     */
    public InsertBatchSomeColumn(String name, Predicate<TableFieldInfo> predicate) {
        super(name);
        this.predicate = predicate;
    }

    @SuppressWarnings("Duplicates")
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
        SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
        List<TableFieldInfo> fieldList = tableInfo.getFieldList();
        String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(true, false) +
            this.filterTableFieldInfo(fieldList, predicate, TableFieldInfo::getInsertSqlColumn, EMPTY);
        String columnScript = LEFT_BRACKET + insertSqlColumn.substring(0, insertSqlColumn.length() - 1) + RIGHT_BRACKET;
        String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(true, ENTITY_DOT, false) +
            this.filterTableFieldInfo(fieldList, predicate, i -> i.getInsertSqlProperty(ENTITY_DOT), EMPTY);
        insertSqlProperty = LEFT_BRACKET + insertSqlProperty.substring(0, insertSqlProperty.length() - 1) + RIGHT_BRACKET;
        String valuesScript = SqlScriptUtils.convertForeach(insertSqlProperty, "list", null, ENTITY, COMMA);
        String keyProperty = null;
        String keyColumn = null;
        // 表包含主键处理逻辑,如果不包含主键当普通字段处理
        if (tableInfo.havePK()) {
            if (tableInfo.getIdType() == IdType.AUTO) {
                /* 自增主键 */
                keyGenerator = Jdbc3KeyGenerator.INSTANCE;
                keyProperty = tableInfo.getKeyProperty();
                keyColumn = tableInfo.getKeyColumn();
            } else {
                if (null != tableInfo.getKeySequence()) {
                    keyGenerator = TableInfoHelper.genKeyGenerator(this.methodName, tableInfo, builderAssistant);
                    keyProperty = tableInfo.getKeyProperty();
                    keyColumn = tableInfo.getKeyColumn();
                }
            }
        }
        String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addInsertMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource, keyGenerator, keyProperty, keyColumn);
    }

}

配置 SQL 注入器

在 config 包下创建 MybatisPlusConfig 配置类:

@Configuration
@MapperScan("com.quanxiaoha.mybatisplusdemo.mapper")
public class MybatisPlusConfig {


    /**
     * 自定义批量插入 SQL 注入器
     */
    @Bean
    public InsertBatchSqlInjector insertBatchSqlInjector() {
        return new InsertBatchSqlInjector();
    }

}

新建 MyBaseMapper

在 config 包下创建 MyBaseMapper 接口,让其继承自 Mybatis Plus 提供的 BaseMapper, 并定义批量插入方法:

public interface MyBaseMapper<T> extends BaseMapper<T> {

	// 批量插入
    int insertBatchSomeColumn(@Param("list") List<T> batchList);

}

新建 UserMapper

在 mapper 包下创建 UserMapper 接口,注意继承刚刚自定义的 MyBaseMapper, 而不是 BaseMapper :

public interface UserMapper extends MyBaseMapper<User> {
}

测试批量插入

完成上面这些工作后,就可以使用 Mybatis Plus 提供的批量插入功能了。我们新建一个单元测试,并注入 UserMapper :

@Autowired
private UserMapper userMapper;

单元测试如下:

@Test
void testInsertBatch() {
    List<User> users = new ArrayList<>();
    for (int i = 0; i < 3; i++) {
        User user = new User();
        user.setName("测试" + i);
        user.setAge(i);
        user.setGender(1);
        users.add(user);
    }

    userMapper.insertBatchSomeColumn(users);
}

性能对比

耗时对比

方式总耗时
for 循环插入722963 ms, 约 12 分钟
savaBatch() 伪批量插入95864 ms, 约一分钟30秒左右
真实批量插入6320 ms, 约 6 秒

8.常用注解

实体类常用注解

@TableName

作用:表名注解,标识实体类对应的表。

使用示例:

@TableName("t_user")
public class User {
    /**
     * 主键 ID
     */
    private Long id;
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 性别
     */
    private Integer gender;
}

@TableId

作用:主键注解。

使用示例:

@TableName("t_user")
public class User {
    /**
     * 主键 ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 性别
     */
    private Integer gender;
}

@TableId 注解有两个属性值:

属性类型必须指定默认值描述
valueString“”主键字段名
typeEnumIdType.NONE (默认雪花算法生成 ID)指定主键类型

@IdType
作用:指定主键 ID 类型。

描述
AUTO数据库 ID 自增
NONE无状态,该类型为未设置主键类型(默认)
INPUT插入数据前,需自行设置主键的值
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分布式全局唯一 ID 长整型类型 (推荐使用 ASSIGN_ID)
UUID32 位 UUID 字符串 (推荐使用 ASSIGN_UUID)
ID_WORKER_STR分布式全局唯一 ID 字符串类型 (推荐使用 ASSIGN_ID)

@TableField

作用:指定数据库字段注解(非主键)。

假设表的字段名与实体类的字段名不一致,可通过它来指定,比如表字段名为 user_name 映射到实体类的字段 name 上,代码如下:

@TableName("t_user")
public class User {
    /**
     * 主键 ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * 姓名
     */
    @TableField("user_name")
    private String name;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 性别
     */
    private Integer gender;
}

@TableLogic

作用:逻辑删除注解。

@TableName("t_user")
public class User {
    /**
     * 主键 ID
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 性别
     */
    private Integer gender;

    /**
     * 逻辑删除
     */
    @TableLogic
    private Integer isDeleted;
}

@Version

作用:乐观锁注解。

@TableName("t_seckill_goods")
public class SeckillGoods {
    /**
     * 主键 ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * 商品名称
     */
    private String goodsName;
    /**
     * 库存
     */
    private Integer count;
    /**
     * 乐观锁版本号
     */
    @Version
    private Integer version;

}

9.Wrapper 条件构造器

Wrapper 继承关系

Wrapper 是个抽象类,先看下它的继承关系图:

解释一下各个子类的作用:

Wrapper  条件构造抽象类
    -- AbstractWrapper 查询条件封装,用于生成 sql 中的 where 语句。
        -- QueryWrapper Entity 条件封装操作类,用于查询。
        -- UpdateWrapper Update 条件封装操作类,用于更新。
        -- AbstractLambdaWrapper 使用 Lambda 表达式封装 wrapper
            -- LambdaQueryWrapper 使用 Lambda 语法封装条件,用于查询。
            -- LambdaUpdateWrapper 使用 Lambda 语法封装条件,用于更新。
    -- AbstractChainWrapper 链式查询条件封装 
    	-- UpdateChainWrapper 链式条件封装操作类,用于更新。
    	-- LambdaQueryChainWrapper 使用 Lambda 语法封装条件,支持链式调用,用于查询
    	-- LambdaUpdateChainWrapper 使用 Lambda 语法封装条件,支持链式调用,用于更新
    	-- QueryChainWrapper 链式条件封装操作类,用于查询。
AbstractWrapper

QueryWrapper (LambdaQueryWrapper) 和 UpdateWrapper (LambdaUpdateWrapper) 的父类 用于生成 sql 的 where 条件, entity 属性也用于生成 sql 的 where 条件

allEq :多字段等于查询

全部自动等于判断,或者个别自动非空判断:

// params : key 为数据库字段名, value 为字段值
allEq(Map<R, V> params)
// null2IsNull : 为 true 则在 map 的 value 为 null 时调用 isNull 方法,为 false 时则忽略 value 为null的
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
  • 代码示例1: allEq({id:1,name:“老王”,age:null}) 相当于条件 id = 1 and name = ‘老王’ and age is null;
  • 代码示例2: allEq({id:1,name:“老王”,age:null}, false)相当于条件id = 1 and name = ‘老王’, ;
// filter : 过滤函数,是否允许字段传入比对条件中
allEq(BiPredicate<R, V> filter, Map<R, V> params)
// 同上
allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull) 
  • 代码示例1: allEq((k,v) -> k.contains(“a”), {id:1,name:“老王”,age:null})相当于条件name = ‘老王’ and age is null;
  • 代码示例2: allEq((k,v) -> k.contains(“a”), {id:1,name:“老王”,age:null}, false)相当于条件name = ‘老王’;

eq :单字段等于

eq(R column, Object val)
eq(boolean condition, R column, Object val)
  • 代码示例: eq(“name”, “老王”)相当于条件name = ‘老王’;

ne :不等于

ne(R column, Object val)
ne(boolean condition, R column, Object val)
  • 代码示例:ne(“name”, “老王”)相当于条件name <> ‘老王’;

gt : 大于

gt(R column, Object val)
gt(boolean condition, R column, Object val)
  • 代码示例: ge(“age”, 18)相当于条件age >= 18;

ge:大于等于

ge(R column, Object val)
ge(boolean condition, R column, Object val)
  • 例: ge(“age”, 18)相当于条件age >= 18;

lt:小于

lt(R column, Object val)
lt(boolean condition, R column, Object val)
  • 例: lt(“age”, 18)相当于条件age < 18;

le : 小于等于

le(R column, Object val)
le(boolean condition, R column, Object val)
  • 例: le(“age”, 18)相当于条件age <= 18;

between

between(R column, Object val1, Object val2)
between(boolean condition, R column, Object val1, Object val2)

作用:BETWEEN 值1 AND 值2

  • 例: between(“age”, 18, 30)相当于条件age between 18 and 30;

notBetween

notBetween(R column, Object val1, Object val2)
notBetween(boolean condition, R column, Object val1, Object val2)

作用:NOT BETWEEN 值1 AND 值2

  • 例: notBetween(“age”, 18, 30)相当于条件age not between 18 and 30;

like : 模糊查询

like(R column, Object val)
like(boolean condition, R column, Object val)

作用:LIKE ‘%值%’

  • 例: like(“name”, “王”)相当于条件name like ‘%王%’;

notLike

notLike(R column, Object val)
notLike(boolean condition, R column, Object val)

作用:NOT LIKE ‘%值%’

  • 例: notLike(“name”, “王”)相当于条件name not like ‘%王%’;

likeLeft

likeLeft(R column, Object val)
likeLeft(boolean condition, R column, Object val)

作用:LIKE ‘%值’

  • 例: likeLeft(“name”, “王”)相当于条件name like '%王;

likeRight

likeRight(R column, Object val)
likeRight(boolean condition, R column, Object val)

作用: LIKE ‘值%’

  • 例: likeRight(“name”, “王”)相当于条件name like '王%;

isNull :为空

isNull(R column)
isNull(boolean condition, R column)

作用:字段 IS NULL

  • 例: isNull(“name”)相当于条件name is null;

isNotNull : 非空

isNotNull(R column)
isNotNull(boolean condition, R column)

作用: 字段 IS NOT NULL

  • 例: isNotNull(“name”)相当于条件name is not null;

in

in(R column, Collection<?> value)
in(boolean condition, R column, Collection<?> value)

说明:字段 IN (value.get(0), value.get(1), …)

  • 例: in(“age”,{1,2,3})相当于条件age in (1,2,3);
in(R column, Object... values)
in(boolean condition, R column, Object... values)

说明:字段 IN (v0, v1, …)

  • 例: in(“age”, 1, 2, 3) 相当于条件age in (1,2,3);

notIn

notIn(R column, Collection<?> value)
notIn(boolean condition, R column, Collection<?> value)

作用:NOT IN (value.get(0), value.get(1), …)

  • 例: notIn(“age”,{1,2,3})相当于条件age not in (1,2,3);

inSql :子查询

inSql(R column, String inValue)
inSql(boolean condition, R column, String inValue)

作用:字段 IN ( sql语句 )

  • 例: inSql(“age”, “1,2,3,4,5,6”)相当于条件age in (1,2,3,4,5,6);
  • 例: inSql(“id”, “select id from table where id < 3”)相当于条件id in (select id from table where id < 3);

notInSql

notInSql(R column, String inValue)
notInSql(boolean condition, R column, String inValue)

作用:NOT IN ( sql语句 )

  • 例: notInSql(“age”, “1,2,3,4,5,6”)相当于条件age not in (1,2,3,4,5,6);
  • 例: notInSql(“id”, “select id from table where id < 3”)相当于条件id not in (select id from table where id < 3);

groupBy:分组

groupBy(R... columns)
groupBy(boolean condition, R... columns)

说明:分组 GROUP BY 字段。

  • 例: groupBy(“id”, “name”)相当于条件group by id,name;

orderByAsc:升序

orderByAsc(R... columns)
orderByAsc(boolean condition, R... columns)

说明:升序排序:ORDER BY 字段, … ASC

  • 例: orderByAsc(“id”, “name”)相当于条件order by id ASC,name ASC;

orderByDesc:降序

orderByDesc(R... columns)
orderByDesc(boolean condition, R... columns)

说明:降序排序:ORDER BY 字段, … DESC

  • 例: orderByDesc(“id”, “name”)相当于条件order by id DESC,name DESC;

orderBy : 排序

orderBy(boolean condition, boolean isAsc, R... columns)

说明:排序:ORDER BY 字段, …

  • 例: orderBy(true, true, “id”, “name”)相当于条件order by id ASC,name ASC;

having

作用:HAVING ( sql语句 )

having(String sqlHaving, Object... params)
having(boolean condition, String sqlHaving, Object... params)
  • 例: having(“sum(age) > 10”)相当于条件having sum(age) > 10
  • 例: having(“sum(age) > {0}”, 11)相当于条件having sum(age) > 11

func

作用:func 方法主要方便在出现if…else下调用不同方法能不断链

func(Consumer<Children> consumer)
func(boolean condition, Consumer<Children> consumer)
  • 例: func(i -> if(true) {i.eq(“id”, 1)} else {i.ne(“id”, 1)})
    or

拼接 or :

or()
or(boolean condition)
  • 例: eq(“id”,1).or().eq(“name”,“老王”)相当于条件id = 1 or name = ‘老王’;

or 嵌套:

  • 例: or(i -> i.eq(“name”, “李白”).ne(“status”, “活着”))相当于条件or (name = ‘李白’ and status <> ‘活着’);

and 嵌套

and(Consumer<Param> consumer)
and(boolean condition, Consumer<Param> consumer)

作用:AND 嵌套

  • 例: and(i -> i.eq(“name”, “李白”).ne(“status”, “活着”))相当于条件and (name = ‘李白’ and status <> ‘活着’);

nested

作用:正常嵌套 不带 AND 或者 OR

nested(Consumer<Param> consumer)
nested(boolean condition, Consumer<Param> consumer)
  • 例: nested(i -> i.eq(“name”, “李白”).ne(“status”, “活着”))相当于条件(name = ‘李白’ and status <> ‘活着’)

apply

作用:拼接 sql

注意:该方法可用于数据库函数 动态入参的params对应前面applySql内部的{index}部分.这样是不会有sql注入风险的,反之会有!

apply(String applySql, Object... params)
apply(boolean condition, String applySql, Object... params)
  • 例: apply(“id = 1”)相当于条件id = 1
  • 例: apply(“date_format(dateColumn,‘%Y-%m-%d’) = ‘2008-08-08’”)相当于条件date_format(dateColumn,‘%Y-%m-%d’) = ‘2008-08-08’")
  • 例: apply(“date_format(dateColumn,‘%Y-%m-%d’) = {0}”, “2008-08-08”)相当于条件date_format(dateColumn,‘%Y-%m-%d’) = ‘2008-08-08’")

last

作用:无视优化规则直接拼接到 sql 的最后。

注意:只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用。

last(String lastSql)
last(boolean condition, String lastSql)
  • 例: last(“limit 1”)。

exists

exists(String existsSql)
exists(boolean condition, String existsSql)

作用:拼接 EXISTS ( sql语句 )

  • 例: exists(“select id from table where age = 1”)相当于条件exists (select id from table where age = 1);

notExists

notExists(String notExistsSql)
notExists(boolean condition, String notExistsSql)

作用:拼接 NOT EXISTS ( sql语句 )

  • 例: notExists(“select id from table where age = 1”)相当于条件not exists (select id from table where age = 1);

QueryWrapper
QueryWrapper 继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件。LambdaQueryWrapper 支持以 lambda 形式组装条件, 可以通过 new QueryWrapper().lambda() 方法获取实例。

select

作用:设置查询字段

select(String... sqlSelect)
select(Predicate<TableFieldInfo> predicate)
select(Class<T> entityClass, Predicate<TableFieldInfo> predicate)
TIP :

以上方法分为两类. 第二类方法为:过滤查询字段(主键除外),入参不包含 class 的
调用前需要wrapper内的entity属性有值! 这两类方法重复调用以最后一次为准
  • 例: select(“id”, “name”, “age”)
  • 例: select(i -> i.getProperty().startsWith(“test”))

UpdateWrapper

UpdateWrapper 继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件。LambdaUpdateWrapper 支持以 lambda 形式组装条件, 可以通过 new QueryWrapper().lambda() 方法获取实例。

set

作用:SET 字段

set(String column, Object val)
set(boolean condition, String column, Object val)
  • 例: set(“name”, “老李头”)
  • 例: set(“name”, “”)—>数据库字段值变为空字符串
  • 例: set(“name”, null)—>数据库字段值变为null
    setSql
    作用:设置 SET 部分 SQL

setSql(String sql)
例: setSql(“name = ‘老李头’”)
lambda
作用:用于获取 LambdaWrapper。

在QueryWrapper中是获取LambdaQueryWrapper;
在UpdateWrapper中是获取LambdaUpdateWrapper.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值