Skip to content
DAILY QUOTE

“ ”

3.1.代码生成

Mapper、Service、PO代码相对固定,重复编写比较麻烦,因此MybatisPlus官方提供了代码生成器根据数据库表结构生成PO、Mapper、Service等相关代码。

但是代码生成器也要编码使用,比较麻烦,这里使用MybatisPlus的插件,可以基于图形化界面完成

3.1.1.安装插件

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

3.1.2.使用

目前address表未生成对应的实体和mapper基础代码,可以利用插件生成。 首先配置数据库地址,在Idea顶部菜单,工具->ConfigDatabase

点击ok保存之后,然后点击工具->Code Generator

3.2.静态工具

Service之间相互调用就会出现循环依赖问题,MybatisPlus提供给了一个静态工具类:Db,其中一些静态方法与IService方法签名基本一致,帮我们实现CRUD功能:

示例:

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

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

java
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,添加地址属性:

接下来修改UserController中根据id查询用户的业务接口:

java
@GetMapping("/{id}")  
@ApiOperation("根据id查询用户")  
public UserVO queryUserById(@PathVariable("id") Long userId) {  
    //基于自定义service方法查询  
    return userService.queryUserAndAddressById(userId);  
}

首先在IUserService中定义方法:

java
package com.itheima.mp.service;  
  
import com.baomidou.mybatisplus.extension.service.IService;  
import com.itheima.mp.domain.po.User;  
import com.itheima.mp.domain.vo.UserVO;  
  
public interface UserService extends IService<User> {  
    void deductBalance(Long id, Integer money);  
  
    UserVO queryUserAndAddressById(Long userId);  
}

实现方法:

java
@Override  
public UserVO queryUserAndAddressById(Long userId) {  
    //1.查询用户  
    User user=getById(userId);  
    if(user==null){  
        return null;  
    }  
    //2.查询收货地址  
    List<Address> addressList= Db.lambdaQuery(Address.class)  
            .eq(Address::getUserId,userId)  
            .list();  
    //3.处理VO  
    UserVO userVO= BeanUtil.copyProperties(user,UserVO.class);  
    userVO.setAddresses(BeanUtil.copyToList(addressList, AddressVO.class));  
    return userVO;  
}

查询地址时,采用了Db的静态方法,避免了注入AddressService,减少了循环依赖的风险。

需求:根据id批量查询用户,并查询出用户对应的所有地址

java
@Override  
public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {  
    //1.查询id查询  
    List<User> users=listByIds(ids);  
    if(CollUtil.isEmpty(users)){  
        return Collections.emptyList();  
    }  
    //2.从用户列表提取用户id  
    List<Long> userIds=users.stream().map(User::getId).collect(Collectors.toList());  
    //3.根据用户id集合批量查询地址  
    List<Address> addresses=Db.lambdaQuery(Address.class).in(Address::getUserId,userIds).list();  
    //4.把地址按照userId分组  
    Map<Long,List<Address>> addressMap=addresses.stream().collect(Collectors.groupingBy(Address::getUserId));  
    //5.转为VO对象  
    List<UserVO> userVOList=users.stream().map(user -> {  
        UserVO userVO=BeanUtil.copyProperties(user,UserVO.class);  
        List<Address> addressList=addressMap.get(user.getId());  
        if(CollUtil.isEmpty(addressList)){  
            userVO.setAddresses(Collections.emptyList());  
        }else {  
            userVO.setAddresses(BeanUtil.copyToList(addressList, AddressVO.class));  
        }  
        return userVO;  
    }).collect(Collectors.toList());  
    return userVOList;  
}

3.3.逻辑删除

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

  • 在表中添加一个字段标记数据是否被删除
  • 当删除数据时把标记置为true
  • 查询时过滤掉标记为true的数据 一旦采用逻辑删除,所有查询和删除逻辑都要跟着变化,为了解决这个问题,MybatisPlus就添加了对逻辑删除的支持。

注意:自定义SQL需要自己手动处理逻辑删除。

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

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

然后给Address实体添加deleted字段

接下来在application.yaml配置逻辑删除字段

yaml
mybatis-plus:  
  type-aliases-package: com.itheima.mp.domain.po  
  global-config:  
    db-config:  
      id-type: auto #全局id类型为自增长  
      logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)  
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)      
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

测试: 首先执行删除操作:

java
@Test  
void testDeleteByLogic() {  
    // 删除方法与以前没有区别  
    addressService.removeById(59L);  
}

看出看到底层SQL逻辑变了:

注意:逻辑删除本身也有自己的问题

  1. 会导致数据库表垃圾数据越来越多,从而影响查询效率
  2. SQL中全都需要对逻辑删除字段做判断,影响查询效率

3.4.通用枚举

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

做业务判断时可以直接基于枚举作比较,但数据库是int类型,对应PO也是Integer。因此业务操作需要手动把枚举与Integer转换,比较麻烦。

因此,MybatisPlus提供了一个处理枚举的类型转换器,帮我们把枚举类型自动转换。

3.4.1.定义枚举

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

代码如下:

java
package com.itheima.mp.enums;  
  
import lombok.Getter;  
  
@Getter  
public enum UserStatus {  
    FORMAL(1,"正常"),  
    FREEZE(2,"冻结")  
    ;  
    private final int value;  
    private final String desc;  
    UserStatus(int value,String desc){  
        this.value = value;  
        this.desc = desc;  
    }  
}

要让MybatisPlus处理枚举与数据库类型自动转换,必须告诉MybatisPlus,枚举中那个字段值作为数据库值。

MybatisPlus提供了@EnumValue注解来标记枚举属性:

3.4.2.配置枚举处理器

application.yaml添加配置:

yaml
mybatis-plus:  
  type-aliases-package: com.itheima.mp.domain.po  
  global-config:  
    db-config:  
      id-type: auto #全局id类型为自增长  
      logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)  
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)      
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)  
      mapper-locations: classpath*:/mapper/**/*.xml  
  configuration:  
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

3.4.3.修改实体类字段

修改User实体类中的status类型为UserStatus

修改业务方法中的判断逻辑:

修改UserVO中的属性类型:

3.4.5.测试

状态变成了FORMAL

3.5.JSON类型处理器

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

User实体类是String类型:

如果要方便获取,info的类型最好是一个Map或者实体类。

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

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

3.5.1.定义实体

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

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

3.5.2.使用类型处理器

接下来将User类的info字段修改为UserInfo类型,并声明类型处理器:

同时在User类上添加注解,声明自动映射:

为了让页面返回的结果也以对象格式返回,要求改UserVO中的info对象

如果在xml手写了#{info},也需要修改: 在页面查询如下: