目录
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。
参考: