你真的知道MyBatisPlus吗?

你真的知道MyBatisPlus吗?

文章目录

  • 你真的知道MyBatisPlus吗?
    • 快速入门
      • 入门案例
      • 常见注解
      • 常见配置
    • 核心功能
      • 条件构造器
        • AbstractWrapper
        • UpdateWrapper
        • 条件构造器的用法
      • 自定义SQL
      • Service接口
    • 扩展功能
      • 代码生成
      • 静态工具
      • 逻辑删除
      • 枚举处理器
      • JSON处理器
    • 插件功能
      • 分页插件
      • 通用分页实体

Mybatis是非常流行的持久层框架,是用来做数据库的增删改查,而MybatisPlus是对Mybatis的一个增强和升级,并不是替代Mybatis而是一种合作关系。

你真的知道MyBatisPlus吗?_第1张图片

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);
}

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大大简化操作,提高开发效率

只需两步

  1. 引入MybatisPlus的起步依赖

    <!—-MybatisPlus-->
    <dependency>
        <groupid>com.baomidougroupId>
        <artifactId>mybatis-plus-boot-starterartifactId>
        <version>3.5.3.1version>
    dependency>
    

    MyBatisPlus官方提供了starter,其中集成了Mybatis和MybatisPlus的所有功能,并且实现了自动装配效果。因此可以用MybatisPlus的starter代替Mybatis的starter。

  2. 定义Mapper,

    自定义Mapperer继承MybatisPlus提供的BaseMapper接口。

    你真的知道MyBatisPlus吗?_第2张图片

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条件,可以满足日常开发的所有需求。

你真的知道MyBatisPlus吗?_第3张图片

你真的知道MyBatisPlus吗?_第4张图片

AbstractWrapper

你真的知道MyBatisPlus吗?_第5张图片
QueryWrapper

你真的知道MyBatisPlus吗?_第6张图片

UpdateWrapper

你真的知道MyBatisPlus吗?_第7张图片

条件构造器的用法
  • QueryWrapperLambdaQueryWrapper通常用来构建select、delete、update的where条件部分
  • UpdateWrapperLambdaUpdateWrapper通常只有在set语句比较特殊才使用
  • 尽量使用LambdaQueryWrapperLambdaUpdateWrapper,避免硬编码

自定义SQL

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

三步走

  1. 基于Wrapper构建where条件

    你真的知道MyBatisPlus吗?_第8张图片

  2. 在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew

    在这里插入图片描述

  3. 自定义SQL,并使用Wrapper条件

    在这里插入图片描述

Service接口

你真的知道MyBatisPlus吗?_第9张图片

MP的Service接口使用流程:

  1. 自定义Service接口继承IService接口

    public interface IUserService extends IService<User> {}
    
  2. 自定义Service实现类,实现自定义接口并继承Servicelmpl类

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

扩展功能

代码生成

你真的知道MyBatisPlus吗?_第10张图片

你真的知道MyBatisPlus吗?_第11张图片

你真的知道MyBatisPlus吗?_第12张图片

你真的知道MyBatisPlus吗?_第13张图片

你真的知道MyBatisPlus吗?_第14张图片

静态工具

你真的知道MyBatisPlus吗?_第15张图片

使用场景,在开发过程中可能多个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
你真的知道MyBatisPlus吗?_第16张图片

通用分页实体

简单分页查询案例:实现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;
    });
}

你可能感兴趣的:(实用工具及技术篇,tomcat,java,mybatis,mybatisplus)