第1章 项目前置课-Mybatis-Plus

一、项目介绍

1、行业背景、整体业务流程

2、技术架构、课程安排

二、MyBatisPlus快速入门

1、是什么

2、准备工作

检查项目依赖

导入数据库脚本

检查配置文件

打开并启动前端项目

3、代码实现

(1)导入依赖

导入Mybatis-Plus的起步依赖,替换掉MyBatis的起步依赖

XML 复制代码
<!--Mybatis-Plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.11</version>
</dependency>

接着,在引导类上添加注解,配置自动扫描Mapper:

java 复制代码
@MapperScan("com.itheima.mapper")//配置自动扫描Mapper
@ServletComponentScan
@SpringBootApplication
public class TliasMangementSystemApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(TliasMangementSystemApplication.class, args);
    }
​
}

(2)定义mapper

为了简化单表 CRUD,Mybatis-Plus提供了一个基础的BaseMapper接口,其中已经实现了单表的CRUD:

因此咱们自定义的Mapper只要实现了这个BaseMapper,就无需自己实现单表CRUD了。

修改DeptMapper接口,让其继承BaseMapper,接口中的代码可以全部注释掉或者删除

java 复制代码
package com.itheima.mapper;
​
import com.baomidou.Mybatis-Plus.core.mapper.BaseMapper;
import com.itheima.pojo.Dept;
​
@Mapper
public interface DeptMapper extends BaseMapper<Dept> {
    /*
    @Select("select id, name, create_time, update_time from dept order by update_time desc ")
    List<Dept> findAll();
​
    @Delete("delete from dept where id = #{id}")
    void deleteById(Integer id);
​
    @Insert("insert into dept(name, create_time, update_time) values (#{name}, #{createTime}, #{updateTime})")
    void save(Dept dept);
​
    @Select("select id, name, create_time, update_time from dept where id = #{id}")
    Dept getById(Integer id);
​
    void update(Dept dept);
    */
}

mapper的实现类也注释掉,加个无法解析的后缀就能注释掉

业务层调用基础mapper接口的方法

java 复制代码
package com.itheima.service.impl;
​
import com.itheima.mapper.DeptMapper;
import com.itheima.pojo.Dept;
import com.itheima.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
​
import java.time.LocalDateTime;
import java.util.List;
​
@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;
​
​
    @Override
    public List<Dept> findAll() {
        return deptMapper.selectList(null);
    }
​
    @Override
    public void deleteById(Integer id) {
        deptMapper.deleteById(id);
    }
​
    @Override
    public void save(Dept dept) {
        // 补全基础属性
        dept.setCreateTime(LocalDateTime.now());
        dept.setUpdateTime(LocalDateTime.now());
​
        // 调用Mapper层方法保存数据
        deptMapper.insert(dept);
    }
​
    @Override
    public Dept getById(Integer id) {
        Dept dept = deptMapper.selectById(id);
        return dept;
    }
​
    @Override
    public void update(Dept dept) {
        // 补全基础属性
        dept.setUpdateTime(LocalDateTime.now());
        deptMapper.updateById(dept);
    }
}

以上的所有操作数据库的方法,都是继承了BaseMapper之后提供好的方法,可以正常操作数据库

最后,修改Dept.java,设置主键增长策略为自增:

java 复制代码
package com.itheima.pojo;
​
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
​
import java.time.LocalDateTime;
​
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dept {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    private String name;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

三、MyBatisPlus常见注解

在刚刚的入门案例中,咱们仅仅引入了依赖,继承了BaseMapper就能使用Mybatis-Plus,非常简单。但是问题来了: Mybatis-Plus如何知道咱们要查询的是哪张表?表中有哪些字段呢?

大家回忆一下,DeptMapper在继承BaseMapper的时候是不是指定了一个泛型呀:

java 复制代码
public interface DeptMapper extends BaseMapper<Dept> {
​
}

泛型中的Dept 就是与数据库表对应的pojo

Mybatis-Plus就是根据pojo实体的信息通过反射来推断出表的信息,从而生成SQL的。规则如下:

  • Mybatis-Plus会把pojo实体的类名驼峰转下划线作为表名

  • Mybatis-Plus会把名为id的字段作为主键

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

但有些情况下,实体类的类名与表名不完全一致 ,或者实体类中的属性名和数据库表中的字段名无法完全对应,因此Mybatis-Plus提供了一些注解便于咱们声明表信息,常用的有下面几个注解:

@TableName: 用来指定表名

@TableField: 用来指定表中的普通字段信息

@TableId: 用来指定表中的主键字段信息

1、TableName、TableField

(1)@TableName

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

  • 使用位置:实体类

  • 示例:

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User {
    private Long id;
​
    private String name;
​
    private Boolean isMarried;
​
    private Integer order;
​
    private String address;
}

(2)@TableField

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

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

  • 成员变量名与数据库一致,但是与数据库的关键字冲突 。使用@TableField注解给结合 ` ` 指定字段名称。

  • 成员变量不是数据库中的字段 ,则需要将exist属性设置为false明确告知MyBatisPlus该字段在数据库表中不存在。

  • 普通字段注解

  • 示例

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
​
    @TableField("username")
    private String name;
​
    private Boolean isMarried;
​
    @TableField("`order`")
    private Integer order;
​
    @TableField(exist = false)
    private String address;
}

2、Tableld

@TableId注解支持两个属性:

value:指定数据库表中的主键字段名称

IdType:指定主键生成类型,支持的类型有:

  • IdType.AUTO:使用数据库自增 ID 作为主键

  • IdType.NONE:无特定生成策略,如果全局配置中有 IdType 相关的配置,则会跟随全局配置。

  • IdType.INPUT:在插入数据前,由用户自行设置主键值

  • IdType.ASSIGN_ID自动分配 ID,适用于 LongIntegerString 类型的主键。

    默认使用雪花算法 通过 IdentifierGeneratornextId 实现,这是默认的ID策略

  • IdType.ASSIGN_UUID:自动分配 UUID,适用于 String 类型的主键。默认实现为 IdentifierGeneratornextUUID 方法。

描述:主键注解,标识实体类中的主键字段和主键生成类型

使用位置:实体类的主键字段

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    private String name;

    private Boolean isMarried;

    private Integer order;

    private String address;
}

四、MyBatisPlus常见配置

Mybatis-Plus也支持基于yml文件 的自定义配置,详见官方文档:使用配置 | MyBatis-Plus

大多数的配置都有默认值 ,因此咱们都无需配置。但还有一些是没有默认值的,接下来,一起来看一些常见的配置:

  • 全局id类型

    TypeScript 复制代码
    mybatis-plus:
      global-config:
        db-config:
          id-type: auto             # 全局id类型为自增长
          update-strategy: not_null # 更新策略:只更新非空字段
  • 需要注意的是,Mybatis-Plus也支持手写SQL的,而mapper层的xml映射文件的读取位置可以自己配置:

    • type-aliases-package:指定 MyBatis 别名包扫描路径,用于给包中的类注册别名

      注册后,在 Mapper 对应的 XML 文件中可以直接使用类名,无需使用全限定类名。

    • mapper-locations:指定 MyBatis 的 Mapper 层 XML 映射文件的位置默认值["classpath*:/mapper/**/*.xml"]

      TypeScript 复制代码
      mybatis-plus:
        type-aliases-package: com.itheima.pojo
        mapper-locations: classpath:mapper/**/*Mapper.xml
        configuration:
          map-underscore-to-camel-case: true
          log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      
        global-config:
          db-config:
            id-type: auto             # 全局id类型为自增长
            update-strategy: not_null # 更新策略:只更新非空字段

mapper-locations也可以配置成classpath*:mapper/**.xml,也就是说咱们只要把mapper.xml文件放置在mapper目录下就一定会被加载。

例如,咱们放开DeptMapper.xml文件中的sql片段,同时将resultType属性中的包名省略:

XML 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.DeptMapper">
    <select id="getById" resultType="com.itheima.pojo.Dept">
        select id, name, create_time, update_time from dept where id = #{id}
    </select>
</mapper>

接着,放开DeptMapper.java中的getById方法:

java 复制代码
public interface DeptMapper extends BaseMapper<Dept> {
    /*
    @Select("select id, name, create_time, update_time from dept order by update_time desc ")
    List<Dept> findAll();

    @Delete("delete from dept where id = #{id}")
    void deleteById(Integer id);

    @Insert("insert into dept(name, create_time, update_time) values (#{name}, #{createTime}, #{updateTime})")
    void save(Dept dept);

    @Update("update dept set name = #{name}, update_time = #{updateTime} where id = #{id}")
    void update(Dept dept);
    */
    
    Dept getById(Integer id);

}

