基於元資料的對映

為了充分利用SDN(Spring Data Neo4j)中的物件對映功能,您應該使用@Node註解來標註您的對映物件。雖然對映框架不需要此註解(即使沒有任何註解,您的POJO也能正確對映),但它允許類路徑掃描器查詢並預處理您的領域物件,以提取必要的元資料。如果您不使用此註解,那麼在您第一次儲存領域物件時,應用程式會受到輕微的效能影響,因為對映框架需要構建其內部元資料模型,以便了解您的領域物件的屬性以及如何持久化它們。

對映註解概覽

來自SDN

  • @Node:應用於類級別,表示此類是對映到資料庫的候選。

  • @Id:應用於欄位級別,標記用於身份目的的欄位。

  • @GeneratedValue:與@Id一起應用於欄位級別,用於指定如何生成唯一識別符號。

  • @Property:應用於欄位級別,用於修改從屬性到屬性的對映。

  • @CompositeProperty:應用於Map型別屬性的欄位級別,該屬性應作為複合屬性讀回。請參閱複合屬性

  • @Relationship:應用於欄位級別,用於指定關係的詳細資訊。

  • @DynamicLabels:應用於欄位級別,用於指定動態標籤的來源。

  • @RelationshipProperties:應用於類級別,表示此類是關係屬性的目標。

  • @TargetNode:應用於用@RelationshipProperties註解的類的欄位,用於從另一端的角度標記該關係的目標。

以下註解用於指定轉換並確保與OGM的向後相容性。

  • @DateLong

  • @DateString

  • @ConvertWith

有關更多資訊,請參閱轉換

來自Spring Data Commons

  • @org.springframework.data.annotation.Id 與SDN的@Id相同,實際上,@Id是用Spring Data Common的Id註解標註的。

  • @CreatedBy:應用於欄位級別,指示節點的建立者。

  • @CreatedDate:應用於欄位級別,指示節點的建立日期。

  • @LastModifiedBy:應用於欄位級別,指示節點最後一次更改的作者。

  • @LastModifiedDate:應用於欄位級別,指示節點的最後修改日期。

  • @PersistenceCreator:應用於一個建構函式,將其標記為讀取實體時的首選建構函式。

  • @Persistent:應用於類級別,表示此類是對映到資料庫的候選。

  • @Version:應用於欄位級別,用於樂觀鎖,並在儲存操作時檢查修改。初始值為零,每次更新時自動增加。

  • @ReadOnlyProperty:應用於欄位級別,將屬性標記為只讀。該屬性將在資料庫讀取期間水合,但不進行寫入。當用於關係時,請注意,如果未透過其他方式關聯,則該集合中的任何相關實體都不會被持久化。

有關所有審計支援的註解,請檢視審計

基本構建塊:@Node

@Node註解用於將類標記為託管領域類,受對映上下文的類路徑掃描。

為了將物件對映到圖中的節點,反之亦然,我們需要一個標籤來識別要對映的類。

@Node具有一個labels屬性,允許您配置一個或多個標籤,用於讀取和寫入註解類的例項。value屬性是labels的別名。如果您未指定標籤,則將使用簡單的類名作為主標籤。如果您想提供多個標籤,可以

  1. labels屬性提供一個數組。陣列中的第一個元素將被視為主標籤。

  2. primaryLabel提供一個值,並將附加標籤放入labels中。

主標籤應始終是最具體地反映您的領域類的標籤。

對於透過儲存庫或Neo4j模板寫入的每個註解類例項,將在圖中寫入至少具有主標籤的一個節點。反之,所有具有主標籤的節點都將對映到註解類的例項。

關於類層次結構的說明

@Node註解不會從超型別和介面繼承。但是,您可以在每個繼承級別單獨註解您的領域類。這允許多型查詢:您可以傳入基類或中間類,並檢索節點的正確具體例項。這僅支援用@Node註解的抽象基類。在此類上定義的標籤將與具體實現的標籤一起用作附加標籤。

我們還在某些場景中支援領域類層次結構中的介面

