Spring 注释对象映射泛型 class 关系

Spring Annotation Object Mapping generic class relationship

我使用 Spring 在 Neo4j 数据库中存储来自基于注释的对象映射的对象。

我有一个通用的 class(InformationMutable),它包含作为 class 属性的另一个 class(InformationEntry) 的列表数据类型 - private List<InformationEntry<T>> entries = new ArrayList<>();.

完整信息可变 class:

package de.skillkiller.project.entity;

import lombok.*;
import org.springframework.data.neo4j.core.schema.Node;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

@EqualsAndHashCode(callSuper = true)
@Node
@Data
@ToString(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public class InformationMutable<T extends Serializable> extends Information {
    private List<InformationEntry<T>> entries = new ArrayList<>();

    public void addEntry(T entry) {
        entries.add(new InformationEntry<>(entry));
    }

    public void removeEntry(InformationEntry<T> entry) {
        entries.remove(entry);
    }
}

目标是让一个对象在列表中只有另一个 class 的对象,但其中具有相同的数据类型。 InformationEntry class 使用统一的创建和更新日期扩展数据类型。

其他要求 classes: 信息条目:

package de.skillkiller.project.entity;

import lombok.*;
import org.springframework.data.neo4j.core.schema.Node;

import java.io.Serializable;

@EqualsAndHashCode(callSuper = true)
@Node
@Data
@ToString(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public class InformationEntry<T extends Serializable> extends BasicNode{
    private T entry;
}

信息:

package de.skillkiller.project.entity;

import lombok.*;
import org.springframework.data.neo4j.core.schema.Node;

@EqualsAndHashCode(callSuper = true)
@Node
@Data
@ToString(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public abstract class Information extends BasicNode{
    private String information;
}

基本节点:

package de.skillkiller.project.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.support.DateLong;

import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
public abstract class BasicNode {
    @Id
    @GeneratedValue
    private Long id;

    @DateLong
    @LastModifiedDate
    private Date changedAt = new Date();

    @DateLong
    @CreatedDate
    private Date enteredAt = new Date();
}

信息存储库:

package de.skillkiller.project.repository;

import de.skillkiller.project.entity.Information;
import org.springframework.data.neo4j.repository.Neo4jRepository;

public interface InformationRepository extends Neo4jRepository<Information, Long> {
}

创作过程:

InformationMutable<Integer> informationMutable1 = new InformationMutable<>();
informationMutable1.addEntry(5);
informationMutable1.addEntry(15);
informationMutable1.setInformation("Test");
informationRepository.save(informationMutable1);

如果我现在从 class InformationMutable 创建一个对象,将值添加到列表并想要保存它,我会收到以下错误消息:

ava.lang.IllegalStateException: Failed to execute CommandLineRunner
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:807) ~[spring-boot-2.4.2.jar:2.4.2]
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:788) ~[spring-boot-2.4.2.jar:2.4.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:333) ~[spring-boot-2.4.2.jar:2.4.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1311) ~[spring-boot-2.4.2.jar:2.4.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1300) ~[spring-boot-2.4.2.jar:2.4.2]
    at de.skillkiller.project.ProjectApplication.main(ProjectApplication.java:19) ~[classes/:na]
Caused by: java.lang.NullPointerException: Cannot invoke "org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity.isNew(Object)" because "targetNodeDescription" is null
    at org.springframework.data.neo4j.core.Neo4jTemplate.lambda$processNestedRelations(Neo4jTemplate.java:495) ~[spring-data-neo4j-6.0.3.jar:6.0.3]
    at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:387) ~[spring-data-commons-2.4.3.jar:2.4.3]
    at org.springframework.data.neo4j.core.Neo4jTemplate.processNestedRelations(Neo4jTemplate.java:452) ~[spring-data-neo4j-6.0.3.jar:6.0.3]
    at org.springframework.data.neo4j.core.Neo4jTemplate.lambda$processNestedRelations(Neo4jTemplate.java:518) ~[spring-data-neo4j-6.0.3.jar:6.0.3]
    at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:387) ~[spring-data-commons-2.4.3.jar:2.4.3]
    at org.springframework.data.neo4j.core.Neo4jTemplate.processNestedRelations(Neo4jTemplate.java:452) ~[spring-data-neo4j-6.0.3.jar:6.0.3]
    at org.springframework.data.neo4j.core.Neo4jTemplate.processRelations(Neo4jTemplate.java:442) ~[spring-data-neo4j-6.0.3.jar:6.0.3]
    at org.springframework.data.neo4j.core.Neo4jTemplate.saveImpl(Neo4jTemplate.java:254) ~[spring-data-neo4j-6.0.3.jar:6.0.3]
    at org.springframework.data.neo4j.core.Neo4jTemplate.save(Neo4jTemplate.java:225) ~[spring-data-neo4j-6.0.3.jar:6.0.3]
    at org.springframework.data.neo4j.repository.support.SimpleNeo4jRepository.save(SimpleNeo4jRepository.java:131) ~[spring-data-neo4j-6.0.3.jar:6.0.3]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new[=18=](RepositoryMethodInvoker.java:289) ~[spring-data-commons-2.4.3.jar:2.4.3]
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137) ~[spring-data-commons-2.4.3.jar:2.4.3]
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121) ~[spring-data-commons-2.4.3.jar:2.4.3]
    at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:524) ~[spring-data-commons-2.4.3.jar:2.4.3]
    at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285) ~[spring-data-commons-2.4.3.jar:2.4.3]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:531) ~[spring-data-commons-2.4.3.jar:2.4.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:156) ~[spring-data-commons-2.4.3.jar:2.4.3]
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:131) ~[spring-data-commons-2.4.3.jar:2.4.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80) ~[spring-data-commons-2.4.3.jar:2.4.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.transaction.interceptor.TransactionInterceptor.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.3.jar:5.3.3]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.3.jar:5.3.3]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137) ~[spring-tx-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) ~[spring-aop-5.3.3.jar:5.3.3]
    at com.sun.proxy.$Proxy67.save(Unknown Source) ~[na:na]
    at de.skillkiller.project.ProjectApplication.lambda$demo[=18=](ProjectApplication.java:57) ~[classes/:na]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:804) ~[spring-boot-2.4.2.jar:2.4.2]
    ... 5 common frames omitted

然而,Spring 和 Neo4j 是否有可能以某种方式存储这样的通用构造并再次读取它?

您设置的问题是 InformationMutable 声明了一个通用字段,SDN 将其视为任何其他合法 @Node 注释实体。 因此,SDN 想要创建类似

的东西
(:Information:InformationMutable) -[:ENTRIES]-> (:InformationEntry) -[:ENTRY] -> (generic entry)

它不认为通用字段是可能的 属性 并且它不能,因为 Integer 类型的定义是在运行时完成的。 例如,右侧的绿色节点是您可以使用的其他真实 @Node 实体,它们包含固定的 Integer 字段。