我定义了一个模式,其中一个实体Event
与另一个实体Person
具有多对多关系。
在Event
的一侧,我根据Person
的一个属性(在我的示例中为name
)对Person
的集合进行排序。
当我查询一个新的Event
示例时,它工作得很好,但是当同一个实体被修改后,它就不工作了。相反,它将保留插入顺序,这是不正确的。
下面是Event
:
@Entity
public class Event {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
private String name;
private LocalDate occursAt;
@ManyToMany
@OrderBy("name")
private Set<Person> participants = new TreeSet<>(Comparator.comparing(Person::getName));
}
下面是Person
:
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
private String name;
@ManyToMany(mappedBy = "participants")
@OrderBy("name")
private Set<Event> events = new TreeSet<>(Comparator.comparing(Event::getName));
}
下面是负责向事件添加新参与者的方法:
@PostMapping("/{event}/participants/{participant}")
public PublicEvent addParticipant(@PathVariable UUID event, @PathVariable UUID participant) {
var originalEvent = eventRepo.getReferenceById(event);
var originalPerson = personRepo.getReferenceById(participant);
originalPerson.getEvents().add(originalEvent);
originalEvent.getParticipants().add(originalPerson);
personRepo.save(originalPerson);
return PublicEvent.from(originalEvent);
}
下面是一个测试,其中最后一个Assert由于意外行为而失败:
@Test
void addParticipant_addOne_oneMore() {
var event = et.createEventWithParticipantsAs("[email protected]");
var lastGuestRef = new AtomicReference<Person>();
up.asUser("[email protected]", () -> {
var lastGuest = new Person().setName("Bob");
lastGuestRef.set(personRepo.save(lastGuest));
});
var lastGuest = lastGuestRef.get();
var response = testRest.perform(
POST,
"/events/%s/participants/%s".formatted(event.getId(), lastGuest.getId()),
null,
PublicEvent.class,
up.header("[email protected]")
);
assertThat(response.status()).isEqualTo(200);
var participants = new ArrayList<>(event.getParticipants());
participants.add(lastGuest);
// Java and PostgreSQL lexicographical order is not quite the same, but it does not matter here
participants.sort(Comparator.comparing(Person::getName));
assertThat(response.body().participants().size()).isEqualTo(event.getParticipants().size() + 1);
assertThat(response.body().participants()).isEqualTo(PersonCtrl.PublicPerson.from(participants));
}
请注意,我使用了一些自定义函数来创建用户并将其存储在SecurityContextManager
中。希望测试逻辑对读者来说仍然是清楚的。
预期结果是:
[PublicPerson[id=05572a20-f82c-4d16-81f3-f41ccd45ac89, name=Alexandre],
PublicPerson[id=955f6354-7699-4b18-8825-80808bacf7e4, name=Benoit],
PublicPerson[id=8f1ec49d-530c-4ea8-bb20-555f4119d96c, name=Bob],
PublicPerson[id=a7fb90ba-06f4-432e-92c9-65c968d4b34c, name=Coraline],
PublicPerson[id=ca4d0019-9c3b-4ed7-a446-8bfb7f9ba58a, name=Denise],
PublicPerson[id=e20da141-304a-4baa-9cbf-bc9a77435869, name=Eleonore],
PublicPerson[id=6bd739c6-c977-4567-95c8-f8ddd60dff08, name=Frank]]
但实际结果是:
[PublicPerson[id=05572a20-f82c-4d16-81f3-f41ccd45ac89, name=Alexandre],
PublicPerson[id=955f6354-7699-4b18-8825-80808bacf7e4, name=Benoit],
PublicPerson[id=a7fb90ba-06f4-432e-92c9-65c968d4b34c, name=Coraline],
PublicPerson[id=ca4d0019-9c3b-4ed7-a446-8bfb7f9ba58a, name=Denise],
PublicPerson[id=e20da141-304a-4baa-9cbf-bc9a77435869, name=Eleonore],
PublicPerson[id=6bd739c6-c977-4567-95c8-f8ddd60dff08, name=Frank],
PublicPerson[id=8f1ec49d-530c-4ea8-bb20-555f4119d96c, name=Bob]]
我知道这个问题来自于Hibernate将我的TreeSortedSet
替换为PersistentSet
,并且只在检索实体时应用@OrderBy
。
现在我想知道,如何以预期的顺序(尊重@OrderBy
)检索具有嵌套集合的实体?
1条答案
按热度按时间vecaoik11#
通过搜索用户“M.代努姆”在评论中,我偶然发现了https://stackoverflow.com/a/42170280/7752551,它比较了
@OrderBy
和@SortNatural
。前者在我的用例中失败了,因为Hibernate
@OrderBy
注解在SQL级别暗示了相应的ORDER BY
子句。此外,它不关心Java级别的任何操作。因此,任何涉及上述列表的java代码都必须自己处理其顺序。特别令人烦恼的是,Java和SQL的字典顺序是不一样的(想想重读的字母)。第二个选项是使用
SortNatural
,SortNatural
使用嵌套实体实现的接口Comparable
。这种解决方案意味着排序完全在Java级别处理。效率不高,但至少是一致的。一个类似的选项是@SortComparator
,它的行为完全相同,但需要提供Comparator
,而不是使用嵌套实体实现Comparable
。