共计 6739 个字符,预计需要花费 17 分钟才能阅读完成。
原题目:Spring 认证中国教育管理中心 -Spring Data Neo4j 教程三(Spring 中国教育管理中心)
Spring 认证:Spring Data Neo4j 教程三
6.2. 解决和提供惟一 ID
6.2.1. 应用外部 Neo4j id
为您的域类提供惟一标识符的最简略办法是在 类型字段上组合 @Id 和(最好是对象,而不是标量,因为字面量是一个更好的批示实例是否是新的):@
GeneratedValueLonglongnull
示例 5. 具备外部 Neo4j id 的可变 MovieEntity
@Node(“Movie”)
public class MovieEntity {
@Id @GeneratedValue
private Long id;
private String name;
public MovieEntity(String name) {this.name = name;}
}
你不须要为字段提供 setter,SDN 会应用反射来调配字段,然而如果有的话就应用 setter。如果你想用外部生成的 id 创立一个不可变的实体,你必须提供一个 wither。
示例 6. 具备外部 Neo4j id 的不可变 MovieEntity
@Node(“Movie”)
public class MovieEntity {
@Id @GeneratedValue
private final Long id;
private String name;
public MovieEntity(String name) {this(null, name);
}
private MovieEntity(Long id, String name) {
this.id = id;
this.name = name;
}
public MovieEntity withId(Long id) {if (this.id.equals(id)) {return this;} else {return new MovieEntity(id, this.title);
}
}
}
批示生成值的不可变最终 id 字段
公共构造函数,由应用程序和 Spring Data 应用
外部应用的构造函数
这就是所谓的 - 属性凋零 id。它创立一个新实体并相应地设置字段,而不批改原始实体,从而使其不可变。
Spring 认证:Spring Data Neo4j 教程三
你要么必须为 id 属性或相似的货色提供一个 setter,如果你想领有
长处:很显著 id 属性是代理业务键,应用它不须要进一步的致力或配置。
毛病:它与 Neo4js 外部数据库 id 相关联,这对于咱们的应用程序实体来说并不是惟一的,仅在数据库生命周期内。
毛病:创立不可变实体须要破费更多精力
6.2.2. 应用内部提供的代理键
@GeneratedValue 正文能够将实现的类作为
org.springframework.data.neo4j.core.schema.IdGenerator 参数。SDN 提供 InternalIdGenerator(默认)并且 UUIDStringGenerator 开箱即用。后者为每个实体生成新的 UUID 并将它们返回为 java.lang.String. 应用它的应用程序实体如下所示:
示例 7. 具备内部生成代理键的可变 MovieEntity
@Node(“Movie”)
public class MovieEntity {
@Id @GeneratedValue(UUIDStringGenerator.class)
private String id;
private String name;
}
对于长处和毛病,咱们必须探讨两件不同的事件。调配自身和 UUID 策略。通用惟一标识符意味着在理论用处中是惟一的。援用 Wikipedia 的话:“因而,任何人都能够创立一个 UUID 并应用它来辨认某些货色,简直能够必定的是,该标识符不会反复曾经或将要创立来辨认其余货色的标识符。”咱们的策略应用 Java 外部 UUID 机制,采纳加密强的伪随机数生成器。在大多数状况下,这应该能够失常工作,但您的里程可能会有所不同。
这留下了工作自身:
长处:应用程序处于齐全管制之下,并且能够生成一个惟一的密钥,该密钥对于应用程序的目标来说是足够惟一的。生成的值将是稳固的,当前不须要更改它。
毛病:生成的策略利用于事物的利用端。在那些日子里,大多数应用程序将部署在多个实例中以很好地扩大。如果您的策略容易产生反复,则插入将失败,因为主键的唯一性属性将被违反。因而,尽管在这种状况下您不用思考惟一的业务密钥,但您必须更多地思考要生成什么。
您有多种抉择来推出本人的 ID 生成器。一种是实现生成器的 POJO:
示例 8. 奢侈序列生成器
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.data.neo4j.core.schema.IdGenerator;
import org.springframework.util.StringUtils;
public class TestSequenceGenerator implements IdGenerator<String> {
private final AtomicInteger sequence = new AtomicInteger(0);
@Override
public String generateId(String primaryLabel, Object entity) {return StringUtils.uncapitalize(primaryLabel) +
"-" + sequence.incrementAndGet();}
}
另一种抉择是提供一个额定的 Spring Bean,如下所示:
示例 9. 基于 Neo4jClient 的 ID 生成器
@Component
class MyIdGenerator implements IdGenerator<String> {
private final Neo4jClient neo4jClient;
public MyIdGenerator(Neo4jClient neo4jClient) {this.neo4jClient = neo4jClient;}
@Override
public String generateId(String primaryLabel, Object entity) {return neo4jClient.query("YOUR CYPHER QUERY FOR THE NEXT ID")
.fetchAs(String.class).one().get();
}
}
精确应用您须要的查问或逻辑。
下面的生成器将被配置为像这样的 bean 援用:
示例 10. 应用 Spring Bean 作为 Id 生成器的可变 MovieEntity
@Node(“Movie”)
public class MovieEntity {
@Id @GeneratedValue(generatorRef = "myIdGenerator")
private String id;
private String name;
}
6.2.3. 应用业务密钥
咱们始终在残缺示例的 MovieEntity 和中应用业务密钥 PersonEntity。该人的姓名是在构建时调配的,由您的应用程序和通过 Spring Data 加载时调配。
这只有在您找到一个稳固的、惟一的业务密钥,但又能生成杰出的不可变域对象的状况下才有可能。
长处:应用业务或天然键作为主键是很天然的。有问题的实体被分明地辨认进去,并且在您的域的进一步建模中大部分工夫感觉恰到好处。
毛病:作为主键的业务键,一旦发现发现的 key 不像你设想的那么稳固,就很难更新。通常事实证明它能够扭转,即便另有承诺。除此之外,很难找到真正惟一的标识符。
6.3.Spring 数据对象映射根底
本节涵盖 Spring Data 对象映射、对象创立、字段和属性拜访、可变性和不变性的基础知识。
Spring Data 对象映射的外围职责是创立域对象的实例并将 store-native 数据结构映射到这些实例上。这意味着咱们须要两个根本步骤:
应用公开的构造函数之一创立实例。
实例填充以实现所有公开的属性。
6.3.1. 对象创立
Spring Data 主动尝试检测长久实体的构造函数以用于实现该类型的对象。解析算法的工作原理如下:
如果有一个无参数的构造函数,它将被应用。其余构造函数将被疏忽。
如果有一个带参数的构造函数,它将被应用。
如果有多个构造函数承受参数,则 Spring Data 应用的构造函数必须应用 @PersistenceConstructor.
值解析假设结构函数参数名称与实体的属性名称匹配,即解析将像要填充属性一样执行,包含映射中的所有自定义(不同的数据存储列或字段名称等)。这还须要类文件中可用的参数名称信息或 @ConstructorProperties 构造函数上存在的正文。
对象创立外部
为了防止反射的开销,Spring Data 对象创立默认应用运行时生成的工厂类,它会间接调用畛域类的构造函数。即对于这个示例类型:
class Person {
Person(String firstname, String lastname) {…}
}
咱们将在运行时创立一个在语义上等同于这个的工厂类:
class PersonObjectInstantiator implements ObjectInstantiator {
Object newInstance(Object… args) {
return new Person((String) args[0], (String) args[1]);
}
}
与反射相比,这给了咱们大概 10% 的性能晋升。要使域类有资格进行此类优化,它须要恪守一组束缚:
它不能是私人课程
它不能是非动态外部类
它不能是 CGLib 代理类
Spring Data 应用的构造函数不能是公有的
如果这些条件中的任何一个匹配,Spring Data 将通过反射回退到实体实例化。
Spring 认证:Spring Data Neo4j 教程三
6.3.2. 物业人口
一旦创立了实体的实例,Spring Data 就会填充该类的所有残余长久属性。除非曾经由实体的构造函数填充(即通过其结构函数参数列表应用),否则将首先填充标识符属性以容许解析循环对象援用。之后,在实体实例上设置所有尚未由构造函数填充的非瞬态属性。为此,咱们应用以下算法:
如果属性是不可变的,但公开了一个 wither 办法(见下文),咱们应用 wither 创立一个具备新属性值的新实体实例。
如果定义了属性拜访(即通过 getter 和 setter 拜访),咱们将调用 setter 办法。
默认状况下,咱们间接设置字段值。
财产人口外部
与咱们在对象结构中的优化相似,咱们还应用 Spring Data 运行时生成的拜访器类与实体实例进行交互。
class Person {
private final Long id;
private String firstname;
private @AccessType(Type.PROPERTY) String lastname;
Person() {
this.id = null;
}
Person(Long id, String firstname, String lastname) {
// Field assignments
}
Person withId(Long id) {
return new Person(id, this.firstname, this.lastame);
}
void setLastname(String lastname) {
this.lastname = lastname;
}
}
示例 11. 生成的属性拜访器
class PersonPropertyAccessor implements PersistentPropertyAccessor {
private static final MethodHandle firstname;
private Person person;
public void setProperty(PersistentProperty property, Object value) {
String name = property.getName();
if ("firstname".equals(name)) {firstname.invoke(person, (String) value);
} else if ("id".equals(name)) {this.person = person.withId((Long) value);
} else if ("lastname".equals(name)) {this.person.setLastname((String) value);
}
}
}
PropertyAccessor 持有底层对象的可变实例。这是为了启用其余不可变属性的渐变。
默认状况下,Spring Data 应用字段拜访来读取和写入属性值。依据字段的可见性规定 private,MethodHandles 用于与字段交互。
该类公开了一个 withId(…)用于设置标识符的办法,例如,当将实例插入数据存储并生成标识符时。调用 withId(…)会创立一个新 Person 对象。所有后续的渐变都将产生在新的实例中,而前一个不变。
应用属性拜访容许间接办法调用而不应用 MethodHandles.
与反射相比,这给了咱们大概 25% 的性能晋升。要使域类有资格进行此类优化,它须要恪守一组束缚:
类型不得位于默认值或 java 包下。
类型及其构造函数必须是 public
作为外部类的类型必须是 static.
应用的 Java 运行时必须容许在原始 ClassLoader. Java 9 和更高版本施加了某些限度。
默认状况下,Spring Data 尝试应用生成的属性拜访器,如果检测到限度,则回退到基于反射的拜访器。
让咱们看一下以下实体:
示例 12. 示例实体
class Person {
private final @Id Long id;
private final String firstname, lastname;
private final LocalDate birthday;
private final int age;
private String comment;
private @AccessType(Type.PROPERTY) String remarks;
static Person of(String firstname, String lastname, LocalDate birthday) {
return new Person(null, firstname, lastname, birthday,
Period.between(birthday, LocalDate.now()).getYears());
}
Person(Long id, String firstname, String lastname, LocalDate birthday, int age) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.birthday = birthday;
this.age = age;
}
Person withId(Long id) {
return new Person(id, this.firstname, this.lastname, this.birthday);
}
void setRemarks(String remarks) {
this.remarks = remarks;
}
}
Spring 认证:Spring Data Neo4j 教程三
标识符属性是最终的,但 null 在构造函数中设置为。该类公开了一个 withId(…)用于设置标识符的办法,例如,当将实例插入数据存储并生成标识符时。Person 创立新实例时,原始实例放弃不变。雷同的模式通常实用于存储管理但可能必须更改以进行持久性操作的其余属性。
firstname 和 lastname 属性是可能通过 getter 裸露的一般不可变属性。
该 age 属性是不可变的,但从该 birthday 属性派生而来。应用所示的设计,数据库值将胜过默认值,因为 Spring Data 应用惟一申明的构造函数。即便用意是应该首选计算,重要的是此构造函数也将 age 其作为参数(可能会疏忽它),否则属性填充步骤将尝试设置年龄字段并因为它是不可变的且不会枯败而失败在场。
该 comment 属性是可变的,通过间接设置其字段来填充。
remarks 属性是可变的,并且能够通过间接设置字段 comment 或调用 setter 办法来填充
该类公开了一个工厂办法和一个用于创建对象的构造函数。这里的核心思想是应用工厂办法而不是额定的构造函数来防止构造函数通过 @PersistenceConstructor. 相同,属性的默认设置是在工厂办法中解决的。