当前位置: 首页 > news >正文

MyBatisPlus 学习笔记

文章目录

  • ` MyBatisPlus` 快速入门
    • 第一步:引入 `MyBaitsPlus` 起步依赖
    • 第二步:自定义的 `Mapper` 继承 `BaseMapper` 接口
      • 新增相关
      • 修改相关
      • 删除相关
      • 查询相关
    • `Mp` 使用示例
  • `MyBaitsPlus` 常见注解
    • `MP` 实体类与数据库信息约定
    • `Mp` 实体类与数据库信息约定不符合解决方法
    • `MP` 自定义常见配置
  • `MyBatisPlus` 条件构造器
    • `AbstractWrapper` 公共方法
      • 比较条件
      • 模糊查询
      • 范围条件
      • 逻辑条件
      • 注意事项
      • 高级特性
    • `QueryWrapper` 特有方法
    • **`UpdateWrapper`** 特有方法
    • `LambdaWrapper` 示例
  • `MyBatisPlus` 自定义 `SQL`
  • `MyBatisPlus` 的 `IService` 接口
    • `IService` 接口使用
      • 第一步:`Service` 接口实现 `IService<T>`
      • 第二步:`Service` 实现类继承 `ServiceImpl<M extends BaseMapper<T>, T>`
      • 第三步:编写 `SQL`
    • `IService` 开发基础业务接口
      • `UserController` 层
      • `USerService` 接口
      • `UserService` 实现类
      • `UserMapper` 层
    • `IService` 的 `Lambda` 查询与更改
      • **常用条件方法**
      • `Iservice` 中的 `LambdaQuery`
      • `Iservice` 中的 `LambdaUpdate`
      • `IService` 批量新增
        • 普通 `for` 方案
        • `Mp` 批量处理方案
        • `Mp` 批量处理配合 `rewriteBatchedStatements=true`
    • 新增数据
    • 删除数据
    • 修改数据
    • 查询数据
    • 统计与分页
    • 链式操作(高级)
  • `MyBatisPlus` 拓展功能
    • `MP` 代码生成器
      • 安装插件
      • 使用
    • `Mp` 静态工具
      • 示例一
      • 示例二
    • `MP` 逻辑删除
    • `Mp` 枚举处理器
    • `Mp` 的 `Json` 处理器
    • `Mp` 插件功能
      • `Mp` 分页插件基本使用
      • `Mp` 通用分页查询
      • `MP` 通用分页实体改造

MyBatisPlus 快速入门

第一步:引入 MyBaitsPlus 起步依赖


在这里插入图片描述

第二步:自定义的 Mapper 继承 BaseMapper 接口


BaseMapper 接口提前定义好了一大堆增删改查的方法

注意:泛型为对应实体类

在这里插入图片描述

新增相关

  • insert (T entity)
