mybatis-plus 用法总结

MyBatis-Plus(简称 MP)是 MyBatis 的增强工具,旨在简化开发者的 CRUD 操作。它在 MyBatis 的基础上提供了更多的功能和便利性,如代码生成器、分页插件、性能分析插件等,使开发者能够更高效地进行数据库操作。MyBatis-Plus 保持了 MyBatis 原有的灵活性和易用性,同时通过一些约定和默认实现减少了重复的代码编写。

1.常见注解

MybatisPlus会根据PO实体的信息来推断出表的信息,从而生成SQL的。默认情况下:

  • MybatisPlus会把PO实体的类名驼峰转下划线作为表名

  • MybatisPlus会把PO实体的所有变量名驼峰转下划线作为表的字段名,并根据变量类型推断字段类型

  • MybatisPlus会把名为id的字段作为主键

但很多情况下,默认的实现与实际场景不符,因此MybatisPlus提供了一些注解便于我们声明表信息。

1.1.@ TableName

描述:表名注解,标识实体类对应的表

java 复制代码
@TableName("user")
public class User {
    private Long id;
    private String name;
}

1.2@TableId

主键注解,标识实体类中的主键字段

属性 类型 必须指定 默认值 描述
value String "" 表名
type Enum IdType.NONE 指定主键类型
java 复制代码
@TableName("user")
public class User {
    @TableId
    private Long id;
    private String name;
}

IdType支持的类型有:

描述
AUTO 数据库 ID 自增
NONE 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUT insert 前自行 set 主键值
ASSIGN_ID 分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
ASSIGN_UUID 分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认 default 方法)

1.3@TableField

普通字段注解

java 复制代码
@TableName("user")
public class User {
    @TableId
    private Long id;
    private String name;
    private Integer age;
    @TableField("isMarried")
    private Boolean isMarried;
    @TableField("concat")
    private String concat;
}

一般情况下我们并不需要给字段添加@TableField注解,一些特殊情况除外:

  • 成员变量名与数据库字段名不一致

  • 成员变量是以isXXX命名,按照JavaBean的规范,MybatisPlus识别字段时会把is去除,这就导致与数据库不符。

  • 成员变量名与数据库一致,但是与数据库的关键字冲突。使用@TableField注解给字段名添加转义字符。

2.常见配置

大多数的配置都有默认值,因此我们都无需配置。但还有一些是没有默认值的,例如:

实体类的别名扫描包

2.1全局id类型

bash 复制代码
mybatis-plus:
  type-aliases-package: com.itheima.mp.domain.po
  global-config:
    db-config:
      id-type: auto # 全局id类型为自增长

2.2手写SQL

需要注意的是,MyBatisPlus也支持手写SQL的,而mapper文件的读取地址可以自己配置,默认值是classpath*:/mapper/**/*.xml,也就是说我们只要把mapper.xml文件放置这个目录下就一定会被加载。

bash 复制代码
mybatis-plus:
  mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,当前这个是默认值。
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.itheima.mp.mapper.UserMapper">

</mapper>

3.Mapper 接口

mapper提供了一些常见的crud方法,继承于BaseMapper后可直接使用

java 复制代码
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.mp.domain.po.User;

public interface UserMapper extends BaseMapper<User> {
}

3.1条件构造器

除了新增以外,修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以id作为where条件以外,还支持更加复杂的where条件。

Wrapper的子类AbstractWrapper提供了where中包含的所有条件构造方法

QueryWrapper在AbstractWrapper的基础上拓展了一个select方法,允许指定查询字段:

UpdateWrapper在AbstractWrapper的基础上拓展了一个set方法,允许指定SQL中的SET部分:

3.2新增

java 复制代码
    @Test
    void testInsert() {
        User user = new User();
        user.setId(5L);
        user.setUsername("Lucy");
        user.setPassword("123");
        user.setPhone("18688990011");
        user.setBalance(200);
        user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        userMapper.insert(user);
    }

3.3通过id查询

java 复制代码
   @Test
    void testQueryById() {
        User user = userMapper.selectById(5L);
        System.out.println("user = " + user);
    }

3.4通过id批量查询

java 复制代码
 @Test
    void testQueryByIds() {
        List<User> users = userMapper.selectBatchIds(List.of(1L, 2L, 3L, 4L));
        users.forEach(System.out::println);
    }

3.5查询一行数据

若实际上有多行数据,会报错

java 复制代码
@Test
    void testSelectOne() {
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .eq("username", "特定用户名");
        User user = userMapper.selectOne(wrapper);
        System.out.println("user = " + user);
    }

3.6查询记录数

java 复制代码
   @Test
    void testCount() {
        QueryWrapper<User> wrapper = new QueryWrapper<User>().like("username", "o");
        long count = userMapper.selectCount(wrapper);
        System.out.println("符合条件的用户数量: " + count);
    }

