MyBatis-Plus中LambdaQueryWrapper的探究

这篇具有很好参考价值的文章主要介绍了MyBatis-Plus中LambdaQueryWrapper的探究。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

MyBatis-Plus的条件构造器LambdaQueryWrapper是开发中常用的工具,和普通的QueryWrapper不同的是,LambdaQueryWrappe

可以识别Lambda表达式,获取Lambda表达式对应的字段名称,在使用上更方便,此外,当对象的字段发生变更时也更安全。

我们知道,Lambda表达式本质上是一个匿名内部类,实现了Function,重写了apply方法。比如下面这样的:

// Lambda表达式
User::getName
// 匿名内部类
new SFunction<User, String>() {
    @Override
    public String apply(User user) {
        return user.getName();
    }
}

那么LambdaQueryWrapper是怎么获取到Lambda表达式对应的字段名称的呢?

看一下AI的解答:LambdaQueryWrapper之所以能获取到Lambda表达式所对应的字段名称,实际上并没有采用通过实例化对象并逐个字段赋值的方式来比较结果这么复杂的手段。而是巧妙地利用了Java 8中Lambda表达式的序列化机制

在Java 8中,Lambda表达式会被编译为一个实现了java.lang.invoke.SerializedLambda接口的类的实例。这个SerializedLambda实例保存了生成它的类、方法签名以及对应方法的参数索引等信息。MyBatis-Plus正是利用了这一特性,通过反射调用Lambda表达式的writeReplace方法得到SerializedLambda对象,进而从中解析出字段名。

具体来说,MyBatis-Plus内部会将Lambda表达式转换为SerializedLambda对象进行反序列化处理,然后分析SerializedLambda对象中的方法描述符和其他相关信息,从而准确地定位到Lambda表达式中引用的实体类属性字段。这样就无需实际操作实例对象,也能智能地构建SQL查询条件所需的字段名。

从个人角度来思考,我们可以通过反射获取到apply方法,进而知道方法的参数是一个User,拿到User.class,但是似乎就止步于此了,反射对apply方法的内部构造 return user.getName()似乎是无能为力的。

也许可以通过newInstance方法获取User实例,给实例的每一个字段赋不同值,然后调用Lambda表达式得到结果,进行一一对比?先不说如何给字段赋值,简单的String,Integer等基本类型倒是好说,集合类型呢,复杂的对象类型呢?这样也太蠢了,而且也并不是所有的对象都可以简单的实例化的。那么MyBatis-Plus又是怎么实现这样的逻辑的呢?先说结论吧,是通过序列化反序列化的方法获取到的字段名称。
看源码:先从LambdaQueryWrapper中随便挑个带有SFunction参数的方法,一路向下找,在AbstractLambdaWrapper类下面会找到这样一个方法

private String getColumn(SerializedLambda lambda, boolean onlyColumn) {
    Class<?> aClass = lambda.getInstantiatedType();
    this.tryInitCache(aClass);
    String fieldName = PropertyNamer.methodToProperty(lambda.getImplMethodName());
    ColumnCache columnCache = this.getColumnCache(fieldName, aClass);
    return onlyColumn ? columnCache.getColumn() : columnCache.getColumnSelect();
}

可以看到是通过lambda.getImplMethodName方法拿到了一个方法名称,然后通过PropertyNamer.methodToProperty方法得到对应的属性名称。后面的步骤我们都会,implMethodName是lambda对象的一个属性,那么我们的关注点就要放在这个方法的参数SerializedLambda lambda上了。我们往回看,

protected String columnToString(SFunction<T, ?> column, boolean onlyColumn) {
    return this.getColumn(LambdaUtils.resolve(column), onlyColumn);
}

LambdaUtils.resolve(column)方法

public static <T> SerializedLambda resolve(SFunction<T, ?> func) {
    Class<?> clazz = func.getClass();
    String name = clazz.getName();
    return (SerializedLambda)Optional.ofNullable((WeakReference)FUNC_CACHE.get(name)).map(Reference::get).orElseGet(() -> {
        SerializedLambda lambda = SerializedLambda.resolve(func);
        FUNC_CACHE.put(name, new WeakReference(lambda));
        return lambda;
    });
}

这儿主要是一个缓存的作用,关于这个缓存,值得一提的是,虽然MyBatis-Plus这儿用的是clazz.getName,但是实际测试后发现同一个class下同一个位置的Lambda表达式,即使在多线程环境中,也会复用同一个对象。因此即使这儿用func做缓存的key,理论上也是可以的。我们继续往下看,找到SerializedLambda.resolve方法