最后,修改DeptServiceImpl中的getById方法:

java 复制代码
@Override
public Dept getById(Integer id) {
    // Dept dept = deptMapper.selectById(id);
    Dept dept = deptMapper.getById(id);
    return dept;
}

重启后端程序,测试点击修改按钮根据id查询功能,依然正常可用,说明此时咱们放在mapper目录下的xml映射文件生效了,同时配置中的别名包扫描路径也生效了。

五、MyBatisPlus核心功能

刚才的案例中都是以id为条件的简单CRUD ,一些复杂条件的SQL语句 就要用到一些更高级的功能了,所以,接下来咱们开始学习Mybatis-Plus中的核心功能,包括两部分内容:

  • 条件构建器

  • IService接口

1、条件构建器简介

除了查询以外,修改、删除的SQL语句都需要指定where条件。

因此BaseMapper中不仅仅提供了接收id 作为where条件的方法,还提供了一组可以接收更加复杂的where条件的方法。

参数中的Wrapper就是条件构建的抽象类 ,其下有很多默认实现,继承关系如图:

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

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

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

接下来,咱们就来看看如何利用Wrapper实现复杂查询。

2、条件构建器QueryWrapper的使用

无论是查询、修改、删除,都可以使用QueryWrapper来构建条件。接下来看一些例子:

查询查询姓名中包含"李"且薪资大于等于5000的员工的 id, name, phone, salary字段。代码如下:

sql 复制代码
select id, name, phone, salary from emp where name like '%李%' and salary >= 5000;

首先让EmpMapper继承自BaseMapper:

java 复制代码
@Mapper
public interface EmpMapper extends BaseMapper<Emp> {

}

为了方便测试,咱们使用单元测试学习QueryWrapper的使用:

java 复制代码
package com.itheima;

import com.baomidou.Mybatis-Plus.core.conditions.query.QueryWrapper;
import com.itheima.mapper.EmpMapper;
import com.itheima.pojo.Emp;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class TestQueryWrapper {

    @Autowired
    private EmpMapper empMapper;

    @Test
    public void testQueryWrapper() {
        // 查询姓名中包含"李"且薪资大于等于5000的员工的 id, name, phone, salary字段
        QueryWrapper<Emp> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("name", "李")
                .ge("salary", 5000)
                .select("id", "name", "phone", "salary");

        List<Emp> emps = empMapper.selectList(queryWrapper);
        emps.forEach(System.out::println);
    }
}

更新更新名为"李忠"的员工的薪水为9000,代码如下:

java 复制代码
@Test
public void testUpdateByQueryWrapper() {
    // 更新名为"李忠"的员工的薪水为9000
    Emp emp = new Emp();
    emp.setSalary(9000);
    QueryWrapper<Emp> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("name", "李忠");
    empMapper.update(emp, queryWrapper);
}

3、条件构建器UpdateWrapper、LambdaQueryWrapper、LambdaUpdateWrapper的使用

(1)UpdateWrapper

更新:更新id为5, 6, 7的员工的薪水,加2000,代码如下:

sql 复制代码
update emp set salary = salary + 2000 where id in (5, 6, 7);
java 复制代码
@Test
public void testUpdateWrapper() {
    // 更新id为5, 6, 7的员工的薪水,加2000
    UpdateWrapper<Emp> updateWrapper = new UpdateWrapper<>();
    updateWrapper.in("id", 5, 6, 7)
            .setSql("salary = salary + 2000");
    empMapper.update(updateWrapper);
}

(2)LambdaQueryWrapper

无论是QueryWrapper还是UpdateWrapper在构造条件的时候都需要写死字段名称 ,这在编程规范中显然是不推荐的 。 那怎么样才能不写字段名,又能知道字段名呢?

其中一种办法是基于变量的getter方法 结合反射技术 来实现,因此咱们只要将条件对应的字段的getter方法传递给Mybatis-Plus ,它就能计算出对应的变量名了。

而传递方法可以使用JDK8中的方法引用Lambda表达式。 因此Mybatis-Plus又提供了一套基于Lambda的Wrapper,包含两个类:

  • LambdaQueryWrapper

  • LambdaUpdateWrapper

分别对应QueryWrapper和UpdateWrapper

其使用方式如下:

java 复制代码
@Test
public void testLambdaQueryWrapper() {
    // 查询姓名中包含"李"且薪资大于等于5000的员工的 id, name, phone, salary字段
    LambdaQueryWrapper<Emp> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.like(Emp::getName, "李")
            .gt(Emp::getSalary, 5000)
            .select(Emp::getId, Emp::getName, Emp::getPhone, Emp::getSalary);

    List<Emp> emps = empMapper.selectList(queryWrapper);
    System.out.println(emps);
}

@Test
public void testLambdaUpdateWrapper() {
    // 更新id为5, 6, 7的员工的薪水,加2000
    LambdaUpdateWrapper<Emp> updateWrapper = new LambdaUpdateWrapper<>();
    updateWrapper.in(Emp::getId, 5, 6, 7)
            .setSql("salary = salary + 2000");
    empMapper.update(updateWrapper);
}

4、IService接口中的基本增删改查方法简介

Mybatis-Plus不仅提供了Map层接口BaseMapper ,还提供了通用的Service层接口及默认实现 ,并在其中封装了一些常用的service模板方法

通用接口为IService,默认实现为ServiceImpl,其中封装的方法可以分为以下几类:

  • save:新增

  • remove:删除

  • update:更新

  • get:查询单个结果

  • list:查询集合结果

  • count:计数

  • page:分页查询

咱们先来看下基本的CRUD接口,

(1)新增

  • save是新增单个元素

  • saveBatch是批量新增

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

  • saveOrUpdateBatch是批量的新增或修改

(2)删除

  • removeById:根据id删除

  • removeByIds:根据id批量删除

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

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

  • ~~removeBatchByIds~~:暂不支持

(3)修改

  • updateById:根据id修改

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

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

  • updateBatchById:根据id批量修改

(4)查询

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

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

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

  • listByIds:根据id批量查询

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

  • list():查询所有

  • count():统计所有数量

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

5、IService接口中的基本使用和快速入门

由于Service中经常需要定义与业务有关的自定义方法 ,因此咱们不能直接使用IService ,而是自定义Service接口 ,然后继承IService 以拓展方法。同时,让自定义的Service实现类继承ServiceImpl ,这样就不用自己实现IService中的接口了。

怎么样?听上去是不是很厉害?那接下来咱们就通过一个入门程序来感受一下是不是如此吧。

需求 :基于Mybatis-Plus的 Iservice 接口,实现Tlias智能学习辅助系统部门管理页面的所有功能:

  • 新增部门

  • 根据id查询部门

  • 根据id更新部门

  • 根据id删除部门

首先,找到DeptService接口,让它继承IService接口,并在泛型上指定实体类的类型:

java 复制代码
public interface DeptService extends IService<Dept> {

}

改完之后,里面原来书写的方法都可以全部删除啦。

接着,找到DeptService接口的实现类 :DeptServiceImpl,让它继承自ServiceImpl类 ,在泛型的位置指定Mapper层接口和实体类

java 复制代码
@Service
public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements DeptService {

}

最后,修改DeptController中报错的代码,改一下方法调用就可以啦。

java 复制代码
@Slf4j
@RequestMapping("/depts")
@RestController
public class DeptController {
    @Autowired
    private DeptService deptService;

    @GetMapping
    public Result findAll() {
        log.info("查询所有部门数据");

        List<Dept> depts = deptService.list();

        return Result.success(depts);
    }

    // 删除部门
    @DeleteMapping
    public Result deleteById(Integer id) {
        log.info("根据id删除部门,删除的部门id:{}", id);
        deptService.removeById(id);
        return Result.success();
    }

    // 新增部门
    @PostMapping
    public Result save(@RequestBody Dept dept) {
        log.info("新增部门,要新增的部门信息:{}", dept);
        deptService.save(dept);
        return Result.success();
    }

    // 根据ID查询部门信息
    @GetMapping("/{id}")
    public Result getById(@PathVariable Integer id) {
        log.info("根据id查询部门,要查询的id:{}", id);

        Dept dept = deptService.getById(id);

        return Result.success(dept);
    }

    // 修改部门
    @PutMapping
    public Result update(@RequestBody Dept dept) {
        log.info("修改部门数据,修改后的部门数据:{}", dept);
        deptService.updateById(dept);
        return Result.success();
    }
}

这样,就搞定了哦,接下来,重启程序,打开页面测试一下部门管理的增删改查是不是都还完全正确吧~

到这里,咱们就搞定了Mybatis-Plus的基本使用啦,接下来,咱们一起通过员工管理页面来实战一下,在实战中也会引入一些新的技巧哦。

六、员工管理页面MyBatis-Plus实战

1、分页查询

员工管理列表页是条件分页查询 ,先来完成基础的分页查询功能,Mybatis-Plus提供了一个分页插件(官网链接:分页插件 | MyBatis-Plus),只有引入这个分页插件,才能支持分页查询。

官网中有一段明确的说明:

v3.5.9 起,PaginationInnerInterceptor 已分离出来。如需使用,则需单独引入 mybatis-plus-jsqlparser 依赖

咱们当前引入的MyBatisPlus版本是:3.5.11。因此,要想使用分页功能,还需要再引入一个依赖:

XML 复制代码
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-jsqlparser</artifactId>
    <version>3.5.11</version>
</dependency>

接下来,在项目中新建一个配置类:com.itheima.config.MybatisConfig

java 复制代码
package com.itheima.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // 初始化Mybatis-Plus核心插件
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        // 添加分页插件
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        paginationInnerInterceptor.setMaxLimit(1000L);
        mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);

        return mybatisPlusInterceptor;
    }
}

引入分页插件后,就可以使用分页的API了:

修改EmpController层代码,调用EmpService层代码完成分页,这一步先不考虑条件查询哦:

EmpController代码如下:

java 复制代码
@GetMapping
public Result page(EmpQueryParam param) {
    log.info("员工列表查询条件:{}", param);

    // 调用Service层查询分页数据
    PageResult<Emp> pageResult = empService.getPageResult(param);

    // 返回结果
    return Result.success(pageResult);
}

EmpService代码如下:

java 复制代码
public interface EmpService extends IService<Emp> {

    /**
     * 分页查询
     * @param param  查询条件
     * @return  分页结果
     */
    PageResult<Emp> getPageResult(EmpQueryParam param);
}

对啦,友情提醒,千万不要忘记EmpService和EmpServiceImpl上要做的操作呢!

还有,要记得,Service层的分页查询 要根据最后修改时间字段倒序排序哦;

EmpServiceImpl代码如下:

java 复制代码
@Service
public class EmpServiceImpl extends ServiceImpl<EmpMapper, Emp> implements EmpService {
    /**
     * 分页查询
     *
     * @param param     查询条件
     * @return 分页结果
     */
    @Override
    public PageResult<Emp> getPageResult(EmpQueryParam param) {
        Page<Emp> page = Page.of(param.getPage(), param.getPageSize());

        // 根据最后修改时间倒序排序
        page.addOrder(OrderItem.desc("update_time"));

        // 调用page方法完成分页查询
        page = page(page);

        // 封装结果并返回
        return new PageResult<>(page.getTotal(), page.getRecords());
    }
}

接下来,解决所属部门列没有数据的问题

部门名称需要关联部门表 查询,而Mybatis-Plus是基于单表操作的,怎么办呢?

将多表操作转换成多次单表操作 ,查询出当前页面所有员工的部门数据 ,在内存中将部门名称关联到员工对象中

先将Emp类中的部门名称字段取消注释,同时添加注解标注表中不存在该字段:

java 复制代码
@Data
public class Emp {
    private Integer id; //ID,主键
    private String username; //用户名
    private String password; //密码
    private String name; //姓名
    private Integer gender; //性别, 1:男, 2:女
    private String phone; //手机号
    private Integer job; //职位, 1:班主任,2:讲师,3:学工主管,4:教研主管,5:咨询师
    private Integer salary; //薪资
    private String image; //头像
    private LocalDate entryDate; //入职日期
    private Integer deptId; //关联的部门ID
    private LocalDateTime createTime; //创建时间
    private LocalDateTime updateTime; //修改时间

    //封装部门名称数
    @TableField(exist = false)
    private String deptName; //部门名称

    // 封装工作经历列表
//    private List<EmpExpr> exprList;
}

