Spring Data Neo4j 投射

如上所述,投射分為兩種形式:基於介面和基於 DTO 的投射。在 Spring Data Neo4j 中,這兩種投射類型都直接影響通過網路傳輸的屬性和關係。因此,如果您處理的節點和實體包含許多屬性,而這些屬性在應用程式的所有使用情境中可能並非都需要的,則這兩種方法都可以減少資料庫的負載。

對於基於介面和基於 DTO 的投射,Spring Data Neo4j 都將使用儲存庫的網域類型來建構查詢。所有可能會變更查詢的屬性上的註解都將被考慮在內。網域類型是透過儲存庫宣告定義的類型(給定類似 interface TestRepository extends CrudRepository<TestEntity, Long> 的宣告,網域類型將為 TestEntity)。

基於介面的投射將始終是基礎網域類型的動態代理。在此類介面上定義的存取器名稱(例如 getName)必須解析為投射實體上存在的屬性(此處為:name)。這些屬性在網域類型上是否具有存取器並不重要,只要它們可以透過常見的 Spring Data 基礎架構存取即可。後者已經得到保證,因為網域類型首先不會是一個持久性實體。

當與自訂查詢一起使用時,基於 DTO 的投射在某種程度上更具彈性。雖然標準查詢是從原始網域類型派生的,因此只能使用在那裡定義的屬性和關係,但自訂查詢可以新增額外的屬性。

規則如下:首先,網域類型的屬性用於填充 DTO。如果 DTO 透過存取器或欄位宣告了額外的屬性,Spring Data Neo4j 會在結果記錄中尋找符合的屬性。屬性名稱必須完全一致,並且可以是簡單類型(如 org.springframework.data.neo4j.core.convert.Neo4jSimpleTypes 中定義的)或已知的持久性實體。支援這些類型的集合,但不支援 Map。

多層級投射

Spring Data Neo4j 也支援多層級投射。

多層級投射範例
interface ProjectionWithNestedProjection {

    String getName();

    List<Subprojection1> getLevel1();

    interface Subprojection1 {
        String getName();
        List<Subprojection2> getLevel2();
    }

    interface Subprojection2 {
        String getName();
    }
}

即使可以建模循環投射或指向將建立循環的實體,投射邏輯也不會遵循這些循環,而只會建立無循環的查詢。

多層級投射受限於它們應該投射的實體。在這種情況下,RelationshipProperties 屬於實體的類別,如果應用投射,則需要予以尊重。

投射的資料操作

如果您已將投射作為 DTO 提取,則可以修改其值。但是,如果您使用的是基於介面的投射,則不能僅僅更新介面。可以使用的一種典型模式是在您的網域實體類別中提供一個方法,該方法使用介面並建立一個網域實體,其中包含從介面複製的值。這樣,您就可以更新實體並再次持久化它,並將投射藍圖/遮罩作為下一節中所述。

投射的持久性

與透過投射檢索資料類似,它們也可以用作持久性的藍圖。Neo4jTemplate 提供了一個流暢的 API,用於將這些投射應用於儲存操作。

您可以為給定的網域類別儲存投射

為給定的網域類別儲存投射
Projection projection = neo4jTemplate.save(DomainClass.class).one(projectionValue);

或者您可以儲存網域物件,但僅考慮投射中定義的欄位。

使用給定的投射藍圖儲存網域物件
Projection projection = neo4jTemplate.saveAs(domainObject, Projection.class);

在這兩種情況下(也適用於基於集合的操作),只有投射中定義的欄位和關係才會被更新。

為了防止資料刪除(例如,關係的移除),您應該始終至少載入所有稍後應持久化的資料。

完整範例

給定以下實體、投射和對應的儲存庫

一個簡單的實體
@Node
class TestEntity {
    @Id @GeneratedValue private Long id;

    private String name;

    @Property("a_property") (1)
    private String aProperty;
}
1 此屬性在圖形資料庫中具有不同的名稱
一個衍生實體,繼承自 TestEntity
@Node
class ExtendedTestEntity extends TestEntity {

    private String otherAttribute;
}
TestEntity 的介面投射
interface TestEntityInterfaceProjection {

    String getName();
}
TestEntity 的 DTO 投射,包括一個額外屬性
class TestEntityDTOProjection {

    private String name;

    private Long numberOfRelations; (1)

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getNumberOfRelations() {
        return numberOfRelations;
    }

    public void setNumberOfRelations(Long numberOfRelations) {
        this.numberOfRelations = numberOfRelations;
    }
}
1 此屬性在投射實體上不存在

TestEntity 的儲存庫如下所示,它的行為將如列表中所述。

TestEntity 的儲存庫
interface TestRepository extends CrudRepository<TestEntity, Long> { (1)

    List<TestEntity> findAll(); (2)

    List<ExtendedTestEntity> findAllExtendedEntities(); (3)

    List<TestEntityInterfaceProjection> findAllInterfaceProjectionsBy(); (4)

    List<TestEntityDTOProjection> findAllDTOProjectionsBy(); (5)

    @Query("MATCH (t:TestEntity) - [r:RELATED_TO] -> () RETURN t, COUNT(r) AS numberOfRelations") (6)
    List<TestEntityDTOProjection> findAllDTOProjectionsWithCustomQuery();
}
1 儲存庫的網域類型為 TestEntity
2 回傳一個或多個 TestEntity 的方法將僅回傳其實例,因為它與網域類型匹配
3 回傳一個或多個擴充網域類型的類別實例的方法將僅回傳擴充類別的實例。相關方法的網域類型將是擴充的類別,它仍然滿足儲存庫本身的網域類型
4 此方法回傳一個介面投射,因此方法的回傳類型與儲存庫的網域類型不同。介面只能存取網域類型中定義的屬性。需要後綴 By 以使 SDN 不會在 TestEntity 中尋找名為 InterfaceProjections 的屬性
5 此方法回傳 DTO 投射。執行它將導致 SDN 發出警告,因為 DTO 將 numberOfRelations 定義為額外屬性,而該屬性不在網域類型的契約中。TestEntity 中帶註解的屬性 aProperty 將正確翻譯為查詢中的 a_property。與上述相同,回傳類型與儲存庫的網域類型不同。需要後綴 By 以使 SDN 不會在 TestEntity 中尋找名為 DTOProjections 的屬性
6 此方法也回傳 DTO 投射。但是,不會發出警告,因為查詢包含適合投射中定義的額外屬性的值
雖然 上面的列表 中的儲存庫使用具體的回傳類型來定義投射,但另一種變體是使用 動態投射,如 Spring Data Neo4j 與其他 Spring Data 專案共用的文件部分中所述。動態投射可以應用於封閉和開放的介面投射以及基於類別的 DTO 投射

動態投射的關鍵是在儲存庫中的查詢方法中將所需的投射類型指定為最後一個參數,例如:<T> Collection<T> findByName(String name, Class<T> type)。這是一個可以新增到上述 TestRepository 的宣告,並允許透過相同方法檢索不同的投射,而無需在多個方法上重複可能的 @Query 註解。