MybatisPlus框架--黑马笔记

1.快速入门

1)MybatisPlus 的基本步骤:

①. 引入 MybatisPlus 依赖,代替 Mybatis 依赖

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

②. 定义 Mapper 接口并继承 BaseMapper,注意要加泛型<>

public interface UserMapper extends BaseMapper<User> {}

2)常用注解

MyBatisPlus 通过扫描实体类,并基于反射获取实体类信息作为数据库表信息。

①.命名方式如下:

  • 类名驼峰转下划线作为表名

  • 名为 id 的字段作为主键

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

②. MybatisPlus 中比较常用的几个注解如下:

  • @TableName :用来指定表名
  • @TableId :用来指定表中的主键字段信息
  • TableField :用来指定表中的普通字段信息

注意:

​ i. 主键字段IdType的枚举方式:

​ - AUTO:数据库自增长

​ - INPUT:通过set方法自行输入

​ - ASSIGN_ID(默认):分配 ID,接口IdentifierGenerator的方法nextId来生成id,默认实现类为DefaultIdentifierGenerator雪花算法

​ ii. 使用@TableField的常见场景:

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

​ - 成员变量名以is开头,且是布尔值(否则,如字段is_married,通过反射会变为married)

​ - 成员变量名与数据库关键字冲突(加``)

​ - 成员变量不是数据库字段

3)常见配置

MyBatisPlus的配置项继承了MyBatis原生配置和一些自己特有的配置。例如:

mybatis-plus:  
	type-aliases-package: com.itheima.mp.domain.po # 别名扫描包  
	mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,默认值 
	configuration:    
		map-underscore-to-camel-case: true # 是否开启下划线和驼峰的映射    
		cache-enabled: false # 是否开启二级缓存
  	global-config:    
  		db-config:      
  			id-type: assign_id # id为雪花算法生成      
  			update-strategy: not_null # 更新策略:只更新非空字段

具体可参考官方文档:使用配置 | MyBatis-Plus (baomidou.com)

2.核心功能

1)条件构造器

​ 条件构造器的用法:

  • QueryWrapper和LambdaQueryWrapper通常用来构建select、delete、update的where条件部分

  • UpdateWrapper和LambdaUpdateWrapper通常只有在set语句比较特殊才使用,不然依旧使用QueryWrapper和LambdaQueryWrapper

  • 尽量使用LambdaQueryWrapper和LambdaUpdateWrapper,避免硬编码

//查询出名字中带o的,存款大于等于1000元的人的id、username、info、balance字段
@Test
void testQueryWrapper(){
    //构建查询条件  
    QueryWrapper<User> wrapper = new QueryWrapper<User>()
            .select("id","username","info","balance")
            .like("username","o")
            .ge("balance",1000);
    //查询
    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}


//更新用户名为jack的用户的余额为2000
@Test
    void testUpdateQueryWrapper(){
        //更新的数据
        User user = new User();
        user.setBalance(2000);
        //构建查询条件  
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .eq("username","Jack");
        //执行更新
        userMapper.update(user,wrapper);
    }

特殊的set语句

//更新id为1,2,4的用户的余额,扣200
@Test
void testUpdateWrapper(){
    List<Long> ids = List.of(1L,2L,3L);
    //构建查询条件  
    UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
            .setSql("balance = balance - 200")
            .in("id",ids);
    //执行更新
    userMapper.update(null,wrapper);
}

使用LambdaQueryWrapper,避免硬编码

@Test

void testLambdaQueryWrapper(){
    //构建查询条件  查询出名字中带o的,存款大于等于1000元的人的id、username、info、balance字段
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
            .select(User::getId,User::getUsername,User::getInfo,User::getBalance)
            .like(User::getUsername,"o")
            .ge(User::getBalance,1000);
    //查询
    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}

2)自定义SQL

可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分。

例:将id在指定范围的用户(例如1、2、4 )的余额扣减指定值。
在这里插入图片描述

①基于Wrapper构建where条件