EmpServiceImpl中修改后的代码如下:

java 复制代码
@Service
public class EmpServiceImpl extends ServiceImpl<EmpMapper, Emp> implements EmpService {
    @Autowired
    private DeptService deptService;

    /**
     * 分页查询
     *
     * @param param     查询条件
     * @return 分页结果
     */
    @Override
    public PageResult<Emp> getPageResult(EmpQueryParam param) {
        Page<Emp> page = Page.of(param.getPage(), param.getPageSize());

        // 根据最后修改时间倒序排序
        page.addOrder(OrderItem.desc("update_time"));

        // 调用page方法完成分页查询
        page = page(page);

        // 获取员工集合
        List<Emp> emps = page.getRecords();

        // 获取所有员工的部门id
        List<Long> deptIds = emps.stream().map(emp -> emp.getDeptId()).collect(Collectors.toList());

        if (!deptIds.isEmpty()) {
            // 批量查询这些部门的信息
            List<Dept> depts = deptService.listByIds(deptIds);
    
            // 将部门名称封装到员工对象中
            emps.forEach(emp -> {
                depts.forEach(dept -> {
                    if (emp.getDeptId() != null && emp.getDeptId().equals(dept.getId())) {
                        emp.setDeptName(dept.getName());
                    }
                });
            });
        }

        // 封装结果并返回
        return new PageResult<>(page.getTotal(), emps);
    }
}

注意第28行最好先判断一下获取到的部门id列表不为空,再查询对应的部门名称,所以要加一个判断,排除掉查询到的部门列表为空的情况,否则程序运行到这里的第33行会报错呢。

最终页面上的效果如下:

3、条件分页查询

怎么样,在Mybatis-Plus中分页查询是不是很简单?那接下来,再来感受一下条件分页查询的强大之处吧。

IService接口中的page方法除了能接收一个Page对象作为参数 外,还支持传入一个Wrapper对象 ,同时封装查询条件

java 复制代码
default <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper) {
    return this.getBaseMapper().selectPage(page, queryWrapper);
}

接下来,就一起来改造分页查询的方法,添加查询条件

实现类中添加查询条件

java 复制代码
    // 根据最后修改时间倒序排序
    page.addOrder(OrderItem.desc("update_time"));

    // 构建查询条件
    LambdaQueryWrapper<Emp> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.like(param.getName() != null && !param.getName().isEmpty(), Emp::getName, param.getName())
            .eq(param.getGender() != null, Emp::getGender, param.getGender())
            .between(param.getBegin() != null && param.getEnd() != null, Emp::getEntryDate, param.getBegin(), param.getEnd());

    // 调用page方法完成分页查询
    page = page(page, queryWrapper);

    // 获取员工集合
    List<Emp> emps = page.getRecords();

到这里,就实现了员工管理的分页查询,不过咱们还可以继续改造它,上面代码片段中绿色背景的代码还要自己new一个Wrapper对象,并将其作为参数传入到page方法内部,还是有点麻烦,这里可以借助于IService中的另外一个方法:lambdaQuery()进一步简化,最终的代码如下:

java 复制代码
    // 根据最后修改时间倒序排序
    page.addOrder(OrderItem.desc("update_time"));
    
    // 使用lambdaQuery方法构建查询条件
    page = lambdaQuery().like(param.getName() != null && !param.getName().isEmpty(), Emp::getName, param.getName())
            .eq(param.getGender() != null, Emp::getGender, param.getGender())
            .between(param.getBegin() != null && param.getEnd() != null, Emp::getEntryDate, param.getBegin(), param.getEnd())
            .page(page);

    // 获取员工集合
    List<Emp> emps = page.getRecords();

4、新增员工

由于初始项目中去除了阿里云OSS相关的代码,本次新增员工不考虑头像上传,只处理员工基本信息和工作经历信息

先将Emp中的工作经历列表字段的注释取消,并标记字段不在数据库中。

java 复制代码
    // 封装工作经历列表
    @TableField(exist = false)
    private List<EmpExpr> exprList;

在EmpController中调用Service层方法,将数据传给Service层处理,代码如下:

java 复制代码
    // 调用service层方法保存员工信息和员工的工作经历信息
    empService.saveEmp(emp);

在EmpService中添加Controller层调用的方法:

java 复制代码
    /**
     * 保存员工信息
     *
     * @param emp 员工信息
     */
    void saveEmp(Emp emp);

在EmpServiceImpl中实现该方法:

java 复制代码
/**
 * 保存员工信息
 *
 * @param emp 员工信息
 */
@Override@
@Transactional(rollbackFor = Exception.class)
public void saveEmp(Emp emp) {
    // 补全基础属性
    emp.setCreateTime(LocalDateTime.now());
    emp.setUpdateTime(LocalDateTime.now());

    // 保存员工基本信息
    save(emp);

    // 获取员工工作经历信息
    List<EmpExpr> exprList = emp.getExprList();

    if (exprList != null && !exprList.isEmpty()) {
        exprList.forEach(expr -> {
            expr.setEmpId(emp.getId());
        });
        // 批量保存工作经历信息
        exprService.saveBatch(exprList);
    }
}

由于这里涉及到了批量保存员工的工作经历,所以,需要在程序中添加EmpExprService和其实现类ServiceImpl:

EmpExprService:

java 复制代码
package com.itheima.service;

import com.baomidou.Mybatis-Plus.extension.service.IService;
import com.itheima.pojo.EmpExpr;

public interface EmpExprService extends IService<EmpExpr> {
}

EmpExprServiceImpl:

java 复制代码
package com.itheima.service.impl;

import com.baomidou.Mybatis-Plus.extension.service.impl.ServiceImpl;
import com.itheima.mapper.EmpExprMapper;
import com.itheima.pojo.EmpExpr;
import com.itheima.service.EmpExprService;
import org.springframework.stereotype.Service;

@Service
public class EmpExprServiceImpl extends ServiceImpl<EmpExprMapper, EmpExpr> implements EmpExprService {
}

EmpExprMapper:

java 复制代码
@Mapper
public interface EmpExprMapper extends BaseMapper<EmpExpr> {
    
}

5、修改员工

修改员工涉及到2个操作:根据id查询员工用于页面回显 和根据id修改员工信息。

先来完成第1步:根据id查询员工。

这里要注意的是,查询回显时不仅仅要查询到员工的基本信息,还要查询到员工的工作经历信息

在Controller层调用Service层方法获取员工数据:

java 复制代码
    // 调用Service层方法获取员工信息
    Emp emp = empService.getEmpById(id);

	return Result.success(emp);

在EmpService中添加以下方法:

java 复制代码
/**
 * 根据id查询员工信息
 * @param id    员工id
 * @return      查询到的员工信息
 */
Emp getEmpById(Integer id);

在EmpServiceImpl中实现该方法:

java 复制代码
/**
 * 根据id查询员工信息
 *
 * @param id 员工id
 * @return 查询到的员工信息
 */
@Override
public Emp getEmpById(Integer id) {
    Emp emp = getById(id);
    
    // 工作经历列表
    emp.setExprList(exprService.list(new LambdaQueryWrapper<EmpExpr>().eq(EmpExpr::getEmpId, id)));
    return emp;
}

接下来,完成点击保存按钮 将修改更新到数据库

这里,又可以拆分为3个小的操作:

  • 根据id更新员工的基本信息

  • 根据id批量删除员工的工作经历信息

  • 批量保存员工的工作经历信息

代码如下:

在Controller层调用Service层方法将数据传给Service层处理:

java 复制代码
    // 调用service层方法修改员工信息
    empService.updateEmp(emp);

在EmpService中添加以下方法:

java 复制代码
/**
 * 修改员工
 * @param emp 员工信息
 */
void updateEmp(Emp emp);

在EmpServiceImpl中实现该方法:

java 复制代码
/**
 * 修改员工
 *
 * @param emp 员工信息
 */
@Override
@Transactional(rollbackFor = Exception.class)
public void updateEmp(Emp emp) {
    // 根据id更新员工的基本信息
    emp.setUpdateTime(LocalDateTime.now());
    updateById(emp);

    // 根据id批量删除员工的工作经历信息
    exprService.remove(new LambdaQueryWrapper<EmpExpr>().eq(EmpExpr::getEmpId, emp.getId()));

    // 批量保存员工的工作经历信息
    List<EmpExpr> exprList = emp.getExprList();
    if (exprList != null && !exprList.isEmpty()) {
        exprList.forEach(expr -> expr.setEmpId(emp.getId()));
    }
    exprService.saveBatch(exprList);
}

