Neo4j的开发方式

目录

1      扩展neo4j

1.1      介绍

1.2      存储过程

1.2.1        调用存储过程

1.2.2        内置存储过程

1.2.3        存储过程的maven配置

1.2.4        书写存储过程

2      远程调试

2.1 Idea的远程调试配置

 

3      APOC

3.1      Apoc:Cypher查询

3.2      apoc.cypher的api学习

3.3      apoc使用触发器

3.3.1        触发器实例-设置连接到节点的属性

3.3.2        实例二-更新标签

3.3.3        在新节点上创建关系

3.3.4        强制要求属性类型

3.4      定时器

3.5      Diff全图、Diff配置、导出配置文件

3.6      Apoc.meta.graph

3.7      虚拟节点和关系

4      Spring-Data-neo4j

4.1      Neo4j Repositories

4.2      查询方法

4.2.1        查询和查找方法

4.2.2        带注解的查询

4.2.3        查找方法名派生的查询

4.2.4        映射查询结果

4.2.5        排序和分页

4.2.6        投影

4.2.7        重塑数据

4.3      事务

4.4      注释实体


Neo4j Java参考

1      扩展neo4j

1.1      介绍

确保在neo4j.conf中禁用-XX:+ TrustFinalNonStaticFields JVM标志。

1.2      存储过程

用户自定义的存储过程通过Java部署到db,从Cypher进行访问。

存储过程用Java编写并编译成jar文件。 可以通过将jar文件放入每个独立服务器或群集服务器上的$ NEO4J_HOME / plugins目录中将它们部署到数据库中。 必须在每台服务器上重新启动数据库以获取新过程。

存储过程是扩展Neo4j的首选方法。 程序的用例示例如下:

提供对Cypher中不可用的功能的访问。

提供对第三方系统的访问。

执行图形全局操作,例如计算连接的组件或查找密集节点。

表达难以用Cypher声明性地表达的程序操作。

1.2.1        调用存储过程

在org.neo4j.examples的包中的名为findDenseNodes的存储过程,定义如下

CALL org.neo4j.examples.findDenseNodes(1000)

1.2.2        内置存储过程

Neo4j本身内置了很多存储过程,可以通过CALL dbms.procedures()来进行展示。

1.2.3        存储过程的maven配置

需要打jar包来调用,下面介绍整体编写、测试和部署neo4j的存储过程。

配置maven文件如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

                     http://maven.apache.org/xsd/maven-4.0.0.xsd">

 <modelVersion>4.0.0</modelVersion>

 <groupId>org.neo4j.example</groupId>

 <artifactId>procedure-template</artifactId>

 <version>1.0.0-SNAPSHOT</version>

 <packaging>jar</packaging>

 <name>Neo4j Procedure Template</name>

 <description>A template project for building a Neo4j Procedure</description>

 <properties>

   <neo4j.version>3.5.3</neo4j.version>

 </properties>

第一个依赖项部分包括存储过程在运行时使用的存储过程API。scope设置为provided,因为一旦将过程部署到Neo4j实例,此依赖关系由Neo4j提供。 如果将非Neo4j依赖项添加到项目中,则它们的作用域通常应该是compiled的。

  <dependency>

     <groupId>org.neo4j</groupId>

     <artifactId>neo4j</artifactId>

     <version>${neo4j.version}</version>

     <scope>provided</scope>

   </dependency>

接下来,添加测试过程所需的依赖项:

Neo4j Harness,一个允许启动轻量级Neo4j实例的实用程序。 它用于启动Neo4j并部署特定的过程,这极大地简化了测试。

Neo4j Java驱动程序,用于发送调用该过程的cypher语句。

JUnit,一个常见的Java测试框架。

<dependency>

     <groupId>org.neo4j.test</groupId>

     <artifactId>neo4j-harness</artifactId>

     <version>${neo4j.version}</version>

     <scope>test</scope>

   </dependency>



   <dependency>

     <groupId>org.neo4j.driver</groupId>

     <artifactId>neo4j-java-driver</artifactId>

     <version>1.7.3</version>

     <scope>test</scope>

   </dependency>



   <dependency>

     <groupId>junit</groupId>

     <artifactId>junit</artifactId>

     <version>4.12</version>

     <scope>test</scope>

   </dependency>

