MyBatisPlus - 润物无声、效率至上、丰富功能

目录

一、简介 

1.1、为什么要使用 MybatisPlus

二、使用指南

2.1、依赖

2.2、配置

2.3、常用注解

2.4、BaseMapper 的使用

2.4.1、定义 Mapper 接口

2.4.2、基于 QueryWrapper 的查询(不推荐)

2.4.3、基于 UpdateWrapper 的修改(不推荐)

2.4.4、基于 Lambda 的查询、修改(推荐)

2.5、自定义 SQL

2.6、IService 和 ServiceImpl

2.7、Db 静态工具

2.8、分页插件

2.8.1、配置类

2.8.2、实际使用


一、简介 


1.1、为什么要使用 MybatisPlus

a)润物无声:

在 MyBatis 基础上只做增强,不做修改,不会对现有工程产生影响,并且在启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作.

b)效率至上:

MP 提供了很多现成的单表操作,只需简单配置,就可以进行单表的 CRUD,节省大量时间.  另外还提供了 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错.

c)扩展功能:

代码生成、自动分页、逻辑删除、自动填充等等.... 但是我觉得这里有些东西可能自己基建更合适.

二、使用指南


2.1、依赖

MyBatisPlus 的 starter 可以直接替代 MyBatis 的 starter

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

2.2、配置

mybatis-plus:
  type-aliases-package: com/cyk/mp/model/domain # 别名扫描包
  # 以下配置都是默认值,可以根据项目情况进行修改
  mapper-locations: "classpath*:/mapper/**/*.xml"  # Mapper.xml 文件地址
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印 sql
    map-underscore-to-camel-case: true # 是否开启下划线和驼峰的映射
    cache-enabled: false # 是否开启二级缓存
  global-config:
    db-config:
      id-type: assign_id # 默认雪花算法,另外还有 auto(自增id) assign_uuid(uuid)
      update-strategy: not_null # 更新策略: 只更新非空字段

2.3、常用注解

a)常用注解如下:

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

具体的注解参数可以参考官网:注解 | MyBatis-Plus

例如 user_info 表如下:

类定义如下:

@Data
@TableName(value = "user_info", autoResultMap = true)
public class User {

    @TableId(value = "id", type = IdType.AUTO) //value 默认是 id, type 默认是雪花算法,这里修改为自增长
    private Long id;
    @TableField("username")
    private String name;
    @TableField("is_married")
    private Boolean isMarried;
    private Integer age;
    //这里会自动驼峰转下划线
    private Date ctTime;
    private Date utTime;

}

Ps:此案例贯穿全文 

b)注意事项如下

@TableField 中若指定 value 生效的前提是: @TableName 中 autoResultMap = true,此设置会按照 TableField 中的设置的 value 自动生成 resultMap.

IdType 枚举类型如下

  • AUTO:数据库 ID 自增.
  • NONE:无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
  • INPUT:通过 set 方法自行注入.
  • ASSIGN_ID:分配 ID(主键类型为 Number(Long 和 Integer)或 String),接口 IdentifierGenerator 的方法 nextid 来生成 id,默认是雪花算法.
  • ASSIGN_UUID:分配 UUID,主键类型为 String

使用 @TableASSIGN_UUIDField 的常见场景:

  • 成员变量与数据库字段名不一致
  • 成员变量名以 is 开头,且是布尔值.
  • 成员变量名与数据库关键字冲突(反引号解决)
  • 成员变量不是数据库字段(@TableField(exist = false) 表示表中不存在该字段,用来排除).

2.4、BaseMapper<T> 的使用

2.4.1、定义 Mapper 接口

自定义的 Mapper 类继承 MyBatisPlus 提供的 BaseMapper 接口:

例如对 User 类操作.

public interface UserMapper extends BaseMapper<User> {

}

可以看到 BaseMapper 接口中提供了很多方法,都可以直接调用~

2.4.2、基于 QueryWrapper 的查询(不推荐)

QueryWrapper 通常用来构建 select、delete 的 select 和 where 条件部分,以及 update 的 where 部分.

