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

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

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

问题

以下是翻译好的内容:

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

我有以下需求需要满足:

  • 在网页上显示所有“模块”并带有复选框
  • 按以下两个子集对模块进行排序:
    1. 要显示在顶部的子集:所有已选择的模块,因此在“信息实体”上保持在“模块”集合内
    2. 要显示在下方的子集:所有尚未被选择的模块(带有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:
    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:

[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 = &quot;bla_modules&quot;, 
    schema = &quot;blaSchema&quot;,
    joinColumns = { @JoinColumn(name = &quot;bla_info_id&quot;) }, 
    inverseJoinColumns = { @JoinColumn(name = &quot;bla_module_id&quot;) }
)
@SortNatural
private Set&lt;Module&gt; modules = new TreeSet&lt;&gt;();

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

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

Inside the information.jsp:

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

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&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:

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:

确定