【问题标题】:Java Spring: Enum keys in (LinkedHash)Map are not orderedJava Spring:(LinkedHash)Map中的枚举键未排序
【发布时间】:2020-04-08 19:48:30
【问题描述】:

我的实体 Mealplan 有一个 mealsPerWeek 属性作为 LinkedHashMap。

@Entity
public class Mealplan {

@Id
private int id;

@ManyToMany
private Map<Weekday, Meal> mealsPerWeek = new LinkedHashMap<>();

public Map<Weekday, Meal> getMealsPerWeek() {
    return mealsPerWeek;
}

}

map属性的key是Weekday,是一个枚举:

public enum Wochentag {
    Monday, Tuesday, Wednesday, Thursday, Friday
}

现在我希望在我的 API 中,Meals 会以枚举的正确顺序显示。但它显示得很随机:

{
id: 1,
mealsPerWeek: {
  Tuesday: {
   id: 3,
   name: "Salad",
   preis: 3,
   art: "vegan"
},
  Monday: {
   id: 4,
   name: "Linsensuppe",
   preis: 23.5,
   art: "vegan"
},

如何在我的 REST API 中对其进行排序,以便以正确的顺序显示密钥?

编辑:我在每次应用程序启动时通过data.sql 插入对象,并意识到每次顺序都不同。

源代码:https://gitlab.com/joshua.olberg/java-spring-backend

【问题讨论】:

  • LinkedHashMap 按键插入映射的顺序排列。它没有排序。您是否希望在创建新对象或查询 Mealplan 时对其进行排序?
  • 问题将是虽然迭代顺序是填充顺序,但填充是由 JPA 实现完成的 - Hibernate 或您正在使用的任何东西。这不是一个答案,因为我不确定如何将排序顺序应用于实体中的 Map 字段 - 不过有人会知道。
  • 感谢您的快速回答。那么,在客户端对密钥进行排序会更好吗? @Lesiak 是的,我希望在创建新对象时对其进行排序。但它始终是相同的顺序,并且始终是每个键一个对象。
  • 您可以对枚举使用顺序值,并在顺序值上使用带有比较器的树形图。
  • 您在创建新对象时说 vorderbis 错误。这是令人惊讶的。您能告诉我们您是如何构建它的吗?

标签: java spring spring-boot spring-data-jpa spring-rest


【解决方案1】:

问题在于,当从 db 加载地图(通常是所有集合)时,hibernate 使用自己的集合类型。

https://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/domain/collections.html

Hibernate 使用自己的集合实现,这些实现丰富了延迟加载、缓存或状态变化检测语义。因此,必须将持久性集合声明为接口类型。实际的接口可能是 java.util.Collection、java.util.List、java.util.Set、java.util.Map、java.util.SortedSet、java.util.SortedMap 甚至其他对象类型(这意味着您将拥有编写 org.hibernate.usertype.UserCollectionType 的实现。

如下例所示,使用接口类型而不是集合实现很重要,正如实体映射中声明的那样。

示例 1. Hibernate 使用自己的集合实现

@Entity(name = "Person")
public static class Person {
    @Id
    private Long id;

  @ElementCollection
    private List<String> phones = new ArrayList<>();

  public List<String> getPhones() {
        return phones;
    }
}

Person person = entityManager.find( Person.class, 1L );
//Throws java.lang.ClassCastException: org.hibernate.collection.internal.PersistentBag cannot be cast to java.util.ArrayList
ArrayList<String> phones = (ArrayList<String>) person.getPhones();

因此:

private Map<Weekday, Meal> mealsPerWeek = new LinkedHashMap<>();

将被 PersistentMap 取代。 这就是使用 EnumMap 或 TreeMap 初始化 Map 失败的原因。

您希望 Hibernate 使用 PersistentSortedMap,并且为此

  • 使用正确的接口SortedMap
  • 添加@SortNatural@SortComparator

【讨论】:

  • 那么 Hibernate 会遵守 SortedMap 声明吗?我想这无济于事,因为无论如何它都会定义排序顺序。真烦人!
  • @Chris:可以通过注解定义排序顺序(SortNatural 或 SortComparator,也有Sort,但不推荐使用)
  • 感谢您的详细解答!有趣的事实:在不更改其他代码或接口的情况下使用 @SortNatural 也可以正常工作。你知道为什么吗?如果可行,我可以继续使用 LinkedHashMap 吗?
  • 我鼓励你保持接口、你的实现和休眠实现同步。这样 1. 新实例(在您的代码中使用 new 创建,使用您定义的 iplementation,而不是休眠实例)将与从数据库加载的实例一致。 2. 检测已经很混乱了,我敢打赌,如果您在此基础上添加不一致的类型,维护此代码的人会非常感激(您不是在混淆代码竞争,是吗?)
  • @Lesiak 是的。 TreeMap 确实有效(使用 @SortNatural 注释)-我注意到 HashMap 有效,但随后我的 @PostMapping 不再有效-我想我会继续使用 TreeMap。
【解决方案2】:

EnumMap 可以帮助你。迭代顺序基于枚举中声明的顺序 - https://docs.oracle.com/javase/7/docs/api/java/util/EnumMap.html

【讨论】:

  • 我做到了:私人Map&lt;Weekday, Meal&gt; mealsPerWeek = new EnumMap&lt;Weekday, Meal&gt;(Weekday.class);,但它仍然没有排序。 ://
  • 啊该死的,我以为这样就可以了 - 抱歉。
  • 现在很好奇为什么它不工作但我的笔记本电脑手边没有。我很想在 ObjectMapper 中设置一个断点,以查看它是如何序列化该映射的,或者只查看源代码。
  • 嗯,用手机快速浏览一下 GitHub 上的源代码让我觉得调试会更容易到达正确的位置。
  • 嘿,我已经在开始代码中添加了我的源代码,但您可能会感到困惑,因为有些关键字是德语。
猜你喜欢
  • 2011-08-29
  • 1970-01-01
  • 1970-01-01
  • 2012-08-20
  • 1970-01-01
  • 2016-04-25
  • 1970-01-01
  • 2022-12-31
  • 2014-07-12
相关资源
最近更新 更多