a)案例一:查询出 name 中带 y 的,age 小于 30 的( id、username、age、is_married )信息.

MP 实现如下:

    @Test
    public void test1() {
        //1.构造查询条件(支持链式编程)
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .select("id", "username", "age", "is_married")
                .like("username", "y")
                .lt("age", 30); // ge大于等于 gt大于 lt小于 le小于等于
        //2.查询
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }

MP 实际生成的 SQL 语句如下:

SELECT id,username,age,is_married FROM user_info WHERE (username LIKE ? AND age < ?)

b)案例二:更新 username 为 cyk 的 is_married 字段为 true.

MP 实现如下:

    @Test
    public void test2() {
        //1.要更新的数据
        User user = new User();
        user.setIsMarried(true);
        //2.构造更新条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .eq("username", "cyk");
        //3.执行更新
        userMapper.update(user, wrapper);
    }

MP 实际生成的 SQL 语句如下:

UPDATE user_info SET is_married=? WHERE (username = ?)

2.4.3、基于 UpdateWrapper 的修改(不推荐)

UpdateWrapper 通常用来构造 update 语句的 set 和 where 部分.

a)案例一:让 id 为 1,2,3 的用户的 age 加 10.

MP 实现如下:

    @Test
    public void test3() {
        List<Long> ids = List.of(1L,2L,3L);
        UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
                .setSql("age = age + 10")
                .in("id", ids);
        userMapper.update(null, wrapper);
    }

MP 实际生成的 SQL 语句如下: 

 UPDATE user_info SET age = age + 10 WHERE (id IN (?,?,?))

2.4.4、基于 Lambda 的查询、修改(推荐)

上面提到的这两种方式实际上是硬编码的写法(不推荐),下面讲的 Lambda 写法才是最合适的.

a)案例一:查询出 name 中带 y 的,age 小于 30 的( id、username、age、is_married )信息.

MP 实现如下:

    @Test
    public void test4() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
                .select(User::getId, User::getName, User::getAge, User::getIsMarried)
                .lt(User::getAge, 30);
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }

MP 底层执行的 sql 如下: 

SELECT id,username,age,is_married FROM user_info WHERE (age < ?)

 b)案例二:在公司经常会遇到一个接口复用多个接口的情况,例如根据 ids、name、is_married 查询用户信息,但是这三个参数都有可能为空.  这里 MP 能很好的抛弃 if 语句,十分优雅

    @Test
    public void test6() {
        //1.假设前端传入请求数据
        UserDTO dto = new UserDTO();
        dto.setIds(List.of(1L,2L,3L));
        dto.setName(null);
        dto.setAgeLeft(10);
        dto.setAgeRight(30);
        //2.构造请求
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
                .in(!dto.getIds().isEmpty(), User::getId, dto.getIds())
                .eq(StringUtils.isNotBlank(dto.getName()), User::getName, dto.getName())
                .between(dto.getAgeLeft() != null && dto.getAgeRight() != null,
                        User::getAge, dto.getAgeLeft(), dto.getAgeRight());
        //3.执行查询
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }

 MP 底层执行的 sql 如下: 

SELECT id,username,is_married,age,ct_time,ut_time FROM user_info WHERE (id IN (?,?,?) AND age BETWEEN ? AND ?)

2.5、自定义 SQL

MyBatisPlus 更适合于构建复杂的 where 的条件(避免使用一些 动态 sql 显得臃肿),对于例如 update 中出现例如 "set age = age + 10" 这样的语句就只能使用硬编码.

而 MyBatisPlus 是包含 MyBatis 的,因此可以通过 MyBatis 定义 sql 语句中剩下的部分.

总而言之:对于复杂 update 语句,自定义 where 条件之前的语句,MP 构建 where 条件之后的语句

案例:让 id 为 1,2,3 的用户的 age 加 10.

