day01-MybatisPlus

YangeIT大约 45 分钟基础服务框架MybatisPlusMybatisPlus

day01-MybatisPlus

目标

  • 能够基于 MyBatisPlus 完成标准 Dao 开发 ❤️
  • 能够掌握 MyBatisPlus 的条件查询 ❤️
  • 能够掌握 MyBatisPlus 的字段映射与表名映射 ❤️
  • 能够掌握 id 生成策略控制
  • 能够理解代码生成器的相关配置

1.MP入门

MP快速入门

MP快速入门

大家在日常开发中应该能发现,单表的 CRUD 功能代码重复度很高,也没有什么难度。而这部分代码量往往比较大,开发起来比较费时。

因此,目前企业中都会使用一些组件来简化或省略单表的 CRUD 开发工作。目前在国内使用较多的一个组件就是 MybatisPlus.

官方网站如下:

image-20231119103607434
image-20231119103607434

当然,MybatisPlus 不仅仅可以简化单表操作,而且还对 Mybatis 的功能有很多的增强。可以让我们的开发更加的简单,高效。

通过今天的学习,我们要达成下面的目标:

  • 能利用 MybatisPlus 实现基本的 CRUD
  • 会使用条件构建造构建查询和更新语句
  • 会使用 MybatisPlus 中的常用注解
  • 会使用 MybatisPlus 处理枚举、JSON 类型字段
  • 会使用 MybatisPlus 实现分页

代码操作

比如我们要实现 User 表的 CRUD,只需要下面几步:

入门案例需提前准备环境(假数据)

  • 引入 MybatisPlus 依赖
  • 定义 Mapper

为了方便测试,我们先创建一个新的项目,并准备一些基础数据。

1.1.环境准备

复制课前资料提供好的一个项目到你的工作空间(不要包含空格和特殊字符):

然后用你的 IDEA 工具打开,项目结构如下:

注意配置一下项目的 JDK 版本为 JDK11。首先点击项目结构设置:

在弹窗中配置 JDK:

接下来,要导入两张表,在课前资料中已经提供了 SQL 文件:

对应的数据库表结构如下:

最后,在 application.yaml 中修改 jdbc 参数为你自己的数据库参数:

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: MySQL123
logging:
  level:
    com.itheima: debug
  pattern:
    dateformat: HH:mm:ss

总结

课堂作业

  1. MybatisPlust是什么?有什么特点?🎤
  2. MybatisPlus和Mybatis的关系?

1.2.常见注解

常见注解

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

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

image
image

泛型中的 User 就是与数据库对应的 PO.

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

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

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

  • 注解1:@TableName 表名注解,标识实体类对应的表
  • 注解2:@TableId 主键注解,标识实体类中的主键字段
  • 注解3:@TableField 普通字段注解,标识实体类中的属性字段

代码操作

@TableName

说明:

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

示例:

@TableName("user")
public class User {
    private Long id;
    private String name;
}

TableName 注解除了指定表名以外,还可以指定很多其它属性:

属性类型必须指定默认值描述
valueString""表名
schemaString""schema
keepGlobalPrefixbooleanfalse是否保持使用全局的 tablePrefix 的值(当全局 tablePrefix 生效时)
resultMapString""xml 中 resultMap 的 id(用于满足特定类型的实体类对象绑定)
autoResultMapbooleanfalse是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建与注入)
excludePropertyString[]{}需要排除的属性名 @since 3.3.1

代码操作:

@Data
@TableName("tb_user") //表名为:tb_user
public class User {

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

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

    /**
     * 密码
     */
    @TableField("password1") //表字段为:password1
    private String password;

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

    /**
     * 详细信息
     */
    private String info;

    //数据库不存在的字段
    @TableField(exist = false)
    private String info1;
    /**
     * 使用状态(1正常 2冻结)
     */
    private Integer status;

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

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

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


}


 





 










 













 
























                                      |

总结

课堂作业

  1. 如果数据库表名是tb_user,而实例类是User.java 怎么解决名字不一致的问题?🎤
  2. 如果数据库tb_user表的主键是user_id,而User.java中,只有id字段,怎么匹配?🎤

1.3.常见配置

常见配置