3.7Map查询

java 复制代码
 @Test
    void testSelectByMap() {
        Map<String, Object> map = new HashMap<>();
        map.put("username", "Jack");
        map.put("phone", "13900112224");
        List<User> users = userMapper.selectByMap(map);
        users.forEach(System.out::println);
    }

3.8多条件构造查询

java 复制代码
    @Test
    void testQueryWrapper() {
        // 1.构建查询条件 where name like "%o%" AND balance >= 1000
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .select("id", "username", "info", "balance")
                .like("username", "o")
                .ge("balance", 1000);
        // 2.查询数据
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }
    @Test
    void testLambdaQueryWrapper() {
        // 1.构建条件 WHERE username LIKE "%o%" AND balance >= 1000
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.lambda()
                .select(User::getId, User::getUsername, User::getInfo, User::getBalance)
                .like(User::getUsername, "o")
                .ge(User::getBalance, 1000);
        // 2.查询
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }

3.9分页查询

java 复制代码
  @Test
    void testPageQuery() {
        Page<User> page = new Page<>(1, 10); // 第一页,每页10条记录
        QueryWrapper<User> wrapper = new QueryWrapper<User>().orderByDesc("id");
        IPage<User> result = userMapper.selectPage(page, wrapper);
        result.getRecords().forEach(System.out::println);
        System.out.println("总记录数: " + result.getTotal());
    }

3.10通过id更新

user中必须设置id,不然更新不了,没有设置默认为null。以为没有id=null的值,故不更新

其他参数没有设置,不会进行修改

java 复制代码
   @Test
    void testUpdateById() {
        User user = new User();
        user.setId(5L);
        user.setBalance(20000);
        userMapper.updateById(user);
    }

3.11构造条件更新

java 复制代码
    @Test
    void testUpdateByQueryWrapper() {
        // 1.构建查询条件 where name = "Jack"
        QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "Jack");
        // 2.更新数据,user中非null字段都会作为set语句
        User user = new User();
        user.setBalance(2000);
        userMapper.update(user, wrapper);
    }

3.12自定义SQL更新

java 复制代码
  @Test
    void testUpdateWrapper() {
        List<Long> ids = List.of(1L, 2L, 4L);
        // 1.生成SQL
        UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
                .setSql("balance = balance - 200") // SET balance = balance - 200
                .in("id", ids); // WHERE id in (1, 2, 4)
        // 2.更新,注意第一个参数可以给null,也就是不填更新字段和数据,
        // 而是基于UpdateWrapper中的setSQL来更新
        userMapper.update(null, wrapper);
    }

3.13通过id删除

java 复制代码
    @Test
    void testDeleteUser() {
        userMapper.deleteById(5L); //删除
    }

3.14通过id批量删除

java 复制代码
    @Test
    void testDeleteUsers() {
        userMapper.deleteBatchIds(List.of(1L, 2L, 3L, 4L)); //批量删除
    }

4.Service 接口

mybatis-plus在service接口中同样也提供了基础的crud方法,在接口在需要继承IService类

java 复制代码
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;

public interface IUserService extends IService<User> {
    // 拓展自定义方法
}

实现IUserService接口,并指定mapper接口

java 复制代码
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.po.service.IUserService;
import com.itheima.mp.mapper.UserMapper;
import org.springframework.stereotype.Service;

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

4.1save新增

  • save是新增单个元素

  • saveBatch是批量新增

  • saveOrUpdate是根据id判断,如果数据存在就更新,不存在则新增

  • saveOrUpdateBatch是批量的新增或修改

4.2remove删除:

  • removeById:根据id删除

  • removeByIds:根据id批量删除

  • removeByMap:根据Map中的键值对为条件删除

  • remove(Wrapper<T>):根据Wrapper条件删除

  • removeBatchByIds

4.3update修改:

  • updateById:根据id修改

  • update(Wrapper<T>):根据UpdateWrapper修改,Wrapper中包含setwhere部分

  • update(T,Wrapper<T>):按照T内的数据修改与Wrapper匹配到的数据

  • updateBatchById:根据id批量修改

4.4查询一条Get:

  • getById:根据id查询1条数据

  • getOne(Wrapper<T>):根据Wrapper查询1条数据

  • getBaseMapper:获取Service内的BaseMapper实现,某些时候需要直接调用Mapper内的自定义SQL时可以用这个方法获取到Mapper

4.5批量查询List:

  • listByIds:根据id批量查询

  • list(Wrapper<T>):根据Wrapper条件查询多条数据

  • list():查询所有

4.6计数Count

  • count():统计所有数量

  • count(Wrapper<T>):统计符合Wrapper条件的数据数量

4.7分页

