使用 Spring Data neo4j - SDN / ReactiveNeo4j 创建重复关系(neo4j 社区版 4.4.4)

问题描述 投票:0回答:2

我正在使用

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"));
    }

如有任何帮助,我们将不胜感激。

谢谢

英里。

spring-boot neo4j spring-data-neo4j
2个回答
2
投票

在 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。


0
投票

我和你有同样的问题。我找到了问题的答案并使用正确的方式保存关系(带有属性)。与你分享。

以我的例子为例。我有两个节点(电影、人物)和一个与属性的关系(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);
        }
    }
}

希望这些信息对您有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.