你真的知道MyBatisPlus吗?
文章目录
- 你真的知道MyBatisPlus吗?
- 快速入门
- 入门案例
- 常见注解
- 常见配置
- 核心功能
- 条件构造器
- AbstractWrapper
- UpdateWrapper
- 条件构造器的用法
- 自定义SQL
- Service接口
- 扩展功能
- 代码生成
- 静态工具
- 逻辑删除
- 枚举处理器
- JSON处理器
- 插件功能
- 分页插件
- 通用分页实体
Mybatis是非常流行的持久层框架,是用来做数据库的增删改查,而MybatisPlus是对Mybatis的一个增强和升级,并不是替代Mybatis而是一种合作关系。
MyBatis-Plus官方网址
- 润物无声:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。
- 效率至上:只需简单配置,即可快速进行单表CRUD操作,从而节省大量时间。
- 丰富功能:代码生成、自动分页、逻辑删除、自动填充等功能一应俱全。
快速入门
入门案例
Mybatis方式:
UserMapper
public interface UserMapper{
void saveUser(User user);
void deleteUser(Long id);
void updateUser(User user);
User queryUserById(@Param ("id") Long id);
List<User> queryUserByIds(@Param("ids") List<Long> ids);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liner.mapper.UserMapper">
<insert id="saveUser" parameterType="com.liner.domain.po.User">
INSERT INTO `user` (`id`, `username`, `password`, `phone`, `info`, `balance`)
VALUES
(#{id}, #{username}, #{password}, #{phone}, #{info}, #{balance});
</insert>
<update id="updateUser" parameterType="com.liner.domain.po.User">
UPDATE `user`
<set>
<if test="username != null">
`username`=#{username}
</if>
<if test="password != null">
`password`=#{password}
</if>
<if test="phone != null">
`phone`=#{phone}
</if>
<if test="info != null">
`info`=#{info}
</if>
<if test="status != null">
`status`=#{status}
</if>
<if test="balance != null">
`balance`=#{balance}
</if>
</set>
WHERE `id`=#{id};
</update>
<delete id="deleteUser" parameterType="com.liner.domain.po.User">
DELETE FROM user WHERE id = #{id}
</delete>
<select id="queryUserById" resultType="com.liner.domain.po.User">
SELECT *
FROM user
WHERE id = #{id}
</select>
<select id="queryUserByIds" resultType="com.liner.domain.po.User">
SELECT *
FROM user
<if test="ids != null">
WHERE id IN
<foreach collection="ids" open="(" close=")" item="id" separator=",">
#{id}
</foreach>
</if>
LIMIT 10
</select>
</mapper>
上述操作繁琐,开发效率慢,而实用MybatisPlus大大简化操作,提高开发效率
只需两步
-
引入MybatisPlus的起步依赖
<!—-MybatisPlus--> <dependency> <groupid>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency>
MyBatisPlus官方提供了starter,其中集成了Mybatis和MybatisPlus的所有功能,并且实现了自动装配效果。因此可以用MybatisPlus的starter代替Mybatis的starter。
-
定义Mapper,
自定义Mapperer继承MybatisPlus提供的BaseMapper接口。
Mybatis-Plus方式:
UserMapper
public interface UserMapper extends BaseMapper<User> {}
常见注解
MyBatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息。
基本规则
- 类名驼峰转下划线作为表名
- 名为id的字段作为主键
- 变量名驼峰转下划线作为表的字段名
MybatisPlus中比较常用的几个注解如下∶
@TableName
:用来指定表名@Tableld
:用来指定表中的主键字段信息IdType
枚举:- AUTO:数据库自增长
- INPUT:通过set方法自行输入
- ASSIGN_ID:分配 ID,接口
ldentifierGenerator
的方法nextld来生成id,默认实现类为DefaultldentifierGenerator
雪花算法
@TableField
:用来指定表中的普通字段信息- 使用场景:
- 成员变量名与数据库字段名不一致
- 成员变量名以is开头,且是布尔值(如:@TableField(" is_ok"))
- 成员变量名与数据库主键字冲突(如:@TableField("
order
")) - 成员变量不是教据库字段(如:@TableField(exist = false))
- 使用场景:
常见配置
MyBatisPlus的配置项继承了MyBatis原生配置和一些自己特有的配置
mybatis-plus:
type-aliases-package: com.liner.domain.po #别名扫描包
mapper-locations: "classpath*:/mapper/**/*.xmi" # 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 #更新策略:只更新非空字段
核心功能
条件构造器
MyBatisPlus支持各种复杂的where条件,可以满足日常开发的所有需求。
AbstractWrapper
QueryWrapper
UpdateWrapper
条件构造器的用法
QueryWrapper
和LambdaQueryWrapper
通常用来构建select、delete、update的where条件部分UpdateWrapper
和LambdaUpdateWrapper
通常只有在set语句比较特殊才使用- 尽量使用
LambdaQueryWrapper
和LambdaUpdateWrapper
,避免硬编码
自定义SQL
利用MyBatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分。
三步走
-
基于Wrapper构建where条件
-
在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew
-
自定义SQL,并使用Wrapper条件
Service接口
MP的Service接口使用流程:
-
自定义Service接口继承IService接口
public interface IUserService extends IService<User> {}
-
自定义Service实现类,实现自定义接口并继承Servicelmpl类
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements IUserService {}
扩展功能
代码生成
静态工具
使用场景,在开发过程中可能多个Service业务相互调用,使用传统方式即@Autowired
,Service相互注入导致循环依赖,因此建议当出现相互调用可使用Db静态工具,和Service方法几乎一样,只需在调用过程中指定字节码,
改造前
@ApiOperation("根据id查询用户接口")
@GetMapping("{id}")
public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id) {
//1.查询用户PO
User user = userService.getById(id);
// 2.把PO拷贝到VO
return BeanUtil.copyProperties(user,UserVO.class);
}
改造后
@ApiOperation("根据id查询用户接口")
@GetMapping("{id}")
public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id) {
return userService.queryUserAndAddressById(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() == 2) {
throw new RuntimeException("用户状态异常!");
}
//2.查询地址
List<Address> address = 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;
}
}
改造前
@ApiOperation("根据id批量查询用户接口")
@GetMapping
public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids) {
//1.查询用户PO
List<User> users = userService.listByIds(ids);
// 2.把PO拷贝到VO
return BeanUtil.copyToList(user,UserVO.class);
}
改造后
@ApiOperation("根据id批量查询用户接口")
@GetMapping
public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids) {
return userService.queryUserAndAddressByIds(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.isEmpty(users)) {
return Collections.emptyList();
}
//2.查询地址
// 2.1.获取用户id集合
List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
// 2.2.根据用户id查询地址
List<Address> address = 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的数据
-
删除操作
UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0;
-
查询操作
SELECT * FROM user WHERE deleted = 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中全都需要对逻辑删除字段做判断,影响查询效率
因此,不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。
枚举处理器
在application.yml
中配置全局枚举处理器
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
添加注解@EnumValue
@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;
}
}
JSON处理器
在数据库表中若有json类型的字段
@Data
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
public class UserInfo{
private Integer age;
private String intro;
private String gender;
}
添加注解@TableField(typeHandler = JacksonTypeHandler.class)
,
因为出现对象嵌套,需要定义复杂的ResultMap,最简单的方法就是开启自动结果集映射,添加注解@TableName(value="user" , autoResultMap = true)
@Data
@TableName(value="user" , autoResultMap = true)
public class User {
private Long id;
private String username;
//private String info;
@TableField(typeHandler = JacksonTypeHandler.class)
private UserInfo info ;
}
插件功能
MyBatisPlus提供的内置拦截器有:
序号 | 拦截器 | 描述 |
---|---|---|
1 | TenantLineInnerInterceptor | 多租户插件 |
2 | DynamicTableNameInnerInterceptor | 动态表名插件 |
3 | PaginationInnerInterceptor | 分页插件 |
4 | OptimisticLockerInnerInterceptor | 乐观锁插件 |
5 | IllegalSQLInnerInterceptor | SQL性能规范插件,检测并拦截垃圾SQL |
6 | BlockAttacklnnerInterceptor | 防止全表更新和删除的插件 |
以上常用的就是分页插件
分页插件
在配置类中注册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
通用分页实体
简单分页查询案例:实现User分页查询
PageQuery
@Data
public class PageQuery {
private Integer pageNo;
private Integer pageSize;
private String sortBy;
private Boolean isAsc;
UserQuery
@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery extends PageQuery{
@ApiModelProperty("用户名关键字")
private String name;
@ApiModelProperty("用户状态:1-正常,2-冻结")
private Integer status;
@ApiModelProperty("余额最小值")
private Integer minBalance;
@ApiModelProperty("余额最大值")
private Integer maxBalance;
}
PageDTO
@Data
@ApiModel(description ="分页结果")
public class PageDTO<T> {
@ApiModelProperty("总条数")
private Long total;
@ApiModelProperty("总页数")
private Long pages;
@ApiModelProperty("集合")
private List<T> list;
}
UserController
@GetMapping("/page")
public PageDTO<UserVO> queryUsersPage(UserQuery query){
return userService.queryUsersPage(query);
}
UserServiceImpl
@Override
public PageDTO<UserVo> queryUsersPage(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.分页查询
lambdaQuery()
.like(name != null,User::getUsername,name)
.eq(status != null,User::getStatus,status)
.page(page);
//3.封装VO结果
PageDTO<UserVO> dto = new PageDTO<>();
// 3.1.总条数
dto.setTotal(p.getTotal());
// 3.2.总页数
dto.setPages(p.getPages());
// 3.3.当前页数据
List<User> records = p.getRecords();
if(CollUtil.isEmpty(records)) {
dto.setList(Collections.emptyList());
return dto;
}
//3.4.拷贝user的VO
dto.setList(BeanUtil.copyToList(records,UserVO.class));
//4.返回
return dto;
}
优化:将上述与业务关系不大的分页处理代码进行抽离封装
需求:
- 在PageQuery中定义方法,将PageQuery对象转为MyBatisPlus中的Page对象
- 在PageDTO中定义方法,将MyBatisPlus中的Page结果转为PageDTO结果
PageDTO
@Data
@ApiModel(description ="分页结果")
public class PageDTO<T> {
@ApiModelProperty("总条数")
private Long total;
@ApiModelProperty("总页数")
private Long pages;
@ApiModelProperty("集合")
private List<T> list;
public static <PO,VO> PageDTO<VO> of(Page<PO> p,Class<VO> clazz){
PageDTO<VO> dto = new PageDTO<>();
//1.总条数
dto.setTotal(p.getTotal());
//2.总页数
dto.setPages(p.getPages());
//3.当前页数据
List<PO> records = p.getRecords();
if(CollUtil.isEmpty(records)) {
dto.setList(Collections.emptyList());
return dto;
}
//4.拷贝user的VO
dto.setList(BeanUtil.copyToList(records,clazz));
//5.返回
return dto;
}
public static <PO,VO> PageDTO<VO> of(Page<PO> p,Function<PO,VO> convertor){
PageDTO<VO> dto = new PageDTO<>();
//1.总条数
dto.setTotal(p.getTotal());
//2.总页数
dto.setPages(p.getPages());
//3.当前页数据
List<PO> records = p.getRecords();
if(CollUtil.isEmpty(records)) {
dto.setList(Collections.emptyList());
return dto;
}
//4.拷贝VO
dto.setList(records.stream().map(convertor).collect(Collectors.toList()));
//5.返回
return dto;
}
}
PageQuery
@Data
public class PageQuery {
private Integer pageNo = 1;
private Integer pageSize = 5;
private String sortBy;
private Boolean isAsc;
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);
}
}
优化后
UserServiceImpl
@Override
public PageDTO<UserVo> queryUsersPage(UserQuery query) {
String name = query.getName();
Integer status = query.getStatus();
//1.构建分页条件
Page<User>page = query.toMpPageDefaultsortByupdateTime();
//2.分页查询
lambdaQuery()
.like(name != null,User::getUsername,name)
.eq(status != null,User::getStatus,status)
.page(page);
//3.封装VO结果
//return PageDTO.of(p,UserVO.class);
return PageDTO.of(p,user -> {
//1.拷贝基础属性
UserVO vo = BeanUtil.copyProperties(user,UserVO.class);
//2.处理特殊逻辑
vo.setUsername(vo.getUsername().subString(0,vo.getUsername().length() - 2) + "**");
return vo;
});
}