@Test
    void testSqlWrapper(){
        List<Long> ids = List.of(1L, 2L, 4L);
        int amount = 200;
        // 1.构建条件
         LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().in(User::getId, ids);
        // 2.自定义SQL方法调用
         userMapper.updateBalanceByIds(wrapper, amount);
    }

②在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew(即Constants.WRAPPER)

void updateBalanceByIds(@Param("ew") LambdaQueryWrapper<User> wrapper, @Param("amount") int amount);

③自定义SQL,并使用Wrapper条件 – ${ew.customSqlSegment}

<update id="updateBalanceByIds">
UPDATE user SET balance = balance - #{amount} ${ew.customSqlSegment}
</update>

3)Service接口

  • 自定义Service接口继承IService接口
public interface UserService extends IService<User> {}
  • 自定义Service实现类,实现自定义接口并继承ServiceImpl类,加泛型
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {}

注意:成员变量的注入方式:

​ - 注解注入 @Autowired (常用,但Spring不推荐)

​ - 构造器注入,变量前加final,方法上加入注解**@RequiredArgsConstructor**

(1)基于Restful风格实现接口

可直接调用提供的api,复杂条件需要自己构建Sql

//controller层
@ApiOperation("新增用户")
@PostMapping
public void add(@ApiParam("用户表单实体") @RequestBody UserFormDTO userDto){
    //转换为po
    User user = BeanUtil.copyProperties(userDto, User.class);
    userService.save(user);
}

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

@ApiOperation("根据id查询用户")
@GetMapping("/{id}")
public UserVO getById(@ApiParam("用户id") @PathVariable("id") Long id){
    User user = userService.getById(id);
    //转换为userVo在前端展示
    return BeanUtil.copyProperties(user, UserVO.class);
}

@ApiOperation("根据id批量查询")
@GetMapping()
public List<UserVO> getByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids){
    List<User> users = userService.listByIds(ids);
    //转换为userVo在前端展示
    return BeanUtil.copyToList(users, UserVO.class);
}

@ApiOperation("根据id扣减余额")
@PutMapping("/{id}/deduction/{money}")
public void getByIds(@ApiParam("用户id") @PathVariable("id") Long id,
                     @ApiParam("扣减金额") @PathVariable("money")Integer money){
    userService.deduction(id,money);
}

//service层 复杂条件需要自己构建
public void deduction(Long id, Integer money) {
        //查询用户
        User user = getById(id);
        //无用户  用户冻结
        if (id == null || user.getStatus() == 2){
            throw new RuntimeException("用户状态异常");
        }
        //余额是否充足
        if (money > user.getBalance()){
            throw new RuntimeException("用户余额不足");
        }
        //扣减余额
//        // 1.构建条件
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().eq(User::getId,id);
//        //自定义sql
        userMapper.updateDeduction(wrapper,money);
}

(2)IService的Lambda查询

需求:实现一个根据复杂条件查询用户的接口,查询条件如下:

  • name:用户名关键字,可以为空

  • status:用户状态,可以为空

  • minBalance:最小余额,可以为空

  • maxBalance:最大余额,可以为空

//controller层
@ApiOperation("根据复杂条件查询用户")
    @GetMapping ("/list")
    public List<UserVO> queryUsers(@ApiParam("用户查询对象") UserQuery query){
        List<User> users = userService.queryUsers(query.getName(),query.getStatus(),query.getMinBalance(),query.getMaxBalance());
        //转换为UserVo
        return BeanUtil.copyToList(users,UserVO.class);
    }

//service层
public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
    return lambdaQuery()
            .like(name != null,User::getUsername,name)
            .eq(status != null, User::getStatus, status)
            .ge(minBalance != null, User::getBalance, minBalance)
            .le(maxBalance != null, User::getBalance, maxBalance)
            .list();
}

(3)IService的Lambda更新

需求:改造根据id修改用户余额的接口,要求如下

  • 完成对用户状态校验
  • 完成对用户余额校验
  • 如果扣减后余额为0,则将用户status修改为冻结状态2
