MybatisPlus MP引入 Mp的使用:
1.mapper层:继承BasMapper接口
2.service层:service接口实现IService接口,service实现类继承IServiceImpl实现类
Mp将实体类和数据库表明对应的约定:
MybatisPlus会把PO实体的类名驼峰转下划线作为表名
MybatisPlus会把PO实体的所有变量名驼峰转下划线作为表的字段名,并根据变量类型推断字段类型
MybatisPlus会把名为id的字段作为主键
常见注解:
@TableName 当实体类名称和数据库名称不一致时,使用该注解可以指定实体类对应的数据库表名
@TableId 一个作用是指定id,另一个作用是规定id的增长逻辑,没有指定默认是雪花算法:
值
描述
AUTO
数据库 ID 自增
NONE
无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUT
insert 前自行 set 主键值
ASSIGN_ID
分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
ASSIGN_UUID
分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认 default 方法)
@TableField 指定字段名,当类中某个属性并不在表中的时候,通过 exist 参数设置为 false 来排除在外 ,注意必须这么做,不然会报错
MP核心功能 条件构造器 1 2 3 4 5 6 AbstractWrapper ├── QueryWrapper (查询条件封装) ├── UpdateWrapper (更新条件封装) └── LambdaWrapper ├── LambdaQueryWrapper (Lambda语法查询) └── LambdaUpdateWrapper (Lambda语法更新)
链式调用 :支持链式编程,代码更简洁
Lambda支持 :避免字段名硬编码,编译时检查
防SQL注入 :自动处理参数,防止SQL注入
多条件组合 :支持AND、OR等复杂逻辑组合
Query Wrapper 1 2 3 4 5 6 7 8 9 10 void testQueryWrapper () { QueryWrapper<User> wrapper = new QueryWrapper <User>() .select("id" , "username" , "info" , "balance" ) .like("username" , "o" ) .ge("balance" , 1000 ); List<User> userList = userMapper.selectList(wrapper); }
UpdateWrapper 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void testUpdateWrapper () { List<Long> ids = List.of(1L ,2L ,3L ); Integer fangzhuru = 200 ; UpdateWrapper<User> wrapper = new UpdateWrapper () .setSql("balance = balance - {0}" ,fangzhuru) .in("id" ,ids); userMapper.update(null ,wrapper); userMapper.update(User,new UpdateWrapper <User>().in("id" ,ids)); }
LambdaQueryWrapper 1 2 3 4 5 6 7 8 9 10 11 void testLambdaQueryWrapper () { QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.lambda() .select(User::getId, User::getUsername, User::getInfo, User::getBalance) .like(User::getUsername, "o" ) .ge(User::getBalance, 1000 ); List<User> users = userMapper.selectList(wrapper); }
自定义Sql 上面的案例中的Update那里的setSql方法实际上是在service层写了Sql语句,这是不符合规范的
但是,我们的目的是使用IService中的好用条件语句 ,所以我们可以这么做:
把条件语句封装到wrapper中,然后传递 下去:
1 2 3 4 5 6 7 8 9 10 11 12 void testUpdateWrapper () { List<Long> ids = List.of(1L ,2L ,3L ); QueryWrapper<User> QueryWrapper = new QueryWrapper .in("id" ,ids); List<User> userList = userMapper.deductBalanceByIds(200 ,wrapper); }
1 2 3 4 @Update("update user set balance = balance - #{money} ${ew.customWrapperSegment}") List<User> deductBalanceByIds (@Param("money") Integer money, @Param("ew") QueryWrapper<User> QueryWrapper) ;
多表联查 1 2 3 4 5 6 7 8 9 10 11 12 13 void testJoinWrapper () { List<Long> ids = List.of(1L ,2L ,3L ); QueryWrapper<User> wrapper = new QueryWrapper () .in("u.id" ,ids); .eq("a.city" ,"北京" ); List<User> userlist = userMapper.getUsersByIdAndAddress(wrapper); } @Select("Select u.* from user u Left join address a on a.user_id = u.id ${ew.customWrapperSegment}") List<User> getUsersByIdAndAddress (@Param("ew") QueryWrapper<User> QueryWrapper) ;
Service接口 service接口可以让很多简单的单表curd,直接在controller里就能完成
Lambda 直接看例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public List<UserVO> queryUsers (UserQuery query) { String username = query.getName(); Integer status = query.getStatus(); Integer minBalance = query.getMinBalance(); Integer maxBalance = query.getMaxBalance(); LambdaQueryWrapper<User> wrapper = new QueryWrapper <User>().lambda() .like(username != null , User::getUsername, username) .eq(status != null , User::getStatus, status) .ge(minBalance != null , User::getBalance, minBalance) .le(maxBalance != null , User::getBalance, maxBalance); List<User> users = userService.list(wrapper); return BeanUtil.copyToList(users, UserVO.class); public List<UserVO> queryUsers (UserQuery query) { String username = query.getName(); Integer status = query.getStatus(); Integer minBalance = query.getMinBalance(); Integer maxBalance = query.getMaxBalance(); List<User> users = userService.lambdaQuery() .like(username != null , User::getUsername, username) .eq(status != null , User::getStatus, status) .ge(minBalance != null , User::getBalance, minBalance) .le(maxBalance != null , User::getBalance, maxBalance) .list(); return BeanUtil.copyToList(users, UserVO.class); }
这个例子可以看出,lambda省略了自定义wrapper和调用service查询接口传入wrapper的过程
直接用IService接口中的lambdaQuery方法,最后一个.list()把返回值也获得了
得到结果的一步不仅可以用list(),可选的方法有:
.one():最多1个结果
.list():返回集合结果
.count():返回计数结果
MybatisPlus会根据链式编程的最后一个方法来判断最终的返回结果
批处理 在jdbc的url后面添加参数&rewriteBatchedStatements=true
1 2 3 4 5 6 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true driver-class-name: com.mysql.cj.jdbc.Driver username: root password: MySQL123
扩展功能 静态工具 service之间如果相互调用,就可能出现问题(也可以通过@Lazy注解解决)
MybatisPlus提供一个静态工具类:Db,其中的一些静态方法与 IService 中方法签名基本一致
相比于使用IService接口中的方法,使用Db中的方法不同之处就在于,要多传入一个参数:目标实体对象的类的字节码,因为静态方法不知道你要用哪个类来和数据库映射
逻辑删除 对于一些比较重要的数据,我们往往会采用逻辑删除的方案,即:
在表中添加一个字段标记数据是否被删除
当删除数据时把标记置为true
查询时过滤掉标记为true的数据
注意 ,只有MybatisPlus生成的SQL语句才支持自动的逻辑删除,自定义SQL需要自己手动处理逻辑删除
在application.yml中加入以下配置:
1 2 3 4 5 6 mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0
通用枚举 定义一个枚举:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.itheima.mp.enums;import com.baomidou.mybatisplus.annotation.EnumValue;import lombok.Getter;@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; } }
这样就可以把用户类中的status字段的类型由Integer改为 UserStatus ,更加直观
那么如何让MP能把数据库中的实际类型为整数的status和枚举类型对上?
第一步:在枚举类型中使用注解:@EnumValue,标注的属性是实际和数据库相匹配的属性
第二部,配置枚举处理器,依旧添加application.yml
1 2 3 mybatis-plus: configuration: default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
同时,我们也可以把返回值VO中的status字段也改成枚举类型,那么在实际返回的时候,如何指定json序列化时,使用的实际属性?
使用 @JsonValue 注解,(这个注解是springmvc的,不是MP的)
JSON类型处理器 假设现在user表中有一个info字段,是json类型,user中却是String类型,那拆分数据就会很麻烦
MP提供了json类型处理器:
第一步,我们自己定义一个实体类UserInfo,属性和json字段相对应
第二步,把user类中的info字段改为UserInfo类型的,并加上注解 @TableField(typeHandler = JacksonTypeHandler.class)
第三步,在user类上加一个注解,声明自动映射 @TableName(value = “user”, autoResultMap = true)
插件功能 分页插件 自定义一个配置类,配置分页插件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.itheima.mp.config;import com.baomidou.mybatisplus.annotation.DbType;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration public class MybatisConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor () { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor (); interceptor.addInnerInterceptor(new PaginationInnerInterceptor (DbType.MYSQL)); return interceptor; } }
这个过程相当于有两层,注入了一个MP的拦截器总对象,这个总对象还可以add小对象,这里就是add了一个分页插件对象
1 2 3 4 5 6 7 int pageNo = 1 , pageSize = 5 ;Page<User> page = Page.of(pageNo, pageSize); page.addOrder(new OrderItem ("balance" , false )); userService.page(page);
通用分页实体 pageQuery:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package com.itheima.mp.domain.query;import com.baomidou.mybatisplus.core.metadata.OrderItem;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;import lombok.Data;@Data public class PageQuery { private Integer pageNo; private Integer pageSize; private String sortBy; private Boolean isAsc; public <T> Page<T> toMpPage (OrderItem ... orders) { Page<T> p = Page.of(pageNo, pageSize); if (sortBy != null ) { p.addOrder(new OrderItem (sortBy, isAsc)); return p; } 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 ); } }
pageDTO:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 package com.itheima.mp.domain.dto;import cn.hutool.core.bean.BeanUtil;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import java.util.Collections;import java.util.List;import java.util.function.Function;import java.util.stream.Collectors;@Data @NoArgsConstructor @AllArgsConstructor public class PageDTO <V> { private Long total; private Long pages; private List<V> list; public static <V, P> PageDTO<V> empty (Page<P> p) { return new PageDTO <>(p.getTotal(), p.getPages(), Collections.emptyList()); } public static <V, P> PageDTO<V> of (Page<P> p, Class<V> voClass) { List<P> records = p.getRecords(); if (records == null || records.size() <= 0 ) { return empty(p); } List<V> vos = BeanUtil.copyToList(records, voClass); return new PageDTO <>(p.getTotal(), p.getPages(), vos); } public static <V, P> PageDTO<V> of (Page<P> p, Function<P, V> convertor) { List<P> records = p.getRecords(); if (records == null || records.size() <= 0 ) { return empty(p); } List<V> vos = records.stream().map(convertor).collect(Collectors.toList()); return new PageDTO <>(p.getTotal(), p.getPages(), vos); } }
业务层便可简化为:
1 2 3 4 5 6 7 8 9 @Override public PageDTO<UserVO> queryUserByPage (PageQuery query) { Page<User> page = query.toMpPageDefaultSortByCreateTimeDesc(); page(page); return PageDTO.of(page, UserVO.class); }
自定义最后到vo的转换过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Override public PageDTO<UserVO> queryUserByPage (PageQuery query) { Page<User> page = query.toMpPageDefaultSortByCreateTimeDesc(); page(page); return PageDTO.of(page, user -> { UserVO vo = BeanUtil.copyProperties(user, UserVO.class); String username = vo.getUsername(); vo.setUsername(username.substring(0 , username.length() - 2 ) + "**" ); return vo; }); }
Stream流 Lambda表达式