Mybatis-Plus 速通

快速上手

拥有一个能用的demo

自己从0开始配,感觉有亿点难,没配成过,所以找了种比较特殊的上手方式——copy别人的demo。

你可以通过下载这个课里的学习资料,黑马的课,快速拥有一个可以用来学习后续内容的demo。

我自己学的时候,其实就是看的这套课。

快速操作一张单表(起步)

这里其实有些地方建表不规范,但是因为咱在这儿学的主要是mp,就先不关心乃些规范了。

这里以操作员工表为例:

  1. sql语句创建员工表
1CREATE TABLE emp (
2    id BIGINT NOT NULL AUTO_INCREMENT,  -- 主键,自增
3    name VARCHAR(255) NOT NULL,          -- 姓名
4    age INT NOT NULL,                   -- 年龄
5    department VARCHAR(255),            -- 部门
6    hire_date DATE,                     -- 入职日期
7    PRIMARY KEY (id)                    -- 设置主键
8);
  1. 创建员工对应的实体类
1package com.itheima.mp.pojo;
2
3import com.baomidou.mybatisplus.annotation.IdType;
4import com.baomidou.mybatisplus.annotation.TableId;
5import com.baomidou.mybatisplus.annotation.TableName;
6import lombok.AllArgsConstructor;
7import lombok.Data;
8import lombok.NoArgsConstructor;
9
10import java.time.LocalDate;
11@AllArgsConstructor
12@NoArgsConstructor
13@Data
14@TableName("emp") // 指定表名
15public class Emp {
16    @TableId(value = "id",type = IdType.AUTO) // 表示主键
17    private Long id;
18    private String name;
19    private Integer age;
20    private String department;
21    private LocalDate hireDate;
22}
  1. 创建对应的Mapper

这个类里边现在不需要写任何代码,因为mp已经帮我们写好了常用的单表操作,通过继承BaseMapper就能直接用了。

1package com.itheima.mp.mapper;
2
3import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4
5import com.itheima.mp.pojo.Emp;
6import org.apache.ibatis.annotations.Mapper;
7
8@Mapper
9public interface EmpMapper extends BaseMapper<Emp> {
10}
  1. 创建对应的Service
1package com.itheima.mp.service;
2
3import com.itheima.mp.pojo.Emp;
4import com.itheima.mp.mapper.EmpMapper;
5import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
6import com.baomidou.mybatisplus.extension.service.IService;
7import org.springframework.stereotype.Service;
8
9@Service
10public class EmpService extends ServiceImpl<EmpMapper, Emp> implements IService<Emp> {
11}
  1. 编写测试类,检查是否可以正常运行。
1package com.itheima.mp;
2
3import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
4import com.itheima.mp.pojo.Emp;
5import com.itheima.mp.service.EmpService;
6import org.junit.jupiter.api.BeforeEach;
7import org.junit.jupiter.api.Test;
8import org.springframework.boot.test.context.SpringBootTest;
9import org.springframework.transaction.annotation.Transactional;
10import org.springframework.test.annotation.Rollback;
11
12import javax.annotation.Resource;
13import java.time.LocalDate;
14import java.util.List;
15
16import static org.junit.jupiter.api.Assertions.*;
17
18@SpringBootTest
19@Transactional  // 表示该测试方法内的所有数据库操作会在方法结束后回滚
20@Rollback // 保证测试完成后,事务会被回滚,不会对数据库产生任何影响
21public class MPLearningTest {
22
23    @Resource
24    private EmpService empService;
25    private Long testEmpId;  // 存储测试时插入员工的ID
26    // 在每个测试方法之前插入一些必要的数据
27    @BeforeEach
28    public void init() {
29        // 插入一个部门为 IT 的员工,确保后面的测试能找到该数据
30        Emp emp = new Emp();
31        emp.setName("John Doe");
32        emp.setAge(30);
33        emp.setDepartment("IT");
34        emp.setHireDate(LocalDate.now());
35        empService.save(emp); // 保存到数据库
36
37        // 获取插入的员工 ID
38        testEmpId = emp.getId();
39        assertNotNull(testEmpId);
40    }
41    @Test
42    public void testSelectById() {
43        // 根据 ID 查询员工
44        Emp emp = empService.getById(testEmpId);
45        assertNotNull(emp);
46        assertEquals("John Doe", emp.getName());
47        assertEquals(30, emp.getAge());
48        assertEquals("IT", emp.getDepartment());
49    }
50}

增、查、改、删

  1. 保存一个实体的数据到数据库
1// 保存一个实体的数据到数据库
2@Test
3public void testSave() {
4    // 创建新员工,并填写数据
5    Emp emp = new Emp();
6    emp.setName("李四");
7    emp.setAge(35);
8    emp.setDepartment("销售部");
9    emp.setHireDate(LocalDate.now());
10    // 执行保存操作。
11    empService.save(emp);
12
13    // 因为我们使用了 @Transactional 注解,所以插入的数据会在测试方法结束后被回滚,
14    // 在数据库里是看不见的,所以需要用其他方式进行验证。
15
16    // 简单的验证方式就是看看emp的id有没有值,因为正常情况下,执行save操作之后,id应该会有值
17    assertNotNull(emp.getId());
18}
  1. 分别通过姓名进行查询

