Mybatis Plus

发布于:2024-10-17 ⋅ 阅读:(16) ⋅ 点赞:(0)

1 MybatisPlus

        MybatisPlus 是基于 MyBatis 的增强工具,它简化了 MyBatis 的开发,并提供了一些常用的自动化功能,如 CRUD 操作的自动生成

        MybatisPlus 的目标是使得开发者 不再编写重复的 SQL 语句,同时保留 MyBatis 的原有功能,使得 MyBatis 更加轻量、简洁、高效

设计表结构:

balance n.用户余额

常规写mybatis的增删改查:

@Insert("insert into spring_cloud.user" +
            "(username, password, phone, balance, info)" +
            "values" +
            "(#{username},#{password},#{phone},#{balance},#{info})")
    void saveUser(User user);

很繁琐

1.1 使用 MP

使用 mybatis plus(MP):

1.引入依赖

注:mp 依赖包含了 mp 和 mybatis,所以可以删除 mybatis 的依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

2.mapper 层继承 BaseMapper<T>

        这样 mapper 就拥有了 BaseMapper 内置的增删改查方法,简单的方法可以从 BaseMapper 直接继承使用

3.service 实现类中直接调用

1.2 常用注解

MybatisPlus 可以自动增删改查的原理:

        通过扫描实体类,并基于反射获取实体类信息作为数据库表信息,然后通过 约定 获得数据库字段信息

约定大于配置

  • 类名驼峰转下划线作为表名
  • 名为 id 的字段作为主键
  • 变量名驼峰转下划线作为字段名

如果不遵守约定,可用注释:

1.3 配置

1.4 条件构造器

BaseMapper 接口含有一些方法,形参 Wrapper 是条件构造器

wrapper 包装器

Wrapper 的继承关系:

1.4.1 QueryWrapper 查询

查找名字带"o",余额大于等于 1000 元的 id、usernam、info、balance 字段

  • eq:等于
  • ne:不等于
  • ge:大于等于
  • gt:大于
  • le:小于等于
  • lt:小于

1.4.2 UpdateWrapper 更新

更新 jack 的余额为 2000

更新方法的传参有两个:一个是 用户实体,一个是 updateWrapper

更新 id 为 1, 2, 3 的用户,余额扣 200

用了 setsql 和 in

注意可以把实体对象的传参写为 null

1.5 自定义 SQL

更新 id 为 1, 2, 3 的用户,余额扣 200

上一节的用法需要在 service 中写 sql 语句,不符合规范

改进:

1.6 IService 接口

Mybatis 提供了 IService 和 其实现类 ServiceImpl

自定义的 UserService 继承 IService

自定义的 UserServiceImpl 继承 ServiceImpl

UserService 继承 IService ,要加上泛型

UserServiceImpl 先继承 Service 然后实现 UserService,对于 IService 的实现在 ServiceImpl 中继承过来了 

UserMapper 必须继承 BaseMapper

然后就可以直接通过注入 UserService ,直接调用方法

1.7 案例

使用 构造函数,而不是使用字段注入:

private final UserService userService;

public UserController(UserService userService) {
    this.userService = userService;
}

使用 lombok简化:

        使用 @RequiredArgsConstructor 注解时,Lombok 会自动生成一个包含所有 final 字段和使用 @NonNull 注解标记的字段的构造函数

新增用户

传递的是 DTO 对象,通过 BeanUtil 复制到 user,然后往 service 传递的是实体类对象 user

这里用的BeanUtil工具类需要引入依赖:

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.20</version>
</dependency>
@PostMapping
@ApiOperation("新增用户")
public Result saveUser(@RequestBody UserDTO userDTO) {
    User user = new User();
    BeanUtil.copyProperties(userDTO, user);
    userService.save(user);
    return Result.success();
}

在 service 和 mapper 层都不需要写任何东西,因为 IService 已提供具体实现

根据 id 扣减余额(自定义)

controller

@ApiOperation("根据id扣减余额")
@PutMapping("/{id}/deduction/{money}")
public Result balanceMinusById(@PathVariable("id") Long id,@PathVariable("money") Double money){
    userService.balanceMinusById(id,money);
    return Result.success();
}

service

先用内置的 getById 获得 user,然后进行非法判断

用 updateWrapper 封装条件

update 有两种重载方法:

其中一种方法直接传入装饰器 updateWrapper,这种方法需要在构造的时候使用 set,指定要更新的操作,通过 User 的 get 方法获得余额

public void balanceMinusById(Long id, Double money) {
        User user = getById(id);
        if (user == null) {
            throw new RuntimeException("用户不存在");
        }
        if (user.getBalance() < money) {
            throw new RuntimeException("用户余额不足");
        }

        UpdateWrapper<User> updateWrapper = new UpdateWrapper<User>()
                .set("balance", user.getBalance() - 200)
                .eq("id", id);

        update(updateWrapper);
    }

1.8 条件查询

 controller

@GetMapping("/query")
    @ApiOperation("条件查询")
    public Result<List<User>> queryCondition(UserQueryDTO userQueryDTO){
        List<User> userList = userService.queryCondition(userQueryDTO);
        return Result.success(userList);
    }

service

这里使用 lambdaQuery

public List<User> queryCondition(UserQueryDTO userQueryDTO) {
        String username = userQueryDTO.getUsername();
        Integer status = userQueryDTO.getStatus();
        Integer minBalance = userQueryDTO.getMinBalance();
        Integer maxBalance = userQueryDTO.getMaxBalance();

        return lambdaQuery()
                .like(username != null, user -> user.getUsername(), username)
                .eq(status != null, User::getStatus, status)
                .ge(minBalance != null, User::getBalance, minBalance)
                .le(maxBalance!=null, User::getBalance, maxBalance)
                .list();
    }

1.9 DB 静态工具类

        DB 是 mp 的静态工具类,为了防止 循环依赖

使用时可以不注入 mapper

        循环依赖(Circular Dependency)是指两个或多个对象(类、模块、Bean 等)相互依赖,形成一个循环的依赖关系。具体来说,如果对象 A 依赖对象 B,而对象 B 又依赖对象 A,这种情况就会导致循环依赖问题。循环依赖可能导致系统无法正确初始化,尤其是在依赖注入框架(如 Spring)中,会抛出异常,无法实例化相关的对象

加入一张 address 表

在 UserVO 中加入

private List<AddressVO> addressList;

1.9.1 查询单个用户及其地址

现在对代码进行改造,根据id查询用户,返回用户信息,也包括对应的 地址 信息

注意,这里很重要

需要操作什么实体类,从而访问对应的表,需要 在mapper 创建一个对应的接口

这个接口要继承 BaseMapper,这样才具有 mp 的方法

这里不用加 @Mapper 的原因是在启动类上加了扫描Mapper的注解:

controller

    @GetMapping("/{id}")
    @ApiOperation("根据id查询用户")
    public Result<UserVO> getUserById(@PathVariable Long id) {
        User user = userService.getById(id);
        UserVO userVO = new UserVO();
//        log.info("UserVO.class:\n"+String.valueOf(UserVO.class)); // class com.wyn.springcloud.pojo.vo.UserVO
        return Result.success(BeanUtil.copyProperties(user, UserVO.class)); // 反射 UserVO.class
    }

service

这里使用 Db 的 lambdaQuery 方法,传参是查询的实体类的 字节码(反射)

根据 userid 查询,并且转为 list

public UserVO queryUserAndAddress(Long id) {

        User user = getById(id);
        if (user == null || user.getStatus() == 0) {
            throw new RuntimeException("用户异常!");
        }
        List<Address> addressList = Db.lambdaQuery(Address.class)
                .eq(Address::getUserId, id)
                .list();
        UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
        userVO.setAddressList(BeanUtil.copyToList(addressList, AddressVO.class));
        return userVO;
    }

1.9.2 查询多个用户及其地址

思路:

根据ids获得users,然后遍历users,根据id查询address,复制到addressvo,user复制到uservo,然后 uservo.setAddress

/**
     * batch query [users] and [addresses] by ids
     *
     * @param ids
     * @return
     */
    public List<UserVO> queryUsersAndAddress(List<Long> ids) {
        List<User> users = listByIds(ids);
        if (users.isEmpty()) {
            throw new RuntimeException("user table is null!");
        }
        users.forEach(user -> {
            if (user.getStatus() == 0) {
                users.remove(user);
            }
        });
        if (users.isEmpty()) {
            throw new RuntimeException("users are frozen");
        }
        // HashMap<Long,List<Address>> addressListMap = new HashMap<>();
        // We don't use HashMap here, because the hash table is 1 to 1, and here it's 1 to many
        List<UserVO> userVOList = new ArrayList<>();
        for (User user : users) {
            List<Address> addressList = Db.lambdaQuery(Address.class)
                    .eq(Address::getUserId, user.getId())
                    .list();
            UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
            List<AddressVO> addressVOList = BeanUtil.copyToList(addressList, AddressVO.class);
            userVO.setAddressList(addressVOList);
            userVOList.add(userVO);
        }
        return userVOList;
    }

1.10 逻辑删除

        逻辑删除 是一种软删除策略,即在数据库中并不直接物理删除数据行,而是通过设置某个 标志位 来标记该数据已被删除。这样做的好处是,数据不会从数据库中被永久删除,仍然可以恢复或用于历史查询

 在 user 中加入 deleted 字段(注意:delete 是保留字,不可以作为字段

在 user 实体类,指定逻辑字段

1.11 枚举处理器

配置枚举处理器:

枚举类

@EnumValue 是 MyBatis-Plus 提供的注解,用于在将枚举类型的值存储到数据库时,自定义枚举的存储和映射行为。具体来说,@EnumValue 可以指定将枚举类中的某个字段作为存储到数据库的值,而不是默认使用枚举的名称或 ordinal 值

@JsonValue 是 Jackson 序列化和反序列化过程中使用的注解之一,它用于指定某个类的方法或字段的返回值作为该类的 JSON 表示。也就是说,使用 @JsonValue 可以将对象序列化为一个单一的值,而不是通常的对象格式

需要进行 status 判断时,使用枚举类

1.12 JSON 处理器

1.13 分页查询

Method: GET

Path: /users/page

args: pageNum, pageSize, soryBy, isAsc, name, status

这里[再次]总结一下三种传参

1. 查询参数(Query Parameters)@RequestParm

请求示例:/users?name=John&age=25

2.JSON 方法体参数 @RequestBody

3.路径参数(Path Parameters)@PathVariable (用于参数名和变量名不一致的情况)

请求示例:/users/{id}

// 通用分页查询实体类
@Data
@ApiModel(description = "分页查询实体类")
public class PageQuery {
    @ApiModelProperty("页码")
    private Integer pageNo;
    @ApiModelProperty("页数")
    private Integer pageSize;
    @ApiModelProperty("排序字段")
    private String sortBy;
    @ApiModelProperty("是否升序")
    private Boolean isAsc;
}
​
// 用户查询实体类
@ApiModel(description = "user condition select entity")
@Data
public class UserQuery extends PageQuery{ // 继承通用类
    private String name;
    private Integer status;
    private Integer minBalance;
    private Integer maxBalance;
}
​
// 分页传输类
@Data
@ApiModel(description = "分页结果")
public class PageDTO<T> {
    @ApiModelProperty("总条数")
    private Integer total;
    @ApiModelProperty("总页数")
    private Integer pages;
    @ApiModelProperty("数据集合")
    private List<T> list;
}

// Controller
@ApiOperation("条件分页查询")
@GetMapping("/page")
public Result<PageDTO<UserVO>> queryUserPage(UserQuery userQuery){
    return Result.success(userService.queryUserPage(userQuery));
}
​
// Service
public PageDTO<UserVO> queryUserPage(UserQuery userQuery) {
    String name = userQuery.getName();
    Integer status = userQuery.getStatus();
    Integer minBalance = userQuery.getMinBalance();
    Integer maxBalance = userQuery.getMaxBalance();
​
    Integer pageNo = userQuery.getPageNo();
    Integer pageSize = userQuery.getPageSize();
    String sortBy = userQuery.getSortBy();
    Boolean isAsc = userQuery.getIsAsc();
​
    Page<User> page = Page.of(pageNo, pageSize);
​
    OrderItem orderItem = new OrderItem();
    // 排序字段非空才可以排序
    if (!sortBy.isEmpty()) {
        orderItem.setColumn(sortBy);
        orderItem.setAsc(isAsc);
    }
    page.addOrder(orderItem);
​
    Page<User> p = lambdaQuery()
        .like(name != null, User::getUsername, name)
        .eq(status != null, User::getStatus, status)
        .ge(minBalance != null, User::getBalance, minBalance)
        .ge(maxBalance != null, User::getBalance, maxBalance)
        .page(page);
​
    // 构造返回结果
    PageDTO<UserVO> dto = new PageDTO<>();
    dto.setTotal(p.getTotal());
    dto.setPages(p.getPages());
    List<User> userList = p.getRecords();
    if (userList.isEmpty()) {
        dto.setList(null);
    } else {
        List<UserVO> userVOList = BeanUtil.copyToList(userList, UserVO.class);
        dto.setList(userVOList);
    }
    return dto;
}

代码可以抽取,作为 PageQueryPageDTO 的方法 -> 复用

这里为什么要返回PageDTO而不是Page——mybatis 的分页类

因为 有些字段不需要

public class Page<T> implements IPage<T> {
    private static final long serialVersionUID = 8545996863226528798L;
    protected List<T> records;
    protected long total;
    protected long size;
    protected long current;
    protected List<OrderItem> orders;
    protected boolean optimizeCountSql;
    protected boolean searchCount;
    protected boolean optimizeJoinOfCountSql;
    protected Long maxLimit;
    protected String countId;
    // ...
}