likes
comments
collection
share

【MyBatisPlus 01】使用 MyBatisPlus 实现单表的 CURD + 分页查询功能

作者站长头像
站长
· 阅读数 24

本文主要有以下内容:

  • MyBatisPlus 更新值的3种写法
  • MyBatisPlus 分页查询功能
  • MyBatisPlus 的新增和删除包括逻辑删除

基础环境搭建步骤:

  • 创建 Spring Boot 工程【省略】
  • 引入 MyBatisPlus 项目依赖 【省略】
  • 创建实体表、实体类,使用注解绑定和实体之间的关系

建表语句:

DROP TABLE IF EXISTS `author`;
CREATE TABLE `author`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `nickname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `username` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `phone_number` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

实体类:

@Data
@TableName("author")
public class AuthorPO {

    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;

    @TableField("username")
    private String username;

    @TableField(value = "email",updateStrategy = FieldStrategy.IGNORED)
    private String email;

    @TableField("password")
    private String password;

    @TableField("nickname")
    private String nickname;

    @TableField(value = "phone_number")
    private String phoneNumber;
    
}
  • @TableName: 绑定数据库的表,和数据库表的名称一致
  • @TableId: 标识数据库表的主键
  • @TableField: 绑定数据库中表的字段。默认情况 MyBatisPlus 会将实体类属性小驼峰命名转为带有"_"的全小写命名

MyBatisPlus中的新增

在 BaseMapper 中定义了save()用于数据的持久化,首先是数据的持久化,在业务逻辑不复杂的情况下,或者说只需要把用户填的数据进行存储没有额外的逻辑,我们在Service层甚至不需要定义新增方法,直接使用封装好的就可,如

@PostMapping("add")
public ResultData<AuthorPO> addAuthor(@RequestBody AuthorPO author) {

ResultData<AuthorPO> resultData = ResultData.success(author);
boolean save = authorService.save(author);
if (save) {
    return resultData;
}
return ResultData.failed("新增作者失败");
}

在这种场景下直接使用即可,如下图

【MyBatisPlus 01】使用 MyBatisPlus 实现单表的 CURD + 分页查询功能 1.使用 save() 新增对象

如果需要对用户的输入数据进行处理,则最好的办法是在Service层定义新增方法,在这里进行业务逻辑处理后在save()

MyBatisPlus中的更新操作

Mapper接口的创建

@Mapper
@Repository
public interface AuthorMapper extends BaseMapper<AuthorPO> {
}

BaseMapper : 是 MyBatisPlus 提供的一个接口,封装了一些基本的CURD功能,如下图:

【MyBatisPlus 01】使用 MyBatisPlus 实现单表的 CURD + 分页查询功能 2.BaseMapper接口

该接口包含插入,删除(delete开头的方法),更新(update开头的这一类方法),各式各样的查询方法(select开头的这一类方法)。

BaseMapper接口中定义和更新相关的两个方法

  • updateById(T)
  • update(T,Wrapper)

updateById 是通过传入一个含有主键的实体类对象进行更新的,实体的属性会自动拼装成 SQL 的更新语句,默认策略是忽略值为null的更新,即当属性值为 null 时,不更新这个字段在数据库的值。MyBatisPlus 一共有如下几种更新策略

  • IGNORED:忽略判断,即不进行属性值的判断,在更新值时,都会把属性的值拼接在sql中去
  • NOT_NULL:非空判断
  • NOT_EMPTY:针对String类型,字符串需要是非空字符串即有长度的字符串才进行值的更新
  • DEFAULT:在全局里代表 NOT_NUL,在注解里代表 跟随全局
  • NEVER:永远不加入 SQL

MyBatisPlus 更新值的方式有以下几种写法

  • 借助updateById(T)实现
  • 借助UpdateWrapper()和LambdaUpdateWrapper()实现

Service层接口创建并添加更新方法定义

public interface AuthorService extends IService<AuthorPO> {

     // LambdaUpdateWrapper 方式更新值
    AuthorPO updateAuthorByLambdaUpdateWrapper(AuthorPO author);
    // UpdateWrapper 
    AuthorPO updateAuthorByUpdateWrapper(AuthorPO author);
}

IService:该接口是 MyBatisPlus 的内部的接口,定义了Service层常用的方法,ServiceImpl实现了这个接口,让我们能够开箱即用。因此在实现自定义接口时,我们需要继承 ServiceImpl 这个类

Service接口实现

@Service
@Slf4j
public class AuthorServiceImpl extends ServiceImpl<AuthorMapper, AuthorPO> implements AuthorService {

    @Resource
    private AuthorMapper authorMapper;

   @Override
    public AuthorPO updateAuthorByUpdateWrapper(AuthorPO author) {

        UpdateWrapper<AuthorPO> updateWrapper = new UpdateWrapper<>();
        updateWrapper.set("nickname",author.getNickname());
        updateWrapper.set("username",author.getUsername());
        updateWrapper.set("password",author.getPassword());
        updateWrapper.set("email",author.getEmail());
        // 等值判定 即 where id = author.getId() 
        updateWrapper.eq("id",author.getId());
        int update = authorMapper.update(null, updateWrapper);
        if (update > 0) {
            return authorMapper.selectById(author.getId());
        }
        return new AuthorPO();
    }

    @Override
    public AuthorPO updateAuthorByLambdaUpdateWrapper(AuthorPO author) {

        log.info("author = {}", author);
        LambdaUpdateWrapper<AuthorPO> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(AuthorPO::getNickname, author.getNickname());
        updateWrapper.set(AuthorPO::getEmail, author.getEmail());
        updateWrapper.set(AuthorPO::getPassword, author.getPassword());
        updateWrapper.eq(AuthorPO::getId, author.getId());
        // int update = authorMapper.update(author, updateWrapper);

        int update = authorMapper.update(null, updateWrapper);
        if (update > 0) {
            return authorMapper.selectById(author.getId());
        }
        return new AuthorPO();
    }

}

Controller类代码实现

        @PostMapping("edit")
        public ResultData<AuthorPO> updateAuthor(@RequestBody AuthorPO author) {
            // 1.默认情况下,忽略更新为null的值
            // 2.使用 updateStrategy = FieldStrategy.IGNORED 可以将属性值更新为null
            boolean save = authorService.updateById(author);
            if (save){
                return ResultData.success(authorService.getById(author.getId()));
            }
            return ResultData.failed("更新失败");
        }

        @PostMapping("lambdaUpdate")
        public ResultData<AuthorPO> updateAuthorByLambdaUpdate(@RequestBody AuthorPO author){

            AuthorPO authorPO = authorService.updateAuthorByLambda(author);
            if (authorPO != null && authorPO.getId() != null){
                return  ResultData.success(authorPO);
            }
            return ResultData.failed("更新失败");
        }

        @PostMapping("updateWrapper")
        public ResultData<AuthorPO> updateAuthorByUpdateWrapper(@RequestBody AuthorPO author){

            AuthorPO authorPO = authorService.updateAuthorByUpdateWrapper(author);
            if (authorPO != null && authorPO.getId() != null){
                return   ResultData.success(authorPO);
            }
            return ResultData.failed("更新失败");
        }

updateAuthor()方法中,调用的是自带的方法,没有添加任何逻辑,在这种情况下,由于默认的更新策略,将会忽略掉null值的更新,除了email属性,

【MyBatisPlus 01】使用 MyBatisPlus 实现单表的 CURD + 分页查询功能 3.数据库中的原始数据

调用updateById方法进行对象更新,结果如下图,可以看到email属性被置为 null ,passwordphone_number属性没做任何更改

【MyBatisPlus 01】使用 MyBatisPlus 实现单表的 CURD + 分页查询功能 4.使用 updateById() 更新数据

删除掉email属性上的updateStrategy = FieldStrategy.IGNORED,重启项目后,访问updateAuthorByUpdateWrapper() ,如下图:

【MyBatisPlus 01】使用 MyBatisPlus 实现单表的 CURD + 分页查询功能 5.使用 UpdateWrapper 更新数据

可以看到更新符合预期,没有传的值并没有更新,但是使用这种方法不好的点在于使用的是硬编码的方式进行更新,和代码耦合严重。因此就有了LambdaUpdateWrapper

使用LambdaUpdateWrapper方式进行值更新,结果如下图:

【MyBatisPlus 01】使用 MyBatisPlus 实现单表的 CURD + 分页查询功能 6.使用 LambdaUpdateWrapper更新数据

可以看到,更新符合预期,且没有采用硬编码的方式进行逻辑处理,这种方式最好!

MyBatisPlus中的分页查询

同样的,除了更新操作有 LambdaUpdateWrapper,查询相关的操作也有对应的LambdQueryWrapper给我们使用,这样在处理操作相关的操作时,也不用硬编码到代码中,

接下来以分页查询为例,进行说明,MyBatisPlus 集成了分页查询功能,只需要我们写一个配置类并添加到Spring容器中即可

@Configuration
public class MpPaginationConfig {

    // 分页配置
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

}

分页查询方法定义 在 AuthorService 中添加如下方法定义,并在实现类中实现该方法

// 分页查询
Page<AuthorPO> findAuthorsByPage(String username,String nickname, Integer currentPage, Integer pageSize);
// AuthorServiceImpl
@Override
public Page<AuthorPO> findAuthorsByPage(String username, String nickname, Integer currentPage, Integer pageSize) {
    Page<AuthorPO> page = new Page<>(currentPage, pageSize);
    LambdaQueryWrapper<AuthorPO> queryWrapper = new LambdaQueryWrapper<>();
    if (StringUtils.hasLength(username)) {
        // 用户名模糊查询
        queryWrapper.like(AuthorPO::getUsername, username);
    }
    if (StringUtils.hasLength(nickname)) {
        // 昵称模糊查询
        queryWrapper.like(AuthorPO::getNickname, nickname);
    }
    return authorMapper.selectPage(page, queryWrapper);
}

Page 类封装了分页查询的相关结果,如页数、总条数、每一页展示的数据多少等,在进行分页查询时,需要先封装一个Page对象,然后调用 BaseMapper 的 selectPage(page,wrapper) 方法,该方法返回一个 Page 对象,这个对象封装了此次查询的结果。

Controller中的代码如下:在该方法中封装了返回的数据。

@GetMapping("page")
public PageResult<List<AuthorPO>> findAuthorsByPage(@RequestParam String username,
                                                    @RequestParam String nickname,
                                                    @RequestParam(defaultValue = "1") Integer currentPage,
                                                    @RequestParam(defaultValue = "5") Integer pageSize) {

    Page<AuthorPO> authors = authorService.findAuthorsByPage(username, nickname, currentPage, pageSize);
    List<AuthorPO> records = authors.getRecords();
    if (!records.isEmpty()) {
        long curPage = authors.getCurrent();
        long totalPage = authors.getPages();
        long size = authors.getSize();
        long totalRecords = authors.getTotal();
        return PageResult.success(totalRecords, totalPage, size, curPage, records);
    }
    return PageResult.successNoData();
}

查询结果如下图所示:

【MyBatisPlus 01】使用 MyBatisPlus 实现单表的 CURD + 分页查询功能 7.分页查询接口调用

MyBatisPlus中的删除

除了上述所提到的方法,同样的在 BaseMapper 中也定义deleteById()delete(entity)deleteBatchIds()用于数据的删除,这是从数据库里物理删除;但一般在实际的应用里我们不会采用物理删除的方式(太残暴了.jpg),而是采用逻辑删除的方式,即给实体添加一个字段用于控制该记录是否删除,如deletion值为0表示没有删除,值为1表示已经被"删除"。

这种方式我们可以使用上面的更新操作进行值的更新从而进行删除操作,MyBatisPlus的开发者也想到了逻辑删除的使用场景,因此他给我们提供一个注解@TableLogic用于处理逻辑删除

给实体类和数据表添加deletion字段

@TableField(value = "deletion")
@TableLogic(value = "0", delval = "1")
private Integer deletion;

在Controller中添加如下代码

@DeleteMapping("del")
public ResultData deleteAuthorById(@RequestBody AuthorPO authorPO){

    boolean b = authorService.removeById(authorPO.getId());
    if (b){
        return  ResultData.success(null);
    }
    return ResultData.failed();
}
    

此时调用删除接口,如下图

【MyBatisPlus 01】使用 MyBatisPlus 实现单表的 CURD + 分页查询功能 8.使用 @TableLogic 配合 removeById 进行逻辑删除

可以看到调用封装的removeById()方法则执行的是更新语句,如果没有使用@TableLogic则将执行delete删除语句。