通过姓名进行查询,其实一共就需要两步,首先得创建一个QueryWrapper,写明我们的查询条件,然后通过getOne方法执行查询。

1// 通过姓名进行查询   
2@Test
3public void testSelectByName() {
4    // 根据姓名查询员工
5    QueryWrapper<Emp> queryWrapper = new QueryWrapper<>();
6    // 注意:这里填写的员工名是初始化时就已经插入数据库的。
7    queryWrapper.eq("name", "John Doe");
8    Emp emp = empService.getOne(queryWrapper);
9    assertNotNull(emp);
10    assertEquals("John Doe", emp.getName());
11    assertEquals(30, emp.getAge());
12    assertEquals("IT", emp.getDepartment());
13}

如果把queryWrapper里的查询条件给变了,其实就可以实现按别的字段进行查询了,比如这样

1...
2queryWrapper.eq("department", "IT");
3...
  1. 修改一个实体的数据

在这里修改的思路其实是先从数据库里把数据查出来,然后先在Java里进行修改,修改完了,再通过updateById方法把修改后的数据更新到数据库里。

如果你想尝试一下直接根据ID直接并且只修改几个特定的字段,也可以尝试一下。

1// 修改一个实体的数据
2@Test
3public void testUpdate() {
4    // 根据 ID 查询员工
5    Emp emp = empService.getById(testEmpId);
6    assertNotNull(emp);
7
8    // 修改员工信息
9    emp.setName("王五");
10    emp.setAge(40);
11    emp.setDepartment("财务部");
12
13    // 执行修改操作
14    empService.updateById(emp);
15
16    // 验证修改后的数据是否正确
17    Emp updatedEmp = empService.getById(testEmpId);
18    assertNotNull(updatedEmp);
19    assertEquals("王五", updatedEmp.getName());
20    assertEquals(40, updatedEmp.getAge());
21    assertEquals("财务部", updatedEmp.getDepartment());
22}
  1. 删除一个实体的数据

这里用的是物理删除,删了之后,数据库里就没有这条记录了。后续还会学到逻辑删除,就是在表里加一个表示该数据是否已经被删除的字段,在执行删除操作时,仅是把这个字段的值设置为1,表示已删除,而不是真的把数据从数据库里删除。

1@Test
2public void testDelete() {
3    // 根据 ID 查询员工
4    Emp emp = empService.getById(testEmpId);
5    assertNotNull(emp);
6
7    // 执行删除操作
8    empService.removeById(testEmpId);
9
10    // 验证是否被删除了
11    Emp deletedEmp = empService.getById(testEmpId);
12    assertNull(deletedEmp);
13}

使用QueryWrapper进行条件查询

这其实是先给出了一个练习,最后咱肯定是得能自己独立敲出来。

如果直接一个一个讲,可能听起来、敲起来都不是很难,学习效果也不好,学了就忘,下回要用,忘了,还得再查。所以直接给出了练习,可以多练几次,练得熟一点。

提示: 使用QueryWrapper进行条件查询的基本流程就是先创建一个QueryWrapper,然后链式地通过eq等方法设置查询条件,最后通过getOne或者list方法执行查询。

1package com.itheima.mp;
2
3import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
4import com.itheima.mp.pojo.Emp;
5import com.itheima.mp.service.EmpService;
6import org.junit.jupiter.api.BeforeEach;
7import org.junit.jupiter.api.Test;
8import org.springframework.boot.test.context.SpringBootTest;
9import org.springframework.transaction.annotation.Transactional;
10import org.springframework.test.annotation.Rollback;
11
12import javax.annotation.Resource;
13import java.time.LocalDate;
14import java.util.List;
15
16import static org.junit.jupiter.api.Assertions.*;
17
18@SpringBootTest
19@Transactional  // 表示该测试方法内的所有数据库操作会在方法结束后回滚
20@Rollback // 保证测试完成后,事务会被回滚,不会对数据库产生任何影响
21public class ComplexQueryWrapperTest {
22
23    @Resource
24    private EmpService empService;
25
26    @BeforeEach
27    public void init() {
28        // 删除之前的数据
29        empService.remove(null);
30        // 插入一些数据,确保后续的测试可以运行
31        insertTestData();
32    }
33
34    private void insertTestData() {
35        // 插入员工数据
36        Emp emp1 = new Emp();
37        emp1.setName("John Doe");
38        emp1.setAge(30);
39        emp1.setDepartment("IT");
40        emp1.setHireDate(LocalDate.of(2019, 1, 1));
41        empService.save(emp1);
42
43        Emp emp2 = new Emp();
44        emp2.setName("Jane Smith");
45        emp2.setAge(35);
46        emp2.setDepartment("销售部");
47        emp2.setHireDate(LocalDate.of(2021, 5, 15));
48        empService.save(emp2);
49
50        Emp emp3 = new Emp();
51        emp3.setName("Jack Brown");
52        emp3.setAge(40);
53        emp3.setDepartment("IT");
54        emp3.setHireDate(LocalDate.of(2020, 8, 20));
55        empService.save(emp3);
56
57        Emp emp4 = new Emp();
58        emp4.setName("David Doe");
59        emp4.setAge(25);
60        emp4.setDepartment("财务部");
61        emp4.setHireDate(LocalDate.of(2018, 3, 10));
62        empService.save(emp4);
63
64        Emp emp5 = new Emp();
65        emp5.setName("Eva Liu");
66        emp5.setAge(38);
67        emp5.setDepartment("销售部");
68        emp5.setHireDate(LocalDate.of(2019, 7, 30));
69        empService.save(emp5);
70    }
71
72    // 1. 根据 age 大于某个值并且 department 等于某个值的条件查询
73    @Test
74    public void testQueryByAgeAndDepartment() {
75        // 查询年龄大于 30 且部门为 "IT" 的员工
76        QueryWrapper<Emp> queryWrapper = new QueryWrapper<>();
77
78        // 执行查询并返回结果
79        List<Emp> empList = empService.list(queryWrapper);  // 你需要在这里补充查询逻辑
80
81        // 断言结果
82        assertFalse(empList.isEmpty());
83        empList.forEach(emp -> {
84            assertTrue(emp.getAge() > 30);
85            assertEquals("IT", emp.getDepartment());
86        });
87    }
88
89    // 2. 查询 name 以 "J" 开头,并且 hire_date 在 2020 年之后的员工
90    @Test
91    public void testQueryByNameAndHireDate() {
92        // 查询名字以 "J" 开头且入职日期在 2020 年之后的员工
93        QueryWrapper<Emp> queryWrapper = new QueryWrapper<>();
94
95        // 执行查询并返回结果
96        List<Emp> empList = empService.list(queryWrapper);  
97
98        // 断言结果
99        assertFalse(empList.isEmpty());
100        empList.forEach(emp -> {
101            assertTrue(emp.getName().startsWith("J"));
102            assertTrue(emp.getHireDate().isAfter(LocalDate.of(2020, 1, 1)));
103        });
104    }
105
106    // 3. 查询所有 age 在 30 到 40 之间,且 department 为 "IT" 或 "销售部" 的员工
107    @Test
108    public void testQueryByAgeAndDepartmentIn() {
109        // 查询年龄在 30 到 40 之间,且部门为 "IT" 或 "销售部" 的员工
110        QueryWrapper<Emp> queryWrapper= new QueryWrapper<>();
111
112        // 执行查询并返回结果
113        List<Emp> empList = empService.list(queryWrapper);  
114
115        // 断言结果
116        assertFalse(empList.isEmpty());
117        empList.forEach(emp -> {
118            assertTrue(emp.getAge() >= 30 && emp.getAge() <= 40);
119            assertTrue(emp.getDepartment().equals("IT") || emp.getDepartment().equals("销售部"));
120        });
121    }
122
123    // 4. 查询 name 包含 "Doe" 的员工,并按 age 降序排序
124    @Test
125    public void testQueryByNameContainsAndSortByAgeDesc() {
126        // 查询 name 包含 "Doe" 的员工,并按年龄降序排序
127        QueryWrapper<Emp> queryWrapper= new QueryWrapper<>();
128        
129        // 执行查询并返回结果
130        List<Emp> empList = empService.list(queryWrapper);  
131
132        // 断言结果
133        assertFalse(empList.isEmpty());
134        empList.forEach(emp -> assertTrue(emp.getName().contains("Doe")));
135
136        // 断言年龄是降序排序
137        Integer previousAge = Integer.MAX_VALUE;
138        for (Emp emp : empList) {
139            assertTrue(emp.getAge() <= previousAge);
140            previousAge = emp.getAge();
141        }
142    }
143}

QueryWrapper的常用方法

方法 作用
eq(String column, Object val) 等于条件,查询指定列等于给定值的记录。
ne(String column, Object val) 不等于条件,查询指定列不等于给定值的记录。
gt(String column, Object val) 大于条件,查询指定列大于给定值的记录。
lt(String column, Object val) 小于条件,查询指定列小于给定值的记录。
ge(String column, Object val) 大于等于条件,查询指定列大于等于给定值的记录。
le(String column, Object val) 小于等于条件,查询指定列小于等于给定值的记录。
like(String column, Object val) 模糊查询,查询指定列包含给定值的记录(匹配 %val%)。
likeLeft(String column, Object val) 模糊查询,查询指定列以给定值开头的记录(匹配 val%)。
likeRight(String column, Object val) 模糊查询,查询指定列以给定值结尾的记录(匹配 %val)。
in(String column, Object... values) 多值匹配条件,查询指定列的值在给定的多个值中。
notIn(String column, Object... values) 不在多个值中的条件,查询指定列的值不在给定的多个值中。
between(String column, Object val1, Object val2) 查询指定列在两个值之间的记录。
isNull(String column) 查询指定列为 NULL 的记录。
isNotNull(String column) 查询指定列不为 NULL 的记录。
orderByAsc(String... columns) 按指定列升序排序。
orderByDesc(String... columns) 按指定列降序排序。
groupBy(String... columns) 按指定列分组查询。
having(String having) 分组查询后的条件(与 SQL 的 HAVING 子句类似)。
last(String sql) 追加 SQL 到查询的最后(比如增加 LIMIT 或其他 SQL 子句)。
apply(String sql) 自定义 SQL 条件应用于查询(常用于复杂的 SQL 表达式)。
or() 在查询条件中加入 OR 逻辑关系,适用于多个条件组合。
not() 逻辑非,用于反转条件判断。
inSql(String column, String sql) 用子查询判断是否在指定范围内。
select(String... columns) 选择查询的列,限制查询结果中返回的列。