在單獨的模組中定義領域模型,主標籤與介面名稱相同
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.Relationship;
import org.springframework.data.neo4j.core.schema.RelationshipId;
import org.springframework.data.neo4j.core.schema.RelationshipProperties;
import org.springframework.data.neo4j.core.schema.TargetNode;

public interface SomeInterface { (1)

    String getName();

    SomeInterface getRelated();
}

@Node("SomeInterface") (2)
public static class SomeInterfaceEntity implements SomeInterface {

    @Id
    @GeneratedValue
    private Long id;

    private final String name;

    private SomeInterface related;

    public SomeInterfaceEntity(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public SomeInterface getRelated() {
        return related;
    }

    public Long getId() {
        return id;
    }

    public void setRelated(SomeInterface related) {
        this.related = related;
    }
}
1 使用純介面名稱,就像您命名您的領域一樣
2 由於我們需要同步主標籤,我們將@Node放在實現類上,該類可能在另一個模組中。請注意,值與所實現介面的名稱完全相同。不允許重新命名。

使用不同的主標籤而不是介面名稱也是可能的

不同的主標籤
@Node("PrimaryLabelWN") (1)
public interface SomeInterface2 {

    String getName();

    SomeInterface2 getRelated();
}

public static class SomeInterfaceEntity2 implements SomeInterface {

    // Overrides omitted for brevity
}
1 @Node註解放在介面上

也可以使用介面的不同實現並擁有多型領域模型。在這種情況下,至少需要兩個標籤:一個標籤用於確定介面,另一個標籤用於確定具體類

多個實現
@Node("SomeInterface3") (1)
public interface SomeInterface3 {

    String getName();

    SomeInterface3 getRelated();
}

@Node("SomeInterface3a") (2)
public static class SomeInterfaceImpl3a implements SomeInterface3 {

    // Overrides omitted for brevity
}

@Node("SomeInterface3b") (3)
public static class SomeInterfaceImpl3b implements SomeInterface3 {

    // Overrides omitted for brevity
}

@Node
public static class ParentModel { (4)

    @Id
    @GeneratedValue
    private Long id;

    private SomeInterface3 related1; (5)

