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
- 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方法引用原理
Q:
Student::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)