LambdaQueryWrapper的使用

假如我们要使用LambdaQueryWrapper的使用完成这个查询任务:根据 age 大于某个值并且 department 等于某个值的条件查询,我们可以这样写:

1@Test
2public void testQueryByAgeAndDepartment() {
3    // 使用 LambdaQueryWrapper
4    LambdaQueryWrapper<Emp> lambdaQueryWrapper = new LambdaQueryWrapper<>();
5    lambdaQueryWrapper.gt(Emp::getAge, 30)  // age > 30
6            .eq(Emp::getDepartment, "IT");  // department = "IT"
7
8    // 执行查询
9    List<Emp> empList = empService.list(lambdaQueryWrapper);
10
11    // 断言结果
12    assertFalse(empList.isEmpty());
13    empList.forEach(emp -> {
14        assertTrue(emp.getAge() > 30);
15        assertEquals("IT", emp.getDepartment());
16    });
17}

Emp::getAgeEmp::getDepartment:通过 Lambda 表达式引用 Emp 类中的字段获取方法。这里避免了直接在查询中使用字段名的字符串,确保了字段的存在和类型安全。

综合练习-用户余额

在这里有许多不规范的地方,比如获取uid、数据类型等,还是因为咱在这里主要是学mp嘛,就简化了。

在这一节咱会通过写一个完整的接口,把之前学的简单串一串。

  1. 使用sql语句建立用户余额表
1CREATE TABLE `user_balance` (
2    `id` BIGINT AUTO_INCREMENT PRIMARY KEY,                      -- 用户ID
3    `username` VARCHAR(255) NOT NULL,                            -- 用户名
4    `email` VARCHAR(255),                                        -- 用户邮箱
5    `balance` DECIMAL(10, 2) DEFAULT 0.00,                       -- 用户余额
6    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,            -- 创建时间
7    `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP  -- 更新时间
8);
  1. 建立对应的实体类
1package com.itheima.mp.domain.pojo;
2
3import com.baomidou.mybatisplus.annotation.TableId;
4import com.baomidou.mybatisplus.annotation.TableName;
5import com.baomidou.mybatisplus.annotation.TableField;
6import com.baomidou.mybatisplus.annotation.FieldFill;
7import lombok.AllArgsConstructor;
8import lombok.Data;
9import lombok.NoArgsConstructor;
10
11import java.math.BigDecimal;
12import java.sql.Timestamp;
13import java.time.LocalDate;
14
15@AllArgsConstructor
16@NoArgsConstructor
17@Data
18@TableName("user_balance")
19public class UserBalance {
20    @TableId
21    private Long id;                           // 用户ID
22    private String username;                   // 用户名
23    private String email;                      // 用户邮箱
24    private BigDecimal balance;                // 用户余额
25    @TableField(fill = FieldFill.INSERT)
26    private Timestamp createdAt; // 这个字段表示创建时间,其实实际写的时候不是这个类型,在这儿先不管心它的类型了。
27    @TableField(fill = FieldFill.INSERT_UPDATE)
28    private Timestamp updatedAt; 
29}
  1. 请实现这个Controller
1package com.itheima.mp.controller;
2
3import com.itheima.mp.domain.pojo.UserBalance;
4import java.math.BigDecimal;
5public interface UserBalanceController {
6    // 其实这里每个方法的返回值在实际写的时候,应该包装一下,这里为了简单起见就先不考虑了。
7    // 查询余额
8    UserBalance getBalance(Long userId);
9    // 创建余额记录
10    UserBalance createBalance(Long userId, UserBalance userBalance);
11    // 更新余额记录
12    UserBalance updateBalance(Long userId, UserBalance userBalance);
13    // 删除余额记录
14    String deleteBalance(Long userId);
15    // 增加余额
16    String increaseBalance(Long userId, BigDecimal amount);
17    // 减少余额
18    String decreaseBalance(Long userId, BigDecimal amount);
19}
  1. Controller层的代码

为了你做练习的时候,更专注于练习mp,所以给出了Controller层的代码。

1package com.itheima.mp.controller;
2
3import com.itheima.mp.domain.pojo.UserBalance;
4import com.itheima.mp.service.UserBalanceService;
5import org.springframework.web.bind.annotation.*;
6
7import javax.annotation.Resource;
8import java.math.BigDecimal;
9
10@RestController
11@RequestMapping("/api/user-balance/{userId}")
12public class UserBalanceControllerImpl implements UserBalanceController {
13    @Resource
14    private UserBalanceService userBalanceService;
15
16    // 查询余额
17    @Override
18    @GetMapping
19    public UserBalance getBalance(@PathVariable Long userId) {
20        return userBalanceService.getById(userId);
21    }
22
23    // 创建余额记录
24    @Override
25    @PostMapping
26    public UserBalance createBalance(@PathVariable Long userId, @RequestBody UserBalance userBalance) {
27        userBalance.setId(userId); // 设置用户ID
28        return userBalanceService.createBalance(userBalance);
29    }
30
31    // 更新余额记录
32    @Override
33    @PutMapping
34    public UserBalance updateBalance(@PathVariable Long userId, @RequestBody UserBalance userBalance) {
35        userBalance.setId(userId); // 设置更新的用户ID
36        return userBalanceService.updateBalance(userBalance);
37    }
38
39    // 删除余额记录
40    @Override
41    @DeleteMapping
42    public String deleteBalance(@PathVariable Long userId) {
43        boolean success = userBalanceService.removeById(userId);
44        if (!success) {
45            throw new RuntimeException("删除失败");
46        }
47        return "删除成功";
48    }
49
50    // 增加余额
51    @Override
52    @PostMapping("/increase")
53    public String increaseBalance(@PathVariable Long userId, @RequestBody BigDecimal amount) {
54        boolean success = userBalanceService.increaseBalance(userId, amount);
55        return success ? "增加余额成功" : "增加余额失败";
56    }
57
58    // 减少余额
59    @Override
60    @PostMapping("/decrease")
61    public String decreaseBalance(@PathVariable Long userId, @RequestBody BigDecimal amount) {
62        boolean success = userBalanceService.decreaseBalance(userId, amount);
63        return success ? "减少余额成功" : "减少余额失败";
64    }
65}
  1. 自定义SQL

一种是写在配置文件里,一种是写在注解里。 要想写在配置文件里,我建议先检查一下配置文件。

1mybatis-plus:
2  mapper-locations: classpath:/mapper/*.xml

在配置文件里的写法:

1<?xml version="1.0" encoding="UTF-8" ?>
2<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
3<mapper namespace="com.itheima.mp.mapper.UserBalanceMapper">
4
5    <!-- 增加余额 -->
6    <update id="increaseBalance">
7        UPDATE user_balance
8        SET balance = balance + #{amount}
9        WHERE id = #{userId}
10    </update>
11
12    <!-- 减少余额 -->
13    <update id="decreaseBalance">
14        UPDATE user_balance
15        SET balance = balance - #{amount}
16        WHERE id = #{userId}
17    </update>
18
19</mapper>

在注解中的写法

1package com.itheima.mp.mapper;
2
3import com.itheima.mp.domain.pojo.UserBalance;
4import org.apache.ibatis.annotations.Param;
5import org.apache.ibatis.annotations.Update;
6import java.math.BigDecimal;
7
8public interface UserBalanceMapper extends BaseMapper<UserBalance> {
9
10    // 增加余额
11    @Update("UPDATE user_balance SET balance = balance + #{amount} WHERE id = #{userId}")
12    int increaseBalance(@Param("userId") Long userId, @Param("amount") BigDecimal amount);
13
14    // 减少余额
15    @Update("UPDATE user_balance SET balance = balance - #{amount} WHERE id = #{userId}")
16    int decreaseBalance(@Param("userId") Long userId, @Param("amount") BigDecimal amount);
17}

扩展

代码生成器

  1. 在IDEA插件中下载MyBatisPlus插件,它的头像不是魂斗罗mp-代码生成器插件的图片

  2. 在数据库里再新建一个表

1CREATE TABLE `orders` (
2    `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '订单ID',
3    `order_no` VARCHAR(64) NOT NULL COMMENT '订单号',
4    `user_id` BIGINT(20) NOT NULL COMMENT '用户ID',
5    `product_id` BIGINT(20) NOT NULL COMMENT '商品ID',
6    `quantity` INT(10) NOT NULL COMMENT '商品数量',
7    `total_price` DECIMAL(10, 2) NOT NULL COMMENT '总价',
8    `order_status` TINYINT(4) NOT NULL COMMENT '订单状态: 1-待支付, 2-已支付, 3-已发货, 4-已完成, 5-已取消',
9    `payment_status` TINYINT(4) NOT NULL COMMENT '支付状态: 1-未支付, 2-已支付, 3-支付失败',
10    `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
11    `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
12    PRIMARY KEY (`id`),
13    UNIQUE KEY `order_no_idx` (`order_no`),
14    KEY `user_id_idx` (`user_id`),
15    KEY `product_id_idx` (`product_id`)
16) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
  1. 使用代码生成器 mp-代码生成器配置Other里先配置下数据库连接,再生成代码。

  2. 补充一点

1@ApiModelProperty(value = "订单ID")
2@ApiModel(value="Orders对象", description="订单表")

这种注解需要引入一个依赖。

1<!-- Swagger 2 -->
2<dependency>
3    <groupId>io.springfox</groupId>
4    <artifactId>springfox-swagger2</artifactId>
5    <version>2.9.2</version>  <!-- 使用合适的版本 -->
6</dependency>

DB静态工具

使用Db之后,可以解决一些循环注入的问题。

