关系实体具有属性的重复关系
Duplicate relationships where relationship entity has an attribute
我正在使用 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。当我再次发送相同的详细信息时,节点和边不会重复。
当我在关系实体中启用权重属性时,尽管权重属性值相同,但关系似乎是重复的。
我记得读过,只要属性相同,就不应创建另一个关系,对吗?
这是预期的行为吗?我需要做什么来防止重复关系?
这是一个可行的解决方案。细说之前:关键是你对什么事情的坚持。您应该以明确的有界上下文为目标,并且只访问对一个聚合的兴趣。我决定让用户成为事物的切入点。用户有兴趣,应该通过用户添加和操作兴趣。
OGM 和 Spring Data Neo4j 负责保存从用户传出的关系。
所以要点是:不要自己保存每个 NodeEntity
。以隐式方式保存实体之间的关联,即:只保存父对象。您可以通过会话本身或像我一样通过存储库来完成此操作。请注意,您不需要为每个实体创建一个存储库。
我省略了自定义策略,因为您没有分享它们。我依赖于生成的 ID。如果我的示例无法满足您的策略,也许这是一个很好的提示,可以在哪里查找错误。
我们有兴趣:
@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
,添加到用户兴趣中,最后设置权重,然后return进一步使用。
然后,我声明 一个 存储库,仅供用户使用:
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 实例的命令行示例 运行,但我认为它已经很好地解释了事情。
我检查用户是否存在。如果没有,请创建它并添加一些兴趣。在接下来的 运行 中,修改现有兴趣并创建新兴趣。任何进一步的 运行 只会修改现有兴趣。
查看结果:
添加奖励:如果您在 Java 11,请参阅 Optional
上的 ifPresentOrElse
。处理 Optionals 的更惯用的方式。
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>
我正在使用 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。当我再次发送相同的详细信息时,节点和边不会重复。
当我在关系实体中启用权重属性时,尽管权重属性值相同,但关系似乎是重复的。
我记得读过,只要属性相同,就不应创建另一个关系,对吗?
这是预期的行为吗?我需要做什么来防止重复关系?
这是一个可行的解决方案。细说之前:关键是你对什么事情的坚持。您应该以明确的有界上下文为目标,并且只访问对一个聚合的兴趣。我决定让用户成为事物的切入点。用户有兴趣,应该通过用户添加和操作兴趣。
OGM 和 Spring Data Neo4j 负责保存从用户传出的关系。
所以要点是:不要自己保存每个 NodeEntity
。以隐式方式保存实体之间的关联,即:只保存父对象。您可以通过会话本身或像我一样通过存储库来完成此操作。请注意,您不需要为每个实体创建一个存储库。
我省略了自定义策略,因为您没有分享它们。我依赖于生成的 ID。如果我的示例无法满足您的策略,也许这是一个很好的提示,可以在哪里查找错误。
我们有兴趣:
@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
,添加到用户兴趣中,最后设置权重,然后return进一步使用。
然后,我声明 一个 存储库,仅供用户使用:
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 实例的命令行示例 运行,但我认为它已经很好地解释了事情。
我检查用户是否存在。如果没有,请创建它并添加一些兴趣。在接下来的 运行 中,修改现有兴趣并创建新兴趣。任何进一步的 运行 只会修改现有兴趣。
查看结果:
添加奖励:如果您在 Java 11,请参阅 Optional
上的 ifPresentOrElse
。处理 Optionals 的更惯用的方式。
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>