mybatis-plus使用和原理剖析之条件构造器

mybatis-plus使用和原理剖析之条件构造器

一、QueryWrapper

1.QueryWrapper

  • QueryWrapper
String name = "张三";
final QueryWrapper<Student> wrapper = new QueryWrapper<>();
// 字符串包含非空字符时才拼接查询条件(默认总是拼接)
wrapper.eq(StrUtil.isNotBlank(name), "name", name);
final List<Student> students = this.list(wrapper);
  • QueryWrapper With Entity
final Student student = new Student();
student.setName("张三");
// 非空时才拼接查询条件
final QueryWrapper<Student> wrapper = new QueryWrapper<>(student);
final List<Student> students = this.list(wrapper);

若想实现同StrUtil.isNotBlank(name)一样仅当包含非空字符是才拼接查询条件,可以更改全局配置mybatis-plus.global-config.db-config.where-strategy=not_empty(默认为not_null)

mybatis-plus:
  global-config:
    db-config:
      where-strategy: not_empty
  • IService 链式调用
String name = "张三";
// 字符串包含非空字符时才拼接查询条件(默认总是拼接)
final List<Student> students = this.query().eq(StrUtil.isNotBlank(name), "name", name).list();

2.LambdaQueryWrapper

  • LambdaQueryWrapper
String name = "张三";
final LambdaQueryWrapper<Student> wrapper = new QueryWrapper<Student>().lambda()
// 字符串包含非空字符时才拼接查询条件(默认总是拼接)
    .eq(StrUtil.isNotBlank(name), Student::getName, name);
final List<Student> students = this.list(wrapper);
  • IService 链式调用lambda 式
String name = "张三";
// 字符串包含非空字符时才拼接查询条件(默认总是拼接)
final List<Student> list = this.lambdaQuery().eq(StrUtil.isNotBlank(name), Student::getName, name).list();

3.总结

1.推荐使用Lambda方式,可以避免列名的硬编码;

2.与前端约定,数据为空时传null或者不传值到后端(vue作为前端框架可能传空字符串),避免使用QueryWrapper With Entity筛选数据时,出现与预期不符的问题;spring-boot-starter-validation校验框架正则校验也是对null值放行,对空字符串拦截。

二、UpdateWrapper

1.UpdateWrapper

  • UpdateWrapper
Integer age = 12;
final UpdateWrapper<Student> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq(StrUtil.isNotBlank(name), "name", name);
updateWrapper.set(ObjectUtil.isNotNull(age), "age", age);
final boolean update = this.update(updateWrapper);
  • UpdateWrapper With Entity
Integer age = 12;
// 查询条件
final Student student = new Student();
student.setName("张三");
final UpdateWrapper<Student> updateWrapper = new UpdateWrapper<>(student);
updateWrapper.set(ObjectUtil.isNotNull(age), "age", age);
final boolean update = this.update(updateWrapper);
final Student student = new Student();
student.setName("张三");
final Student saveData = new Student();
saveData.setAge(12);
final UpdateWrapper<Student> updateWrapper = new UpdateWrapper<>(student);
final boolean update = this.update(saveData, updateWrapper);
  • IService 链式调用
String name = "张三";
this.update().eq(StrUtil.isNotBlank(name), "name", name).set(ObjectUtil.isNotNull(age), "age", age).update();

2.LambdaUpdateWrapper

  • LambdaUpdateWrapper
String name = "张三";
Integer age = 12;
final LambdaUpdateWrapper<Student> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(StrUtil.isNotBlank(name), Student::getName, name);
updateWrapper.set(ObjectUtil.isNotNull(age), Student::getAge, age);
final boolean update = this.update(updateWrapper);
String name = "张三";
final Student saveData = new Student();
saveData.setAge(12);
final LambdaUpdateWrapper<Student> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(StrUtil.isNotBlank(name), Student::getName, name);
final boolean update = this.update(saveData, updateWrapper);
  • IService 链式调用lambda 式
String name = "张三";
final boolean update = this.lambdaUpdate().eq(StrUtil.isNotBlank(name), Student::getName, name)
    .set(ObjectUtil.isNotNull(age), Student::getAge, age)
    .update();