public void deduction(Long id, Integer money) {
        //查询用户
        User user = getById(id);
        //无用户  用户冻结
        if (id == null || user.getStatus() == 2){
            throw new RuntimeException("用户状态异常");
        }
        //余额是否充足
        if (money > user.getBalance()){
            throw new RuntimeException("用户余额不足");
        }
        //修改余额
        int remainBalance = user.getBalance() - money;
        lambdaUpdate()
                .set(User::getBalance, remainBalance)
                .set(remainBalance == 0, User::getStatus, 2)
                .eq(User::getId,id)
                .update();

    }

(4)IService批量新增

三种方式:

  • 普通for循环插入:逐条插入速度极差,不推荐
  • IService的批量插入:MP的批量新增,基于预编译的批处理,性能不错
  • 开启rewriteBatchedStatements=true参数:配置jdbc参数,开rewriteBatchedStatements,性能最好

MP的批量新增:

void testSaveBatch(){
    List<User> list = new ArrayList<>(1000);
    for (int i = 0; i < 100000; i++) {
        //添加一个user
        list.add(buildUser(i));
        //每1000条批量插入一次
        if (i % 1000 == 0){
            userService.saveBatch(list);
            //清空集合,准备下一批数据
            list.clear();
        }
    }
}

然后在application.yml中的数据库配置中加上参数rewriteBatchedStatements=true:

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true

3.扩展功能

1)代码生成

下载插件生成基础代码

  • 连接数据库

在这里插入图片描述

  • 生成代码
    在这里插入图片描述
    在这里插入图片描述

2)DB静态工具

两张表相互注入service会导致循环依赖(可注入mapper),或者可用静态工具DB提供的接口实现

①改造根据id查询用户的接口,查询用户的同时,查询出用户对应的所有地址

public UserVO queryUSersById(Long id) {
    //查询用户
    User user = getById(id);
    if (user == null || user.getStatus() == 2){
        throw new RuntimeException("用户状态异常");
    }
    //转换对象
    UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
    //获取用户地址
    List<Address> addressList = Db.lambdaQuery(Address.class).
            eq(Address::getUserId, id)
            .list();
    //设置地址
    if (ObjectUtil.isNotEmpty(addressList)){
        userVO.setAddressVO(BeanUtil.copyToList(addressList,AddressVO.class));
    }
    return userVO;
}

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

public List<UserVO> queryUSersByIds(List<Long> ids) {
    //查询用户
    List<User> users = listByIds(ids);
    //用户为空
    if (CollUtil.isEmpty(users)){
        return Collections.emptyList();
    }
    //获取用户id
    List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
    //获取用户地址
    List<Address> addressList = Db.lambdaQuery(Address.class).
            in(Address::getUserId, userIds)
            .list();
    //转换地址
    List<AddressVO> addressVOS = BeanUtil.copyToList(addressList, AddressVO.class);
    //用户地址集合分组处理,相同用户的放入一个集合 
    Map<Long, List<AddressVO>> addressMap = new HashMap<>();
    if (ObjectUtil.isNotEmpty(addressVOS)){
        addressMap = addressVOS.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
    }
    //转换为UserVo返回
    List<UserVO> userVO = new ArrayList<>(users.size());
    for (User user : users) {
        UserVO uv = BeanUtil.copyProperties(user, UserVO.class);
        userVO.add(uv);
        //设置地址
        uv.setAddressVO(addressMap.get(user.getId()));
    }
    return userVO;
}

③实现根据用户id查询收货地址功能,需要验证用户状态,冻结用户抛出异常(练习)

public List<AddressVO> queryUSersAddressById(Long id) {
        List<Address> address = lambdaQuery().eq(Address::getUserId, id).list();
    //用户状态冻结
        List<User> list = Db.lambdaQuery(User.class).eq(User::getId,id).eq(User::getStatus, 2).list();
        if (CollUtil.isNotEmpty(list)){
            throw new RuntimeException("用户状态异常");
        }
        return BeanUtil.copyToList(address,AddressVO.class);
    }

3)逻辑删除