最后自己加上maven shade对应的依赖文件。

 

1.2.4        书写存储过程

所有的存储过程需要被标注为存储过程@procedure

@procedure可以采取三个可选参数

name用于为过程指定与生成的默认名称不同的名称,即class.path.nameOfMethod。如果mode已指定,则还name必须指定。

mode用于声明过程将执行的交互类型。默认mode是READ。可以使用以下模式:

  • READ 此过程仅对图形执行读取操作。
  • WRITE 此过程将对图形执行读写操作。
  • SCHEMA此过程将对模式执行操作,即创建和删除索引和约束。使用此模式的过程能够读取图形数据,但不能写入。
  • DBMS此过程将执行系统操作,如用户管理和查询管理。使用此模式的过程无法读取或写入图形数据。
  • Eager是一个默认为false的bool类型,如果其被设置为true,那么Cypher计划器在调用存储过程之前,会计划一个额外的eager操作。

如果需要存储过程的上下文所使用的资源相同,则注解为@Context。

2      远程调试

设置远程调试参数:

dbms.jvm.additional=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

加到neo4j对应的conf文件后重新启动。

 

2.1 Idea的远程调试配置

 

 

3      APOC

APOC是Neo4j 3.3版本推出时正式推荐的一个Java存储过程包,里面包含丰富的函数和过程,作为对Cypher所不能提供的复杂图算法和数据操作功能的补充,APOC还具有使用灵活、高性能等优势。

从neo4j3.0开始,引入了用户定义的存储过程的这一概念。简单地说,存储过程:

  • 用Java实现
  • 可以在neo4j数据库启动时加载,提高查询效率
  • 实现用Cypher很难实现的任何功能

APOC was also the first bundled A Package Of Component for Neo4j in 2009.

APOC also stands for "Awesome Procedures On Cypher"

3.1      Apoc:Cypher查询

Call apoc.cypher.*

可以在Cypher里面调用存储过程,然后在过程里面使用Cypher查询。

使用apoc来执行cypher查询的好处:

1.     可以动态构造查询语句

2.     控制查询的执行时间

3.     条件化查询分支when,case

4.     更灵活的查询执行任务控制:批次大小,并行执行,重试等等

3.2      apoc.cypher的api学习

apoc的neighbor方式

match (e:Eth_Port_Test)
call apoc.neighbors.tohop(e,'RELY_ON_TEST>',3) yield node
return node

3.3      apoc使用触发器

触发器的好处在于自动触发而不需要手动执行

CALL apoc.trigger.add(name, statement, selector) yield name, statement, installed

可以使用’createNode’、‘deleteNode’等类似的名称下声明对应的语句,选择器是before、after、rollback返回上一个和新的触发器信息

//删除以前添加的触发器 返回触发器信息
CALL apoc.trigger.remove(name) yield name, statement, installed

//删除所有先前添加的触发器,返回触发器信息
CALL apoc.trigger.removeAll() yield name, statement, installed

//更新并列出所有已安装的触发器
CALL apoc.trigger.list() yield name, statement, installed

//暂停触发器
Call apoc.trigger.pause(name)

//恢复暂停触发器
Call apoc.trigger.resume(name)

辅助函数

 

apoc.trigger.nodesByLabel({assignedLabels/assign

 

edNodeProperties},'Label')

 

 

 

 

 

apoc.trigger.propertiesByKey({assigned

 

NodeProperties},'key')

 

 

 

//实际适用语句

 

apoc.trigger.propertiesByKey({assignedNodeProp

 

erties},"surname")

 

 

函数以property-key过滤propertyEntries,以在具有{assignedNode / RelationshipProperties}和{removedNode / RelationshipProperties}的触发器语句中使用。返回[{old,[new],key,node,relationship}]

 

3.3.1        触发器实例-设置连接到节点的属性

apoc官方文档的触发器的demo里面存在一个小问题,会引发循环触发,如何避免?

