MyBatis-plus(8.10)

一、创建数据库

数据库mp73,包含两张表department(部门表),employee(员工表)。

image-20260810164922005

image-20260810164456529

二、创建项目

选择 Spring Boot 项目,选择Maven,要一个Lombok依赖。

pom.xml中,删除build,把父项目spring-boot-starter-parent版本改为2.7.1

三、添加依赖

<dependencies>
    <!-- Spring Boot 核心基础启动器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <!-- Lombok:通过注解简化实体类代码编写 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

    <!-- Spring Boot 测试启动器(包含 JUnit5 等测试工具) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!-- Spring Boot Web 开发启动器(内置 Tomcat 和 Spring MVC) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- MySQL JDBC 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <!-- MyBatis-Plus Spring Boot 整合启动器 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.2</version>
    </dependency>
</dependencies>

四、编写application.properties配置文件

# 应用名称
spring.application.name=mp73

# 数据库连接配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mp73?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=1234

# 开启 MyBatis-Plus 控制台 SQL 日志打印(方便调试)
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

五、编写实体类entity

1.Department实体类

(1)在 MyBatis-Plus 中,@TableId 注解用于标识实体类中对应数据库表的主键字段,并可以指定主键的生成策略

(2)@TableId(type = IdType.AUTO):
核心作用:明确告诉 MyBatis-Plus,被修饰的 id 字段是数据库表的主键。
策略指定type = IdType.AUTO 表示主键的生成完全交由底层数据库处理(依赖数据库的 AUTO_INCREMENT 自增机制)。在执行插入操作时,框架不会在 SQL 中拼接id字段,而是等数据库生成后回填到实体对象中。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Department {
    // 
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private String sn;
}

2.Employee实体类后面用代码生成器生成

六、创建mapper层

(1)在主启动类上添加@MapperScan("cn.wolfcode.mapper")

(2)新建mapper.DepartmentMapper接口。

(3)在 MyBatis-Plus 中,之所以不写 XML 和 SQL 就能执行数据库操作,核心在于其提供了 BaseMapper 接口。开发者只需让自己的 Mapper 接口继承 BaseMapper<T>,即可自动获得强大的单表 CRUD 能力。

public interface DepartmentMapper extends BaseMapper<Department> {
}

七、创建Mp73ApplicationTests测试类

(1)在DepartmentMapper接口中右键,选择create test创建Mp73ApplicationTests测试类。

(2)加上@SpringBootTest注解

(3)查询所有部门

@SpringBootTest
class Mp73ApplicationTests {

    @Autowired
    private DepartmentMapper departmentMapper;

    @Test
    void list() {
        List<Department> departments = departmentMapper.selectList(null);
        for( Department department: departments){
            System.out.println(department);
        }
    }
}

(4)根据id查询部门

   @Test
    void get(){
        Department department = departmentMapper.selectById(1L);
        System.out.println(department);
    }

(5)添加部门

    @Test
    void insert(){
        Department department = new Department();
        department.setName("测试部");
        department.setSn("test");
        int insert = departmentMapper.insert(department);
        System.out.println(insert);
    }

(6)根据id修改部门

   @Test
    void update(){
        Department department = new Department();
        department.setId(43L);
        department.setName("43部");
        int update = departmentMapper.updateById(department);
        System.out.println(update);
    }

(7)根据id删除部门

    @Test
    void delete(){
        int delete = departmentMapper.deleteById(43L);
        System.out.println(delete);
    }

八、使用代码生成器

​ 使用代码生成器生成employee

(1)添加依赖

    <!-- MyBatis-Plus 代码生成器 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
        <version>3.5.2</version>
    </dependency>

    <!-- Freemarker 模板引擎(通常配合代码生成器使用) -->
    <dependency>
        <groupId>org.freemarker</groupId>
        <artifactId>freemarker</artifactId>
    </dependency>

(2)把CodeGenerator复制到启动类同级目录下

​ ①修改连接数据库的url、账号、密码

​ ②修改要生成的表名(可以选择生成多个表)

​ ③设置作者

​ ④修改输出路径