MybatisPlus 也支持基于 yaml 文件的自定义配置,详见官方文档:

imageopen in new window
image

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

  • 实体类的别名扫描包
  • 全局 id 类型
mybatis-plus:
  type-aliases-package: com.itheima.mp.domain.po # 实体类的别名扫描包
  global-config:
    db-config:
      id-type: auto # 全局id类型为自增长

需要注意的是,MyBatisPlus 也支持手写 SQL 的,而 mapper 文件的读取地址可以自己配置,点击进入官方文档open in new window:👈

mybatis-plus:
  mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,当前这个是默认值。

可以看到默认值是 classpath*:/mapper/**/*.xml,也就是说我们只要把 mapper.xml 文件放置这个目录下就一定会被加载。

代码操作

配置application.yml后:

# 注释mybatis的配置,
#mybatis:
#  mapper-locations: classpath*:mapper/*.xml
mybatis-plus:
  type-aliases-package: com.itheima.mp.domain.po
  global-config:
    db-config:
      id-type: auto #数据库自增
      update-strategy: not_null #更新策略,只更新非空字段
  mapper-locations: "classpath*:/mapper/**/*.xml"  # 映射文件的位置,默认值
  configuration:
    map-underscore-to-camel-case: true #配置驼峰命名




 









例如,我们新建一个 UserMapper.xml 文件:

然后在其中定义一个方法:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mp.mapper.UserMapper">

    <select id="queryById" resultType="User">
        SELECT * FROM tb_user WHERE id = #{id}
    </select>
</mapper>

然后在测试类 UserMapperTest 中测试该方法:

@Test
void testQuery() {
    User user = userMapper.queryById(1L);
    System.out.println("user = " + user);
}

总结

课堂作业

  1. 实体类的别名扫描包的配置有何作用,能提高程序员的工作效率吗?🎤
  2. yml配置文件中的mapper-locations:配置有什么作用?

2.核心功能

刚才的案例中都是以 id 为条件的简单 CRUD,一些复杂条件的 SQL 语句就要用到一些更高级的功能了。

2.1.条件构造器

条件构造器

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

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

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

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

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

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

代码操作

相关信息

  1. QueryWrapper
  2. UpdateWrapper
  3. LambdaQueryWrapper

2.1.1.QueryWrapper

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

查询:查询出名字中带 o 的,存款大于等于 1000 元的人。🎯

代码如下:

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

更新:更新用户名为 jack 的用户的余额为 2000,代码如下:

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

总结

课堂作业

  1. QueryWrapper和UpdateWrapper的区别是什么?🎤
  2. QueryWrapper和LambdaQueryWrapper的区别是什么?🎤
  3. 练习下查询姓名包含a,create_time时间小于2023-06-19的数据 ✏️

2.2.自定义SQL 🍐

自定义SQL

在演示 UpdateWrapper 的案例中,我们在代码中编写了更新的 SQL 语句:

这种写法在某些企业也是不允许的,因为 SQL 语句最好都维护在持久层,而不是业务层。就当前案例来说,由于条件是 in 语句,只能将 SQL 写在 Mapper.xml 文件,利用 foreach 来生成动态 SQL。 这实在是太麻烦了。假如查询条件更复杂,动态 SQL 的编写也会更加复杂。

所以,MybatisPlus 提供了自定义 SQL 功能,可以让我们利用 Wrapper 生成查询条件,再结合 Mapper.xml 编写 SQL

代码操作

2.2.1.基本用法

以当前案例来说,我们可以这样写:

@Test
void testCustomWrapper() {
    // 1.准备自定义查询条件
   List<Long> ids = ListUtil.of(1L,2L,4L);
    QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);

    // 2.调用mapper的自定义方法,直接传递Wrapper
    userMapper.deductBalanceByIds(200, wrapper);
}

然后在 UserMapper 中自定义 SQL:

注意: 此处用了$ 拼接符



public interface UserMapper extends BaseMapper<User> {
    @Select("UPDATE t_user SET balance = balance - #{money} ${ew.customSqlSegment}")
    void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper);
}

运行结果:

image
image

这样就省去了编写复杂查询条件的烦恼了。

总结