1package com.itheima.mp;
2
3import com.baomidou.mybatisplus.extension.toolkit.Db;
4import com.itheima.mp.entity.Order;
5import org.junit.jupiter.api.BeforeEach;
6import org.junit.jupiter.api.Test;
7import org.springframework.boot.test.context.SpringBootTest;
8import org.springframework.test.annotation.Rollback;
9import org.springframework.transaction.annotation.Transactional;
10import java.math.BigDecimal;
11import java.time.LocalDateTime;
12
13import static org.junit.jupiter.api.Assertions.*;
14
15@SpringBootTest
16@Transactional  // 表示该测试方法内的所有数据库操作会在方法结束后回滚
17@Rollback // 保证测试完成后,事务会被回滚,不会对数据库产生任何影响
18public class DbToolTest {
19
20    // 在每个测试方法执行之前都会插入一个初始订单
21    private Long orderId;
22
23    @BeforeEach
24    public void setUp() {
25        // 插入初始订单
26        Order order = new Order()
27                .setOrderNo("ORD123456")
28                .setUserId(1L)
29                .setProductId(1001L)
30                .setQuantity(2)
31                .setTotalPrice(new BigDecimal("399.98"))
32                .setOrderStatus(1) // 订单状态:待支付
33                .setPaymentStatus(1) // 支付状态:未支付
34                .setCreatedAt(LocalDateTime.now())
35                .setUpdatedAt(LocalDateTime.now());
36
37        // 使用 DB 工具插入订单
38        boolean result = Db.save(order);
39
40        // 获取插入后的订单ID,用于后续操作
41        if (result) {
42            orderId = order.getId();
43        }
44    }
45
46    /**
47     * 测试使用 DB 静态工具查询订单
48     */
49    @Test
50    public void testSelectOrder() {
51        // 使用 lambdaQuery 进行查询
52        Order order = Db.lambdaQuery(Order.class)
53                .eq(Order::getProductId,1001L)
54                .one();
55        // 断言至少有一条订单记录
56        assertNotNull(order);
57    }
58
59    /**
60     * 测试使用 DB 静态工具更新订单
61     */
62    @Test
63    public void testUpdateOrderStatus() {
64        // 更新订单状态为已支付
65        Order order = new Order();
66        order.setId(orderId);
67        order.setOrderStatus(2); // 已支付
68        // 使用 DB 工具更新订单
69        boolean result = Db.updateById(order);
70        // 断言更新成功
71        assertTrue(result);
72        assertEquals(2,Db.getById(orderId, Order.class).getOrderStatus());
73    }
74
75    /**
76     * 测试使用 DB 静态工具删除订单
77     */
78    @Test
79    public void testDeleteOrder() {
80        // 使用 lambdaRemove 进行删除
81        boolean result = Db.removeById(orderId, Order.class);
82        // 断言删除成功
83        assertTrue(result);
84    }
85}

逻辑删除

  1. 先在yaml里添加上逻辑删除的配置。
1mybatis-plus:
2  mapper-locations: classpath:/mapper/*.xml
3  type-aliases-package: com.itheima.domain.po
4  global-config:
5    db-config:
6      id-type: auto
7      logic-delete-field: deleted
8      logic-delete-value: 1
9      logic-not-delete-value: 0
  1. 在原来的订单表里加一个字段。
1`deleted` TINYINT(4) NOT NULL DEFAULT 0 COMMENT '是否已删除,0未删除,1已删除',
  1. 重新生成代码。

  2. 测试

1package com.itheima.mp;
2
3
4import com.baomidou.mybatisplus.extension.toolkit.Db;
5import com.itheima.mp.entity.Order;
6import org.junit.jupiter.api.BeforeEach;
7import org.junit.jupiter.api.Test;
8import org.springframework.boot.test.context.SpringBootTest;
9
10import java.math.BigDecimal;
11import java.time.LocalDateTime;
12
13import static org.junit.jupiter.api.Assertions.*;
14
15@SpringBootTest
16public class LogicDeleteTest {
17
18    // 在每个测试方法执行之前都会插入一个初始订单
19    private Long orderId;
20
21    @BeforeEach
22    public void setUp() {
23        // 插入初始订单
24        Order order = new Order()
25                .setOrderNo("ORD123456")
26                .setUserId(1L)
27                .setProductId(1001L)
28                .setQuantity(2)
29                .setTotalPrice(new BigDecimal("399.98"))
30                .setOrderStatus(1) // 订单状态:待支付
31                .setPaymentStatus(1) // 支付状态:未支付
32                .setCreatedAt(LocalDateTime.now())
33                .setUpdatedAt(LocalDateTime.now())
34                .setDeleted(0); // 未删除
35
36        // 使用 DB 工具插入订单
37        boolean result = Db.save(order);
38
39        // 获取插入后的订单ID,用于后续操作
40        if (result) {
41            // 假设我们可以获取插入的订单ID
42            orderId = order.getId();
43        }
44    }
45
46
47
48    /**
49     * 测试逻辑删除
50     */
51    @Test
52    public void testLogicDelete() {
53        // 执行逻辑删除
54        boolean result = Db.removeById(orderId,Order.class);
55
56        // 断言逻辑删除成功
57        assertTrue(result);
58
59        // 验证订单是否已逻辑删除
60        Order deletedOrder = Db.getById(orderId, Order.class);
61        assertNull(deletedOrder);
62    }
63}

分页查询

使用mp进行分页查询,得先建好配置类,然后就可以在其他地方使用了。

  1. 添加些模拟数据:
1INSERT INTO `orders` (`order_no`, `user_id`, `product_id`, `quantity`, `total_price`, `order_status`, `payment_status`, `created_at`, `updated_at`, `deleted`)
2VALUES
3('ORD0001', 1, 101, 3, 299.99, 1, 1, NOW(), NOW(), 0),
4('ORD0002', 2, 102, 2, 149.90, 2, 2, NOW(), NOW(), 0),
5('ORD0003', 3, 103, 5, 799.50, 1, 1, NOW(), NOW(), 0),
6('ORD0004', 4, 104, 1, 99.00, 4, 2, NOW(), NOW(), 0),
7('ORD0005', 5, 105, 2, 399.80, 3, 3, NOW(), NOW(), 0),
8('ORD0006', 6, 106, 3, 399.00, 2, 2, NOW(), NOW(), 0),
9('ORD0007', 7, 107, 4, 449.60, 1, 1, NOW(), NOW(), 0),
10('ORD0008', 8, 108, 1, 199.00, 3, 3, NOW(), NOW(), 0),
11('ORD0009', 9, 109, 2, 249.90, 4, 2, NOW(), NOW(), 0),
12('ORD0010', 10, 110, 6, 539.40, 2, 1, NOW(), NOW(), 0),
13('ORD0011', 11, 111, 3, 899.70, 1, 2, NOW(), NOW(), 0),
14('ORD0012', 12, 112, 4, 699.20, 4, 1, NOW(), NOW(), 0),
15('ORD0013', 13, 113, 5, 799.00, 3, 3, NOW(), NOW(), 0),
16('ORD0014', 14, 114, 2, 149.50, 2, 1, NOW(), NOW(), 0),
17('ORD0015', 15, 115, 3, 399.90, 1, 2, NOW(), NOW(), 0),
18('ORD0016', 16, 116, 6, 1199.80, 2, 3, NOW(), NOW(), 0),
19('ORD0017', 17, 117, 4, 499.60, 3, 1, NOW(), NOW(), 0),
20('ORD0018', 18, 118, 5, 399.50, 4, 2, NOW(), NOW(), 0),
21('ORD0019', 19, 119, 2, 249.80, 1, 1, NOW(), NOW(), 0),
22('ORD0020', 20, 120, 3, 399.99, 3, 3, NOW(), NOW(), 0);
  1. 配置类
1package com.itheima.mp.config;
2
3import com.baomidou.mybatisplus.annotation.DbType;
4import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
5import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
6import org.springframework.context.annotation.Bean;
7import org.springframework.context.annotation.Configuration;
8
9@Configuration
10public class MybatisPlusConfig {
11
12    @Bean
13    public MybatisPlusInterceptor paginationInterceptor() {
14        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
15        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
16        return interceptor;
17    }
18
19}
  1. 测试代码

在这里我们对之前创建的order进行分页查询。 Controller层的代码:

1package com.itheima.mp.controller;
2
3import com.itheima.mp.domain.query.OrderQuery;
4import com.itheima.mp.entity.Order;
5import com.itheima.mp.service.IOrdersService;
6import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
7import org.springframework.beans.factory.annotation.Autowired;
8import org.springframework.web.bind.annotation.*;
9
10import io.swagger.annotations.Api;
11import io.swagger.annotations.ApiOperation;
12
13@RestController
14@RequestMapping("/api/orders")
15@Api(value="Order Management", tags = "订单管理")
16public class OrdersController {
17
18    @Autowired
19    private IOrdersService ordersService;
20
21    @ApiOperation(value = "分页查询订单", notes = "根据查询条件分页查询订单")
22    @PostMapping("/query")
23    public Page<Order> queryOrders(@RequestBody OrderQuery orderQuery) {
24        return ordersService.queryOrders(orderQuery);
25    }
26}

服务层接口:IOrdersService

1package com.itheima.mp.service;
2
3import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
4import com.itheima.mp.domain.query.OrderQuery;
5import com.itheima.mp.entity.Order;
6import com.baomidou.mybatisplus.extension.service.IService;
7
8/**
9 * <p>
10 * 订单表 服务类
11 * </p>
12 *
13 * @author urfread
14 * @since 2024-12-19
15 */
16public interface IOrdersService extends IService<Order> {
17    Page<Order> queryOrders(OrderQuery orderQuery);
18}

服务层实现类:OrdersServiceImpl

1package com.itheima.mp.service.impl;
2
3import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
4import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
5
6import com.itheima.mp.domain.query.OrderQuery;
7import com.itheima.mp.entity.Order;
8import com.itheima.mp.mapper.OrdersMapper;
9import com.itheima.mp.service.IOrdersService;
10import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
11import org.springframework.stereotype.Service;
12
13import java.math.BigDecimal;
14
15@Service
16public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Order> implements IOrdersService {
17
18    @Override
19    public Page<Order> queryOrders(OrderQuery orderQuery) {
20        // 构建查询条件
21        LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<>();
22
23        // 忽略 orderNo 为 null 或空字符串的情况
24        if (orderQuery.getOrderNo() != null && !orderQuery.getOrderNo().isEmpty()) {
25            queryWrapper.eq(Order::getOrderNo, orderQuery.getOrderNo());
26        }
27
28        // 忽略 userId 为 0 或 null 的情况
29        if (orderQuery.getUserId() != null && orderQuery.getUserId() != 0) {
30            queryWrapper.eq(Order::getUserId, orderQuery.getUserId());
31        }
32
33        // 忽略 productId 为 0 或 null 的情况
34        if (orderQuery.getProductId() != null && orderQuery.getProductId() != 0) {
35            queryWrapper.eq(Order::getProductId, orderQuery.getProductId());
36        }
37
38        // 忽略 orderStatus 为 0 或 null 的情况
39        if (orderQuery.getOrderStatus() != null && orderQuery.getOrderStatus() != 0) {
40            queryWrapper.eq(Order::getOrderStatus, orderQuery.getOrderStatus());
41        }
42
43        // 忽略 paymentStatus 为 0 或 null 的情况
44        if (orderQuery.getPaymentStatus() != null && orderQuery.getPaymentStatus() != 0) {
45            queryWrapper.eq(Order::getPaymentStatus, orderQuery.getPaymentStatus());
46        }
47
48        // 忽略 startTime 为 null 的情况
49        if (orderQuery.getStartTime() != null) {
50            queryWrapper.ge(Order::getCreatedAt, orderQuery.getStartTime());
51        }
52
53        // 忽略 endTime 为 null 的情况
54        if (orderQuery.getEndTime() != null) {
55            queryWrapper.le(Order::getCreatedAt, orderQuery.getEndTime());
56        }
57
58        // 忽略 minPrice 为 0 或 null 的情况
59        if (orderQuery.getMinPrice() != null && orderQuery.getMinPrice().compareTo(BigDecimal.ZERO) != 0) {
60            queryWrapper.ge(Order::getTotalPrice, orderQuery.getMinPrice());
61        }
62
63        // 忽略 maxPrice 为 0 或 null 的情况
64        if (orderQuery.getMaxPrice() != null && orderQuery.getMaxPrice().compareTo(BigDecimal.ZERO) != 0) {
65            queryWrapper.le(Order::getTotalPrice, orderQuery.getMaxPrice());
66        }
67
68        // 分页查询
69        return this.page(new Page<>(orderQuery.getPage(), orderQuery.getSize()), queryWrapper);
70    }
71}