基于代码逻辑模拟删除效果,但并不会真正删除数据。思路如下:

  • 在表中添加一个字段标记数据是否被删除

  • 当删除数据时把标记置为1

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

MybatisPlus提供了逻辑删除功能,无需改变方法调用的方式,而是在底层自动修改CRUD的语句。所以,只需要在application.yaml文件中配置逻辑删除的字段名称和值即可

mybatis-plus:
  global-config:
    db-config:
        logic-delete-field: flag # 全局逻辑删除的实体字段名,字段类型可以是boolean、integer
        logic-delete-value: 1 # 逻辑已删除值(默认为 1)
        logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

缺点:

  • 会导致数据库表垃圾数据越来越多,影响查询效率

  • SQL中全都需要对逻辑删除字段做判断,影响查询效率

因此,不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。

4)枚举处理器

  • 给枚举中的与数据库对应value值添加@EnumValue注解
  • 向前端展示添加@JsonValue注解
@Getter
public enum UserStatus {
    NORMAL(1, "正常"),
    FREEZE(2, "冻结")
    ;
    @EnumValue
    private final int value;
    @JsonValue
    private final String desc;
    UserStatus(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }
}
  • 在application.yml中配置全局枚举处理器,实现类型转换
mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

5)JSON处理器

数据库中user表中有一个json类型的字段

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
    private Integer age;
    private String intro;
    private String gender;
}

在实体类中加入注解@TableField(typeHandler = JacksonTypeHandler.class),@TableName(autoResultMap = true)

@TableName(value = "user", autoResultMap = true)
public class User {
    @TableField(typeHandler = JacksonTypeHandler.class)
    private UserInfo info;
}

4.插件功能

序号拦截器描述
1TenantLineInnerInterceptor多租户插件
2DynamicTableNameInnerInterceptor动态表名插件
3PaginationInnerInterceptor分页插件
4OptimisticLockerInnerInterceptor乐观锁插件
5IllegalSQLInnerInterceptorSQL性能规范插件,检测并拦截垃圾SQL
6BlockAttackInnerInterceptor防止全表更新和删除的插件

1)分页

  • 在配置类中注册MyBatisPlus的核心插件,同时添加分页插件
@Configuration
public class MybatisConfig {
 @Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    // 1.初始化核心插件
     MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    // 2.添加分页插件
     PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
     pageInterceptor.setMaxLimit(1000L); // 设置分页上限
     interceptor.addInnerInterceptor(pageInterceptor);
     return interceptor;
     }
}
  • 调用API实现
@Test
void testPageQuery() {
    // 1.查询
     int pageNo = 1, pageSize = 5;
    // 1.1.分页参数
     Page<User> page = Page.of(pageNo, pageSize);
    // 1.2.排序参数, 通过OrderItem来指定
     page.addOrder(new OrderItem("balance", false));
    // 1.3.分页查询
     Page<User> p = userService.page(page);
    // 2.总条数
     System.out.println("total = " + p.getTotal());
    // 3.总页数
     System.out.println("pages = " + p.getPages());
    // 4.分页数据
     List<User> records = p.getRecords();
     records.forEach(System.out::println);
}

2)通用分页实体

分页请求参数

@Data
public class PageQuery {
    private Integer pageNo = 1;
    private Integer pageSize = 5;
    private String sortBy;
    private Boolean isAsc = true;
}

pageDTO传输对象实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageDTO<V> {
    private Long total;
    private Long pages;
    private List<V> list;
}

service层实现