public static SerializedLambda resolve(SFunction<?, ?> lambda) {
		if (!lambda.getClass().isSynthetic()) {
			throw ExceptionUtils.mpe("该方法仅能传入 lambda 表达式产生的合成类", new Object[0]);
		} else {
			try {
				// 先序列化成byte[],在封装成ObjectInputStream
				ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(SerializationUtils.serialize(lambda))) {
					protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException {
						Class clazz;
						try {
							clazz = ClassUtils.toClassConfident(objectStreamClass.getName());
						} catch (Exception var4) {
							clazz = super.resolveClass(objectStreamClass);
						}

						// 这儿用自定义的SerializedLambda替换java原生的SerializedLambda
						return clazz == java.lang.invoke.SerializedLambda.class ? SerializedLambda.class : clazz;
					}
				};

				SerializedLambda var2;
				try {
					// 反序列化成SerializedLambda
					var2 = (SerializedLambda) objIn.readObject();
				} catch (Throwable var5) {
					try {
						objIn.close();
					} catch (Throwable var4) {
						var5.addSuppressed(var4);
					}

					throw var5;
				}

				objIn.close();
				return var2;
			} catch (IOException | ClassNotFoundException var6) {
				0
				throw ExceptionUtils.mpe("This is impossible to happen", var6, new Object[0]);
			}
		}
	}

可以看到,在这个方法中主要做了3件事

1.将Lambda表达式序列化,

2.将序列化后的Lambda表达式反序列化,

3.使用自定义的SerializedLambda顶替java原生的SerializedLambda作为反序列化结果。

那么问题来了,按照正常的思维,我们将一个对象序列化,再反序列化,应该得到这个对象的本身,为什么会得到一个SerializedLambda呢?要弄明白这个问题,我们还得回头看看SerializedLambda的源码介绍,开头有这样一段话

 * <p>Implementors of serializable lambdas, such as compilers or language
 * runtime libraries, are expected to ensure that instances deserialize properly.
 * One means to do so is to ensure that the {@code writeReplace} method returns
 * an instance of {@code SerializedLambda}, rather than allowing default
 * serialization to proceed.
翻译:可序列化lambda的实现程序(如编译器或语言运行库)应确保实例正确反序列化。
这样做的一种方法是确保{@code writeReplace}方法返回{@code SerializedLambda}的实例,而不是允许默认序列化继续进行。

提取几个关键词,可序列化的Lambda,writeReplace,序列化。

先说可序列化的Lambda,回头看LambdaQueryWrapper的参数,是一个SFunction,除了实现了Function外,还实现了Serializable,确实满足可序列化的Lambda的条件。也就是说,文章最开头匿名内部类的写法是错误的,不支持的。

再往后,提到了writeReplace方法,我们如果看编译后的class文件,可以看到Lambda表达式里面出现了一个writeReplace方法,那么,我们可不可以使用反射直接调用这个方法呢?答案是可以的。

public SerializedLambda getSerializedLambda(SFunction<?, ?> column){
    Class<?> columnClass = column.getClass();
    if (!columnClass.isSynthetic()) {
        throw new RuntimeException("该方法仅能传入lambda表达式产生的合成类");
    }
    Method writeReplace;
    try {
        writeReplace = columnClass.getDeclaredMethod("writeReplace");
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    }
    writeReplace.setAccessible(true);

    SerializedLambda serializedLambda;
    try {
        serializedLambda = (SerializedLambda) writeReplace.invoke(column);
    } catch (IllegalAccessException | InvocationTargetException e) {
        throw new RuntimeException("解析失败Lambda表达式失败");
    }
}

这样的代码同样可以获取到SerializedLambda对象,并且由于省略的反序列化的过程,性能上是要比MyBatis-Plus的那种方法要快的。至于MyBatis-Plus为什么不用这种方法,我们就无从得知了。

最后总结一下:

1. LambdaQueryWrapper中的SFunction只能用Lambda表达式,不能用内部类。
2. 可序列化的Lambda表达式在编译后会生成一个writeReplace方法,返回值是一个SerializedLambda对象。
3. SerializedLambda对象中包含了很多Lambda表达式的详细信息,比如实现的方法名称等。
文章来源地址https://www.toymoban.com/news/detail-793698.html