java 复制代码
@Service
public class UserService extends ServiceImpl<UserMapper, User> {

    /**
     * 根据用户名获取用户列表的分页数据
     *
     * @param currentPage 当前页码
     * @param pageSize 每页大小
     * @param username 用户名
     * @return 分页后的用户列表
     */
    public IPage<User> getUserPageByName(int currentPage, int pageSize, String username) {
        // 创建分页对象
        Page<User> page = new Page<>(currentPage, pageSize);

        // 创建查询条件
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("username", username); // 使用 like 进行模糊匹配
        
        // 执行分页查询,带条件
        return this.page(page, queryWrapper);
    }
}

5.静态工具

有的时候Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:Db,其中的一些静态方法与IService中方法签名基本一致,也可以帮助我们实现CRUD功能。Db中的静态方法,必须

示例:

User.class表示查询user表中,id为1的值

java 复制代码
@Test
void testDbGet() {
    User user = Db.getById(1L, User.class);
    System.out.println(user);
}

利用Db实现复杂条件查询

java 复制代码
@Test
void testDbList() {
    List<User> list = Db.lambdaQuery(User.class)
            .like(User::getUsername, "o")
            .ge(User::getBalance, 1000)
            .list();
    list.forEach(System.out::println);
}

我们采用了Db的静态方法,因此避免了注入其他service,减少了循环依赖的风险。

6.逻辑删除

用户注销账号后,我们往往不会真正的删除该用户,而是通过一个字段表示用户在逻辑上已经删除了。但是这样的操作,在以后业务查询上,都需要确认用户有没有删除,造成不必要的麻烦。假如deleted(1删除,0未删除)字段,我们仅需要在配置文件中进行下述配置,mybatis-plus就会自动进行判断

bash 复制代码
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

注意,只有MybatisPlus生成的SQL语句才支持自动的逻辑删除,自定义SQL需要自己手动处理逻辑删除。

7.定义枚举

java 复制代码
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;

@Getter
public enum UserStatus {
    NORMAL(1, "正常"),
    FREEZE(2, "冻结");
    @EnumValue //标记枚举属性
    private final int value;
    @JsonValue //标记JSON序列化时展示的字段
    private final String desc;

    UserStatus(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }
}

要让MybatisPlus处理枚举与数据库类型自动转换,我们必须告诉MybatisPlus,枚举中的哪个字段的值作为数据库值。 MybatisPlus提供了@EnumValue注解来标记枚举属性,并且在UserStatus枚举中通过@JsonValue注解标记JSON序列化时展示的字段

配置枚举处理

bash 复制代码
mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

8.JSON 类型处理器

数据库的user表中有一个info字段,是JSON类型:

{"age": 20, "intro": "佛系青年", "gender": "male"}

而目前User实体类中却是String类型:

这样一来,我们要读取info中的属性时就非常不方便。如果要方便获取,info的类型最好是一个Map或者实体类。

而一旦我们把info改为对象类型,就需要在写入数据库时手动转为String,再读取数据库时,手动转换为对象,这会非常麻烦。

因此MybatisPlus提供了很多特殊类型字段的类型处理器,解决特殊字段类型与数据库类型转换的问题。例如处理JSON就可以使用JacksonTypeHandler处理器。

接下来,我们就来看看这个处理器该如何使用。

定义实体

首先,我们定义一个单独实体类来与info字段的属性匹配:

java 复制代码
import lombok.Data;

@Data
public class UserInfo {
    private Integer age;
    private String intro;
    private String gender;
}

3.4.2.使用类型处理器

接下来,将User类的info字段修改为UserInfo类型,并声明类型处理器:

java 复制代码
   /**
     * 详细信息
     */
    @TableField(typeHandler = JacksonTypeHandler.class)
    private UserInfo info;

9.配置加密

目前我们配置文件中的很多参数都是明文,如果开发人员发生流动,很容易导致敏感信息的泄露。所以MybatisPlus支持配置文件的加密和解密功能。

我们以数据库的用户名和密码为例。

生成 秘钥

首先,我们利用AES工具生成一个随机秘钥,然后对用户名、密码加密:

java 复制代码
    @Test
    void contextLoads() {
        // 生成 16 位随机 AES 密钥
        String randomKey = AES.generateRandomKey();
        System.out.println("randomKey = " + randomKey);

        // 利用密钥对用户名加密
        String username = AES.encrypt("root", randomKey);
        System.out.println("username = " + username);

        // 利用密钥对用户名加密
        String password = AES.encrypt("MySQL123", randomKey);
        System.out.println("password = " + password);

    }

randomKey = 6234633a66fb399f

username = px2bAbnUfiY8K/IgsKvscg==

password = FGvCSEaOuga3ulDAsxw68Q==

bash 复制代码
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: mpw:QWWVnk1Oal3258x5rVhaeQ== # 密文要以 mpw:开头
    password: mpw:EUFmeH3cNAzdRGdOQcabWg== # 密文要以 mpw:开头

10.分页插件

java 复制代码
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // 初始化核心插件
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}
java 复制代码
@Test
void testPageQuery() {
    // 1.分页查询,new Page()的两个参数分别是:页码、每页大小
    Page<User> p = userService.page(new Page<>(2, 2));
    // 2.总条数
    System.out.println("total = " + p.getTotal());
    // 3.总页数
    System.out.println("pages = " + p.getPages());
    // 4.数据
    List<User> records = p.getRecords();
    records.forEach(System.out::println);
}

这里用到了分页参数,Page,即可以支持分页参数,也可以支持排序参数。

java 复制代码
int pageNo = 1, pageSize = 5;
// 分页参数
Page<User> page = Page.of(pageNo, pageSize);
// 排序参数, 通过OrderItem来指定
page.addOrder(new OrderItem("balance", false));

userService.page(page);

分页实体PageDTO

java 复制代码
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.List;

@Data
@ApiModel(description = "分页结果")
public class PageDTO<T> {
    @ApiModelProperty("总条数")
    private Long total;
    @ApiModelProperty("总页数")
    private Long pages;
    @ApiModelProperty("集合")
    private List<T> list;
}

定义分页查询时,查询的页号。用于前端的参数

java 复制代码
@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {
    @ApiModelProperty("页码")
    private Long pageNo;
    @ApiModelProperty("页面大小")
    private Long pageSize;
    @ApiModelProperty("排序字段")
    private String sortBy;
    @ApiModelProperty("是否升序")
    private Boolean isAsc;
}

11.自动填充

我们在设计表时,一般都需要保存数据的创建时间和更新时间,为了不必要的麻烦,每次都需要设置,我们可以使用mybatis-plus提供的进行自动填充

构造抽象类

后续所有的实体类,都可以继承这个基础的实体类,以提高代码的复用性

在需要自动创建更新的属性上,加上@TableField注解

java 复制代码
public abstract class BasePojo {
    //文件创建,自动填充。需要实现MetaObjectHandle接口,进行处理
    @TableField(fill = FieldFill.INSERT)
    private Date created;
    //文件更新,自动填充
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updated;
}

实现MetaObjectHandle接口

java 复制代码
package com.tanhua.sso.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        // 使用严格模式插入填充,如果字段为null则进行填充
        this.strictInsertFill(metaObject, "created", Date.class, new Date()); // 创建时间
        this.strictInsertFill(metaObject, "updated", Date.class, new Date()); // 更新时间
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        // 使用严格模式更新填充,总是设置更新时间
        this.strictUpdateFill(metaObject, "updated", Date.class, new Date());
    }
}

版本兼容性问题

若业务中mybatis-plus版本较低,可能不含strict方法,可以采用以下写法

java 复制代码
package com.tanhua.sso.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        //字段为空,自动填充字段
        Object created = getFieldValByName("created", metaObject);
        if(null==created){
            setFieldValByName("created",new Date(),metaObject);
        }
        Object updated = getFieldValByName("updated", metaObject);
        if(null==updated){
            setFieldValByName("updated",new Date(),metaObject);
        }
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        //更新数据时,更新字段
        setFieldValByName("updated",new Date(),metaObject);
    }
}
相关推荐
庞传奇5 分钟前
【LC】191. 位1的个数
java·数据结构·算法·leetcode
禁默1 小时前
深入浅出:Java 抽象类与接口
java·开发语言
小万编程2 小时前
【2025最新计算机毕业设计】基于SSM的医院挂号住院系统(高质量源码,提供文档,免费部署到本地)【提供源码+答辩PPT+文档+项目部署】
java·spring boot·毕业设计·计算机毕业设计·项目源码·毕设源码·java毕业设计
白宇横流学长2 小时前
基于Java的银行排号系统的设计与实现【源码+文档+部署讲解】
java·开发语言·数据库
123yhy传奇2 小时前
【学习总结|DAY027】JAVA操作数据库
java·数据库·spring boot·学习·mybatis
code2roc2 小时前
SpringBoot集成ECDH密钥交换
spring boot·ecdh·密钥交换·密钥协商
想要打 Acm 的小周同学呀2 小时前
亚信科技Java后端外包一面
java·求职·java后端
lishiming03086 小时前
TestEngine with ID ‘junit-jupiter‘ failed to discover tests 解决方法
java·junit·intellij-idea
HEU_firejef6 小时前
设计模式——工厂模式
java·开发语言·设计模式
Kobebryant-Manba6 小时前
单元测试学习2.0+修改私有属性
java·单元测试·log4j