.outputDir(System.getProperty("user.dir") + "/src/main/java")

  • System.getProperty("user.dir") ---> 当前项目路径 D:/java周末/三阶段/Mybatis-plus/mp73

  • /src/main/java ---> 指定 src ---> main ---> java

    ⑤这里先把.enableSwagger() // 开启swagger注解注释掉

    builder.parent("cn.wolfcode") ---> 指定生成的包

    ⑦直接运行即可

public class CodeGenerator {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3309/mp73?useSSL=false&serverTimezone=GMT%2B8";
        String username = "root";
        String password = "123456";
//        String moduleName = "system";
        String[] tableNames = {"employee"}; // 要生成的表名

        FastAutoGenerator.create(url, username, password)
            .globalConfig(builder -> {
                builder.author("y") // 设置作者
                    .outputDir(System.getProperty("user.dir") + "/src/main/java") // 输出路径
                   // .enableSwagger() // 开启swagger注解
                    .disableOpenDir(); // 禁止打开输出目录
            })
            .packageConfig(builder -> {
                builder.parent("cn.wolfcode");
            })
            .strategyConfig(builder -> {
                builder.addInclude(tableNames) // 包含的表
                    .entityBuilder() // 实体配置
                    .enableLombok() // 启用Lombok
                    .controllerBuilder() // 控制器配置
                    .enableRestStyle(); // 启用RestController
            })
            .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎
            .execute();
    }
}

image-20260810184557424

(3)点击查看生成的文件

​ ①实体类可以添加

@Data
@NoArgsConstructor
@AllArgsConstructor

​ ②代码生成器会在mapper包下生成mapper和对应的xml

​ Maven 在打包时,默认只会把 src/main/resources 下的文件视为配置文件,而 src/main/java 目录下的 .xml 文件会被直接忽略。这就导致了代码生成器生成的 XML 文件无法被正确加载。

解决:

  • 将 XML 放入 Resource 目录

  • 修改 Maven 配置,让 Java 目录也认 XML

    pom.xml<build> 节点下添加 resources 配置,强制 Maven 打包时包含 Java 目录下的 XML 文件:

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>
    
  • 直接删除 XML:如果仅仅是简单的单表增删改查,直接删掉生成的 XML 文件。

    ③在 MyBatis-PlusServiceImpl 实现类中,调用 Mapper 的方式有两种:

  • 本实体 Mapper(免注入)
    直接使用父类 ServiceImpl 已自动注入的 baseMapper 即可,无需再手动编写 @Autowired

  • 其他实体 Mapper(需手动注入)
    若需调用其他实体的 Mapper(如 DepartmentMapper),则必须手动添加 @Autowired 进行依赖注入。

@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements IEmployeeService {

    @Autowired
    private DepartmentMapper departmentMapper;

    public void test(){
        baseMapper.selectList(null);
    }

}

④查看Controller,我们可以直接在Controller中调用IEmployeeService,执行CRUD操作。

@RestController
@RequestMapping("/employee")
public class EmployeeController {

    @Autowired
    private IEmployeeService employeeService;

    @GetMapping
    public Object list(){
        return employeeService.list();
    }
}

(1)MyBatis-Plus 提供了两套 CRUD 体系:

  • Service 层(业务级):继承 IService,提供 list()save() 等方法。
  • Mapper 层(数据级):继承 BaseMapper,提供 selectList()insert() 等方法。

(2)调用策略

  • 简单场景(首选 Service层):
    • 适用情况:标准的单表增删改查,无需复杂 SQL 拼接。
    • 核心优势:除了基础的 CRUD,Service 层(IService)还额外提供了 saveBatch() 等批量操作方法,在需要大批量处理数据时能显著提升性能。
  • 复杂场景(回归 Mapper层):
    • 适用情况:当标准方法无法满足需求,需要对参数进行特殊处理、组装,或者需要编写自定义 SQL 逻辑时。
    • 操作方式:在 Mapper 层自定义专属方法,由 Service 层进行调用,从而实现对底层数据的精细化控制。

九、常见注解

1.@Table

  • 作用:用于标注实体类中的主键字段。

  • 必要性:MP 默认会寻找名为 id 的字段作为主键。如果数据库主键字段名不是 id(例如 user_id),或者需要指定特定的生成策略,则必须使用此注解。

  • 五种主键策略 (IdType)

