> 文档中心 > mybatis-plus使用和原理剖析之条件构造器

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


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

文章目录

  • mybatis-plus使用和原理剖析之条件构造器
    • 一、QueryWrapper
      • 1.QueryWrapper
      • 2.LambdaQueryWrapper
      • 3.总结
    • 二、UpdateWrapper
      • 1.UpdateWrapper
      • 2.LambdaUpdateWrapper
      • 3.总结
  • 三、原理剖析
      • 1.Lambda方法引用原理
      • 2.TableInfo初始化过程分析
        • (1)XML支线
        • (2)MapperFactoryBean支线

一、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
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) Children-AbstractWrapper子类型// SFunction - 支持序列化的 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)

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

com.baomidou.mybatisplus.core.conditions.AbstractLambdaWrapper#getColumnCache(com.baomidou.mybatisplus.core.toolkit.support.SFunction)

=> 利用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   类型,被调用的 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)