原标题: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
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
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. 相反,属性的默认设置是在工厂方法中处理的。