到了这里,关于MyBatis-Plus中LambdaQueryWrapper的探究的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Mybatis Plus中使用LambdaQueryWrapper进行分页以及模糊查询对比传统XML方式进行分页

    传统的XML方式只能使用limit以及offset进行分页,通过判断name和bindState是否为空,不为空则拼接条件。 只需要在Service实现类中直接调用Mybatis Plus的方法即可进行操作。 return PageSanitationCompanyStaff类型可以得到数据的总数,你也可以通过.getRecords()方式获取List集合 这样子,我们就

    2024年02月12日
    浏览(58)
  • 【Mybatis-Plus】Mybatis-Plus快速入门

    Mybatis-Plus是基于Mybatis的数据库操作组件,其实现的功能完全是Mybatis的功能拓展,不改变Mybatis的使用方式,可以兼容Mybatis的操作方式。 创建一个数据库、一个表进行基础操作: 创建一个Spring项目,项目通过Spring Initlizer创建,不导入任何依赖包,在POM.xml文件中进行依赖导入

    2024年02月07日
    浏览(46)
  • Mybatis-Plus 进阶开发 -- Mybatis-Plus 入门教程(二)

    为了巩固所学的知识,作者尝试着开始发布一些学习笔记类的博客,方便日后回顾。当然,如果能帮到一些萌新进行新技术的学习那也是极好的。作者菜菜一枚,文章中如果有记录错误,欢迎读者朋友们批评指正。 (博客的参考源码可以在我主页的资源里找到,如果在学习的

    2024年02月10日
    浏览(51)
  • Mybatis-Plus(三)--Mybatis-Plus配置和条件构造器

    在MP中有大量的配置,其中有一部分是Mybatis原生的配置,另一部分是MP的配置,详情:https://mybatis.plus/config 【1】configLocation--自己单独的MyBatis配置的路径 SpringMVC的xml中写法: 【2】mapperLocations--MyBatis Mapper所对应的XML文件位置 如果你在Mapper中有自定义方法(XML中有自定义实现

    2024年02月15日
    浏览(62)
  • Mybatis-Plus通用枚举功能 [MyBatis-Plus系列] - 第493篇

    历史文章( 文章 累计490+) 《国内最全的Spring Boot系列之一》 《国内最全的Spring Boot系列之二》 《

    2024年02月08日
    浏览(39)
  • mybatis-plus分页total为0,分页失效,mybatis-plus多租户插件使用

    背景:项目使用mybatis分页插件不生效,以及多租户使用时读取配置异常 多租户插件使用遇到的问题: 最开始在MyTenantLineHandler中使用 @Value(\\\"${tables}\\\"),服务启动时能从配置中心拉取到配置,但在运行时获取到的值为空,试了很多方法都不生效,后面将配置中心的配置在调用My

    2024年02月06日
    浏览(51)
  • Mybatis-Plus

    官方网站     MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。   无侵入 :只做增强不做改变,引入它不会对现有工程产

    2024年01月15日
    浏览(37)
  • 【MyBatis-Plus】MyBatis进阶使用

    目录 一、MyBatis-Plus简介 1.1 介绍 1.2 优点 1.3 结构 二、MyBatis-Plus基本使用 2.1 配置 2.2 代码生成 2.3 CRUD接口测试 三、MyBatis-Plus策略详解 3.1 主键生成策略 3.2 雪花ID生成器 3.3 字段自动填充策略 3.4 逻辑删除 四、MyBatis-Plus插件使用 4.1 乐观锁插件 4.1.1 什么是乐观锁和悲观锁? 4.

    2024年02月04日
    浏览(49)
  • Mybatis 框架 ( 三 ) Mybatis-Plus

    官网 : https://www.baomidou.com/ MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上封装了大量常规操作,减少了SQL的编写量。 使用时通常通过Springboot框架整合使用 并且使用Lombok框架简化实体类 重点注意 : 与 SpringBoot整合时, 在启动类增加注解 @MapperScan(\\\"mapper接口路径 \\\") 或者

    2024年02月01日
    浏览(38)
  • mybatis升级到mybatis-plus

    mybatis升级到mybatis-plus,两个共存 之前依赖只有mybatis,没有plus 做法: 修改之后的依赖 更换 完整配置代码 1.添加plus的插件 注入 3.完整代码 就是上面升级plus 的代码. 参考文章:https://www.likecs.com/show-308411339.html

    2024年02月11日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包