a)基于 Wrapper 构建 where 条件

        //让 age 增加 10
        int addAge = 10;
        //要修改哪些用户
        List<Long> ids = List.of(1L, 2L, 3L);
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
                .in(User::getId, ids);
        //updateBatchByIds 是自定义的方法
        userMapper.updateBatchByIds(wrapper, addAge);

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

@Mapper
public interface UserMapper extends BaseMapper<User> {

    //wrapper 的变量声明必须是 ew
    void updateBatchByIds(
            @Param("ew") LambdaQueryWrapper<User> wrapper,
            @Param("addAge") int addAge
    );

}

如果忘记是 ew 这个东西,也可以通过 Constants 这个常量来找到.

 

c)在 xml 文件中自定义 sql,并使用 Wrapper 条件

    <update id="updateBatchByIds">
        update user_info
        set age = age + #{addAge} ${ew.customSqlSegment}
    </update>

MP 实际生成 sql 语句如下:

 update user_info set age = age + ? WHERE (id IN (?,?,?))

2.6、IService 和 ServiceImpl

Ps:不太建议在 Service 层乱造,可以自己封装以下... 当然 BaseMapper 也够用.

IService 接口提供中也提供了很多的 方法,并在 ServiceImpl 也给出了具体的实现.

这里的方法和上面讲到的 BaseMapper 大差不差,就不展开讲了.

下面以 User 类为例.

a)首先自定义 mapper 接口,继承 BaseMapper

@Mapper
public interface UserMapper extends BaseMapper<User> {

}

a)我们只需要在我们自定义的 IXXXService 上继承 IService

public interface IUserService extends IService<User> {

}

b)并在自定义的 XXXServiceImpl 上继承 ServiceImpl 即可

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

}

d)之后这些方法都可以随便用了,例如 getById

    @Override
    public void xxxMethod(Long id) {
        User byId = getById(id);
        //...
    }

e)当然,这里也提供了对应的 lambda 表达式

    @Override
    public List<User> query(QueryUserDTO dto) {
        return lambdaQuery()
                .in(!dto.getIds().isEmpty(), User::getId, dto.getIds())
                .eq(StringUtils.hasLength(dto.getName()), User::getName, dto.getName())
                .between(dto.getMinAge() != null && dto.getMaxAge() != null,
                        User::getAge, dto.getMinAge(), dto.getMaxAge())
                .list(); //生成一个 list    当然也可以选择生成 page 后面会讲
    }

模拟创建请求调用 query 方法如下:

        QueryUserDTO dto = QueryUserDTO
                .builder()
                .ids(List.of(1L,2L,3L))
                .name(null)
                .minAge(10)
                .maxAge(30)
                .build();
        List<User> users = userService.query(dto);
        users.forEach(System.out::println);

 MP 实际执行的 sql 语句

SELECT id,username,is_married,age,ct_time,ut_time FROM user_info WHERE (id IN (?,?,?) AND age BETWEEN ? AND ?)

2.7、Db 静态工具

可以看到和 IService 中方法几乎一摸一样,只不过 IService 接口里面的方法都是非静态 ,而 DB 这个静态工具里面的方法自然也都是静态的

而且可以看到一些细小的变化,他的方法除了新增和修改以外都需要指定泛型,这是因为他需要通过反射才能拿到实体类的字节码,才知道你的表信息.

那这玩意到底和 IService 有什么区别呢?

假设有两张表,用户表和地址表,并且业务需要根据用户 id 查询地址,又需要根据地址查询用户id,那么就你就可能去 UserService 中注入 AddressService ,而又在 AddressService 中注入 UserService,这样就产生了循环依赖问题.

虽然循环依赖在较新的 Spring 版本中已被解决,但是最好还是需要我们开发人员去注意这个问题,保证系统的稳定性.

此时就可以通过 DB 静态工具类来实现了. 

2.8、分页插件

2.8.1、配置类

首先,要在配置类种注册 MyBatisPlus 的核心插件,同时添加分页插件:

Java 如下:

