我正在使用
NameEntity
类实现一个基本节点 - [关系] - 节点,该类具有 Relationship(type="LINK", direction = INCOMING)
注释。
Link
类有一个@TargetNode
NameEntity
。
我有一个单元测试,它创建三个节点,具有两个关系。我第一次运行单元测试时,会创建节点和关系:
我第二次运行单元测试时,正在创建重复的关系:
我对 Neo4j 还很陌生(我使用的是社区版本 4.4.4)。
我不希望创建重复的第二个关系(具有相同的
Link
类型)。我很感激我没有使用 Version 属性。
neo4j 创建第二个关系(具有相同属性)是默认行为吗?有没有办法不创建第二个(重复)关系?
我已附上复制的
NameEntity
和 Link
pojo、单元测试以及我运行单元测试时(第一次和第二次)运行的 Cypher。
我“认为”解决方案可能是覆盖
equals
和hashCode
,但非常欢迎一个具体的例子。我还没找到呢
NameEntity
:
@Node("Name")
@Getter
public class NameEntity {
@Id
private String name;
@Relationship(type = "LINK", direction = INCOMING)
private List<Link> nameLinks;
public NameEntity(final String name) {
this.name = name;
}
public NameEntity() {}
public void install(Link nameLink) {
if (nameLinks == null) {
nameLinks = new ArrayList<>();
}
nameLinks.add(nameLink);
}
}
Link
(与TargetNode
的关系):
@RelationshipProperties
@Getter
public class Link {
@RelationshipId
private Long id;
private String value;
@TargetNode
private NameEntity nameEntity;
public Link(NameEntity nameEntity, String value) {
this.nameEntity = nameEntity;
this.value = value;
}
// equals and hashCode override does not work
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof Link))
return false;
Link link = (Link) o;
return Objects.equals(value, link.value)
&& Objects.equals(nameEntity.getName(), link.nameEntity.getName());
}
@Override
public int hashCode() {
return Objects.hash(value);
}
public Link() {}
public NameEntity getNameEntity() {
return nameEntity;
}
public String getValue() {
return value;
}
}
Spring Boot首次运行单元测试:
2022-04-13 18:31:33.243 DEBUG 25444 --- [o-auto-1-exec-1] .d.n.c.t.ReactiveNeo4jTransactionManager : Creating new transaction with name [org.springframework.data.neo4j.repository.support.SimpleReactiveNeo4jRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2022-04-13 18:31:33.464 WARN 25444 --- [o4jDriverIO-2-2] o.s.d.n.c.m.DefaultNeo4jIsNewStrategy : Instances of class com.chocksaway.neo4j.entity.NameEntity with an assigned id will always be treated as new without version property!
2022-04-13 18:31:33.529 DEBUG 25444 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher : Executing:
MERGE (nameEntity:`Name` {name: $__id__}) SET nameEntity += $__properties__ RETURN nameEntity
2022-04-13 18:31:33.639 DEBUG 25444 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher : Executing:
MERGE (nameEntity:`Name` {name: $__id__}) SET nameEntity += $__properties__ RETURN nameEntity
2022-04-13 18:31:33.655 DEBUG 25444 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher : Executing:
MATCH (startNode:`Name`) WHERE startNode.name = $fromId MATCH (endNode) WHERE id(endNode) = $toId CREATE (startNode)<-[relProps:`LINK`]-(endNode) SET relProps += $__properties__ RETURN id(relProps)
2022-04-13 18:31:33.663 DEBUG 25444 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher : Executing:
MERGE (nameEntity:`Name` {name: $__id__}) SET nameEntity += $__properties__ RETURN nameEntity
2022-04-13 18:31:33.669 DEBUG 25444 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher : Executing:
MATCH (startNode:`Name`) WHERE startNode.name = $fromId MATCH (endNode) WHERE id(endNode) = $toId CREATE (startNode)<-[relProps:`LINK`]-(endNode) SET relProps += $__properties__ RETURN id(relProps)
2022-04-13 18:31:33.682 DEBUG 25444 --- [o4jDriverIO-2-2] .d.n.c.t.ReactiveNeo4jTransactionManager : Initiating transaction commit
2022-04-13 18:31:33.762 INFO 25444 --- [ionShutdownHook] o.neo4j.driver.internal.InternalDriver : Closing driver instance 2107393518
2022-04-13 18:31:33.764 INFO 25444 --- [ionShutdownHook] o.n.d.i.async.pool.ConnectionPoolImpl : Closing connection pool towards localhost:7687
Spring Boot第二次运行单元测试:
2022-04-13 18:33:44.751 DEBUG 16572 --- [o-auto-1-exec-1] .d.n.c.t.ReactiveNeo4jTransactionManager : Creating new transaction with name [org.springframework.data.neo4j.repository.support.SimpleReactiveNeo4jRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2022-04-13 18:33:44.981 WARN 16572 --- [o4jDriverIO-2-2] o.s.d.n.c.m.DefaultNeo4jIsNewStrategy : Instances of class com.chocksaway.neo4j.entity.NameEntity with an assigned id will always be treated as new without version property!
2022-04-13 18:33:45.044 DEBUG 16572 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher : Executing:
MERGE (nameEntity:`Name` {name: $__id__}) SET nameEntity += $__properties__ RETURN nameEntity
2022-04-13 18:33:45.143 DEBUG 16572 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher : Executing:
MERGE (nameEntity:`Name` {name: $__id__}) SET nameEntity += $__properties__ RETURN nameEntity
2022-04-13 18:33:45.160 DEBUG 16572 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher : Executing:
MATCH (startNode:`Name`) WHERE startNode.name = $fromId MATCH (endNode) WHERE id(endNode) = $toId CREATE (startNode)<-[relProps:`LINK`]-(endNode) SET relProps += $__properties__ RETURN id(relProps)
2022-04-13 18:33:45.168 DEBUG 16572 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher : Executing:
MERGE (nameEntity:`Name` {name: $__id__}) SET nameEntity += $__properties__ RETURN nameEntity
2022-04-13 18:33:45.185 DEBUG 16572 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher : Executing:
MATCH (startNode:`Name`) WHERE startNode.name = $fromId MATCH (endNode) WHERE id(endNode) = $toId CREATE (startNode)<-[relProps:`LINK`]-(endNode) SET relProps += $__properties__ RETURN id(relProps)
2022-04-13 18:33:45.211 DEBUG 16572 --- [o4jDriverIO-2-2] .d.n.c.t.ReactiveNeo4jTransactionManager : Initiating transaction commit
2022-04-13 18:33:45.290 INFO 16572 --- [ionShutdownHook] o.neo4j.driver.internal.InternalDriver : Closing driver instance 1728266914
2022-04-13 18:33:45.293 INFO 16572 --- [ionShutdownHook] o.n.d.i.async.pool.ConnectionPoolImpl : Closing connection pool towards localhost:7687
单元测试:
@Test
public void testAddName() throws URISyntaxException {
RestTemplate restTemplate = new RestTemplate();
final String baseUrl = "http://localhost:"+randomServerPort+"/name";
final Link nameEntity1 = new Link(new NameEntity("name001", "Person"), "Link");
final Link nameEntity2 = new Link(new NameEntity("name002", "Person"), "Link");
final NameEntity bob = new NameEntity("Bob", "Person");
bob.setNameLinks(Set.of(nameEntity1, nameEntity2));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
final ResponseEntity<String> response = restTemplate.postForEntity(baseUrl, bob, String.class);
Assertions.assertEquals(200, response.getStatusCodeValue());
Assertions.assertTrue(response.hasBody());
Assertions.assertTrue(response.getBody().contains("Bob"));
}
如有任何帮助,我们将不胜感激。
谢谢
英里。
在 Cypher 中,通常使用 MERGE 来避免以这种方式创建重复项。如果您查看 Spring Boot 日志,您将看到节点的创建过程如下:
MERGE (nameEntity:`Name` {name: $__id__})
SET nameEntity += $__properties__
RETURN nameEntity
使用 MERGE 而不是 CREATE 可确保您的节点不重复。您还可以创建一个约束,在节点上强制执行唯一的“名称”属性,但是当您尝试创建重复项时,这会出错。
现在,如果我们查看创建关系的日志,我们会看到:
MATCH (startNode:`Name`)
WHERE startNode.name = $fromId
MATCH (endNode)
WHERE id(endNode) = $toId
CREATE (startNode)<-[relProps:`LINK`]-(endNode)
SET relProps += $__properties__
RETURN id(relProps)
此代码查找(匹配)节点,然后创建(创建)关系。这应该是合并,以避免重复。我不知道如何让你的框架输出这个 Cypher。
我和你有同样的问题。我找到了问题的答案并使用正确的方式保存关系(带有属性)。与你分享。
以我的例子为例。我有两个节点(电影、人物)和一个与属性的关系(ActedIn)。代码列表如下。
电影:
@Node(labels= {"Movie"})
public class Movie {
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Set<ActedIn> getActors() {
return actors;
}
public void setActors(Set<ActedIn> actors) {
this.actors = actors;
}
@Id
@Property("title")
private String title;
@Property("tagline")
private String description;
@Relationship(type="ACTED_IN", direction=Direction.INCOMING)
private Set<ActedIn> actors = new HashSet<>();
public Movie() {
this.title = "";
this.description = "";
}
public Movie(String title, String description) {
this.title = title;
this.description = description;
}
}
人:
@Node("Person")
public class Person {
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getBorn() {
return born;
}
public void setBorn(Integer born) {
this.born = born;
}
@Id
private String name = "";
private Integer born = 0;
public Person() {
this.name = "";
this.born = 0;
}
public Person(String name, Integer born) {
this.name = name;
this.born = born;
}
}
出演:
@RelationshipProperties
public class ActedIn {
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((person == null) ? 0 : person.hashCode());
result = prime * result + ((role == null) ? 0 : role.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ActedIn other = (ActedIn) obj;
if (person == null) {
if (other.person != null)
return false;
} else if (!person.equals(other.person))
return false;
if (role == null) {
if (other.role != null)
return false;
} else if (!role.equals(other.role))
return false;
return true;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
@RelationshipId
private Long id;
@TargetNode
private Person person;
@Property
private String role;
public ActedIn(Person person, String role) {
this.person = person;
this.role = role;
}
public static ActedIn instanceOf(Person person, String role) {
return new ActedIn(person, role);
}
}
关系(带属性)使用自动生成的 id。重复关系的原因是我们没有为已经存在的关系实例设置原始ID。所以 Spring Data Neo4j 为我们创建了其他关系。
保存节点和关系(带属性)的正确方法如下:
电影存储库:
public interface MovieRepository extends Neo4jRepository<Movie, String> {
}
电影服务:
@Service
public class MovieService {
private MovieRepository movieRepo;
public MovieService(MovieRepository movieRepo) {
this.movieRepo = movieRepo;
}
public Movie createOrUpdate(Movie movie) {
Optional<Movie> opM = movieRepo.findById(movie.getTitle());
Movie oldM = null;
Set<ActedIn> oldActedIns = null;
Set<ActedIn> newActedIns = new HashSet<ActedIn>();
newActedIns.addAll(movie.getActors());
if(opM.isPresent()) {
oldM = opM.get();
oldActedIns = oldM.getActors();
for(ActedIn newActedIn: newActedIns) {
for(ActedIn oldActedIn : oldActedIns) {
if(newActedIn.equals(oldActedIn)) {
movie.getActors().remove(newActedIn);
movie.getActors().add(oldActedIn);
break;
}
}
movie.getActors().add(newActedIn);
}
}
movieRepo.save(movie);
return movie;
}
@Transactional
public List<Movie> batchCreateOrUpdate(List<Movie> movieList) {
List<Movie> result = new ArrayList<Movie>();
for (Movie m : movieList) {
createOrUpdate(m);
result.add(m);
}
return result;
}
}
最后一个代码展示了如何使用MovieService。
@SpringBootApplication
public class Application implements CommandLineRunner {
private MovieService movieService;
private PersonService personService;
public Application(MovieService movieService, PersonService personService) {
this.movieService = movieService;
this.personService = personService;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... args) throws Exception {
// 新增
Movie m1 = new Movie("A計劃", "香港在英國殖民時期的警察故事。");
Movie m2 = new Movie("飛鷹計算", "精典的冒險搜寶故事。");
Person p1 = new Person("成龍", 50);
Person p2 = new Person("梅豔芳", 50);
List<Movie> movieList = new ArrayList<Movie>();
movieList.add(m1);
movieList.add(m2);
// 設定m1的relationship
m1.getActors().add(ActedIn.instanceOf(p1, "Jacky"));
m1.getActors().add(ActedIn.instanceOf(p2, "梅姑"));
movieService.createOrUpdate(m1);
// 設定m2的relationship
m2.getActors().add(ActedIn.instanceOf(p1, "Jacky"));
movieService.createOrUpdate(m2);
// 批次新增
movieList = movieService.batchCreateOrUpdate(movieList);
System.out.println("新增:");
for(Movie m: movieList) {
System.out.println(m);
}
}
}
希望这些信息对您有帮助。