public PageDTO<UserVO> queryPage(UserQuery query) {
    String name = query.getName();
    Integer status = query.getStatus();
    //1.构建分页条件
    //1.1 分页
    Page<User> page = Page.of(query.getPageNo(), query.getPageSize());
    //1.2 排序 如果排序条件为空就默认排序
    if (StrUtil.isNotBlank(query.getSortBy())){
        page.addOrder(new OrderItem(query.getSortBy(),query.getIsAsc()));
    }else{
        page.addOrder(new OrderItem("update_time",false));
    }
    //2.分页查询
    Page<User> userPage = lambdaQuery()
            .like(name != null, User::getUsername, name)
            .eq(status != null, User::getStatus, status)
            .page(page);
    //3.封装VO结果
    PageDTO<UserVO> userVoPageDTO = new PageDTO<UserVO>();
    //总条数
    userVoPageDTO.setTotal(userPage.getTotal());
    //总页数
    userVoPageDTO.setPages(userPage.getPages());
    //当前页数据
    List<User> records = userPage.getRecords();
    if (CollUtil.isEmpty(records)){
        userVoPageDTO.setList(Collections.emptyList());
        return userVoPageDTO;
    }
    //转换对象
    List<UserVO> userVOS = BeanUtil.copyToList(records, UserVO.class);
    userVoPageDTO.setList(userVOS);
    return userVoPageDTO;
}

进一步封装,方便调用:

  • 封装分页条件
//可变形参
public <T>  Page<T> toMpPage(OrderItem ... orders){
    // 1.分页条件
    Page<T> p = Page.of(pageNo, pageSize);
    // 2.排序条件
    // 2.1.先看前端有没有传排序字段
    if (sortBy != null) {
        p.addOrder(new OrderItem(sortBy, isAsc));
        return p;
    }
    // 2.2.再看有没有手动指定排序字段
    if(orders != null){
        p.addOrder(orders);
    }
    return p;
}

public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc){
    return this.toMpPage(new OrderItem(defaultSortBy, isAsc));
}
//默认--创建时间
public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {
    return toMpPage("create_time", false);
}
//默认--更新时间
public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {
    return toMpPage("update_time", false);
}
  • 封装VO结果
/**
 * 将MybatisPlus分页结果转为 VO分页结果
 * @param p MybatisPlus的分页结果
 * @param voClass 目标VO类型的字节码
 * @param <V> 目标VO类型
 * @param <P> 原始PO类型
 * @return VO的分页对象
 */
public static <V, P> PageDTO<V> of(Page<P> p, Class<V> voClass) {
    // 1.非空校验
    List<P> records = p.getRecords();
    if (records == null || records.size() <= 0) {
        // 无数据,返回空结果
        return empty(p);
    }
    // 2.数据转换
    List<V> vos = BeanUtil.copyToList(records, voClass);
    // 3.封装返回
    return new PageDTO<>(p.getTotal(), p.getPages(), vos);
}

/**
 * 将MybatisPlus分页结果转为 VO分页结果,允许用户自定义PO到VO的转换方式
 * @param p MybatisPlus的分页结果
 * @param convertor PO到VO的转换函数
 * @param <V> 目标VO类型
 * @param <P> 原始PO类型
 * @return VO的分页对象
 */
public static <V, P> PageDTO<V> of(Page<P> p, Function<P, V> convertor) {
    // 1.非空校验
    List<P> records = p.getRecords();
    if (records == null || records.size() <= 0) {
        // 无数据,返回空结果
        return empty(p);
    }
    // 2.数据转换
    List<V> vos = records.stream().map(convertor).collect(Collectors.toList());
    // 3.封装返回
    return new PageDTO<>(p.getTotal(), p.getPages(), vos);
}
  • service层改造
public PageDTO<UserVO> queryPage(UserQuery query) {
        String name = query.getName();
        Integer status = query.getStatus();
        //1.构建分页条件
        Page<User> page = query.toMpPage(query.getSortBy(),query.getIsAsc());
        //2.分页查询
        Page<User> userPage = lambdaQuery()
                .like(name != null, User::getUsername, name)
                .eq(status != null, User::getStatus, status)
                .page(page);
        //3.封装VO结果
//        return PageDTO.of(userPage, user -> BeanUtil.copyProperties(user, UserVO.class));
        return PageDTO.of(userPage,user -> {
            //拷贝基础属性
            UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
            //处理特殊逻辑
            userVO.setUsername(userVO.getUsername().substring(0,userVO.getUsername().length()-2) + "***");
            return userVO;
        });
    }
  • 16
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值