Hibernate/JPA/Spring问题:持久化(排序)集合存在问题

huangapple go评论92阅读模式
英文:

Hibernate/JPA/Spring Problems with persisting (sorted) collection

问题

以下是翻译好的内容:

最初我想将这写成一个问题,但在撰写这篇帖子时,我找到了一个解决方案。如果这里有人有更好的解决方案,因为我的解决方案有点丑陋,可以随意发表。否则,我将把我的解决方案存档在这里,以便其他遇到类似问题的人会知道。(解决方案可在底部找到。)

我有以下需求需要满足:

  • 在网页上显示所有“模块”并带有复选框
  • 按以下两个子集对模块进行排序:
    1. 要显示在顶部的子集:所有已选择的模块,因此在“信息实体”上保持在“模块”集合内
    2. 要显示在下方的子集:所有尚未被选择的模块(带有ID > 0)并直接从数据库中检索
  • 对这些子集的每个部分进行字母表排序(不区分大小写)

结果应该如下所示:

  1. [x] aaaModule
  2. [x] AbbModule
  3. [x] aBcModule
  4. [ ] aabModule
  5. [ ] acbModule

请注意,排序需要不区分大小写,这就是为什么 @OrderBy 不能满足要求。因此,我选择实现 Comparable 接口到实体类,以便对其进行排序,并在 @ManyToMany 映射上使用 Hibernate 的 @SortNatural 注解。

我的当前解决方案(当然是简化的)如下所示:

在 Information.java 实体内:

  1. @ManyToMany
  2. @JoinTable(
  3. name = "bla_modules",
  4. schema = "blaSchema",
  5. joinColumns = { @JoinColumn(name = "bla_info_id") },
  6. inverseJoinColumns = { @JoinColumn(name = "bla_module_id") }
  7. )
  8. @SortNatural
  9. private Set<Module> modules = new TreeSet<>();

在 Controller.java 内(在 GET 映射中):

  1. Set<Module> modulesToDisplay = new LinkedHashSet<>();
  2. modulesToDisplay.addAll(currentInformation.getModules());
  3. modulesToDisplay.addAll(moduleRepository.findByIdGreaterThanOrderByLabelAsc(0));
  4. model.addAttribute("modulesToDisplay", modulesToDisplay);

在 information.jsp 内:

  1. <form:form method="POST" action="/informations.bla/editInformation" modelAttribute="currentInformation">
  2. <ul style="list-style-type:none">
  3. <c:forEach items="${modulesToDisplay}" var="module">
  4. <li>
  5. <div class="inline">
  6. <form:checkbox path="modules" value="${module}" label="[${module.shortLabel}] ${module.label}"/>
  7. </div>
  8. </li>
  9. </c:forEach>
  10. </ul>
  11. <input type="submit" value="Save">
  12. </form:form>

在 Controller.java 内(在 POST 映射中):

  1. public String editInformation(@ModelAttribute Information submittedInformation) {
  2. 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:
    1. Subset to be displayed at the top: All the modules that are selected and thus held inside the "modules" collection on the Information-Entity
    2. 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:

  1. [x] aaaModule
  2. [x] AbbModule
  3. [x] aBcModule
  4. [ ] aabModule
  5. [ ] 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:

  1. @ManyToMany
  2. @JoinTable(
  3. name = &quot;bla_modules&quot;,
  4. schema = &quot;blaSchema&quot;,
  5. joinColumns = { @JoinColumn(name = &quot;bla_info_id&quot;) },
  6. inverseJoinColumns = { @JoinColumn(name = &quot;bla_module_id&quot;) }
  7. )
  8. @SortNatural
  9. private Set&lt;Module&gt; modules = new TreeSet&lt;&gt;();

Inside the Controller.java (in the GET-Mapping):

  1. Set&lt;Module&gt; modulesToDisplay = new LinkedHashSet&lt;&gt;();
  2. modulesToDisplay.addAll(currentInformation.getModules());
  3. modulesToDisplay.addAll(moduleRepository.findByIdGreaterThanOrderByLabelAsc(0));
  4. model.addAttribute(&quot;modulesToDisplay&quot;, modulesToDisplay);

Inside the information.jsp:

  1. &lt;form:form method=&quot;POST&quot; action=&quot;/informations.bla/editInformation&quot; modelAttribute=&quot;currentInformation&quot;&gt;
  2. &lt;ul style=&quot;list-style-type:none&quot;&gt;
  3. &lt;c:forEach items=&quot;${modulesToDisplay}&quot; var=&quot;module&quot;&gt;
  4. &lt;li&gt;
  5. &lt;div class=&quot;inline&quot;&gt;
  6. &lt;form:checkbox path=&quot;modules&quot; value=&quot;${module}&quot; label=&quot;[${module.shortLabel}] ${module.label}&quot;/&gt;
  7. &lt;/div&gt;
  8. &lt;/li&gt;
  9. &lt;/c:forEach&gt;
  10. &lt;/ul&gt;
  11. &lt;input type=&quot;submit&quot; value=&quot;Save&quot;&gt;
  12. &lt;/form:form&gt;

Inside the Controller.java (the POST-Mapping):

  1. public String editInformation(@ModelAttribute Information submittedInformation) {
  2. 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 内部,只需执行以下操作:

  1. submittedInformation.setModules(new TreeSet&lt;Module&gt;(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:

  1. submittedInformation.setModules(new TreeSet&lt;Module&gt;(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.

huangapple
  • 本文由 发表于 2020年9月4日 22:17:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/63742919.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定