@Configuration
public class MybatisConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        //1.初始化核心插件
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //2.创建分页插件
        PaginationInnerInterceptor page = new PaginationInnerInterceptor(DbType.MYSQL);
        //设置分页上线
        page.setMaxLimit(100L);
        //3.添加分页插件到 MybatisPlus
        interceptor.addInnerInterceptor(page);
        return interceptor;
    }

}

Kotlin 如下:

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor
import com.baomidou.mybatisplus.annotation.DbType

@Configuration
class MybatisConfig {

    @Bean
    fun mybatisPlusInterceptor(): MybatisPlusInterceptor {
        // 初始化核心插件
        val interceptor = MybatisPlusInterceptor()
        // 创建分页插件
        val page = PaginationInnerInterceptor(DbType.MYSQL)
        // 设置分页上限
        page.maxLimit = 25L
        // 添加分页插件到 MybatisPlus
        interceptor.addInnerInterceptor(page)
        return interceptor
    }
}

 

Ps:缺少以上配置,就没法使用分页.  这个插件本质就是在执行指令之前被拦截器拦截下来, 添加分页命令.

2.8.2、实际使用

案例:在公司经常会遇到一个接口复用多个接口的情况,例如根据 ids、name、is_married 查询用户信息,但是这三个参数都有可能为空,最后的结果分页显示(limit 3 offset 0),并根据 id 升序排序.

Ps:在 Page 中若没有使用 addOrder 方法,或者方法参数为空,默认按照创建时间排序.

a)MP 实现如下:

        //1.默认请求
        PageUserDTO dto = PageUserDTO
                .builder()
                .start(2)
                .limit(1)
                .ids(List.of(1L,2L,3L))
                .name(null)
                .minAge(10)
                .maxAge(30)
                .build();
        //2.分页查询
        //2.1 构建查询
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
                .in(!dto.getIds().isEmpty(), User::getId, dto.getIds())
                .eq(StringUtils.isNotBlank(dto.getName()), User::getName, dto.getName())
                .between(dto.getMinAge() != null && dto.getMaxAge() != null, User::getAge, dto.getMinAge(), dto.getMaxAge());
        //2.2 构建分页
        Page<User> page = Page.of(dto.getStart(), dto.getLimit()); //起始页码(非下标), 每页显示条数
        page.addOrder(new OrderItem("id", true)); //true 表示升序
        //3.查询
        Page<User> result = userService.page(page, wrapper);
        //3.1 分页数据
        List<User> records = result.getRecords();
        //3.2 总条数
        long total = result.getTotal();
        //3.3 总页数
        long pages = result.getPages();

MP 底层执行的 sql 如下:

SELECT COUNT(*) AS total FROM user_info WHERE (id IN (?, ?, ?) AND age BETWEEN ? AND ?)

SELECT id, username, is_married, age, ct_time, ut_time FROM user_info WHERE (id IN (?, ?, ?) AND age BETWEEN ? AND ?) ORDER BY id ASC LIMIT ?,?

b)但是实际开发一般也不会写的像上述代码一样这么麻烦,更多的可能是这样

        //1.默认请求
        PageUserDTO dto = PageUserDTO
                .builder()
                .start(2)
                .limit(1)
                .ids(List.of(1L,2L,3L))
                .name(null)
                .minAge(10)
                .maxAge(30)
                .build();
        //2.构建分页条件
        Page<User> page = Page.of(dto.getStart(), dto.getLimit()); //起始页码(非下标), 每页显示条数
        page.addOrder(new OrderItem("id", true)); //true 表示升序
        //3.查询
        Page<User> result = userService.lambdaQuery()
                .in(!dto.getIds().isEmpty(), User::getId, dto.getIds())
                .eq(StringUtils.isNotBlank(dto.getName()), User::getName, dto.getName())
                .between(dto.getMinAge() != null && dto.getMaxAge() != null, User::getAge, dto.getMinAge(), dto.getMaxAge())
                .page(page);

        //3.1 分页数据
        List<User> records = result.getRecords();
        //3.2 总条数
        long total = result.getTotal();
        //3.3 总页数
        long pages = result.getPages();

  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈亦康

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值