String name = "张三";
final Student saveData = new Student();
saveData.setAge(12);
final boolean update = this.lambdaUpdate().eq(StrUtil.isNotBlank(name), Student::getName, name)
    .update(saveData);

3.总结

1.推荐使用Lambda方式,可以避免列名的硬编码;

2.注意set语法部分,必须存在否则SQL异常;

3.注意查询条件缺失全表更新问题;

三、原理剖析

1.Lambda方法引用原理

QStudent::getName怎样对应到数据库列名?

String name = "张三";
final LambdaQueryWrapper<Student> wrapper = new QueryWrapper<Student>().lambda()
    .eq(StrUtil.isNotBlank(name), Student::getName, name);
final List<Student> students = this.list(wrapper);
  • 泛型说明
// T-实体类 R-列名提供者类型 Children-AbstractWrapper子类型
AbstractWrapper<T, R, Children extends AbstractWrapper<T, R, Children>>

// T-实体类 R-列名提供者类型(String) Children-AbstractWrapper子类型
QueryWrapper<T> extends AbstractWrapper<T, String, QueryWrapper<T>>

// T-实体类 R-列名提供者类型(SFunction<T, ?>) Children-AbstractWrapper子类型
// SFunction<T, ?> - 支持序列化的 Function,输入T的实例返回对象,对象的返回值(类型为?)不重要,方法名本身才重要
AbstractLambdaWrapper<T, Children extends AbstractLambdaWrapper<T, Children>>
    extends AbstractWrapper<T, SFunction<T, ?>, Children>
// T-实体类  
LambdaQueryWrapper<T> extends AbstractLambdaWrapper<T, LambdaQueryWrapper<T>>
  • 调用链

=> 调用抽象父类方法

com.baomidou.mybatisplus.core.conditions.AbstractWrapper#eq

=>

com.baomidou.mybatisplus.core.conditions.AbstractWrapper#addCondition

=> 列名提供者类型R转sql片段(String)

com.baomidou.mybatisplus.core.conditions.AbstractWrapper#columnToSqlSegment

=> 列名提供者类型R转列名字符串 QueryWrapper直接调用AbstractWrapper#columnToString;Lambda方式调用AbstractLambdaWrapper#columnToString

com.baomidou.mybatisplus.core.conditions.AbstractLambdaWrapper#columnToString(com.baomidou.mybatisplus.core.toolkit.support.SFunction<T,?>)

=> 根据列名提供者类型R,通过com.baomidou.mybatisplus.core.toolkit.LambdaUtils提取LambdaMeta元数据(包含实体类名和实现方法名),获取引用的实现方法名称(重点

com.baomidou.mybatisplus.core.conditions.AbstractLambdaWrapper#getColumnCache(com.baomidou.mybatisplus.core.toolkit.support.SFunction<T,?>)

=> 利用mybatis包提供的工具类将方法名转为实体属性名重点

org.apache.ibatis.reflection.property.PropertyNamer#methodToProperty

=> 初始化实体

com.baomidou.mybatisplus.core.conditions.AbstractLambdaWrapper#tryInitCache

=> 将实体属性名映射为数据库表列名

com.baomidou.mybatisplus.core.toolkit.LambdaUtils#getColumnMap

=> 根据实体类名,使用TableInfoHelper工具类获取TableInfo信息(包含当前实体配置的属性名和列名的映射关系、主键策略、逻辑删除、乐观锁等配置)

com.baomidou.mybatisplus.core.metadata.TableInfoHelper#getTableInfo(java.lang.Class<?>)

注: TableInfo信息的初始化在工程启动的时候完成,此处不再详述!

com.baomidou.mybatisplus.core.metadata.TableInfoHelper#initTableInfo(org.apache.ibatis.builder.MapperBuilderAssistant, java.lang.Class<?>)

  • 核心方法
    /**
     * 该缓存可能会在任意不定的时间被清除
     *
     * @param func 需要解析的 lambda 对象
     * @param <T>  类型,被调用的 Function 对象的目标类型
     * @return 返回解析后的结果
     */
    public static <T> LambdaMeta extract(SFunction<T, ?> func) {
        // 1. IDEA 调试模式下 lambda 表达式是一个代理
        if (func instanceof Proxy) {
            return new IdeaProxyLambdaMeta((Proxy) func);
        }
        // 2. 反射读取
        try {
            Method method = func.getClass().getDeclaredMethod("writeReplace");
            return new ReflectLambdaMeta((SerializedLambda) ReflectionKit.setAccessible(method).invoke(func));
        } catch (Throwable e) {
            // 3. 反射失败使用序列化的方式读取
            return new ShadowLambdaMeta(com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda.extract(func));
        }
    }
  public static String methodToProperty(String name) {
    if (name.startsWith("is")) {
      name = name.substring(2);
    } else if (name.startsWith("get") || name.startsWith("set")) {
      name = name.substring(3);
    } else {
      throw new ReflectionException("Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");
    }

    if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
      name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
    }

    return name;
  }
    /**
     * 获取实体对应字段 MAP
     *
     * @param clazz 实体类
     * @return 缓存 map
     */
    public static Map<String, ColumnCache> getColumnMap(Class<?> clazz) {
        return CollectionUtils.computeIfAbsent(COLUMN_CACHE_MAP, clazz.getName(), key -> {
            TableInfo info = TableInfoHelper.getTableInfo(clazz);
            return info == null ? null : createColumnCacheMap(info);
        });
    }

2.TableInfo初始化过程分析

说在前面:记住TableInfoHelper这个工具类你可能会派上大用场,例如获取表名、字段名、列名、主键名、主键策略、实体属性名和数据库列名互转等等。

(1)XML支线
  • 调用链

=> 注入Bean对象

com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration#sqlSessionFactory

=>

com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean#afterPropertiesSet

=> 工厂Bean

com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean#buildSqlSessionFactory

=> XML解析

org.apache.ibatis.builder.xml.XMLMapperBuilder#parse

com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)

=> 名称空间绑定

org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace

=> 增加Mapper

com.baomidou.mybatisplus.core.MybatisConfiguration#addMapper

=> MybatisMapperRegistry仓库

com.baomidou.mybatisplus.core.MybatisMapperRegistry#addMapper

=> 解析

com.baomidou.mybatisplus.core.MybatisMapperAnnotationBuilder#parse

=> 解析注入

com.baomidou.mybatisplus.core.MybatisMapperAnnotationBuilder#parserInjector

=>

com.baomidou.mybatisplus.core.injector.AbstractSqlInjector#inspectInject

=>

com.baomidou.mybatisplus.core.metadata.TableInfoHelper#initTableInfo(org.apache.ibatis.builder.MapperBuilderAssistant, java.lang.Class<?>)

(2)MapperFactoryBean支线

使用MapperScans导入

=> org.mybatis.spring.annotation.MapperScans

=> 导入MapperScannerConfigurer

org.mybatis.spring.annotation.MapperScannerRegistrar.RepeatingRegistrar

=>

org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry

=>

org.mybatis.spring.mapper.ClassPathMapperScanner#doScan

对象实例化

=> Mapper对象实例化

org.mybatis.spring.mapper.MapperFactoryBean#getObject

=>

com.baomidou.mybatisplus.core.MybatisConfiguration#getMapper

=>动态代理

com.baomidou.mybatisplus.core.override.MybatisMapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)

=>

org.springframework.dao.support.DaoSupport#afterPropertiesSet

=> 解析

org.mybatis.spring.mapper.MapperFactoryBean#checkDaoConfig

以下同XML支线

=> 增加Mapper

com.baomidou.mybatisplus.core.MybatisConfiguration#addMapper

=> MybatisMapperRegistry仓库

com.baomidou.mybatisplus.core.MybatisMapperRegistry#addMapper

=> 解析

com.baomidou.mybatisplus.core.MybatisMapperAnnotationBuilder#parse

=> 解析注入

com.baomidou.mybatisplus.core.MybatisMapperAnnotationBuilder#parserInjector

=>

com.baomidou.mybatisplus.core.injector.AbstractSqlInjector#inspectInject

=>

com.baomidou.mybatisplus.core.metadata.TableInfoHelper#initTableInfo(org.apache.ibatis.builder.MapperBuilderAssistant, java.lang.Class<?>)

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

搬山境KL攻城狮

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

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

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

打赏作者

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

抵扣说明:

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

余额充值