现在工作中开始使用Neo4j,但对照网络上的教程,导入starter后,没有@NodeEntity这个注释,所以参考官方文档,开发了一个简单demo
官方文档
简单DEMO
- 导入starter
<!-- neo4j -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
- 创建节点实体和Repository
@Node("Movie")
public class MovieEntity {
@Id
@GeneratedValue
private Long id;
private final String title;
@Property("tagline")
private final String description;
@Relationship(type = "ACTED_IN", direction = Direction.INCOMING)
private List<ActorEntity> actors;
public MovieEntity(String title, String description) {
this.id = null;
this.title = title;
this.description = description;
}
public MovieEntity withId(Long id) {
if (this.id.equals(id)) {
return this;
} else {
MovieEntity newObject = new MovieEntity(this.title, this.description);
newObject.id = id;
return newObject;
}
}
public MovieEntity addActor(ActorEntity actor) {
if (this.actors == null) {
this.actors = new ArrayList<>();
}
this.actors.add(actor);
return this;
}
// getter
// ...
}
@Repository
public interface MovieRepository extends Neo4jRepository<MovieEntity, Long> {
@Query("MATCH (m:Movie) where m.title=$title RETURN m")
List<MovieEntity> findByTitle(@Param("title") String title);
}
- 添加测试
@Test
public void testAdd() {
MovieEntity entity = new MovieEntity("让子弹飞", "民国故事");
entity.addActor(new ActorEntity("姜文", "中国"))
.addActor(new ActorEntity("周润发", "中国"));
movieRepository.save(entity);
}
添加自定义的RelationShip
参考relationShipProperties注释
上面的简单demo中,@Relationship声明的list中,ActorEntity类使用的是@Node注释,所以在保存时Actor被作为节点保存,而现在将类使用@RelationshipProperties声明即可
但是要注意,使用@RelationshipProperties声明的类必须有一个@TargetNode作为目标节点
// movieEntity代码
public class MovieEntity {
@Id
@GeneratedValue
private Long id;
private final String title;
@Property("tagline")
private final String description;
@Relationship(value = "ACTED_IN", direction = Relationship.Direction.INCOMING)
private List<RoleRelationShip> roles;
}
// RoleRelationShip类代码
@RelationshipProperties
public class RoleRelationShip {
@Id
@GeneratedValue
private Long id;
private final String name;
@TargetNode
private final PersonEntity person;
}
// PersonEntity类与movie类相似
// 测试代码
@Test
public void testAdd() {
MovieEntity movie = new MovieEntity("肖申克的救赎", "自我救赎,重获自由!");
movie.addRole(new RoleRelationShip("安迪·杜佛兰", new PersonEntity("Tim Robbins", "美国")))
.addRole(new RoleRelationShip("瑞德", new PersonEntity("Morgan Freeman", "美国")));
movieRepository.save(movie);
}
可以看到relation也添加了需要的属性
自定义的Query
@Query的value可以使用SpEL,只需要使用:#{ 和 } 包装即可
使用MovieEntity更新对象
@Query("MATCH (m:Movie) where id(m)=:#{#query.id} set m.tagline=:#{#query.description} RETURN m")
MovieEntity updateById(@Param("query") MovieEntity entity);
除了@Query外的其他定制查询
主要对照文档的11.12. Is @Query the only way to use custom queries?
- 创建一个接口
接口实现Neo4jRepository和CypherdslConditionExecutor接口
@Repository
public interface QueryDSLPersonRepository extends Neo4jRepository<PersonEntity, Long>, CypherdslConditionExecutor<PersonEntity> {
}
- 简单的查询
Node person = Cypher.node("Person").named("n");// [1]
Property name = person.property("name");
Property nation = person.property("nation");
Collection<PersonEntity> collection = queryDSLPersonRepository.findAll(
name.eq(Cypher.anonParameter("Morgan Freeman")).or(nation.eq(Cypher.parameter("someName", "America"))),// [2]
nation.descending()
);
for (PersonEntity personEntity : collection) {
System.out.println(personEntity);
}
几点注意:
3. [1]处声明一个节点,named()方法的介绍是 Creates a copy of this node with a new symbolic name.
,但我不太理解,为什么在(n:Person)内没有被修改名称,所以我只能使用"n"
4. [2]处就是查询的条件,Cypher.parameter(“someName”, “America”)和Cypher.anonParameter(“Morgan Freeman”),目前来看效果是一样的,只不过Cypher.parameter("someName", "America")
就是参数使用$someName, 而不是默认的占位符
第二种定制查询
简单的基于关系的查询
@Repository
public interface PersonStatementRepository extends Neo4jRepository<PersonEntity, Long>, CypherdslStatementExecutor<PersonEntity> {
}
查询的测试代码
Node p = Cypher.node("Person").named("p");
Node m = Cypher.node("Movie").named("m");
Relationship r = p.relationshipTo(m, "ACTED_IN");
// 查询参演了电影标题是"黑水"的演员
ResultStatement build = Cypher.match(r).where(m.property("title").isEqualTo(Cypher.anonParameter("黑水")))
.returning(Functions.collect(p)).build();
Collection<PersonEntity> all = personStatementRepository.findAll(build);
for (PersonEntity personEntity : all) {
System.out.println(personEntity);
}
System.out.println("break;");
// 查询名称以"Freeman"结尾的演员,并且以"福克斯"这个名称参演得电影
build = Cypher.match(p, r, m).where(
p.property("name").endsWith(Cypher.parameter("lastName", "Freeman"))
.and(r.property("name").isEqualTo(Cypher.anonParameter("福克斯"))))
.returning(
// Functions.collect(p),
// Functions.collect(r),
Functions.collect(m)
).build();
Collection<MovieEntity> all1 = movieStatementRepository.findAll(build);
for (MovieEntity movie : all1) {
System.out.println(movie);
}
控制台输出:
PersonEntity{id=5, name='Anne Hathaway', from='美国'}
PersonEntity{id=1, name='Tim Robbins', from='美国'}
break;
MovieEntity{id=6, title='蝙蝠侠:黑暗骑士', description='广受好评的超级英雄电影', roles=[]}
MovieEntity{id=9, title='蝙蝠侠:黑暗骑士崛起', description='又一部广受好评的超级英雄电影', roles=[]}
目前的几个问题
1. Cypher.match(p, r, m)的入参到底是什么?第一个statement只传入了relationship,但可以查出Person
2. 如何获得一个关联的列表?第二个查询怎么样把其关联的roles查询出来
3. returning()的用法,目前只发现一种用法,这个也行可以解决2的问题
这一种查询的复杂用法
参考这种方法,可以自定义到一些较为复杂的查询,例如下面的:查询和名称以"Freeman"结尾的演员共同演出过的演员
@Test
public void testHyperCustomerQuery() {
Node p = Cypher.node("Person").named("p");
Node m = Cypher.node("Movie").named("m");
Node p2 = Cypher.node("Person").named("p2");
Relationship r1 = p.relationshipTo(m, "ACTED_IN");
Relationship r2 = p2.relationshipTo(m, "ACTED_IN");
ResultStatement build = Cypher.match(r1, r2)
.where(p.property("name").endsWith(Cypher.parameter("lastName", "Freeman")))
.returning(Functions.collect(p2)).build();
Collection<PersonEntity> list = personStatementRepository.findAll(build);
for (PersonEntity personEntity : list) {
System.out.println(personEntity);
}
}