    private SomeInterface3 related2;
}
1 在這種情況下,需要顯式指定標識介面的標籤
2 適用於第一個……
3 以及第二個實現
4 這是一個客戶端或父模型,透明地使用SomeInterface3進行兩個關係
5 未指定具體型別

所需的資料結構顯示在以下測試中

使用多個不同介面實現所需的資料結構
void mixedImplementationsRead(@Autowired Neo4jTemplate template) {

    Long id;
    try (Session session = this.driver.session(this.bookmarkCapture.createSessionConfig());
            Transaction transaction = session.beginTransaction()) {
        id = transaction
            .run("""
                CREATE (s:ParentModel{name:'s'})
                CREATE (s)-[:RELATED_1]-> (:SomeInterface3:SomeInterface3b {name:'3b'})
                CREATE (s)-[:RELATED_2]-> (:SomeInterface3:SomeInterface3a {name:'3a'})
                RETURN id(s)""")
            .single()
            .get(0)
            .asLong();
        transaction.commit();
    }

    Optional<Inheritance.ParentModel> optionalParentModel = this.transactionTemplate
        .execute(tx -> template.findById(id, Inheritance.ParentModel.class));

    assertThat(optionalParentModel).hasValueSatisfying(v -> {
        assertThat(v.getName()).isEqualTo("s");
        assertThat(v).extracting(Inheritance.ParentModel::getRelated1)
            .isInstanceOf(Inheritance.SomeInterfaceImpl3b.class)
            .extracting(Inheritance.SomeInterface3::getName)
            .isEqualTo("3b");
        assertThat(v).extracting(Inheritance.ParentModel::getRelated2)
            .isInstanceOf(Inheritance.SomeInterfaceImpl3a.class)
            .extracting(Inheritance.SomeInterface3::getName)
            .isEqualTo("3a");
    });
}
介面不能定義識別符號欄位。因此,它們不是儲存庫的有效實體型別。

動態或“執行時”管理的標籤

透過簡單類名隱式定義或透過@Node註解顯式定義的所有標籤都是靜態的。它們不能在執行時更改。如果您需要可以在執行時操作的附加標籤,可以使用@DynamicLabels@DynamicLabels是欄位級別的註解,將型別為java.util.Collection<String>(例如ListSet)的屬性標記為動態標籤的來源。

如果存在此註解,則節點上存在的所有標籤(未透過@Node和類名靜態對映的標籤)將在載入期間收集到該集合中。在寫入期間,節點的所有標籤將替換為靜態定義的標籤加上集合的內容。

如果您有其他應用程式向節點新增附加標籤,請不要使用@DynamicLabels。如果託管實體上存在@DynamicLabels,則生成的標籤集將是寫入資料庫的“真相”。

標識例項:@Id

雖然@Node在類與具有特定標籤的節點之間建立對映,但我們還需要在類的各個例項(物件)與節點例項之間建立連線。

這就是@Id發揮作用的地方。@Id將類的屬性標記為物件的唯一識別符號。在一個理想的世界中,該唯一識別符號是一個唯一的業務鍵,換句話說,是一個自然鍵。@Id可用於所有具有受支援簡單型別的屬性。

然而,自然鍵很難找到。例如,人們的名字很少是唯一的,會隨著時間而改變,更糟的是,並非每個人都有名字和姓氏。

因此,我們支援兩種不同型別的代理鍵

StringlongLong型別的屬性上,@Id可以與@GeneratedValue一起使用。Longlong對映到Neo4j內部ID。String對映到自Neo4j 5以來可用的elementId。兩者都不是節點或關係上的屬性,通常不可見,而是對映到屬性,並允許SDN檢索類的單個例項。

@GeneratedValue提供屬性generatorClassgeneratorClass可用於指定實現IdGenerator的類。IdGenerator是一個函式式介面,其generateId接受主標籤和要生成ID的例項。我們開箱即用地支援UUIDStringGenerator作為一種實現。

您還可以透過generatorRef@GeneratedValue上指定應用程式上下文中的Spring Bean。該Bean也需要實現IdGenerator,但可以使用上下文中的所有內容,包括Neo4j客戶端或模板來與資料庫互動。

請勿跳過唯一ID的處理和提供中關於ID處理的重要注意事項。

樂觀鎖:@Version

Spring Data Neo4j透過在Long型別欄位上使用@Version註解支援樂觀鎖。此屬性在更新期間會自動遞增,不得手動修改。

例如,如果不同執行緒中的兩個事務想要修改版本為x的同一物件,則第一個操作將成功持久化到資料庫。此時,版本欄位將遞增,變為x+1。第二個操作將因OptimisticLockingFailureException而失敗,因為它試圖修改資料庫中不再存在的版本x的物件。在這種情況下,操作需要重試,從資料庫中重新獲取具有當前版本的物件開始。

如果使用業務ID@Version屬性也是強制性的。Spring Data Neo4j將檢查此欄位以確定實體是新的還是之前已持久化。

對映屬性:@Property

@Node註解類的所有屬性都將作為Neo4j節點和關係的屬性持久化。如果沒有進一步配置,Java或Kotlin類中的屬性名稱將用作Neo4j屬性。

如果您正在使用現有的Neo4j模式或只是想根據您的需求調整對映,您將需要使用@Propertyname用於指定資料庫內屬性的名稱。

連線節點:@Relationship

@Relationship註解可用於所有非簡單型別的屬性。它適用於用@Node註解的其他型別的屬性,或其集合和對映。

typevalue屬性允許配置關係型別,direction允許指定方向。SDN中的預設方向是Relationship.Direction#OUTGOING

我們支援動態關係。動態關係表示為Map<String, AnnotatedDomainClass>Map<Enum, AnnotatedDomainClass>。在這種情況下,與其他領域類的關係型別由map的key給出,並且不得透過@Relationship進行配置。

對映關係屬性

Neo4j不僅支援在節點上定義屬性,還支援在關係上定義屬性。為了在模型中表達這些屬性,SDN提供@RelationshipProperties,應用於一個簡單的Java類。在屬性類中,必須有一個欄位被標記為@TargetNode,以定義關係指向的實體。或者,在INCOMING關係上下文中,是來自何處。

關係屬性類及其用法可能如下所示

關係屬性Roles
@RelationshipProperties
public class Roles {