封装查询参数:OrderQuery

1package com.itheima.mp.domain.query;
2
3import io.swagger.annotations.ApiModel;
4import io.swagger.annotations.ApiModelProperty;
5import lombok.Data;
6
7import java.math.BigDecimal;
8import java.time.LocalDateTime;
9
10@Data
11@ApiModel(value="Order查询对象", description="订单查询参数")
12public class OrderQuery {
13
14    @ApiModelProperty(value = "订单号")
15    private String orderNo;
16
17    @ApiModelProperty(value = "用户ID")
18    private Long userId;
19
20    @ApiModelProperty(value = "商品ID")
21    private Long productId;
22
23    @ApiModelProperty(value = "订单状态: 1-待支付, 2-已支付, 3-已发货, 4-已完成, 5-已取消")
24    private Integer orderStatus;
25
26    @ApiModelProperty(value = "支付状态: 1-未支付, 2-已支付, 3-支付失败")
27    private Integer paymentStatus;
28
29    @ApiModelProperty(value = "开始时间")
30    private LocalDateTime startTime;
31
32    @ApiModelProperty(value = "结束时间")
33    private LocalDateTime endTime;
34
35    @ApiModelProperty(value = "最小总价")
36    private BigDecimal minPrice;
37
38    @ApiModelProperty(value = "最大总价")
39    private BigDecimal maxPrice;
40
41    @ApiModelProperty(value = "页码")
42    private Integer page = 1;
43
44    @ApiModelProperty(value = "每页大小")
45    private Integer size = 10;
46}
  1. 怎么调用这个接口?

其实有很多种方式,你可以把controller和Query对象发给AI,让它用html写一个可以调用这个接口的界面,也可以用knief4j来调用, 或者用postman来调用。 我这里新学的用kinfe4j来调用,所以笔记里就写kinfe4j的了。

首先得引两个依赖(说实话,我也不清楚他俩是干嘛的,反正引完,到用的时候,在网页上就会出现对应接口的快捷调用方式):

1<!-- knife4j -->
2<dependency>
3    <groupId>com.github.xiaoymin</groupId>
4    <artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
5    <version>4.3.0</version> 
6</dependency>
7<!-- Swagger 2 -->
8<dependency>
9    <groupId>io.springfox</groupId>
10    <artifactId>springfox-swagger2</artifactId>
11    <version>2.9.2</version> 
12</dependency>

然后编写kinfe4j的配置类:

1package com.itheima.mp.config;
2
3import io.swagger.v3.oas.models.OpenAPI;
4import io.swagger.v3.oas.models.info.Info;
5
6import org.springdoc.core.GroupedOpenApi;
7import org.springframework.context.annotation.Bean;
8import org.springframework.context.annotation.Configuration;
9// http://localhost:8080/doc.html#/home 访问地址
10@Configuration
11public class Knife4jConfig {
12    @Bean
13    public OpenAPI openAPI(){
14        return new OpenAPI()
15                .info(new Info()
16                        .title("Contact Management API")
17                        .version("1.0")
18                        .description("API for managing contacts and contact groups."));
19    }
20    // Combine both APIs into one group
21    @Bean
22    public GroupedOpenApi combinedAPI() {
23        return GroupedOpenApi.builder()
24                .group("all-api")
25                .pathsToMatch("/api/user-balance/**")// 如果后续有其他接口,请通过这个方法继续添加,就是敲个逗号,然后再写一个路径——用字符串包好。
26                .build();
27    }
28
29}

编辑查询参数并肉眼检查:

1{
2  "orderNo": "",
3  "userId": 1,
4  "productId": 0,
5  "orderStatus": 0,
6  "paymentStatus": 0,
7  "startTime": "2024-12-20T00:00:00",
8  "endTime": "2024-12-31T23:59:59",
9  "minPrice": 0,
10  "maxPrice": 0,
11  "page": 0,
12  "size": 100
13}