英文:
Hibernate/JPA/Spring Problems with persisting (sorted) collection
问题
以下是翻译好的内容:
最初我想将这写成一个问题,但在撰写这篇帖子时,我找到了一个解决方案。如果这里有人有更好的解决方案,因为我的解决方案有点丑陋,可以随意发表。否则,我将把我的解决方案存档在这里,以便其他遇到类似问题的人会知道。(解决方案可在底部找到。)
我有以下需求需要满足:
- 在网页上显示所有“模块”并带有复选框
- 按以下两个子集对模块进行排序:
- 要显示在顶部的子集:所有已选择的模块,因此在“信息实体”上保持在“模块”集合内
- 要显示在下方的子集:所有尚未被选择的模块(带有ID > 0)并直接从数据库中检索
- 对这些子集的每个部分进行字母表排序(不区分大小写)
结果应该如下所示:
[x] aaaModule
[x] AbbModule
[x] aBcModule
[ ] aabModule
[ ] acbModule
请注意,排序需要不区分大小写,这就是为什么 @OrderBy 不能满足要求。因此,我选择实现 Comparable 接口到实体类,以便对其进行排序,并在 @ManyToMany 映射上使用 Hibernate 的 @SortNatural 注解。
我的当前解决方案(当然是简化的)如下所示:
在 Information.java 实体内:
@ManyToMany
@JoinTable(
name = "bla_modules",
schema = "blaSchema",
joinColumns = { @JoinColumn(name = "bla_info_id") },
inverseJoinColumns = { @JoinColumn(name = "bla_module_id") }
)
@SortNatural
private Set<Module> modules = new TreeSet<>();
在 Controller.java 内(在 GET 映射中):
Set<Module> modulesToDisplay = new LinkedHashSet<>();
modulesToDisplay.addAll(currentInformation.getModules());
modulesToDisplay.addAll(moduleRepository.findByIdGreaterThanOrderByLabelAsc(0));
model.addAttribute("modulesToDisplay", modulesToDisplay);
在 information.jsp 内:
<form:form method="POST" action="/informations.bla/editInformation" modelAttribute="currentInformation">
<ul style="list-style-type:none">
<c:forEach items="${modulesToDisplay}" var="module">
<li>
<div class="inline">
<form:checkbox path="modules" value="${module}" label="[${module.shortLabel}] ${module.label}"/>
</div>
</li>
</c:forEach>
</ul>
<input type="submit" value="Save">
</form:form>
在 Controller.java 内(在 POST 映射中):
public String editInformation(@ModelAttribute Information submittedInformation) {
Information informationAfterSaving = informationRepository.save(submittedInformation);
在我将 @SortNatural 注解应用到集合上之前,所有事情都进行得很顺利,但在那种情况下,JPA 在从数据库加载 TreeSet 时没有对其进行排序。
如果我从数据库加载现有的 Information 实体,显示它,更改属性,然后将其保存回去,它仍然运行良好。
然而,当我加载一个新的 Information 实体时,该实体只是在 Controller 中通过 "new Information()" 创建的,当 GET 映射未收到有效的 ID 进行加载时,然后在 Controller 的 POST 映射中执行 Information informationAfterSaving = informationRepository.save(submittedInformation);
时,我突然遇到错误 java.lang.ClassCastException: class java.util.LinkedHashSet cannot be cast to class java.util.SortedSet
。
解决方案可以在下面的我的答案中找到。希望它能在未来的某一天对某人有所帮助。
再见
英文:
Originally I wanted to write this as a question, but upon writing this post, I found a solution. If someone here got a better one, since mine is a bit ugly, feel free to post it. Otherwise I will just archive my solution here so that other people facing a similar problem will know. (Solution can be found at the bottom.)
I have the following demands which need to be met:
- Display all "modules" with Checkboxes on the webpage
- Sort the modules by two subsets of each other:
- Subset to be displayed at the top: All the modules that are selected and thus held inside the "modules" collection on the Information-Entity
- Subset to be displayed beneath: All the modules that haven't been selected yet (with ID>0) and are retrieved from the database directly
- Sort each of these subsets alphabetically (case insensitive)
The result should look like this:
[x] aaaModule
[x] AbbModule
[x] aBcModule
[ ] aabModule
[ ] acbModule
Note that the sorting needs to be case insensitive, which is why @OrderBy doesn't cut it. Instead I went with implementing the Comparable interface to the Entity-Class to be sorted and used the @SortNatural-Annotation of Hibernate on the @ManyToMany mapping.
My current solution (simplified of course) looks as follows:
Inside the Information.java Entity:
@ManyToMany
@JoinTable(
name = "bla_modules",
schema = "blaSchema",
joinColumns = { @JoinColumn(name = "bla_info_id") },
inverseJoinColumns = { @JoinColumn(name = "bla_module_id") }
)
@SortNatural
private Set<Module> modules = new TreeSet<>();
Inside the Controller.java (in the GET-Mapping):
Set<Module> modulesToDisplay = new LinkedHashSet<>();
modulesToDisplay.addAll(currentInformation.getModules());
modulesToDisplay.addAll(moduleRepository.findByIdGreaterThanOrderByLabelAsc(0));
model.addAttribute("modulesToDisplay", modulesToDisplay);
Inside the information.jsp:
<form:form method="POST" action="/informations.bla/editInformation" modelAttribute="currentInformation">
<ul style="list-style-type:none">
<c:forEach items="${modulesToDisplay}" var="module">
<li>
<div class="inline">
<form:checkbox path="modules" value="${module}" label="[${module.shortLabel}] ${module.label}"/>
</div>
</li>
</c:forEach>
</ul>
<input type="submit" value="Save">
</form:form>
Inside the Controller.java (the POST-Mapping):
public String editInformation(@ModelAttribute Information submittedInformation) {
Information informationAfterSaving = informationRepository.save(submittedInformation);
It all worked great prior to me having the @SortNatural annotation on the collection, but in that case JPA didn't sort the TreeSet when loading it from the database.
It also still works great, if I load an existing Information-Entity from the database, display that, change attributes, and then save it back.
However, when I load a new Information-Entity, which just got created in the Controller via "new Information()", when the GET-Mapping doesn't receive a valid ID to load, then I suddenly get the error java.lang.ClassCastException: class java.util.LinkedHashSet cannot be cast to class java.util.SortedSet
when the Information informationAfterSaving = informationRepository.save(submittedInformation);
inside the POST-Mapping of the Controller is executed.
The solution can be found in my own answer below. I hope it might help someone someday.
Bye
答案1
得分: 0
SOLUTION
在调用 Repository 的 .save 方法之前,在控制器的 POST-Mapping 内部,只需执行以下操作:
submittedInformation.setModules(new TreeSet<Module>(submittedInformation.getModules()));
我现在感觉有点愚蠢,早些时候竟然没有想到这个明显的解决方法,不瞒你说。
尽管解决方法还有点不太优雅,但如果有人知道如何告诉 JPA/Hibernate 如何正确地将 Spring 接收到的集合转换回 TreeSet,或者告诉 Spring 将表单中的复选框集合附加为 TreeSet,请告诉我。
替代方案
对我来说不是一个合适的解决方案,但 @MartinFrey 也提供了一个可能的解决方案:在后端跳过排序,改为在 GUI 端进行排序。
英文:
SOLUTION
Inside the POST-Mapping of the Controller prior to calling the .save method of the Repository, just do the following:
submittedInformation.setModules(new TreeSet<Module>(submittedInformation.getModules()));
I feel a bit stupid now that I didn't think of this obvious solution earlier, not gonna lie.
The solution is still a bit ugly though, so if someone knows how to tell JPA/Hibernate to properly convert the received collection from Spring back into a TreeSet, or to tell Spring to attach the Checkbox-Collection in the form of a TreeSet, then please tell me.
Alternative
Not a suitable solution for me, but @MartinFrey also gave a possible solution: Just skip the sorting on the backend side and instead sort on the GUI side.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论