到这里,修改员工接口就完成啦,接下来咱们再深入一下:小伙伴还记得Web阶段根据id更新员工的Mapper层xml文件是怎么写的吗?是不是下面这样呀?

XML 复制代码
<update id="update">
    update emp
    <set>
        <if test="username != null and username != ''">
            username=#{username},
        </if>
        <if test="password != null and password != ''">
            password=#{password},
        </if>
        <if test="name != null and name != ''">
            name=#{name},
        </if>
        <if test="gender != null">
            gender=#{gender},
        </if>
        <if test="phone != null and phone != ''">
            phone=#{phone},
        </if>
        <if test="job != null">
            job=#{job},
        </if>
        <if test="salary != null">
            salary=#{salary},
        </if>
        <if test="image != null and image != ''">
            image=#{image},
        </if>
        <if test="entryDate != null">
            entry_date=#{entryDate},
        </if>
        <if test="deptId != null">
            dept_id=#{deptId},
        </if>
        <if test="updateTime != null">
            update_time=#{updateTime}
        </if>
    </set>
    where id = #{id}
</update>

这里面做了很多的条件判断,那刚刚咱们在EmpServiceImplupdateEmp方法中调用的updateById()方法能不能实现条件更新呢?来做一个测试:

在APIFox中发起一个请求,修改id等于1的员工数据,json中只保留id、username、phone三个字段:

java 复制代码
{
    "id": 1,
    "username": "shinaian1",
    "phone": "13309090301"
}

发起请求后,重点查看控制台打印的SQL:

TypeScript 复制代码
==>  Preparing: UPDATE emp SET username=?, phone=?, update_time=? WHERE id=?
==> Parameters: shinaian1(String), 13309090301(String), 2024-09-22T23:57:23.060488600(LocalDateTime), 1(Integer)
<==    Updates: 1

从执行的SQL中能看出来,Mybatis-Plus自动执行了条件更新,值为null的字段并没有出现在set关键字的后方。

6、批量删除员工

员工管理页面上每条记录后面的删除按钮和批量删除按钮共用一个方法即可,方法代码如下:

java 复制代码
    // 调用service层方法批量删除员工信息和员工工作经历信息
    empService.removeEmpsByIds(ids);

在EmpService中添加一个方法:

java 复制代码
/**
 * 批量删除员工
 * @param ids   要删除的员工id
 */
void removeEmpsByIds(List<Integer> ids);

在EmpServiceImpl中重写Service层新添加的方法来执行批量删除操作:

java 复制代码
/**
 * 批量删除员工
 *
 * @param ids 要删除的员工id
 */
@Override
@Transactional(rollbackFor = Exception.class)
public void removeEmpsByIds(List<Integer> ids) {
    // 调用service层方法批量删除员工信息
    removeBatchByIds(ids);

    // 删除员工工作经历信息
    exprService.remove(Wrappers.<EmpExpr>lambdaQuery().in(EmpExpr::getEmpId, ids));
}

七、微实战

1、内容介绍

2、细节和难点讲解

相关推荐
jaysee-sjc6 小时前
【JavaWeb】Tlias智能学习辅助系统|后端Web实战(登录认证)
java·开发语言·前端·学习·mybatis
步行cgn7 小时前
MyBatis 动态 SQL 完全指南
sql·tomcat·mybatis
步行cgn1 天前
错误:Invalid bound statement (not found) 详解与解决方案
mybatis
sugar__salt1 天前
MyBatis-Plus 从入门到实战:高效简化数据库开发的完整指南
数据库·spring boot·mybatis·数据库开发
bbq粉刷匠2 天前
手写 SQL 被一次 mvn generate 抹掉之后:我用一个 extension/ 子目录躲开 MyBatis 两个坑
数据库·sql·mybatis
ChaHae-In2 天前
MyBatis入门操作
java·mybatis
sugar__salt2 天前
MyBatis ③动态 SQL 完全指南 —— 从条件拼接到批量操作
java·sql·mybatis
元界metalite3 天前
MyBatis多数据源常见难题-事务开始了为何数据源还没选出来
后端·mybatis
霸道流氓气质3 天前
Mysql中慢 SQL 优化实战:减少不必要的 JOIN 关联
sql·mysql·mybatis