原标题:Spring认证中国教育管理中心-ApacheSolr的SpringData教程二(Spring中国教育管理中心)ApacheSolr的SpringData教程二3.6。文件映射尽管SolrJ已经支持实体映射,但SpringDataSolr附带了它自己的映射机制(在下一节中描述)。DocumentObjectBinder具有优越的性能。因此,如果您不需要客户映射,我们建议您使用它。您可以通过在SolrJConverter中注册DocumentObjectBinder来切换到SolrTemplate。3.6.1.对象映射基础本节涵盖SpringData对象映射、对象创建、字段和属性访问、可变性和不可变性的基础知识。请注意,本节仅适用于不对底层数据存储(例如JPA)使用对象映射的SpringData模块。此外,请务必查阅存储特定对象映射的存储特定部分,例如索引、自定义列或字段名称等。SpringData对象映射的核心职责是创建域对象的实例并映射存储本机这些实例上的数据结构。这意味着我们需要两个基本步骤:使用公开的构造函数之一创建一个实例。该实例被填充以实现所有暴露的属性。对象创建SpringData自动尝试检测用于为该类型对象具体化持久实体的构造函数。解析算法的工作原理如下:如果只有一个构造函数,则使用它。如果有多个构造函数并且只有一个用@PersistenceConstructor注释,则使用它。如果存在无参数构造函数,则使用它。其他构造函数将被忽略。值解析假定构造函数参数名称与实体的属性名称相匹配,即解析将像要填充属性一样执行,包括映射中的任何自定义(不同的数据存储列或字段名称等)。这还需要类文件中可用的参数名称信息或构造函数中存在的@ConstructorProperties注释。可以使用SpringFramework的值注释通过使用@Value存储特定的SpEL表达式来自定义值解析。有关详细信息,请参阅商店特定映射部分。内部对象创建为了避免反射的开销,SpringData对象创建默认使用运行时生成的工厂类,它会直接调用领域类构造函数。即对于此示例类型:classPerson{Person(Stringfirstname,Stringlastname){...}}我们将在运行时创建一个语义等同于此的工厂类:classPersonObjectInstantiatorimplementsObjectInstantiator{ObjectnewInstance(Object...args){returnnewPerson((String)args[0],(String)args[1]);}}与反射相比,这使我们的性能提高了大约10%。对于有资格进行此类优化的域类,它需要遵守一组约束:它不能是私有类它不能是非静态内部类它不能是SpringData使用的CGLib代理类构造函数不能是私有的如果这些条件之一匹配,SpringData将通过反射回退到实体实例化。一旦创建了实体的实例,SpringData就会填充该类的所有剩余持久属性。除非实体的构造函数已经被填充(即通过其构造函数参数列表使用),否则将首先填充标识符属性以允许解析循环对象引用。之后,构造函数未填充的所有非瞬态属性都设置在实体实例上。为此,我们使用以下算法:如果属性不可变但公开了一个with...方法(见下文),我们使用该with...方法创建一个具有新属性值的新实体实例。如果定义了属性访问(即通过getter和setter访问),我们将调用setter方法。如果属性是可变的,我们直接设置字段。如果属性是不可变的,我们使用持久性操作使用的构造函数创建实例的副本(请参阅对象创建)。默认情况下,我们直接设置字段值。属性填充内部类似于我们在对象构造中的优化,我们也使用SpringData运行时生成的访问器类来与实体实例进行交互。类人{privatefinalLongid;私有字符串名字;private@AccessType(Type.PROPERTY)Stringlastname;Person(){this.id=null;}Person(Longid,Stringfirstname,Stringlastname){//字段赋值}PersonwithId(Longid){returnnewPerson(id,this.firstname,this.lastame);}voidsetLastname(Stringlastname){this.lastname=lastname;}}示例61.生成的属性访问器classPersonPropertyAccessorimplementsPersistentPropertyAccessor{privatestaticfinalMethodHandlefirstname;私人Person人;publicvoidsetProperty(PersistentPropertyproperty,Objectvalue){Stringname=property.getName();if("firstname".equals(name)){firstname.invoke(person,(String)value);}elseif("id".equals(name)){this.person=person.withId((Long)value);}elseif("lastname".equals(name)){this.person.setLastname((String)value);}}}PropertyAccessor持有底层对象的可变实例。这是为了启用其他不可变属性的突变。默认情况下,SpringData使用字段访问来读取和写入属性值。MethodHandles用于根据私有字段的可见性规则与字段进行交互。此类公开了一个withId(...)方法来设置标识符,例如,当一个实例被插入到数据存储中并生成一个标识符时。调用withId(...)创建一个新的Person对象。所有后续突变都将在新实例中发生,而前一个实例保持不变。使用属性访问允许在不使用MethodHandles的情况下直接调用方法。与反射相比,这使我们的性能提高了大约25%。对于符合此类优化条件的域类,它需要遵守一组约束:类型不得位于默认或java包下。类型及其构造函数必须是公共的属于内部类的类型必须是静态的。使用的Java运行时必须允许原始的ClassLoader。Java9及更新版本施加了某些限制。默认情况下,SpringData尝试使用生成的属性访问器,如果检测到限制,则回退到基于反射的访问器。让我们看一下下面的实体:示例62.示例实体类Person{privatefinal@IdLongid;privatefinalString名字,姓氏;私人最终LocalDate生日;私人最终年龄;私人字符串评论;private@AccessType(Type.PROPERTY)String备注;staticPersonof(Stringfirstname,Stringlastname,LocalDatebirthday){returnnewPerson(null,firstname,lastname,birthday,Period.between(birthday,LocalDate.now()).getYears());}Person(Longid,Stringfirstname,Stringlastname,LocalDatebirthday,intage){this.id=id;this.firstname=firstname;this.lastname=lastname;this.birthday=birthday;this.age=age;}PersonwithId(长id){returnnewPerson(id,this.firstname,this.lastname,this.birthday,this.age);}voidsetRemarks(Stringremarks){this.remarks=remarks;}}标识符属性是最终的,但在构造函数中设置为null。这个类公开了一个withId(...)方法来设置标识符,例如,当一个实例被插入到数据存储中并生成一个标识符时。创建Person的新实例时,原始实例保持不变。相同的模式通常适用于存储管理的其他属性,但可能必须更改以进行持久操作。Wither方法是可选的,因为持久性构造函数(参见图6)实际上是一个复制构造函数,设置该属性将转化为创建一个应用了新标识符值的新实例。firstname和lastname属性是普通的不可变属性,可以通过getter公开。age属性是不可变的,但派生自birthday属性。使用所示的设计,数据库值将胜过默认值,因为SpringData使用唯一声明的构造函数。尽管意图是计算应该是首选,但重要的是这个构造函数也将年龄作为参数(它可以被忽略),否则属性填充步骤将尝试设置年龄字段并失败,因为它是不可变的并且有不存在...方法。comment属性是可变的,并通过直接设置其字段来填充。remarks属性是可变的,可以通过直接设置注释字段或通过调用类的setter方法来填充。该类公开了用于创建对象的工厂方法和构造函数。这里的核心思想是使用工厂方法而不是额外的构造函数来避免需要传递@PersistenceConstructor。相反,属性的默认设置是在工厂方法中处理的。一般的建议是尽可能坚持不可变对象-不可变对象很容易创建,因为实现对象只是调用其构造函数的问题。此外,这避免了让您的域对象与允许客户端代码操纵对象状态的setter方法混淆。如果您需要这些,最好将它们打包保护起来,这样它们只能被有限数量的并置类型调用。仅构造函数的实现比属性填充快30%。提供一个全参数构造函数-即使您不能或不想将您的实体建模为不可变值,提供一个将实体的所有属性(包括可变属性)作为参数的构造函数仍然有价值,因为这允许用于对象映射跳过属性填充以获得最佳性能。通过使用工厂方法而不是重载构造函数来避免@PersistenceConstructor——为了获得需要全参数构造函数的最佳性能,我们通常希望公开更多省略自动生成标识符等待的特定于应用程序案例的构造函数。这是一种既定模式,而不是使用静态工厂方法来公开全参数构造函数的这些变体。确保遵守允许使用生成的实例化器和属性访问器类的约束?——?对于要生成的标识符,仍然使用final字段结合全参数持久构造函数(首选)或...方法?——?使用Lombok来避免样板代码——由于持久化操作通常需要一个接受所有参数的构造函数,因此它们的声明变成了字段分配样板参数的乏味重复,而Kotlin的则使用Lombok的@AllArgsConstructor进行了调整。允许创建和更改对象的详细信息。Kotlin对象创建Kotlin类支持实例化,默认情况下所有类都是不可变的,需要显式声明属性才能定义可变属性。考虑以下数据类Person:dataclassPerson(valid:String,valname:String)上述类编译为具有显式构造函数的典型类。我们可以通过添加另一个构造函数来自定义此类,并使用注解@PersistenceConstructor来指示构造函数偏好:)}Kotlin通过允许在未提供参数时使用默认值来支持参数可选性。当SpringData检测到具有参数默认值的构造函数时,如果数据存储不提供值(或简单地返回null),它会使这些参数不存在,因此Kotlin可以应用参数默认值。考虑下面的类名数据类Person(varid:String,valname:String="unknown")其中应用了参数默认值。每次名称参数不是结果的一部分或其值为空时,名称默认为未知。Kotlin数据类的属性在Kotlin中填充,默认情况下所有类都是不可变的,并且需要显式属性声明来定义可变属性。考虑以下数据类Person:dataclassPerson(valid:String,valname:String)这个类实际上是不可变的。它允许创建新实例,因为Kotlin生成用于创建新对象实例的copy(...)方法,该方法从现有对象复制所有属性值并将作为参数提供的属性值应用到该方法。3.6.2.MappingSolrConverterMappingSolrConverter允许您为SolrDocument和SolrInputDocument以及嵌套在bean中的其他类型注册自定义转换器。Converter与DocumentObjectBinder并非100%兼容,@Indexed必须添加readonly=true以忽略写入Solr的字段。以下示例映射文档中的多个字段:示例63.文档映射示例=true)@Field("property_*")privateList