场景 策略类型 特点与权衡
单表 / 传统项目 AUTO (数据库自增) 依赖数据库自身的自增机制,简单直接。
连表 / 自定义 INPUT (用户输入) 需要开发者手动设置 ID,或者由外部系统生成。
分布式系统 方案 A (推荐) (对应 MP 中的 ASSIGN_ID) 特点:唯一 且 可以排序(趋势递增)。 缺点:性能相对较差(相比纯数字自增,生成算法有开销)。
分布式系统 方案 B (对应 MP 中的 ASSIGN_UUID) 特点:保证唯一。 缺点:不能排序(无序),且占用空间大。

​ 示例:value = "id":指定数据库表中主键列的名字是 id。

  • 如果不写value,MyBatis-Plus 会默认去找数据库里叫 id 的列。

  • 如果字段名就是 id:那么 value = "id" 确实是多余的,完全可以省略

  • 只有当 Java 字段名(或者你想映射的数据库列名)不是 id 时,才需要显式指定。

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

2.@TableField

​ 这个注解主要用于解决 Java 实体类属性与数据库表字段之间的映射问题,特别是处理那些“只存在于代码中,不存在于数据库中”的字段。

核心功能

  • 默认机制:MyBatis-Plus 默认开启驼峰命名自动映射(例如 Java 的 userName 会自动对应数据库的 user_name)。如果符合这个规则,通常不需要写此注解。
  • 显式指定:当字段名不符合驼峰规则,或者需要特殊处理时使用。

关键场景:exist = false(重点)

前端展示需求 vs 数据库存储结构的差异

  • 问题描述:
    • 数据库表里只有 dept_id(部门ID)。
    • 前端页面列表需要显示 dept_name(部门名称)。
    • MP 的自动 CRUD(如 selectById)是根据实体类生成 SQL 的。如果实体类里加了 deptName 属性,MP 默认会生成 SELECT ..., dept_name FROM table,导致数据库报错“列不存在”。
  • 解决方案
    在实体类中添加该属性,并使用 exist = false 告诉 MP 忽略它。
public class User {
    private Long id;
    private Long deptId; // 数据库里有

    // 数据库里没有,仅用于接收查询结果或前端展示
    @TableField(exist = false) 
    private String deptName; 
}

3.@TableName

这个注解用于建立 Java 实体类与数据库物理表之间的对应关系。

核心功能

  • 默认机制:MP 默认会将实体类名转换为下划线风格去匹配表名(例如 UserDept 类默认找 user_dept 表)。
  • 显式指定:当类名与表名无法通过默认规则匹配时,必须使用此注解。

使用场景

  • 表名不规范:例如数据库表叫 t_user_info,但类名叫 User
  • 多数据源/分表:动态指定操作哪一张表。
// 告诉 MP:这个类对应数据库里的 t_sys_user 表
@TableName("t_sys_user") 
public class User {
    // ...
}

十、条件构造器

Wrapper(条件构造器)的继承体系

image-20260810201805936

1. 查询 vs 更新:看独有接口

  • QueryWrapper (查)
    • 独有select(...) —— 指定要查哪些列(如 SELECT id, name)。
    • 用途:只负责构建 WHERE 条件和 SELECT 字段。
  • UpdateWrapper (改)
    • 独有set(...) —— 指定要改哪个字段(如 SET age = 18)。
    • 用途:除了构建 WHERE 条件,还负责封装修改的数据。

2. Lambda vs 普通版:看字段引用

  • 普通版 (Query/UpdateWrapper)
    • 写法:用字符串写字段名。
    • 例子.eq("name", "张三")
    • 缺点:字段名写错编译不报错,运行时才崩;重构麻烦。
  • Lambda 版 (LambdaQuery/LambdaUpdateWrapper)
    • 写法:用方法引用 (Entity::getField)。
    • 例子.eq(User::getName, "张三")
    • 优点类型安全,写错编译直接报错;支持智能提示,重构无忧。

3.示例:

生成EmployeeMapperTest

@SpringBootTest
class EmployeeMapperTest {

    @Autowired
    private EmployeeMapper employeeMapper;

    //gt> lt< ge>= le<=
    @Test
    void 查询年龄小于等于25的员工(){
        //建立条件构造器
        QueryWrapper<Employee> wrapper = new QueryWrapper<>();
        //添加条件
        wrapper.le("age", 25);
        //执行查询
        List<Employee> list = employeeMapper.selectList(wrapper);
        for (Employee employee : list) {
            System.err.println(employee);
        }
    }

