当前位置: 首页 > article >正文

[MyBatis-Plus]扩展功能详解

代码生成

使用MP的步骤是非常固定的几步操作

基于插件, 可以快速的生成基础性的代码

  1. 安装插件
  2. 安装完成后重启IEDA
  3. 连接数据库

  • mp是数据库的名字
  • ?serverTimezone=UTC 是修复mysql时区, 不加会报错
  1. 生成代码

  • TablePrefix选项是用于去除表名的前缀, 比如根据tb_user表生成实体类User, 就填写tb_

静态工具

业务开发中, 可能会存在service之间互相调用, 如果使用传统的资源注入, 就会出现循环依赖的问题

建议就是service之间的相互调用, 使用DB静态工具进行方法的调用, 避免循环依赖的问题

DB静态工具的方法和IService的方法几乎一样, 只是调用时需要传入字节码

案例1: 改造根据id查询用户的接口, 查询用户的同时, 查询出对应的地址

/**
 * @author  
 * @since 2023-07-01
 */
@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;
}
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {

    @ApiModelProperty("用户id")
    private Long id;

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

    @ApiModelProperty("详细信息")
    private String info;

    @ApiModelProperty("使用状态(1正常 2冻结)")
    private UserStatus status;

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

    @ApiModelProperty("用户的收货地址")
    private List<AddressVO> addresses;

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

    private final IUserService userService;

    @ApiOperation("根据id查询用户接口")
    @GetMapping("/{id}")
    public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id){
        return userService.queryUserAndAddressById(id);
    }
}
public interface IUserService extends IService<User> {
    
    UserVO queryUserAndAddressById(Long id);

}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    public UserVO queryUserAndAddressById(Long id) {
        //1,查询用户
        User user = getById(id);
        if (user == null || user.getStatus() == UserStatus.FROZEN) {
            throw new RuntimeException("用户状态异常");
        }

        //2查询地址
        List<Address> addresses = Db.lambdaQuery(Address.class).eq(Address::getUserId, id).list();

        //3封装VO
        //3.1把user的PO转成VO
        UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);

        //3.2转地址为VO
        if (CollUtil.isNotEmpty(addresses)) {
            userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
        }

        return userVO;
    }

}

案例2: 改造根据id批量查询的接口, 查询用户的同时, 查询出用户对应的所有地址

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

    private final IUserService userService;

    @ApiOperation("根基id批量查询用户接口")
    @GetMapping
    public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids) {
        return userService.queryUserAndAddressByIds(ids);
    }

}
public interface IUserService extends IService<User> {

    List<UserVO> queryUserAndAddressByIds(List<Long> ids);

}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {


    @Override
    public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {
        //1,查询用户
        List<User> users = listByIds(ids);
        if (CollUtil.isNotEmpty(users)) {
            return Collections.emptyList();
        }

        //2,查询地址
        //2.1获取用户id集合
        List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
        //2.2根据用户id查询地址
        List<Address> addresses = Db.lambdaQuery(Address.class).in(Address::getUserId, userIds).list();
        //2.3转换地址VO
        List<AddressVO> addressVOList = BeanUtil.copyToList(addresses, AddressVO.class);
        //2.4用户地址集合分组处理, 相同用户的放入一个集合(组)中
        Map<Long, List<AddressVO>> addressMap = new HashMap<>(0);
        if (CollUtil.isNotEmpty(addressVOList)) {
            addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
        }

        //3,转换VO返回
        List<UserVO> list = new ArrayList<>(users.size());
        for (User user : users) {
            //3.1转换USER的PO为VO
            UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
            list.add(vo);
            //3.2转换地址VO
            vo.setAddresses(addressMap.get(user.getId()));
        }

        return list;
    }

}

逻辑删除

逻辑删除就是基于代码逻辑, 模拟删除效果, 不会真正删除数据

  • 在表中添加一个字段标记数据是否被删除
  • 当删除数据时把标记置为1

  • 查询时只查询标记为0的数据

MP提供了逻辑删除功能, 无需改变方法调用的方式, 只需要简单配置, MP就会在底层自动修改CRUE的语句

  1. 在application.yaml文件中配置逻辑删除的字段名称和值就可以

案例

mybatis-plus:
  type-aliases-package: com.itheima.mp.domain.po
  global-config:
    db-config:
      logic-delete-field: deleted # 配置逻辑删除字段
@SpringBootTest
class IAddressServiceTest {

    @Autowired
    private IAddressService addressService;

    @Test
    void testLogicDelete() {
        //1,删除
        addressService.removeById(59L); // 代码是正常删除,但执行的是更新操作,更新deleted字段
        //2,查询
        Address address = addressService.getById(59L);
        System.out.println("address=" + address); // null,已经删除的数据查不到
    }
}

逻辑删除存在的问题:

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

其他方案

  1. 如果数据不能删除, 可以把在删除之前, 把数据迁移到备份的表中,
  2. 然后执行删除操作, 这样主表中的数据就比较干净, 而且数据也会保留

枚举处理器

作用: 解决java的enum类型和数据库INT类型的相互转换问题

  1. 开发过程中, 我们会使用枚举类型 替换 固定数值
  2. 但数据表中存储的字段依然是整数类型
  3. 所以java实体类中和数据库中数据类型要相互转换

原理: MP提供了枚举类型处理器, 用于自动转换实体类的枚举类型

  1. 存储数据: 自动把枚举类型转成INT类型存入数据库
  2. 封装数据: 自动把数据库INT类型数据转成枚举类型保存

配置全局枚举处理器, 让枚举处理器生效

mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

给枚举类添加注解, 告诉MP要对哪个成员变量进行类型处理

@Getter
public enum UserStatus {
    NORMAL(1, "正常"),
    FROZEN(2, "冻结"),
    ;
    @EnumValue  // 指定写入数据库的数据
    private final int value;
    @JsonValue  // 指定返回前端的数据
    private final String desc;

    UserStatus(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }
}
  1. 枚举做JSON处理时, 默认会以枚举项的名字返回
  2. 如果想以value或者desc返回, 可以通过注解告诉SpringMVC要返回那个值

在实体类中使用枚举类型

@Data
@ApiModel(description = "用户VO实体")
public class UserVO {
    ... ...

    @ApiModelProperty("使用状态(1正常 2冻结)")
    // private Integer status;
    private UserStatus status;
}
@Data
@TableName(value = "user",autoResultMap = true)
public class User {
    ... ...
    
    /**
     * 使用状态(1正常 2冻结)
     */
    private UserStatus status;
}

业务代码中, 愉快的使用枚举替代魔法数值

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    @Transactional
    public void deductBalance(long id, Integer money) {
        //1,查询用户
        User user = getById(id);
        //2,校验用户状态
        if (user == null || user.getStatus() == UserStatus.FROZEN) {
            throw new RuntimeException("用户状态异常!");
        }
        //3.检查余额是否充足
        if (user.getBalance() < money) {
            throw new RuntimeException("用户余额不足!");
        }
        //4,扣减余额
        int remainBalance = user.getBalance() - money;
        lambdaUpdate()
                .set(User::getBalance, remainBalance)
                .set(remainBalance == 0, User::getStatus, 2)
                .eq(User::getId, id)
                .eq(User::getBalance, user.getBalance()) // 乐观锁
                .update();

    }
}

JSON处理器

问题说明

  1. user表中有一个info字段是json类型, java中没有对应的数据类型, 通常用字符串类型接收
  2. MuBatis可以自动进行字符串和JSON的转换
  3. 使用字符串接收JSON类型数据, 从数据库的操作层面是没有问题, 增删改查都是正常的
  4. 但是从业务层面就会出现麻烦, 比如需要取出info中的age信息, 处理起来就比较麻烦

MP提供了JSON类型处理器, 可以把java对象和数据库的JSON对象 相互转换

  1. SpringMVC使用的JacksonJSON处理器
  2. 使用JacksonTypeHandler不需要额外引用依赖

使用JSON处理器的步骤

  1. 使用 @TableField() 注解, 指定并开启JSON处理器
  2. 使用JSON数据后, 就出现了对象嵌套对象的情况, User对象嵌套UserInfo对象
  3. 就要开启自动结果映射 autoResultMap = true

实际操作

@Data
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
public class UserInfo {
    private Integer age;
    private String intro;
    private String gender;
}
  1. 定义PO对象封装JSON数据

@Data
@TableName(value = "user",autoResultMap = true)
public class User {
    ... ...
    
    /**
     * 详细信息
     */
    @TableField(typeHandler = JacksonTypeHandler.class)
    // private String info;
    private UserInfo info;

}
  1. 使用java对象替代String接收数据库数据
  2. 使用@TableField()注解开启JSON处理器
  3. 设置autoResultMap = true, 开启自动结果映射
  4. 查询结果对比


http://www.kler.cn/news/358704.html

相关文章:

  • 如何重置MySQL的root密码
  • 一站式解决方案,打造科学化的城市防洪防汛管理系统
  • pdf文件太大如何变小?
  • PHP函数:preg_replace()和preg_replace_callback() 【详记】
  • Android 10.0 第三方app接收不到开机广播问题的解决以及开机自启动功能实现一
  • asp.net mvc return json()设置maxJsonLength
  • Day37 || 509. 斐波那契数 、70. 爬楼梯、746. 使用最小花费爬楼梯
  • 15分钟学Go 第9天:函数的定义与调用
  • C语言 | Leetcode C语言题解之第486题预测赢家
  • 用OpenCV写一个简单的尺寸检测程序
  • python+ffmpeg 屏幕录制程序
  • 多系统萎缩患者必看!这些维生素是你的“生命守护者”✨
  • 美国Honeywell霍尼韦尔气体检测仪SPXCDALMCX说明书
  • XML Schema 复合空元素
  • 若依前后端框架学习——新建模块(图文详解)
  • 深度学习相关知识点
  • 029 elasticsearch文档管理(ElasticsearchRepository、ElasticsearchRestTemplate)
  • VScode远程服务器之远程 远程容器 进行开发(五)
  • 第二代GPT-SoVITS V2:让声音克隆变得简单
  • Spark广播变量(类似小表广播)