INSERT INTO {表名} 
({字段1}, {字段2}, ...) 
VALUES (#{entity.属性1}, #{entity.属性2}, ...)

修改相关

  • updateById(T entity)
UPDATE {表名} 
SET {字段1}=#{entity.属性1}, {字段2}=#{entity.属性2}, ... 
WHERE id = #{entity.id}
  • update(T entity, Wrapper<T> wrapper)\
UPDATE {表名} 
SET {字段1}=#{entity.属性1}, {字段2}=#{entity.属性2}, ... 
WHERE {wrapper动态生成的条件}

删除相关

  • deleteById(Serializable id)
DELETE FROM {表名} WHERE id = #{id}
  • deleteByMap(Map<String, Object> map)
DELETE FROM {表名} 
WHERE {map中的键值对条件} 
-- 例如:name = 'John' AND age = 25
  • deleteBatchIds(Collection<?> ids)
DELETE FROM {表名} 
WHERE id IN (id1, id2, id3...)

查询相关

  • selectById(Serializable id)
SELECT * FROM {表名} WHERE id = #{id}
  • selectBatchIds(Collection<?> ids)
SELECT * FROM {表名} 
WHERE id IN (id1, id2, id3...)
  • selectOne(Wrapper<T> wrapper)
SELECT * FROM {表名} 
WHERE {wrapper动态生成的条件} 
LIMIT 1
  • selectList(Wrapper<T> wrapper)
SELECT * FROM {表名} 
WHERE {wrapper动态生成的条件}

Mp 使用示例

 	//增
 	@Test
    void testInsert() {
        User user = new User();
        //user.setId(5L);
        user.setUsername("ErGouZi");
        user.setPassword("123");
        user.setPhone("18688990013");
        user.setBalance(200);
        user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        userMapper.insert(user);
    }
 
 //查
 @Test
    void testQueryByIds() {
        List<User> users = userMapper.selectBatchIds(List.of(1L, 2L, 3L, 4L));
        users.forEach(System.out::println);
    }
 
 //改
  @Test
    void testUpdateById() {
        User user = new User();
        user.setId(5L);
        user.setBalance(20000);
        userMapper.updateById(user);
    }
 
 //查
   @Test
    void testSelectById() {
        User user = userMapper.selectById(5L);
        System.out.println("user = " + user);
    }   
  
  //删
  @Test
    void testDeleteUser() {
        userMapper.deleteById(5L);
    }  

MyBaitsPlus 常见注解

MP 实体类与数据库信息约定


在这里插入图片描述

Mp 实体类与数据库信息约定不符合解决方法


实体类表名字段和数据库约定不符

  • 加上对应的 @TableName@TableId, TbaleField

主键自增长问题

  • 没用 type 默认采用雪花算法

  • 数据库配置了 AUTO_INCREMENT,实体类必须标注 @TableId(type = IdType.AUTO)

  • 数据库未配置 AUTO_INCREMENT,可以选择 INPUT 或者 ASSIGN_ID

实体类 is 开头问题

  • 实体类 isMarried,数据库 is_Married 符合映射规则。但是 Java 中会忽略 is 所以还是需要手动加上 @TableField

字段名和数据库关键字冲突问题

  • 比如下面的 order。和数据库关键字冲突,所以笔记加上 ```飘号。

实体类有,数据库没有的字段问题

  • 使用 @TableField(exist = false)

在这里插入图片描述

MP 自定义常见配置


全局 id 配置配了,我们字段可以不用配置 type = ....

图中除了 type-aliases-package 其他都是默认值

MyBatisPlus 条件构造器

在这里插入图片描述
在这里插入图片描述

 @Test
    void testQueryWrapper() {
        // 1.构建查询条件
        QueryWrapper<User> warpper = new QueryWrapper<User>()
                    .select("id", "username", "info", "balance")
                    .like("username", "o")
                    .ge("balance", 1000);

        // 2.查询
        userMapper.selectList(warpper);
    }

AbstractWrapper 公共方法

比较条件


  • eq(R column, Object val)column = val
  • ne(R column, Object val)column ≠ val
  • gt(R column, Object val)column > val
  • ge(R column, Object val)column ≥ val
  • lt(R column, Object val)column < val
  • le(R column, Object val)column ≤ val

模糊查询

  • like(R column, Object val)column LIKE '%val%'
  • notLike(R column, Object val)column NOT LIKE '%val%'
  • likeLeft(R column, Object val)column LIKE '%val(左模糊)
  • likeRight(R column, Object val)column LIKE 'val%'(右模糊)

范围条件

  • between(R column, Object val1, Object val2)BETWEEN val1 AND val2
  • notBetween(R column, Object val1, Object val2)NOT BETWEEN
  • in(R column, Object... values)in(...)

逻辑条件

  • and()AND 连接多个条件
  • or()OR 连接多个条件
  • nested() → 嵌套条件

注意事项

  1. 链式调用:通过 . 连接多个条件(默认 AND 逻辑)
  2. 防 SQL 注入:优先使用 LambdaQueryWrapper 避免字段名硬编码
  3. 性能优化:避免在循环中频繁创建 Wrapper 对象
  4. 空值处理eq(null, val) 会忽略条件,需手动判空

高级特性

  • 实体绑定setEntity(T entity) 自动从实体类提取条件
  • 字段过滤columnsSqlInjectFilter() 防止非法字段注入
  • Lambda 支持LambdaQueryWrapper 提供类型安全的字段引用(如 User::getName

QueryWrapper 特有方法


  • select(String... columns)SELECT column1, column2(指定返回字段)

  • allEq(Map<R, V> params) → 批量等值匹配(如 column1=val1 AND column2=val2

  @Test
    void testQueryWrapper() {
        // 1.构建查询条件
        QueryWrapper<User> warpper = new QueryWrapper<User>()
                    .select("id", "username", "info", "balance")
                    .like("username", "o")
                    .ge("balance", 1000);

        // 2.查询
        List<User> users = userMapper.selectList(warpper);
        users.forEach(System.out::println);
    }
 @Test
    void testUpdateByQueryWrapper2() {

        // 1. 要更新的信息
        User user = new User();
        user.setBalance(2000);
        // 2. 更新的条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "jack");

        // 3. 执行更行
        int update = userMapper.update(user, wrapper);

    }

UpdateWrapper 特有方法


  • set(String column, Object value)SET column = value
  • setSql(String sql) → 直接拼接 SET 语句(如 SET balance = balance + 100
 @Test
    void testQueryWrapper2() {

        User user = new User();

        UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
                .set("balance", 2000)
                .eq("username", "jack");


        int update = userMapper.update(user, wrapper);

    }
 @Test
    void testUpdateWrapper() {
        User user = new User();
        UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
                .setSql("balance = balance - 200")
                .in("id", 1, 2, 3);

        userMapper.update(user, wrapper);

    }

LambdaWrapper 示例


在这里插入图片描述

    @Test
    void testLambdaQueryWrapper() {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>()
                .select(User::getId, User::getUsername, User::getInfo, User::getBalance)
                .like(User::getUsername, "o")
                .ge(User::getBalance, 1000);
    }
 .select(User::getId, User::getUsername, User::getInfo, User::getBalance) 
 
 转为常规 Lambda 底层是 Function
 new Function<Long, User>() {
     @Override
     public Long apply(User user) {
		return user.getId()
     }
 }    
 
 .select(user -> user.getId(), user -> user.getUsername(), user -> user.getInfo(), user -> user.getBalance());

MyBatisPlus 自定义 SQL

很多公司数据库代码不能写在业务层。所以我们可以写好复杂条件,然后去数据库层拼接

在这里插入图片描述

 @Test
    void testCustomSqlUpdate() {
        // 1.更新条件
        List<Long> ids = List.of(1L, 2L, 4L);
        int amount = 200;

        // 2.定义条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .in("id", ids);

        // 3.调用自定义SQL方法
        userMapper.updateBalanceByIds(wrapper, amount);

    }
public interface UserMapper extends BaseMapper<User> {
	//这里条件必须叫 ew
	void updateBalanceByIds(@Param("ew") QueryWrapper<User> wrapper,@Param("amount") int amount);
}
<?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">

    <update id="updateBalanceByIds">
        <!--  ${ew.customSQlSegment} 固定写法 拼接条件 这里是 in(...)-->
        <!-- UPDATE tb_user SET balance = balance - ? WHERE (id IN (?,?,?))-->
        UPDATE tb_user SET balance = balance - #{amount} ${ew.customSQlSegment}
    </update>

</mapper>

MyBatisPlusIService 接口

IService 接口使用


我们自己的接口需要继承 Iservice 接口,我们自己的实现类需要继承 ServiceImpl

在这里插入图片描述

第一步:Service 接口实现 IService<T>

//IService<T> T 是实体类 entity
public interface IUserService extends IService<User> {

}

第二步:Service 实现类继承 ServiceImpl<M extends BaseMapper<T>, T>

//ServiceImpl<Mapper的类型, 实体类entity>
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService{

}

第三步:编写 SQL

@SpringBootTest
class IUserServiceTest {

    @Autowired
    private IUserService userservice;

    @Test
    void testSaveUser() {
        User user = new User();
        //user.setId(5L);
        user.setUsername("LiLei");
        user.setPassword("123");
        user.setPhone("18688990013");
        user.setBalance(200);
        user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        userservice.save(user);
    }

    @Test
    void testQuery() {
        List<User> users = userservice.listByIds(List.of(1L, 2L, 4L));
        users.forEach(System.out::println);
    }

}

IService 开发基础业务接口


在这里插入图片描述

UserController

@Api(tags  = "用户管理接口")
@RequestMapping("/users")
@RestController
//对一开始需要初始化的变量去做构造函数。配合注解
@RequiredArgsConstructor
public class UserController {


    //final 必须要初始化。这里建议在构造函数初始化, 自动注入
    private final IUserService userService;


    @ApiOperation("新增用户接口")
    @PostMapping
    public void saveUser(@RequestBody UserFormDTO userDTO) {
        // 1. 把DTO拷贝到PO 从哪里拷贝到哪里
        User user = BeanUtil.copyProperties(userDTO, User.class);
        // 2. 新增
        userService.save(user);
    }

    @ApiOperation("根据id删除用户接口")
    @DeleteMapping("{id}")
    public void deleteUserById(@ApiParam("用户id") @PathVariable("id") Integer id) {
        userService.removeById(id);
    }

    @ApiOperation("根据id查询用户接口")
    @GetMapping("{id}")
    public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Integer id) {

        // 1. 查询用户 PO
        User user = userService.getById(id);

        // 2. 把 PO 拷贝到 VO
        return BeanUtil.copyProperties(user, UserVO.class);
    }

    @ApiOperation("根据id批量查询用户接口")
    @GetMapping
    public List<UserVO> queryUserById(@ApiParam("用户id的集合") @RequestParam("ids") List<Long> ids) {

        // 1. 查询用户 PO
        List<User> users = userService.listByIds(ids);

        // 2. 把 PO 拷贝到 VO
        // 把原始list 里面的对象拷贝到新的list 里面 转为别的类型
        return BeanUtil.copyToList(users, UserVO.class);
    }

    @ApiOperation("扣减用户余额接口")
    @DeleteMapping("{id}/deduction/{money}")
    public void deductBalance(
            @ApiParam("用户id") @PathVariable("id") Integer id,
            @ApiParam("扣减的金额") @PathVariable("money") Integer money) {
        userService.deductMoneyById(id, money);
    }
}

USerService 接口

public interface IUserService extends IService<User> {

    /**
     * 扣减用户余额
     * @param id
     * @param money
     */
    void deductMoneyById(Integer id, Integer money);
}

UserService 实现类

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

    /**
     * 扣减用户余额
     * @param id
     * @param money
     */
    @Override
    public void deductMoneyById(Integer id, Integer money) {
        // 1.查询用户状态  自己调自己的方法
        User user = getById(id);

        // 2.校验用户状态
        if (user == null || user.getStatus() == 2) {
            throw new RuntimeException("状态异常");
        }

        // 3.校验余额是否充足
        if (user.getBalance() < money) {
            throw new RuntimeException("余额不足");
        }

        // Service 实现类可直接用 baseMapper 调用 Mapper 方法,无需手动注入。 因为 extends ServiceImpl
        // 4.扣减余额 update tb_user set balance = balance - ? where id = ?
        baseMapper.deductBalanceById(id, money);
    }

}


UserMapper

public interface UserMapper extends BaseMapper<User> {
    
	@Update("update tb_user set balance = balance - #{money} where id = #{id}")
    void deductBalanceById(@Param("id") Integer id, @Param("money") Integer money);
}

IServiceLambda 查询与更改


常用条件方法

方法SQL 等价说明示例
eq()column = value等于.eq(User::getStatus, 1)
ne()column <> value不等于.ne(User::getRole, "admin")
gt()column > value大于.gt(User::getAge, 18)
ge()column >= value大于等于.ge(User::getScore, 60)
lt()column < value小于.lt(User::getBalance, 1000)
le()column <= value小于等于.le(User::getBalance, maxBalance)
like()column LIKE '%value%'模糊匹配.like(User::getName, "Tom")
between()column BETWEEN min AND max范围查询.between(User::getAge, 20, 30)
in()column IN (v1, v2...)包含在集合中.in(User::getId, idsList)

Iservice 中的 LambdaQuery

动态查询

在这里插入图片描述

  • 原始 MyBatis

在这里插入图片描述

  • MyBaitsPlus 改造
//就是动态SQL
@Override
    public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
        List<User> users = lambdaQuery().like(name != null, User::getUsername, name)
                //第一个参数条件,第二个参数要查询的字段 username,第三个参数要传进来匹配的值 username 的值			
            	.like(name != null, User::getUsername, name)
                .eq(status != null, User::getStatus, status)
               	.between(
                    minBalance != null && maxBalance != null, // 强制参数共存
                    User::getBalance, 
                    minBalance, 
                    maxBalance
				 )		
                .list();
        //- .one():最多1个结果
		//- .list():返回集合结果
		//- .count():返回计数结果

        return users;
    }

Iservice 中的 LambdaUpdate

在这里插入图片描述

  • 传统 xml
<update id="updateUserBalanceAndStatus">
    UPDATE tb_user
    <set>
        balance = #{remainBalance},
        <if test="remainBalance == 0">
            status = 2,
        </if>
    </set>
    WHERE
        id = #{id}
        AND balance = #{user.balance} <!-- 乐观锁:基于旧值校验 -->
</update>
  • LambdaUpdate 改造
   	@Override
    @Transactional
    public void deductMoneyById(Integer id, Integer money) {
        // 1.查询用户状态  自己调自己的方法
        User user = getById(id);

        // 2.校验用户状态
        if (user == null || user.getStatus() == 2) {
            throw new RuntimeException("状态异常");
        }

        // 3.校验余额是否充足
        if (user.getBalance() < money) {
            throw new RuntimeException("余额不足");
        }

        // Service 实现类可直接用 baseMapper 调用 Mapper 方法,无需手动注入。 因为 extends ServiceImpl
        // 4.扣减余额 update tb_user set balance = balance - ? where id = ?
        int remainBalance = user.getBalance() - money;
        lambdaUpdate()
                .set(User::getBalance, remainBalance)
                .set(remainBalance == 0, User::getStatus, 2)
                .eq(User::getId, id)
                .eq(User::getBalance, user.getBalance()) //乐观锁 用户的 balance = 查到的 balance 乐观锁校验
                .update();
    }

IService 批量新增

在这里插入图片描述

普通 for 方案

这种方案会发起 10000 次网络请求,mysql 内部有执行 10000 次查询。效率最低,不推荐

    @Test
    private User buildUser(int i) {
        User user = new User();
        user.setUsername("user_" + i);
        user.setPassword("123");
        user.setPhone("" + (186889900L + i));
        user.setBalance(2000);
        user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        return user;
    }

    @Test
    void testSaveOneByOne() {
        long b = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            userservice.save(buildUser(i));
        }
        long e = System.currentTimeMillis();
        System.out.println("耗时:" + (e - b));
    }

Mp 批量处理方案

这里我们每1000 条数据就发送一次网络请求过去。然后 MySQL 再逐条执行。效率还是差一点

  @Test
    private User buildUser(int i) {
        User user = new User();
        user.setUsername("user_" + i);
        user.setPassword("123");
        user.setPhone("" + (186889900L + i));
        user.setBalance(2000);
        user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        return user;
    }

    @Test
    void testSaveOneByOne() {
        long b = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            userservice.save(buildUser(i));
        }
        long e = System.currentTimeMillis();
        System.out.println("耗时:" + (e - b));
    }

    @Test
    void testSaveBatch() {
        // 我们每次批量插入 1000 条数据, 插入 100此机 10万条数据
        List<User> list = new ArrayList<>(1000);
        long b = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            // 2.添加一个 user
            list.add(buildUser(i));
            // 3.每1000条批量插入一次 1000条数据之后打包到  mysql 。也就是 1000条数据只用一次网络请求
            if (i % 1000 == 0) {
                userservice.saveBatch(list);
                // 4.清空集合 准备下一批数据
                list.clear();
            }
        }
        long e = System.currentTimeMillis();
        System.out.println("耗时:" + (e - b));
    }
Mp 批量处理配合 rewriteBatchedStatements=true

这里我们每1000 条数据就发送一次网络请求过去。然后将多态 SQL 合并未一条 MySQL 只执行一次。效率最高

INSERT INTO user ( username, password, phone, info, balance, create_time, update_time )
VALUES 
(user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01),
(user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01),
(user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01),
(user_4, 123, 18688190004, "", 2000, 2023-07-01, 2023-07-01);
  • 配置文件 yaml
//配置rewriteBatchedStatements=true
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: root
    password: Ting123321
  • 代码
  @Test
    private User buildUser(int i) {
        User user = new User();
        user.setUsername("user_" + i);
        user.setPassword("123");
        user.setPhone("" + (186889900L + i));
        user.setBalance(2000);
        user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        return user;
    }

    @Test
    void testSaveOneByOne() {
        long b = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            userservice.save(buildUser(i));
        }
        long e = System.currentTimeMillis();
        System.out.println("耗时:" + (e - b));
    }

    @Test
    void testSaveBatch() {
        // 我们每次批量插入 1000 条数据, 插入 100此机 10万条数据
        List<User> list = new ArrayList<>(1000);
        long b = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            // 2.添加一个 user
            list.add(buildUser(i));
            // 3.每1000条批量插入一次 1000条数据之后打包到  mysql 。也就是 1000条数据只用一次网络请求
            if (i % 1000 == 0) {
                userservice.saveBatch(list);
                // 4.清空集合 准备下一批数据
                list.clear();
            }
        }
        long e = System.currentTimeMillis();
        System.out.println("耗时:" + (e - b));
    }

新增数据


  • save(T): 新增单条数据 → 返回操作是否成功 (boolean)
  • saveBatch(Collection<T>): 批量新增 → 返回是否成功
  • saveBatch(Collection<T>, int batchSize): 指定批次大小的批量新增

删除数据


  • removeById(Serializable id): 根据 ID 删除单条数据
  • removeByMap(Map条件): 根据 Map 条件删除
  • removeByIds(Collection<?> ids): 批量删除(根据 ID 集合)
  • removeBatchByIds(Collection<?>, int batchSize): 指定批次大小的批量删除

修改数据


  • updateById(T): 根据 ID 修改单条数据 → 返回是否成功
  • saveOrUpdate(T): 存在则更新,否则新增(单条)
  • saveOrUpdateBatch(Collection<T>): 批量新增或更新

查询数据


  • getById(Serializable id): 根据 ID 查询单条 → 返回实体 (T)
  • getOne(Wrapper<T>条件): 根据条件查询单条 → 返回实体
  • listByIds(Collection<?> ids): 根据 ID 集合批量查询 → 返回列表 (List<T>)
  • listByMap(Map条件): 根据 Map 条件查询 → 返回列表

统计与分页


  • count(): 统计总数据量 → 返回 long
  • count(Wrapper<T>条件): 根据条件统计 → 返回 long
  • page(): 分页查询(需配合分页参数使用)

链式操作(高级)


  • lambdaQuery(): 链式 Lambda 条件查询(如 lambdaQuery().eq(...).list()
  • lambdaUpdate(): 链式 Lambda 条件更新(如 lambdaUpdate().set(...).eq(...)

MyBatisPlus 拓展功能

MP 代码生成器


在使用MybatisPlus以后,基础的MapperServicePO代码相对固定,重复编写也比较麻烦。因此MybatisPlus官方提供了代码生成器根据数据库表结构生成POMapperService等相关代码。只不过代码生成器同样要编码使用,也很麻烦。

在这里插入图片描述

安装插件

在这里插入图片描述

使用

  • 首先需要配置配置数据库地址

在这里插入图片描述
在这里插入图片描述

  • 然后选择 Code Generator,填写信息

在这里插入图片描述

在这里插入图片描述

Mp 静态工具


在这里插入图片描述

示例一

需求:改造根据id用户查询的接口,查询用户的同时返回用户收货地址列表

  • 收货地址 VO
@Data
@ApiModel(description = "收货地址VO")
public class AddressVO{

    @ApiModelProperty("id")
    private Long id;

    @ApiModelProperty("用户ID")
    private Long userId;

    @ApiModelProperty("省")
    private String province;

    @ApiModelProperty("市")
    private String city;

    @ApiModelProperty("县/区")
    private String town;

    @ApiModelProperty("手机")
    private String mobile;

    @ApiModelProperty("详细地址")
    private String street;

    @ApiModelProperty("联系人")
    private String contact;

    @ApiModelProperty("是否是默认 1默认 0否")
    private Boolean isDefault;

    @ApiModelProperty("备注")
    private String notes;
}
  • 用户 VO
    • 注意添加一个地址属性 List<AddressVO> addresses
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {

    @ApiModelProperty("用户id")
    private Long id;

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("详细信息")
    private String info;

    @ApiModelProperty("使用状态(1正常 2冻结)")
    private Integer status;

    @ApiModelProperty("账户余额")
    private Integer balance;

    //对多关系。
    @ApiModelProperty("用户的收获地址")
    private List<AddressVO> addresses;
}
  • UserController
    @ApiOperation("根据id查询用户接口")
    @GetMapping("{id}")
    public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Integer id) {

        return userService.queryUserAndAddressById(id);
    }
  • UserServiceImpl
    @Override
    public UserVO queryUserAndAddressById(Integer id) {
        // 1.查询用户
        User user = getById(id);
        if (user == null || user.getStatus() == 2) {
            throw new RuntimeException("用户状态异常!");
        }

        // 2.查询地址
        List<Address> addresses = Db.lambdaQuery(Address.class)
                .eq(Address::getUserId, id)
                .list();

        // 3.封装 VO
        // 3.1.转User的PO为VO
        UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
        // 3.2.转地址VO
        if(CollUtil.isNotEmpty(addresses)) {
            List<AddressVO> addressVos = BeanUtil.copyToList(addresses, AddressVO.class);
            userVO.setAddresses(addressVos);
        }
        return userVO;
    }

示例二

根据id批量查询用户,并查询出用户对应的所有地址

  • 收货地址 VO
@Data
@ApiModel(description = "收货地址VO")
public class AddressVO{

    @ApiModelProperty("id")
    private Long id;

    @ApiModelProperty("用户ID")
    private Long userId;

    @ApiModelProperty("省")
    private String province;

    @ApiModelProperty("市")
    private String city;

    @ApiModelProperty("县/区")
    private String town;

    @ApiModelProperty("手机")
    private String mobile;

    @ApiModelProperty("详细地址")
    private String street;

    @ApiModelProperty("联系人")
    private String contact;

    @ApiModelProperty("是否是默认 1默认 0否")
    private Boolean isDefault;

    @ApiModelProperty("备注")
    private String notes;
}
  • 用户 VO
    • 注意添加一个地址属性 List<AddressVO> addresses
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {

    @ApiModelProperty("用户id")
    private Long id;

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("详细信息")
    private String info;

    @ApiModelProperty("使用状态(1正常 2冻结)")
    private Integer status;

    @ApiModelProperty("账户余额")
    private Integer balance;

    //对多关系。
    @ApiModelProperty("用户的收获地址")
    private List<AddressVO> addresses;
}
  • UserController
    @ApiOperation("根据id查询用户接口")
    @GetMapping("{id}")
    public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Integer id) {

        return userService.queryUserAndAddressById(id);
    }
  • UserServiceImpl
    @Override
    public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {
        // 1.查询用户
        List<User> users = listByIds(ids);
        if(CollUtil.isEmpty(users)) {
            return Collections.emptyList();
        }

        // 2.查询地址
        // 2.1.获取用户id集合
        List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
        // 2.2.根据用户id查询地址
        List<Address> addresses = Db.lambdaQuery(Address.class)
                .in(Address::getUserId, userIds)
                .list();

        // 2.3.转换地址VO
        List<AddressVO> addressVOList = BeanUtil.copyToList(addresses, AddressVO.class);
        // 2.4.梳理地址集合, 分类整理, 相同用户的放入一个集合中
        Map<Long, List<AddressVO>> addressMap = new HashMap<>(0);
        if (CollUtil.isNotEmpty(addressVOList)) {
            addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
        }


        // 3.转换VO返回
        List<UserVO> list = new ArrayList<>(users.size());
        for (User user : users) {
            // 3.1.转User的PO为VO
            UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
            list.add(vo);
            // 3.2.转换Address的PO为VO
            vo.setAddresses(addressMap.get(user.getId()));
        }
        return list;

    }

MP 逻辑删除


或者直接在字段上面标注 @TableLogic 注解

在这里插入图片描述

在这里插入图片描述

  • 配置逻辑删除字段
mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-field: deleted #配置逻辑删除字段
  • 正常操作就进行逻辑删除

@SpringBootTest
class IAddressServiceTest {

    @Autowired
    private IAddressService addressService;

    @Test
    void testLogicDelete() {
        // 1. 删除
        addressService.removeById(59L);

        // 2. 查询
        Address adress = addressService.getById(59L);
        System.out.println("address = " + adress);
    }
}

在这里插入图片描述

Mp 枚举处理器


status这种字段我们一般会定义一个枚举,做业务判断的时候就可以直接基于枚举做比较。但是我们数据库采用的是int类型,对应的PO也是Integer。因此业务操作时必须手动把枚举Integer转换,非常麻烦。因此,MybatisPlus提供了一个处理枚举的类型转换器,可以帮我们把枚举类型与数据库类型自动转换。@EnumValue 表示把哪个字段往数据库写

在这里插入图片描述

  • 定义枚举类
@Getter
public enum UserStatus {
    NORMAL(1, "正常"),
    FREEZE(2, "冻结")
    ;
    private final int value;
    private final String desc;

    UserStatus(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }
}
  • 修改 User POJO 枚举字段为枚举类型
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {

    @ApiModelProperty("用户id")
    private Long id;

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("详细信息")
    private String info;

    //这里改为枚举类型
    @ApiModelProperty("使用状态(1正常 2冻结)")
    private UserStatus status;

    @ApiModelProperty("账户余额")
    private Integer balance;

    @ApiModelProperty("用户的收获地址")
    private List<AddressVO> addresses;
}

  • 枚举类中用 EnumValue 标记哪个字段的值作为数据库的值
public enum UserStatus {
    NORMAL(1, "正常"),
    FROZEN(2, "异常");

    @EnumValue
    private final int value;
    private final String desc;

    UserStatus(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }
}
  • 配置枚举处理器
mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
  • 枚举类用 @JsonValue 标记 JSON 序列化时展示的字段
    • 如果没标记默认用枚举值
public enum UserStatus {
    NORMAL(1, "正常"),
    FROZEN(2, "异常");

    @EnumValue
    private final int value;
    @JsonValue
    private final String desc;

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

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

MpJson 处理器


我们数据库User表中有一个 info 字段,是 JSON 类型吗,而对应 User 实体类的字段确实 String 类型,这样一来 info 属性要转来转去很麻烦。所以可以用 Mp 中的 JacksonTypeHandler 处理器自动处理

在这里插入图片描述

  • 定义一个单独的实体类和 Info 字段匹配
@Data
@NoArgsConstructor
//生成一个静态工厂方法调用全参构造
@AllArgsConstructor(staticName = "of")
public class UserInfo {
    private Integer age;
    private String intro;
    private String gender;
}

  • User 实体类修改字段类型为 UserInfo 类型,并声明类型处理器,同时声明自动映射
@Data
//声明自动映射
@TableName(value = "tb_user", autoResultMap = true)
public class User {

    /**
     * 用户id
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 注册手机号
     */
    private String phone;

    /**
     * 详细信息
     */
    //声明类型处理器
    @TableField(typeHandler = JacksonTypeHandler.class)
    private UserInfo info;

    /**
     * 使用状态(1正常 2冻结)
     */
    private UserStatus status;

    /**
     * 账户余额
     */
    private Integer balance;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    private LocalDateTime updateTime;
}

  • 测试
    @Test
    void testInsert() {
        User user = new User();
        //user.setId(5L);
        user.setUsername("ErGouZi");
        user.setPassword("123");
        user.setPhone("18688990013");
        user.setBalance(200);
        //这里正常插入数据就行,自动转为 JSON 类型
        user.setInfo(UserInfo.of(24, "英文老师", "female"));
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        userMapper.insert(user);
    }

Mp 插件功能


Mp 分页插件基本使用

  • 新建配置类,配置分页插件
@Configuration
public class MyBatisConfig {

   @Bean
    public MybatisPlusInterceptor myPlusInterceptor() {
    
      // 初始化核心插件
       MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

       // 1.创建分页插件
       PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
       // 1.1配置分页上限
       paginationInnerInterceptor.setMaxLimit(1000L);
       // 2.添加分页插件
       interceptor.addInnerInterceptor(paginationInnerInterceptor);
	
       //如果要添加其他插件,就先创建出来然后add进去就行
        
       return interceptor;
   }
}

  • 开始分页查询
   @Test
    void testPageQuery() {
        int pageNo = 1, pageSize = 2;
        // 1.准备分页条件
        Page<User> page = Page.of(pageNo, pageSize);
        // 1.1. 添加排序条件:按照 balance 升序排序,相同再按照 id 升序排序
        page.addOrder(new OrderItem("balance", true));
        page.addOrder(new OrderItem("id", true));

        // 2.开始分页查询
        Page<User> p = userservice.page(page);


        // 3.解析
        long total = p.getTotal();
        System.out.println("总记录数:" + total);
        long pages = p.getPages();
        System.out.println("总页数:" + pages);
        List<User> records = p.getRecords();
        records.forEach(System.out::println);
    }

Mp 通用分页查询

  • UserQuery:分页查询条件的实体,包含分页、排序参数、过滤条件
  • PageDTO:分页结果实体,包含总条数、总页数、当前页数据
  • UserVO:用户页面视图实体
  • 创建 PageQuery 分页条件类,并让 UserQuery 继承
    • pageNo:页码
    • pageSize:每页数据条数
    • sortBy:排序字段
    • isAsc:是否升序

其中缺少的仅仅是分页条件,而分页条件不仅仅用户分页查询需要,以后其它业务也都有分页查询的需求。因此建议将分页查询条件单独定义为一个PageQuery实体

@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {
    @ApiModelProperty("页码")
    private Long pageNo;
    @ApiModelProperty("页码")
    private Long pageSize;
    @ApiModelProperty("排序字段")
    private String sortBy;
    @ApiModelProperty("是否升序")
    private Boolean isAsc;
}
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery extends PageQuery {
    @ApiModelProperty("用户名关键字")
    private String name;
    @ApiModelProperty("用户状态:1-正常,2-冻结")
    private Integer status;
    @ApiModelProperty("余额最小值")
    private Integer minBalance;
    @ApiModelProperty("余额最大值")
    private Integer maxBalance;
}
  • UserVO
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {

    @ApiModelProperty("用户id")
    private Long id;

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("详细信息")
    private UserInfo info;

    @ApiModelProperty("使用状态(1正常 2冻结)")
    private UserStatus status;

    @ApiModelProperty("账户余额")
    private Integer balance;

    @ApiModelProperty("用户的收获地址")
    private List<AddressVO> addresses;
}

  • 分页实体 PageDTO
@Data
@ApiModel(description = "分页结果")
public class PageDTO<T> {
    @ApiModelProperty("总条数")
    private Long total;
    @ApiModelProperty("总页数")
    private Long pages;
    @ApiModelProperty("集合")
    private List<T> list;
}
  • Controller
 @ApiOperation("根据复杂条件分页查询接口")
    @GetMapping("/page")
    public PageDTO<UserVO> queryUsersPage(UserQuery query) {
        return userService.queryUserPage(query);
    }
  • UserServiceImpl
  /**
     * 根据复杂条件分页查询接口
     * @param query
     * @return
     */
    @Override
    public PageDTO<UserVO> queryUserPage(UserQuery query) {

        String name = query.getName();
        Integer status = query.getStatus();

        // 1.构建查询条件
        // 1.1.分页条件
        Page<User> page = Page.of(query.getPageNum(), query.getPageSize());
        if (StrUtil.isNotBlank(query.getSortBy())) {
            //不为空
            page.addOrder(new OrderItem(query.getSortBy(), query.getIsAsc()));
        } else {
            //为空, 默认按照更新时间排序
            page.addOrder(new OrderItem("update_time", false));
        }

        // 2.分页查询
        lambdaQuery()
                .like(name != null, User::getUsername, name)
                .eq(status != null, User::getStatus, status)
                .page(page);

        // 3.封装VO结果
        PageDTO<UserVO> dto = new PageDTO<>();

        // 3.1. 设置总条数
        dto.setTotal(page.getTotal());
        // 3.2. 设置总页数
        dto.setPages(page.getPages());
        // 3.3. 设置当前页数据
        List<User> records = page.getRecords();
        if (CollUtil.isEmpty(records)) {
            dto.setList(Collections.emptyList());
            return dto;
        }
        // 3.4 拷贝 user 的VO
        List<UserVO> vos = BeanUtil.copyToList(records, UserVO.class);
        dto.setList(vos);

        // 4.返回
        return dto;
    }

MP 通用分页实体改造

  • PageQuery
@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {

    @ApiModelProperty("页码")
    private Integer pageNum = 1;
    @ApiModelProperty("每页条数")
    private Integer pageSize = 5;
    @ApiModelProperty("排序字段")
    private String sortBy;
    @ApiModelProperty("是否升序")
    private Boolean isAsc = true;

    public <T> Page<T> toMpPage(OrderItem ... items) {
        // 1.分页条件
        Page<T> page = Page.of(pageNum, pageSize);
        // 2.排序条件
        if (StrUtil.isNotBlank(sortBy)) {
            //不为空
            page.addOrder(new OrderItem(sortBy, isAsc));
        } else if(items != null){
            //为空, 默认按照更新时间排序
            page.addOrder(items);
        }
       return page;
    }

    public <T> Page<T> toMoPage(String defaultSortBy, Boolean defaultAsc) {
        return toMpPage(new OrderItem("create_time", defaultAsc));
    }

    public <T> Page<T> toMoPageDefaultSortByCreateTime() {
        return toMpPage(new OrderItem("create_time", false));
    }

    public <T> Page<T> toMoPageDefaultSortByUpdateTime() {
        return toMpPage(new OrderItem("update_time", false));
    }


}

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


    public static <PO, VO> PageDTO<VO> of(Page<PO> p, Function<PO, VO> convertor) {

        PageDTO<VO> dto = new PageDTO<>();

        // 3.1. 设置总条数
        dto.setTotal(p.getTotal());
        // 3.2. 设置总页数
        dto.setPages(p.getPages());
        // 3.3. 设置当前页数据
        List<PO> records = p.getRecords();
        if (CollUtil.isEmpty(records)) {
            dto.setList(Collections.emptyList());
            return dto;
        }
        // 4 拷贝 user 的VO

        dto.setList(records.stream().map(convertor).collect(Collectors.toList()));

        // 4.返回
        return dto;
    }


}

  • UserServiceImpl
  @Override
    public PageDTO<UserVO> queryUserPage(UserQuery query) {

        String name = query.getName();
        Integer status = query.getStatus();

        // 1.构建查询条件
        Page<User> page = query.toMoPageDefaultSortByUpdateTime();

        // 2.分页查询
        Page<User> p = lambdaQuery()
                .like(name != null, User::getUsername, name)
                .eq(status != null, User::getStatus, status)
                .page(page);

        // 3.封装 VO 结果
        return PageDTO.of(p, user -> {
            // 1.拷贝基础属性
            UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
            // 2.处理特殊逻辑
            vo.setUsername(user.getUsername().substring(0, vo.getUsername().length() -2) + "**");

            return vo;
        });


    }

相关文章:

  • STM32单片机中EXTI的工作原理
  • GPIO_ReadInputData和GPIO_ReadInputDataBit区别
  • 批量给文件编排序号,支持数字序号及时间日期序号编排文件
  • 通过nvm管理多个node版本
  • 芯片同时具备Wi-Fi、蓝牙、Zigbee,MAC地址会打架吗?
  • 【android bluetooth 框架分析 01】【关键线程 6】【主线程与核心子线程协作机制】
  • 什么是八步工作法?
  • Windows下安装WSL2下的Ubuntu、docker容器的IP地址(上)
  • ESP32S3 链接到 WiFi
  • Python对Airbnb北京与上海链家租房数据用逻辑回归、决策树、岭回归、Lasso、随机森林、XGBoost、神经网络、聚类
  • Catch2 中对浮点数进行比较
  • Kingbase逻辑备份与恢复标准化实施文档
  • AI图像生成
  • MCU选型的五大维度--助力嵌入式产品设计
  • QT样式表实现一键换肤
  • Windows下 Eigen3 安装
  • 定制一款国密浏览器(4):修改浏览器logo
  • C++23 多维下标运算符:探索 P2128R6 提案
  • AI领域再突破,永洪科技荣获“2025人工智能+创新案例”奖
  • SpringBoot集成阿里云文档格式转换实现pdf转换word,excel
  • 做网站的税是多少/app推广拉新渠道
  • 创网通信科技有限公司/中小企业网站优化
  • wordpress技术博客主题/温州seo结算
  • 潜江网站开发/杭州产品推广服务公司
  • 哪有可以专门做外包项目的网站/市场推广是做什么的
  • 上海网站论坛建设/网站推广的方式有