    @Test
    void 查询名字中含有明字的员工(){
        //建立条件构造器
        QueryWrapper<Employee> wrapper = new QueryWrapper<>();
        //添加条件
        wrapper.like("name", "明");
        //执行查询
        List<Employee> list = employeeMapper.selectList(wrapper);
        for (Employee employee : list) {
            System.err.println(employee);
        }
    }

    @Test
    void 查询名字中含有明字的员工1(){
        //建立条件构造器
        LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
        //添加条件
        wrapper.like(Employee::getName, "明");
        //执行查询
        List<Employee> list = employeeMapper.selectList(wrapper);
        for (Employee employee : list) {
            System.err.println(employee);
        }
    }
//-----------------------   高级查询   -----------------------------
    @Test
    void 查询名字赵一明的员工(){
        //建立条件构造器
        LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
        //添加条件
        wrapper.eq(Employee::getName, "赵一明");
        //执行查询
        List<Employee> list = employeeMapper.selectList(wrapper);
        for (Employee employee : list) {
            System.err.println(employee);
        }
    }

    @Test
    void 查询名字为赵总李总孙总的员工(){
        //建立条件构造器
        LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
        //添加条件
        wrapper.in(Employee::getName, "赵总","李总","孙总");
        //执行查询
        List<Employee> list = employeeMapper.selectList(wrapper);
        for (Employee employee : list) {
            System.err.println(employee);
        }
    }

  	 /**
     * 连表查询
     * select *
     * from employee
     * where dept_id in (
     *     SELECT id
     *     from department
     *     where name = '财务部')
     */
    @Test
    void 查询财务部的所有员工(){
        //建立条件构造器
        LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
        //添加条件
        wrapper.inSql(Employee::getDeptId, "SELECT id from department where name = '财务部'");
        //执行查询
        List<Employee> list = employeeMapper.selectList(wrapper);
        for (Employee employee : list) {
            System.err.println(employee);
        }
    }

    @Test
    void 查询年龄在28到35的员工(){
        //建立条件构造器
        LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
        //添加条件
        wrapper.ge(Employee::getAge, 28);
        wrapper.le(Employee::getAge, 35);
        //执行查询
        List<Employee> list = employeeMapper.selectList(wrapper);
        for (Employee employee : list) {
            System.err.println(employee);
        }
    }

    @Test
    void 查询年龄在28到35的员工1(){
        //建立条件构造器
        LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
        //添加条件
        wrapper.between(Employee::getAge, 28, 35);
        //执行查询
        List<Employee> list = employeeMapper.selectList(wrapper);
        for (Employee employee : list) {
            System.err.println(employee);
        }
    }

    //likeRight查xxx开头的;likeLeft查xxx结尾的 哪边随意用哪个
    @Test
    void 查询李开头员工(){
        //建立条件构造器
        LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
        //添加条件
        wrapper.likeRight(Employee::getName, "李");
        //执行查询
        List<Employee> list = employeeMapper.selectList(wrapper);
        for (Employee employee : list) {
            System.err.println(employee);
        }
    }

    @Test
    void 查询email为null员工(){
        //建立条件构造器
        LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
        //添加条件
        wrapper.isNull(Employee::getEmail);
        //执行查询
        List<Employee> list = employeeMapper.selectList(wrapper);
        for (Employee employee : list) {
            System.err.println(employee);
        }
    }

    //查询所有员工,如果有年龄为30的员工就输出所有比他年龄大的员工信息;否则什么都不输出
    @Test
    void exists(){
        //建立条件构造器
        QueryWrapper<Employee> wrapper = new QueryWrapper<>();
        //添加条件
        wrapper.exists("select age from employee where age = 30")
                .gt("age", 30);
        //执行查询
        List<Employee> list = employeeMapper.selectList(wrapper);
        for (Employee employee : list) {
            System.err.println(employee);
        }
    }


    @Test
    void 查询名字为赵一明或者年龄大于等于30的员工(){
        //建立条件构造器
        LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
        //添加条件
        wrapper.eq(Employee::getName, "赵一明")
                .or()
                .gt(Employee::getAge, 30);
        //执行查询
        List<Employee> list = employeeMapper.selectList(wrapper);
        for (Employee employee : list) {
            System.err.println(employee);
        }
    }

