Дублирующиеся отношения, где сущность отношения имеет атрибут - PullRequest
0 голосов
/ 29 сентября 2018

Я использую Spring Data Neo4J 5.0.10 с Spring Boot 2.0.5.У меня есть следующие 2 узла сущности, пользовательский интерес и пользовательский интерес объекта отношения.

@NodeEntity
public class User {

    private Long id;    

    @Id 
    @GeneratedValue(strategy = UserIdStrategy.class)
    @Convert(UuidStringConverter.class)
    private UUID userId;

    @Relationship(type = UserInterest.TYPE, direction = Relationship.OUTGOING)
    private Set<UserInterest> interests = new HashSet<>();

    ... getters/setters

@NodeEntity
public class Interest {

    private Long id;

    @Id 
    @GeneratedValue(strategy = InterestIdStrategy.class)
    private String interestId;

    private String name;

    ... getters/setters

@RelationshipEntity(type = UserInterest.TYPE)
public class UserInterest {

    public static final String TYPE = "INTERESTED_IN";

    private Long id;

    @StartNode
    private User start;

    @EndNode
    private Interest end;

    //private Long weight;

    ... getters/setters

Это прекрасно работает.Я могу создать нового пользователя и связать пользователя с userInterest.Когда я снова отправляю те же данные, узлы и ребра не дублируются.

Когда я включаю атрибут веса в сущности отношения, создается впечатление, что отношение дублируется, даже если значение атрибута веса одинаково.

Я вспоминаю, что читал, что, поскольку атрибуты одинаковы, не следует создавать другие отношения, это правильно?

Это ожидаемое поведение, что мне нужно сделать, чтобы избежать дублированияотношения?

1 Ответ

0 голосов
/ 05 октября 2018

Вот рабочее решение.Прежде чем я углублюсь в детали: ключ в том, какие вещи вы упорствуете.Вы должны стремиться к четкому ограниченному контексту и просто получить доступ к интересам к одному агрегату.Я решил для пользователя быть точкой входа в вещи.У пользователя есть интересы, и интересы следует добавлять и манипулировать через пользователя.

OGM и Spring Data Neo4j заботятся о сохранении отношений, исходящих от пользователя.

Итак, основные моменты: ДонНе спасай каждый NodeEntity сам.Сохранять ассоциации между сущностями неявным образом, то есть: сохранять только родительский объект.Вы можете сделать это через сам сеанс или, как я это сделал, через хранилище.Обратите внимание, что вам не нужен репозиторий для каждой сущности.

Я опустил пользовательские стратегии, поскольку вы ими не делитесь.Я полагаюсь на сгенерированные идентификаторы.Если мой пример не соответствует вашим стратегиям, возможно, это хороший совет, где искать ошибки.

У нас есть интерес:

@NodeEntity
public class Interest {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

А интерес пользователей:

@RelationshipEntity(type = UserInterest.TYPE)
public class UserInterest {

    public static final String TYPE = "INTERESTED_IN";

    private Long id;

    @StartNode
    private User start;

    @EndNode
    private Interest end;

    private Long weight;

    public void setStart(User start) {
        this.start = start;
    }

    public Interest getEnd() {
        return end;
    }

    public void setEnd(Interest end) {
        this.end = end;
    }

    public void setWeight(Long weight) {
        this.weight = weight;
    }
}

И, наконец, пользователь:

public class User {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Relationship(type = UserInterest.TYPE, direction = Relationship.OUTGOING)
    private Set<UserInterest> interests = new HashSet<>();

    public void setName(String name) {
        this.name = name;
    }

    public Interest setInterest(String interstName, long weight) {

        final UserInterest userInterest = this.interests.stream()
            .filter(i -> interstName.equalsIgnoreCase(i.getEnd().getName()))
            .findFirst()
            .orElseGet(() -> {
                // Create a new interest for the user
                Interest interest = new Interest();
                interest.setName(interstName);

                // add it here to the interests of this user
                UserInterest newUserInterest = new UserInterest();
                newUserInterest.setStart(this);
                newUserInterest.setEnd(interest);
                this.interests.add(newUserInterest);
                return newUserInterest;
            });
        userInterest.setWeight(weight);
        return userInterest.getEnd();
    }
}

См. setInterest.Это один из способов использования User в качестве совокупного корня для доступа ко всем вещам.Здесь: интерес.Если он существует, просто измените вес, в противном случае создайте новый, включая UserInterest, добавьте его к интересам пользователей, наконец установите вес и затем верните его для дальнейшего использования.

Затем я заявляю один репозиторий, только для пользователя:

public interface UserRepository extends Neo4jRepository<User, Long> {
    Optional<User> findByName(String name);
}

А теперь приложение:

@SpringBootApplication
public class SorelationshipsApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(SorelationshipsApplication.class, args);
    }

    private final UserRepository userRepository;

    private final SessionFactory sessionFactory;

    public SorelationshipsApplication(UserRepository userRepository, SessionFactory sessionFactory) {
        this.userRepository = userRepository;
        this.sessionFactory = sessionFactory;
    }

    @Override
    public void run(String... args) throws Exception {
        Optional<User> optionalUser = this.userRepository
            .findByName("Michael");
        User user;

        ThreadLocalRandom random = ThreadLocalRandom.current();
        if(optionalUser.isPresent()) {

            // Redefine interests and add a new one
            user = optionalUser.get();
            user.setInterest("Family", random.nextLong(100));
            user.setInterest("Bikes", random.nextLong(100));
            user.setInterest("Music", random.nextLong(100));
        } else {
            user = new User();
            user.setName("Michael");

            user.setInterest("Bikes", random.nextLong(100));
            user.setInterest("Music", random.nextLong(100));

        }

        userRepository.save(user);
        // As an alternative, this works as well...
        // sessionFactory.openSession().save(user);
    }
}

Это всего лишь пример командной строки, работающий с моим локальным экземпляром Neo4j, ноЯ думаю, это объясняет все достаточно хорошо.

Я проверяю, существует ли пользователь.Если нет, создайте его и добавьте интерес.На следующем этапе измените существующие интересы и создайте новый.Любой дальнейший прогон просто изменяет существующие интересы.

Смотрите результат:

User and interests

Добавьте бонус: если вы на Java 11,см ifPresentOrElse на Optional.Намного более идиоматический способ иметь дело с Дополнительными.

userRepository.findByName("Michael").ifPresentOrElse(existingUser -> {
    existingUser.setInterest("Family", random.nextLong(100));
    existingUser.setInterest("Bikes", random.nextLong(100));
    existingUser.setInterest("Music", random.nextLong(100));
    userRepository.save(existingUser);
}, () -> {
    User user = new User();
    user.setName("Michael");

    user.setInterest("Bikes", random.nextLong(100));
    user.setInterest("Music", random.nextLong(100));
    userRepository.save(user);
});

Надеюсь, это поможет.

Редактировать: Вот мои зависимости:

<?xml version="1.0" encoding="UTF-8"?>
<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>com.example</groupId>
    <artifactId>sorelationships</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>sorelationships</name>
    <description>Demo project for Spring Boot</description>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-neo4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
...