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<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<?>)