    @Test
    void 查询所有员工的姓名年龄和部门id(){
        //建立条件构造器
        LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
        //添加条件
        wrapper.select(Employee::getName, Employee::getAge, Employee::getDeptId);
        //执行查询
        List<Employee> list = employeeMapper.selectList(wrapper);
        for (Employee employee : list) {
            System.err.println(employee);
        }
    }

    @Test
    void 查询所有员工的姓名年龄和部门id按照年龄降序排列(){
        //建立条件构造器
        LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
        //添加条件
        wrapper.select(Employee::getName, Employee::getAge, Employee::getDeptId);
        wrapper.orderByDesc(Employee::getAge);
        //执行查询
        List<Employee> list = employeeMapper.selectList(wrapper);
        for (Employee employee : list) {
            System.err.println(employee);
        }
    }

    @Test
    void 根据部门分组查询每个部门的员工数量和平均年龄(){
        //建立条件构造器
        QueryWrapper<Employee> wrapper = new QueryWrapper<>();
        //添加条件
        wrapper.groupBy("dept_id");
        wrapper.select("dept_id, count(1) c, avg(age) avgage");
        //执行查询 用selectList是查原表结构,查分组要用selectMaps
        List<Map<String, Object>> list = employeeMapper.selectMaps(wrapper);
        for (Map<String, Object> map : list) {
            System.err.println(map);
        }
    }

    @Test
    void 根据部门分组查询每个部门的员工数量和平均年龄列出平均年龄大于等于30的部门信息(){
        //建立条件构造器
        QueryWrapper<Employee> wrapper = new QueryWrapper<>();
        //添加条件
        wrapper.groupBy("dept_id");
        wrapper.select("dept_id, count(1) c, avg(age) avgage");
        wrapper.having("avgage >= 30");
        //执行查询
        List<Map<String, Object>> list = employeeMapper.selectMaps(wrapper);
        for (Map<String, Object> map : list) {
            System.err.println(map);
        }
    }

    @Test
    void 查询所有员工信息按照年龄升序排列分页展示每页5条当前第一页(){
        //高级查询条件(条件构造器)
        QueryWrapper<Employee> wrapper = new QueryWrapper<>();
        wrapper.orderByAsc("age");
        //分页条件
        IPage<Employee> page = new Page<>();
        page.setCurrent(1);//当前页
        page.setSize(5);//每页条数
        //查分页
        IPage<Employee> pageResult = employeeMapper.selectPage(page, wrapper);
        System.err.println(pageResult.getCurrent());
        System.err.println(pageResult.getSize());
        System.err.println(pageResult.getTotal());
        System.err.println(pageResult.getPages());
        //当前页数据
        List<Employee> records = pageResult.getRecords();
        for (Employee record : records) {
            System.err.println(record);
        }
    }

    @Test
    void delete(){
        employeeMapper.deleteById(10);
    }

}

注意:mp分页需要自己加分页拦截器,在启动类加。

@MapperScan("cn.wolfcode.mapper")
@SpringBootApplication
public class Mp73Application {

    public static void main(String[] args) {
        SpringApplication.run(Mp73Application.class, args);
    }

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件,指定数据库类型
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);

        // 常用配置项
        paginationInnerInterceptor.setOverflow(false);    // false:超出最大页返回空;true:回到第一页
        paginationInnerInterceptor.setMaxLimit(1000L);    // 单页最大条数,防止一次性查大量数据
        paginationInnerInterceptor.setOptimizeJoin(true); // join优化

        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        return interceptor;
    }

}

十一、自定义Sql

1.mapper.xml方式

MyBatis-Plus 完美兼容原生 MyBatis 的 XML 写法。当遇到 MP 的 Wrapper 无法实现的复杂 SQL(如多表关联、复杂子查询、存储过程调用)时,自定义 SQL 是最佳选择。

2.注解方式

(1)单表查询

@Select("select e.* from employee e")
List<Employee> listByAnnoSingle();

(2)关联查询

@Select("select e.*, d.id d_id, d.name d_name, d.sn d_sn from employee e left join department d on e.dept_id = d.id")
@Results({
    @Result(column = "d_id", property = "dept.id"),
    @Result(column = "d_name", property = "dept.name"),
    @Result(column = "d_sn", property = "dept.sn")
})
List<Employee> listByAnnoJoin();

十二、通用Service接口

Service 层通常由一个接口和一个实现类组成。

  1. 接口定义:继承 IService<T>
  2. 实现类定义:继承 ServiceImpl<M, T> 并实现你的接口。
// 1. 接口
public interface EmployeeService extends IService<Employee> {
}

// 2. 实现类
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
}

批量插入

这是 IService 相比 BaseMapper 最显著的优势之一。

  • Mapper 层 (BaseMapper)
    • 原生只提供了 insert(T entity),一次只能插入一条。
    • 如果要批量,通常需要自己写 foreach 循环调用 insert,或者手写 XML SQL。
  • Service 层 (IService)
    • 提供了 saveBatch(Collection<T> entityList) 方法。
    • 原理:它默认会将列表分批(默认每批 1000 条),利用 MyBatis 的 ExecutorType.BATCH 模式执行,性能远高于循环单条插入。
@Autowired
private EmployeeService employeeService;

public void addEmployees() {
    List<Employee> list = new ArrayList<>();
    // ... 添加数据到 list ...

    // 直接调用 Service 的批量保存方法
    // 第二个参数是 batchSize,可选,默认 1000
    employeeService.saveBatch(list, 500);
}

接口:

public interface IEmployeeService extends IService<Employee> {
}

实现类:

  • implements IEmployeeService(实现接口)
    • 它告诉 Spring 容器和编译器:“我是一个正规的 Service,请允许别人通过接口来注入我”。
  • extends ServiceImpl<...>(继承类)
    • ServiceImpl 是 MP 官方写好的一个“超级实现类”,里面已经写满了 saveBatchlist 等方法的底层代码。
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements IEmployeeService {

    @Autowired
    private DepartmentMapper departmentMapper;

    public void test(){
        baseMapper.selectList(null);
    }

}

十三、逻辑删除

(1)MyBatis-Plus 的逻辑删除功能会在执行数据库操作时自动处理逻辑删除字段。以下是它的工作方式:

  • 插入:逻辑删除字段的值不受限制。
  • 查找:自动添加条件,过滤掉标记为已删除的记录。
  • 更新:防止更新已删除的记录。
  • 删除:将删除操作转换为更新操作,标记记录为已删除。

例如:

  • 删除update user set deleted=1 where id = 1 and deleted=0
  • 查找select id,name,deleted from user where deleted=0

(2)支持的数据类型

​ 逻辑删除字段支持所有数据类型,但推荐使用 IntegerBooleanLocalDateTime

​ 如果使用 datetime 类型,可以配置逻辑未删除值为 null(长度为4的字符串,在yaml中需使用转义符(单引号)包裹),已删除值可以使用函数如 now() 来获取当前时间

​ 如果使用 bigint 类型,可以配置逻辑未删除值为 0,已删除值可以使用函数如 UNIX_TIMESTAMP() 来获取当前时间戳作为删除标识。适合用于将删除字段作为唯一索引组成列,可以多次逻辑删除。

(3)使用方法

方法 1: 配置全局逻辑删除属性

application.yml 中配置 MyBatis-Plus 的全局逻辑删除属性:

mybatis-plus:

  global-config:

    db-config:

      logic-delete-field: deleted # 全局逻辑删除字段名(deleted为实体类属性名称)

      logic-delete-value: 1 # 逻辑已删除值。可选,默认值为 1

      logic-not-delete-value: 0 # 逻辑未删除值。可选,默认值为 0

方法 2: 如不想使用全局配置,可以在实体类中使用 @TableLogic 注解,对类单独进行配置

在实体类中,对应数据库表的逻辑删除字段上添加 @TableLogic 注解:

import com.baomidou.mybatisplus.annotation.TableLogic;
public class User {    
    // 其他字段...
    @TableLogic    
    private Integer deleted;
}

同样,逻辑未删除值默认为 0,逻辑已删除值默认为 1。这两个值可以通过设置 @TableLogic 注解的 value 属性和 delval 属性的值来进行修改。

posted on 2026-08-10 21:31  冬冬咚  阅读(11)  评论(0)    收藏  举报