//创建数据集
CREATE (d:Person {name:'Daniel'})
CREATE (l:Person {name:'Mary'})
CREATE (t:Person {name:'Tom'})
CREATE (j:Person {name:'John'})
CREATE (m:Person {name:'Michael'})
CREATE (a:Person {name:'Anne'})
CREATE (l)-[:DAUGHTER_OF]->(d)
CREATE (t)-[:SON_OF]->(d)
CREATE (t)-[:BROTHER]->(j)
CREATE (a)-[:WIFE_OF]->(d)
CREATE (d)-[:SON_OF]->(m)
CREATE (j)-[:SON_OF]->(d)

//使用propertiesBykey在surname属性上添加触发器
CALL apoc.trigger.add('setAllConnectedNodes','UNWIND apoc.trigger.propertiesByKey({assignedNodeProperties},"surname") as prop
WITH prop.node as n
MATCH(n)-[]-(a)
SET a.surname = n.surname', {phase:'after'});

//在节点上添加surname属性时,它会添加到所有的连接的节点(但是添加之后又会循环触发,循环触发的体现见APOC Trigger 循环触发,这样的情况如何处理)

要注意的是,这样的情况会引发数据库后台进程阻塞,需要后台重启neo4j实例,来阻止对应的阻塞进程。前端刷新并不生效。仅仅在前端刷新,会阻塞对Person标签类对象的一切操作,包括delete与set。

//修改相应的触发器语句,此时成功执行,不再阻塞,但是在前端显示不会说Set 6 //properties。仅仅显示//Set 1 property,因为其他5个节点的属性是后台触发完成的。

CALL apoc.trigger.add('setAllConnectedNodes','UNWIND apoc.trigger.propertiesByKey({assignedNodeProperties},"surname") as prop
WITH prop.node as n
MATCH(n:Person)-[]-(a:Person)
where not exists(a.surname) or n.surname<>a.surname
SET a.surname = n.surname', {phase:'after'});

3.3.2        实例二-更新标签

数据集

CREATE (k:Actor {name:'Keanu Reeves'})
CREATE (l:Actor {name:'Laurence Fishburne'})
CREATE (c:Actor {name:'Carrie-Anne Moss'})
CREATE (m:Movie {title:'Matrix'})
CREATE (k)-[:ACT_IN]->(m)
CREATE (l)-[:ACT_IN]->(m)
CREATE (c)-[:ACT_IN]->(m)

使用apoc.trigger.nodesByLabel来创建触发器,当一个节点的Actor标签被删除了之后,用Person标签更新所有的Actor标签

CALL apoc.trigger.add('updateLabels',"UNWIND apoc.trigger.nodesByLabel({removedLabels},'Actor') AS node
MATCH (n:Actor)
REMOVE n:Actor SET n:Person SET node:Person", {phase:'before'})


MATCH(k:Actor {name:'Keanu Reeves'})
REMOVE k:Actor

3.3.3        在新节点上创建关系

//当新节点的标签是actor,name属性是某些特定值时,为这些新节点创建关系。
CALL apoc.trigger.add('create-rel-new-node',"UNWIND {createdNodes} AS n
MATCH (m:Movie {title:'Matrix'})
WHERE n:Actor AND n.name IN ['Keanu Reeves','Laurence Fishburne','Carrie-Anne Moss']
CREATE (n)-[:ACT_IN]->(m)", {phase:'before'})



CREATE (k:Actor {name:'Keanu Reeves'})
CREATE (l:Actor {name:'Laurence Fishburne'})
CREATE (c:Actor {name:'Carrie-Anne Moss'})
CREATE (a:Actor {name:'Tom Hanks'})
CREATE (m:Movie {title:'Matrix'})



//暂停触发器
Call apoc.trigger.pause(‘’)

//恢复之前暂停的触发器
Call apoc.trigger.resume(‘’)

 

3.3.4        强制要求属性类型

和之前一样,创建before类型的触发器

我们希望所有的reference属性都是string类型

CALL apoc.trigger.add("forceStringType",
"UNWIND apoc.trigger.propertiesByKey({assignedNodeProperties}, 'reference') AS prop
CALL apoc.util.validate(apoc.meta.type(prop) <> 'STRING', 'expected string property type, got %s', [apoc.meta.type(prop)]) RETURN null", {phase:'before'})

//验证:
CREATE (a:Node) SET a.reference = 1

//触发器的其他例子
CALL apoc.trigger.add('timestamp','UNWIND {createdNodes} AS n SET n.ts = timestamp()');

CALL apoc.trigger.add('lowercase','UNWIND {createdNodes} AS n SET n.id = toLower(n.name)');

CALL apoc.trigger.add('txInfo',   'UNWIND {createdNodes} AS n SET n.txId = {transactionId}, n.txTime = {commitTime}', {phase:'after'});

CALL apoc.trigger.add('count-removed-rels','MATCH (c:Counter) SET c.count = c.count + size([r IN {deletedRelationships} WHERE type(r) = "X"])')

CALL apoc.trigger.add('lowercase-by-label','UNWIND apoc.trigger.nodesByLabel({assignedLabels},'Person') AS n SET n.id = toLower(n.name)')

触发器的参数列表

 

声明

 

 

描述

 

 

transactionId

 

 

返回事务的id

 

 

commitTime

 

 

以毫秒为单位返回事务日期

 

 

createdNodes

 

 

创建节点时,触发器触发(节点列表)

 

 

createdRelationships

 

 

当创建一个关系时,我们的触发器会触发(关系列表)

 

 

deletedNodes

 

 

当一个节点被驱逐时,我们的触发器会触发(节点列表)

 

 

deletedRelationships

 

 

当关系降低时,我们的触发器会触发(关系列表)

 

 

removedLabels

 

 

当一个标签被删除时,我们的触发器会触发(标签映射到节点列表)

 

 

removedNodeProperties

 

 

当删除节点的属性时,我们的触发器触发(键映射到键,旧,节点的映射列表)

 

 

removedRelationshipProperties

 

 

当删除关系的属性时,我们的触发器触发(键映射到键,旧,关系的映射列表)

 

 

assignedLabels

 

 

当一个labes被分配时我们的触发器触发(标签到节点列表的映射)

 

 

assignedNodeProperties

 

 

当分配节点属性时,我们的触发器触发(键映射到key,old,new,node的映射列表)

 

 

assignedRelationshipProperties

 

 

当关系属性被分配时,我们的触发器触发(键的映射列表,键,旧,新,关系)

 

3.4      定时器

3.5      Diff全图、Diff配置、导出配置文件

3.6      Apoc.meta.graph

快速理清各种标签的节点之间的关系

 

3.7      虚拟节点和关系

图中并不实际存在虚拟节点和关系,它们仅返回给UI以表示图的投影。存在负id。

具体的实例:

1.     将关系聚合为一个

2.     将中间节点折叠成虚拟关系,出于安全考虑隐藏属性或者中间节点/关系。

3.     只返回节点/rels的几个属性到可视化,例如你有巨大的文本属性。

4.     对图算法找到的聚类进行可视化。

5.     将信息聚合到更高的抽象层次。

6.     跳过较长路径的中的中间节点,

7.     图表分组。

8.     将来自其他来源的数据csv、xml、json的数据可视化为图形,甚至不存储它。

9.     投射部分数据。

要记住的一件事是:由于您无法从图中查找已创建的虚拟节点,因此必须将它们保存在您自己的查找结构中。适合它的东西是apoc.map.groupBy从实体列表创建一个映射,由给定属性的字符串值键入。

到目前为止,虚拟实体可以在所有表​​面上工作,Neo4j-Browser,Bloom,neovis以及所有驱动程序,即使它最初并非如此,它们也非常酷。

它们主要用于可视化,但Cypher本身无法访问它们(它们的ID,标签,类型,属性)。这就是为什么我们添加了许多函数来访问它们的属性,标签和rel-types。

在某些将来,它们可能会被图形视图所包含,能够在Cypher 10中返回图形和可组合的Cypher查询。

4      Spring-Data-neo4j

Neo4j的原生api的抽象层次较低,使用起来不是很方便,原生api的代码要复杂不少,而使用Spring data的则简洁很多。

4.1      Neo4j Repositories

Neo4j repository本身与继承它的repository,它们的实例都已经被Spring自动创建,即意味着我只需要声明接口的引用,而不需要把它和具体的对象相关联,即我们不需要进行实例化的操作。Spring在检测到引用之后已经自动创建对应的实例,我们也不需要为声明的接口方法提供实现。

提供存储库的推荐方法是为每个聚合根定义存储库接口,而不是为每个域类定义存储库接口。底层Spring存储库基础结构将自动检测这些存储库以及其他实现类,并创建可在服务或其他Spring bean中使用的可注入存储库实现。

Spring Data Neo4j提供的存储库构建在Spring Data Commons中的可组合存储库基础结构上。这些允许基于接口的存储库组合,包括为某些接口提供的默认实现和其他方法的其他自定义实现。

Spring Data Neo4j带有一个org.springframework.data.repository.PagingAndSortingRepository专门Neo4jRepository<T. ID>用于所有对象图映射存储库的特殊化。此子接口还添加了特定的finder方法,这些方法采用深度参数来控制获取和保存相关实体的范围。通常,它提供所有期望的存储库方法。如果需要其他操作,则应将其他存储库接口添加到单个接口声明中。

4.2      查询方法

4.2.1        查询和查找方法

您通常在存储库上触发的大多数数据访问操作都将导致对Neo4j数据库执行查询。定义这样的查询只需要在存储库接口上声明一个方法。

public interface PersonRepository extends PagingAndSortingRepository<Person, String> {
    List<Person> findByLastname(String lastname);                     
    Page<Person> findByFirstname(String firstname, Pageable pageable);
    Person findByShippingAddresses(Address address);                  
    Stream<Person> findAllBy();                                        
}

1、该方法显示具有给定姓氏的所有人的查询。将派生查询解析可以与And和连接的约束的方法名称Or。因此,方法名称将导致查询表达式为{"lastname" : lastname}。

2、将分页应用于查询。只需为方法签名配备一个Pageable参数,让方法返回一个Page实例,我们将自动相应地分页查询。

3、显示您可以基于非基本类型的属性进行查询。

4、使用Java 8 Stream,它在迭代流时读取和转换单个元素。           

4.2.2        带注解的查询

可以使用@Query注释提供使用Cypher图形查询语言的查询。

这意味着注释的存储库方法

@Query("MATCH (:Actor {name:{name}})-[:ACTED_IN]->(m:Movie) return m")

将使用提供的查询从Neo4j中检索数据。

命名或索引参数{param}将由实际方法参数替换。节点和关系实体直接处理并转换为它们各自的ID。所有其他参数类型被直接提供(即String,Long等)。

注意:

自定义查询不支持自定义深度。此外,@Query不支持将路径映射到域实体,因此,不应从Cypher查询返回路径。相反,返回节点和关系以将它们映射到域实体。

使用@Query放置在存储库方法上的Cypher查询示例,其中值用方法参数替换,如“ 注释查询”部分所述。

public interface MovieRepository extends Neo4jRepository<Movie, Long> {
    // returns the node with id equal to idOfMovie parameter
    @Query("MATCH (n) WHERE id(n)={0} RETURN n")
    Movie getMovieFromId(Integer idOfMovie);

    // returns the nodes which have a title according to the movieTitle parameter
    @Query("MATCH (movie:Movie {title={0}}) RETURN movie")
    Movie getMovieFromTitle(String movieTitle);

    // same with optional result
    @Query("MATCH (movie:Movie {title={0}}) RETURN movie")
    Optional<Movie> getMovieFromTitle(String movieTitle);

    // returns a Page of Actors that have a ACTS_IN relationship to the movie node with the title equal to movieTitle parameter.
    @Query(value = "MATCH (movie:Movie {title={0}})<-[:ACTS_IN]-(actor) RETURN actor", countQuery= "MATCH (movie:Movie {title={0}})<-[:ACTS_IN]-(actor) RETURN count(actor)")
    Page<Actor> getActorsThatActInMovieFromTitle(String movieTitle, PageRequest page);


    // returns a Page of Actors that have a ACTS_IN relationship to the movie node with the title equal to movieTitle parameter with an accurate total count
    @Query(value = "MATCH (movie:Movie {title={0}})<-[:ACTS_IN]-(actor) RETURN actor", countQuery = "MATCH (movie:Movie {title={0}})<-[:ACTS_IN]-(actor) RETURN count(*)")
    Page<Actor> getActorsThatActInMovieFromTitle(String movieTitle, Pageable page);


    // returns a Slice of Actors that have a ACTS_IN relationship to the movie node with the title equal to movieTitle parameter.
    @Query("MATCH (movie:Movie {title={0}})<-[:ACTS_IN]-(actor) RETURN actor")
    Slice<Actor> getActorsThatActInMovieFromTitle(String movieTitle, Pageable page);

    // returns users who rated a movie (movie parameter) higher than rating (rating parameter)
    @Query("MATCH (movie:Movie)<-[r:RATED]-(user) " +
           "WHERE id(movie)={movieId} AND r.stars > {rating} " +
           "RETURN user")
    Iterable<User> getUsersWhoRatedMovieFromTitle(@Param("movieId") Movie movie, @Param("rating") Integer rating);



    // returns users who rated a movie based on movie title (movieTitle parameter) higher than rating (rating parameter)
    @Query("MATCH (movie:Movie {title:{0}})<-[r:RATED]-(user) " +
           "WHERE r.stars > {1} " +
           "RETURN user")
    Iterable<User> getUsersWhoRatedMovieFromTitle(String movieTitle, Integer rating);


    @Query(value = "MATCH (movie:Movie) RETURN movie;")
    Stream<Movie> getAllMovies();

}

4.2.3        查找方法名派生的查询

使用底层对象 - 图形映射器中的元数据基础结构,可以将查找器方法名称拆分为其语义部分并转换为Cypher查询。沿关系的导航将反映在生成的MATCH子句中,并且具有运算符的属性将最终作为WHERE子句中的表达式。参数将按它们在方法签名中出现的顺序使用,因此它们应与方法名称中指定的表达式对齐。这里的意思是SDN(Spring-Data-Neo4j)的框架,在Repository接口中可以自动为我们按照某种格式定义的方法补全方法体,避免了我们造轮子的行为,我们只需要声明一个方法名就可以了。

public interface PersonRepository extends Neo4jRepository<Person, Long> {
    // MATCH (person:Person {name={0}}) RETURN person
    Person findByName(String name);


    // MATCH (person:Person)
    // WHERE person.age = {0} AND person.married = {1}
    // RETURN person
    Iterable<Person> findByAgeAndMarried(int age, boolean married);


    // MATCH (person:Person)
    // WHERE person.age = {0}
    // RETURN person ORDER BY person.name SKIP {skip} LIMIT {limit}
    Page<Person> findByAge(int age, Pageable pageable);



    // MATCH (person:Person)
    // WHERE person.age = {0}
    // RETURN person ORDER BY person.name
    List<Person> findByAge(int age, Sort sort);


    // Allow a custom depth as a parameter
    Person findByName(String name, @Depth int depth);


    // Set a fix depth of 0 for the query
    @Depth(value = 0)
    Person findBySurname(String surname);
}

 

4.2.4        映射查询结果

将复杂的Cypher查询结果转换为自定义的Java对象。

对于通过@Query存储库方法执行的查询,可以指定将复杂查询结果转换为POJO。然后使用查询结果数据填充这些结果对象。这些POJO更易于处理,并且可以用作数据传输对象(DTO),因为它们不附加到Session任何生命周期并且不参与任何生命周期。要利用此功能,请使用带注释的类@QueryResult作为方法返回类型。

public interface MovieRepository extends Neo4jRepository<Movie, Long> {
    @Query("MATCH (movie:Movie)-[r:RATING]->(), (movie)<-[:ACTS_IN]-(actor:Actor) " +
           "WHERE movie.id={0} " +
           "RETURN movie as movie, COLLECT(actor) AS cast, AVG(r.stars) AS averageRating")
    MovieData getMovieData(String movieId);
}


@QueryResult
public class MovieData {
    Movie movie;
    Double averageRating;
    Set<Actor> cast;
}

4.2.5        排序和分页

Spring Data Neo4j支持在使用Spring Data Pageable和Sort接口时对结果进行排序和分页。

//基于repository的分页
Pageable pageable = PageRequest.of(0, 3);
Page<World> page = worldRepository.findAll(pageable, 0);

//基于repository的排序
Sort sort = new Sort(Sort.Direction.ASC, "name");
Iterable<World> worlds = worldRepository.findAll(sort, 0)) {

//基于repository的分页排序
Pageable pageable = PageRequest.of(0, 3, Sort.Direction.ASC, "name");
Page<World> page = worldRepository.findAll(pageable, 0);

4.2.6        投影

Spring Data Repositories通常在使用查询方法时返回域模型(作为@NodeEntity或作为a @QueryResult)。但是,有时您可能出于各种原因需要该模型的不同视图。在本节中,您将学习如何定义投影以提供简化和简化的资源视图。

查看以下域模型:

@NodeEntity
public class Cinema [w1] {
  private Long id;
  private String name, location;

  @Relationship(type = "VISITED", direction = Relationship.INCOMING)
  private Set<User> visited = new HashSet<>();

  @Relationship(type = "BLOCKBUSTER", direction = Relationship.OUTGOING)
  private Movie blockbusterOfTheWeek;
  …

}

这Cinema有几个属性:

  • id 是图id
  • name与location是数据属性
  • visited与blockbusterOfTheWeek是指向其他域对象的链接

现在假设我们按如下方式创建相应的存储库:

public interface CinemaRepository extends Neo4jRepository<Cinema, Long> {
  Cinema findByName(String name);
}

Spring Data将返回域对象,包括其所有属性以及访问此影院的所有用户。这可能是大量数据,可能导致性能问题。

有如下几种方法可以避免findByName的问题

  • 使用自定义depth进行加载
  • 使用自定义带注解的@Query
  • 使用投影

简单投影

public interface CinemaNameAndBlockbuster { 
            public String getName();
            public Movie getBlockbusterOfTheWeek();
}

此投影具有以下详细信息:

一个普通的Java接口,使其具有声明性。

仅导出实体的某些属性。

该CinemaNameAndBlockbuster投影仅具有name和blockbusterOfTheWeek的getter方法,意味着它不会提供之前域实体的用户信息,在这种情况下,查询方法定义返回CinemaNameAndBlockbuster而不是Cinema。

interface CinemaRepository extends Neo4jRepository<Cinema, Long> {
  CinemaNameAndBlockbuster findByName(String name);
}

投影声明了基础类型与公开属性相关的方法签名之间的规则。因此,需要根据基础类型的属性名称来命名getter方法为getName,否则Spring Data无法查找对应的源属性。这种类型的投影也称为闭合投影

 

4.2.7        重塑数据

到目前为止,您已经了解了如何使用投影来减少呈现给用户的信息。投影可用于调整公开的数据模型。您可以为投影添加虚拟属性。请看以下投影界面:

interface RenamedProperty {                

  @Value("#{target.name}")
  String getCinemaName();                  

  @Value("#{target.blockbusterOfTheWeek.name}")
  String getBlockbusterOfTheWeekName();    
}

此投影具有以下详细信息:

  • 一个普通的Java接口,使其具有声明性。
  • 将name属性公开为名为的虚拟属性cinemaName。
  • name将链接Movie实体的子属性导出为虚拟属性。
interface RenamedProperty {
  @Value("#{target.name} #{(target.location == null) ? '' : target.location}")
  String getNameAndLocation();
}

在此示例中,仅当影院名称可用时,才会将该位置附加到影院名称。

4.3      事务

Neo4j是一个事务型数据库,只允许在事务内执行操作。Spring Data Neo4j通过@Transaction支持声明式事务,利用TransactionTemplate支持手动事务处理。

其目的是为非CRUD操作定义事务边界

4.4      注释实体

关于JSON序列化的注释

查看上面给出的示例,可以很容易地发现节点和富关系之间对类级别的循环依赖性。只要不序列化对象,它就不会对您的应用程序产生任何影响。今天使用的一种序列化是使用Jackson映射器的JSON序列化。如果数据在SpringBoot或JavaEE等框架中导出,则将使用此映射器库。遍历对象树时,它会在访问Role后访问一个部分Actor。显然它会找到Actor对象并再次访问它,依此类推。这将最终成为一个StackOverflowError。要打破此解析周期,必须通过为类提供注释来支持映射器。这可以通过添加其中之一来完成@JsonIgnore在导致循环或属性的属性上@JsonIgnoreProperties。

参考:

htps://blog.csdn.net/GraphWay/article/details/78957415

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值