课堂作业

  1. 什么情况下需要用到自定义SQL?🎤
  2. MybatisPlus支持多表关联查询吗?体验好吗?🎤
  3. Mybatis中$#的区别🎤

2.3.Service 接口

Service接口

MybatisPlus 不仅提供了 BaseMapper,还提供了通用的 Service 接口及默认实现,封装了一些常用的 service 模板方法。

官方文档:https://www.baomidou.com/pages/49cc81/#service-crud-接口open in new window

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

  • save:新增
  • remove:删除
  • update:更新
  • get:查询单个结果
  • list:查询集合结果
  • count:计数
  • page:分页查询

代码操作

2.3.2.基本用法

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

首先,定义 IUserService,继承 IService

package com.itheima.mp.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;

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





 


然后,编写 UserServiceImpl 类,继承 ServiceImpl,实现 UserService

package com.itheima.mp.service.impl;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>{
               // 拓展自定义方法  
}



 


项目结构如下:

任务:实现下面 4 个接口:🎯

编号接口请求方式请求路径请求参数返回值
1新增用户POST/users用户表单实体
2删除用户DELETE/users/用户 id
3根据 id 查询用户GET/users/用户 id用户 VO
4根据 id 批量查询GET/users用户 id 集合用户 VO 集合
  1. 首先,我们在项目中引入几个依赖:
<!--swagger-->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
    <version>4.1.0</version>
</dependency>
<!--web-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  1. 然后需要配置 swagger 信息:
knife4j:
  enable: true
  openapi:
    title: 用户管理接口文档
    description: "用户管理接口文档"
    email: huyan@itcast.cn
    concat: yangeit
    url: https://www.itcast.cn
    version: v1.0.0
    group:
      default:
        group-name: default
        api-rule: package
        api-rule-resources:
          - com.itheima.mp.controller
  1. 然后,接口需要两个实体:
  • UserFormDTO:代表新增时的用户表单
  • UserVO:代表查询的返回结果

3.1 首先是 UserFormDTO:

package com.itheima.mp.domain.dto;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel(description = "用户表单实体")
public class UserFormDTO {

    @ApiModelProperty("id")
    private Long id;

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

    @ApiModelProperty("密码")
    private String password;

    @ApiModelProperty("注册手机号")
    private String phone;

    @ApiModelProperty("详细信息,JSON风格")
    private String info;

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

3.2 然后是 UserVO:

package com.itheima.mp.domain.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@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;
}

4. 最后,按照 Restful 风格编写 Controller 接口方法:

package com.itheima.mp.controller;


@Api(tags = "用户管理接口")
@RequiredArgsConstructor
@RestController
@RequestMapping("users")
public class UserController {

    private final IUserService userService;

    @PostMapping
    @ApiOperation("新增用户")
    public void saveUser(@RequestBody UserFormDTO userFormDTO){
        // 1.转换DTO为PO
        User user = BeanUtil.copyProperties(userFormDTO, User.class);
        // 2.新增
        userService.save(user);
    }

    @DeleteMapping("/{id}")
    @ApiOperation("删除用户")
    public void removeUserById(@PathVariable("id") Long userId){
        userService.removeById(userId);
    }

    @GetMapping("/{id}")
    @ApiOperation("根据id查询用户")
    public UserVO queryUserById(@PathVariable("id") Long userId){
        // 1.查询用户
        User user = userService.getById(userId);
        // 2.处理vo
        return BeanUtil.copyProperties(user, UserVO.class);
    }

    @GetMapping
    @ApiOperation("根据id集合查询用户")
    public List<UserVO> queryUserByIds(@RequestParam("ids") List<Long> ids){
        // 1.查询用户
        List<User> users = userService.listByIds(ids);
        // 2.处理vo
        return BeanUtil.copyToList(users, UserVO.class);
    }
}

可以看到上述接口都直接在 controller 即可实现,无需编写任何 service 代码,非常方便。 👍

不过 ,一些带有业务逻辑的接口则需要在 service 中自定义实现 了。例如下面的需求:🎯

  • 根据 id 扣减用户余额

这看起来是个简单修改功能,只要修改用户余额即可。但这个业务包含一些业务逻辑处理:

  • 判断用户状态是否正常
  • 判断用户余额是否充足

这些业务逻辑都要在 service 层来做,另外更新余额需要自定义 SQL,要在 mapper 中来实现。因此,我们除了要编写 controller 以外,具体的业务还要在 service 和 mapper 中编写。

  1. 首先在 UserController 中定义一个方法:
@PutMapping("{id}/deduction/{money}")
@ApiOperation("扣减用户余额")
public void deductBalance(@PathVariable("id") Long id, @PathVariable("money")Integer money){
    userService.deductBalance(id, money);
}
  1. 然后是 UserService 接口:
package com.itheima.mp.service;
public interface IUserService extends IService<User> {
    void deductBalance(Long id, Integer money);
}
  1. 最后是 UserServiceImpl 实现类:
package com.itheima.mp.service.impl;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Override
    public void deductBalance(Long 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("用户余额不足");
        }
        // 4.扣减余额
        baseMapper.deductMoneyById(id, money);
    }
}
  1. 最后是 mapper:
@Update("UPDATE user SET balance = balance - #{money} WHERE id = #{id}")
void deductMoneyById(@Param("id") Long id, @Param("money") Integer money);

因此:如果是常见的单表操作,Iservice可以快速实现,而一些特殊需求,则需要使用Mybatis书写sql,因此在实际使用过程中,要根据需求进行选择方案。

总结

课堂作业

  1. IService 接口是什么?有什么作用?🎤
  2. IService能实现动态sql吗?怎么实现?🎤
  3. 批量插入数据,默认情况速度是最快的?对吗?🎤

3.扩展功能

3.1.代码生成

代码生成

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

这里推荐大家使用一款 MybatisPlus 的插件,它可以基于图形化界面完成 MybatisPlus 的代码生成,非常简单。

代码操作

分析思路

准备工作:安装插件

  1. 使用插件生成业务代码

安装插件

Idea 的 plugins 市场中搜索并安装 MyBatisPlus 插件:

然后重启你的 Idea 即可使用。 image

总结

课堂作业

  1. MybatisPlus插件有什么作用?🎤
  2. 使用MybatisPlus自动生成代码插件,完成Address的代码生成,并运行

3.2.静态工具

静态工具

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

示例:

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

@Test
void testDbList() {
    // 利用Db实现复杂条件查询
    List<User> list = Db.lambdaQuery(User.class)
            .like(User::getUsername, "o")
            .ge(User::getBalance, 1000)
            .list();
    list.forEach(System.out::println);
}

@Test
void testDbUpdate() {
    Db.lambdaUpdate(User.class)
            .set(User::getBalance, 2000)
            .eq(User::getUsername, "Rose");
}

代码操作

需求

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

首先,我们要添加一个收货地址的 VO 对象:

package com.itheima.mp.domain.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@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;
}

然后,改造原来的 UserVO,添加一个地址属性:

总结

课堂作业

  1. Db工具类有什么作用?🎤
  2. 练习:根据 id 批量查询用户,并查询出用户对应的所有地址 ✏️

3.3.逻辑删除

逻辑删除

对于一些比较重要的数据,我们往往会采用逻辑删除的方案,即:

  • 在表中添加一个字段标记数据是否被删除
  • 当删除数据时把标记置为 true
  • 查询时过滤掉标记为 true 的数据

一旦采用了逻辑删除,所有的查询和删除逻辑都要跟着变化,非常麻烦。

为了解决这个问题,MybatisPlus 就添加了对逻辑删除的支持。

代码操作

例如,我们给 address 表添加一个逻辑删除字段:

alter table address add deleted bit default b'0' null comment '逻辑删除';

然后给 Address 实体添加 deleted 字段:

接下来,我们要在 application.yml 中配置逻辑删除字段:

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

总结

课堂作业

  1. 什么是逻辑删除?逻辑删除是真实的删除吗?逻辑删除有什么应用场景?🎤
  2. 和物理删除相比,逻辑删除 有什么优势? 谈一谈🎤
  3. 完成代码生成、静态工具、逻辑删除的练习 ✏️

3.3.通用枚举

通用枚举

User 类中有一个用户状态字段:

像这种字段我们一般会定义一个枚举,做业务判断的时候就可以直接基于枚举做比较。但是我们数据库采用的是 int 类型,对应的 PO 也是 Integer。因此业务操作时必须手动把 枚举Integer 转换,非常麻烦。