	@RelationshipId
	private Long id;

	private final List<String> roles;

	@TargetNode
	private final PersonEntity person;

	public Roles(PersonEntity person, List<String> roles) {
		this.person = person;
		this.roles = roles;
	}


	public List<String> getRoles() {
		return roles;
	}

	@Override
	public String toString() {
		return "Roles{" +
				"id=" + id +
				'}' + this.hashCode();
	}
}

您必須為生成的內部ID (@RelationshipId) 定義一個屬性,以便SDN在儲存時可以確定哪些關係可以安全地覆蓋而不會丟失屬性。如果SDN找不到用於儲存內部節點ID的欄位,它將在啟動時失敗。

為實體定義關係屬性
@Relationship(type = "ACTED_IN", direction = Direction.INCOMING)
private List<Roles> actorsAndRoles = new ArrayList<>();

關係查詢備註

通常,建立查詢的關係/跳數沒有限制。SDN會解析從您建模的節點開始的整個可達圖。

話雖如此,當存在雙向對映關係的想法時,即您在實體的兩端定義關係,您可能會得到超出預期的結果。

考慮一個例子,一部電影演員,您想獲取一部特定的電影及其所有演員。如果從電影演員的關係是單向的,這不會有問題。在雙向場景中,SDN將獲取特定的電影,其演員,以及根據關係的定義,該演員定義的所有其他電影。在最壞的情況下,這會導致為單個實體獲取整個圖。

一個完整的例子

將所有這些放在一起,我們可以建立一個簡單的領域模型。我們使用具有不同角色的電影和人物

示例 1. MovieEntity
import java.util.ArrayList;
import java.util.List;

import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.Property;
import org.springframework.data.neo4j.core.schema.Relationship;
import org.springframework.data.neo4j.core.schema.Relationship.Direction;

@Node("Movie") (1)
public class MovieEntity {

	@Id (2)
	private final String title;

	@Property("tagline") (3)
	private final String description;

	@Relationship(type = "ACTED_IN", direction = Direction.INCOMING) (4)
	private List<Roles> actorsAndRoles = new ArrayList<>();


	@Relationship(type = "DIRECTED", direction = Direction.INCOMING)
	private List<PersonEntity> directors = new ArrayList<>();

	public MovieEntity(String title, String description) { (5)
		this.title = title;
		this.description = description;
	}

	// Getters omitted for brevity

}
1 @Node用於將此類標記為託管實體。它也用於配置Neo4j標籤。如果您只使用純@Node,則標籤預設為類的名稱。
2 每個實體都必須有一個ID。我們使用電影的名稱作為唯一識別符號。
3 這顯示了@Property,它是一種為欄位使用與圖屬性不同名稱的方法。
4 這配置了一個指向人物的傳入關係。
5 這是您的應用程式程式碼和SDN都將使用的建構函式。

人物在這裡以兩種角色對映,actorsdirectors。領域類是相同的

示例 2. PersonEntity
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;

@Node("Person")
public class PersonEntity {

	@Id
	private final String name;

	private final Integer born;

	public PersonEntity(Integer born, String name) {
		this.born = born;
		this.name = name;
	}

	public Integer getBorn() {
		return this.born;
	}

	public String getName() {
		return this.name;
	}

}
我們沒有雙向建模電影和人物之間的關係。這是為什麼呢?我們將MovieEntity視為聚合根,擁有這些關係。另一方面,我們希望能夠從資料庫中提取所有人,而無需選擇與他們相關聯的所有電影。請在嘗試雙向對映資料庫中的每個關係之前,考慮您的應用程式用例。雖然您可以這樣做,但您最終可能會在物件圖中重建一個圖資料庫,這不是對映框架的意圖。如果您必須建模您的迴圈或雙向領域模型,並且不想獲取整個圖,您可以透過使用投影來定義您想要獲取的資料的細粒度描述。
© . This site is unofficial and not affiliated with VMware.