因此,MybatisPlus 提供了一个处理枚举的类型转换器,可以帮我们把枚举类型与数据库类型自动转换

代码操作

3.3.1.定义枚举

我们定义一个用户状态的枚举:

代码如下:

package com.itheima.mp.enums;

import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;

@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 类中的 status 字段改为 UserStatus 类型:

要让 MybatisPlus 处理枚举与数据库类型自动转换,我们必须告诉 MybatisPlus,枚举中的哪个字段的值作为数据库值。 MybatisPlus 提供了 @EnumValue 注解来标记枚举属性:

总结

课堂作业

  1. 通用枚举有什么应用场景?🎤

3.4.JSON类型处理器 ✏️

JSON类型处理器

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

格式像这样:

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

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

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

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

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

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

代码操作

3.4.1.定义实体

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

代码如下:

package com.itheima.mp.domain.po;

import lombok.Data;

@Data
public class UserInfo {
    private Integer age;
    private String intro;
    private String gender;
}
package com.itheima.mp.domain.po;

import lombok.Data;

@Data
public class UserInfo {
    private Integer age;
    private String intro;
    private String gender;
}
@Data
@TableName(value = "tb_user",autoResultMap = true) //开启自动映射
public class User {

}

总结

课堂作业

  1. JSON类型的数据有什么应用场景?一定要使用吗?🎤
  2. MP是怎么解决JSON类型数据的适配工作的?

3.5.配置加密(选学)

配置加密

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

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

代码操作

分析思路

  1. 生成用户名、秘钥对
  2. 在yml配置文件中使用秘钥
  3. 在启动参数中添加解密秘钥,观察控制台输出

3.5.1.生成秘钥

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

package com.itheima.mp;

import com.baomidou.mybatisplus.core.toolkit.AES;
import org.junit.jupiter.api.Test;

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

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

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

    }
}

打印结果如下:

randomKey = 6234633a66fb399f
username = px2bAbnUfiY8K/IgsKvscg==
password = FGvCSEaOuga3ulDAsxw68Q==

总结

课堂作业

  1. 数据库密码加密有什么应用场景?有合意义🎤

4.插件功能

MybatisPlus 提供了很多的插件功能,进一步拓展其功能。目前已有的插件有:

  • PaginationInnerInterceptor:自动分页
  • TenantLineInnerInterceptor:多租户
  • DynamicTableNameInnerInterceptor:动态表名
  • OptimisticLockerInnerInterceptor:乐观锁
  • IllegalSQLInnerInterceptor:sql 性能规范
  • BlockAttackInnerInterceptor:防止全表更新与删除

这里我们以分页插件为里来学习插件的用法。

4.1.分页插件

分页插件

在未引入分页插件的情况下,MybatisPlus 是不支持分页功能的,IServiceBaseMapper 中的分页方法都无法正常起效。 所以,我们必须配置分页插件。

代码操作

4.1.1.配置分页插件

在项目中新建一个配置类:

其代码如下:

package com.itheima.mp.config;

@Configuration
public class MybatisConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // 初始化核心插件
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

总结

课堂作业

  1. MP使用用分页,需要引入pagehelper插件吗🎤

5.作业

课后作业

🚩 1. 重点完成上述的课堂作业

  1. 晚自习第一节课的前30分钟,总结完毕之后,每个同学先必须梳理今日知识点 (记得写不知道的,以及感恩三件事);整理好的笔记可以发给组长,组长交给班长,意在培养大家总结的能力)

  2. 晚自习第一节课的后30分钟开始练习(记住:程序员是代码堆起来的):

    • 先要把今天的所有案例或者课堂练习,如果没练完的,练完他
    • 尝试改造项目一中的 Service 层和 Mapper 层实现,用 MybatisPlus 代替单表的 CRUD 👈
  3. 剩余的时间:预习第二天的知识,预习的时候一定要注意:

  • 预习不是学习,不要死看第二天的视频(很容易出现看了白看,为了看视频而看视频)
  • 预习看第二天的笔记,把笔记中标注重要的知识,可以找到预习视频,先看一遍,如果不懂的 ,记住做好标注。