【问题标题】:How do I map a List of Maps in Hibernate using annotations?如何使用注释在 Hibernate 中映射地图列表?
【发布时间】:2015-02-09 21:51:41
【问题描述】:

我正在尝试使用 Hibernate 注释将地图列表映射到一个类中。

我希望我的最后一堂课看起来像这样:

@Entity
@Table(name = "PERSON")
public class Person {
    @Id
    @GeneratedValue
    private long id;

    @OneToMany(mappedBy = "person")
    private List<Map<String, Trip>> trips; 
}

Trip 表有一个行程编号,每个行程的多行包含行程经过的状态。

@Entity
@Table(name = "TRIP")
public class Trip {
    @Id
    @GeneratedValue
    private long id;

    @Column(name = "PERSON_ID")
    private Person person;

    @Column(name = "TRIP_NO")
    private int trip_no;

    @Column(name = "STATE")
    private String state;

    ...
}

所以 TRIP 表有这些列:

Name         Type
ID           NUMBER(9)
PERSON_ID    NUMBER(9)
TRIP_NO      LONG
STATE        VARCHAR2(16)
.
.
.

ID  PERSON_ID  TRIP_NO  STATE ...
1   1          1        MN    ...
2   1          1        WI    ...
3   1          2        ND    ...
4   1          2        MT    ...
5   2          1        IA    ...

所以在Map的List中,TRIP_NO是List的索引,STATE是Map的key。 1人去了2趟,第一次去明尼苏达州和威斯康星州,第二次去北达科他州和蒙大拿州。第 2 个人去过爱荷华州 1 次。

我在浏览如何指定此配置时遇到问题。

第二个问题:没有 JPA 2 可以做到吗?

【问题讨论】:

  • @OneToMany 注释和朋友描述了数据库实体之间的关系。我无法确定 Map&lt;String, Trip&gt; 是什么实体。
  • Map 正在映射一组以状态为键的行程数据。因此,地图将包含所有具有相同 TRIP_NO 的行程,以及由行程所在州键入的人员。所以,person1.getTrips().get(1) is {{"MN", tripdata1},{"WI", tripdata2}}person1.getTrips().get(2) is {{"ND", tripdata1}, {"MT", tripdata2}}person2.getTrips().get(1) is {{"IA", tripdata1}}.
  • 啊,我明白了。那么您的 TRIP 表中的信息太多了。它包含旅行和旅行目的地实体。请参阅我发布的答案。

标签: java hibernate jakarta-ee jpa


【解决方案1】:

这是我的方法:

// composite primary key
public class TripId implements Serializable {
    private int id;
    private long trip_no;

    public TripId(int id, long trip_no) {
        this.id = id;
        this.trip_no = trip_no;
    }

    // getters, setters, hashCode, equals
}

编辑:在威尔士巴德从下面的 cmets 提出问题后,我不得不更新实体模型。变化是:

  • Person 实体:Map&lt;String, Trip&gt; trips; 替换为 List&lt;Trip&gt; trips;
  • Trip 实体:添加了 String state; 字段以替换 Person 实体中的 trips
  • 用于填充数据/输出模式的构造函数/设置器/代码 sn-p 已相应更新
@Entity
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
    private List<Trip> trips;

    public Person() {
        this.trips = new List<>();
    }

    public void setTrips(List<Trip> trips) {
        this.trips = trips;
    }

    ...
}
@Entity
@IdClass(TripId.class)
public class Trip {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @Id
    private long trip_no;

    private String state;

    @ManyToOne
    private Person person;

    public Trip() {

    }

    public Trip(String state, long trip_no, Person person) {
        this.state = state;
        this.trip_no = trip_no;
        this.person = person;
    }

    ...
}

我们现在可以使用上面的 ORM 并填充一些数据:

Person person1 = new Person();
Person person2 = new Person();

Trip mn1Trip1 = new Trip("MN", 1, person1);
Trip wi1Trip1 = new Trip("WI", 1, person1);
Trip nd2Trip1 = new Trip("ND", 2, person1);
Trip mt2Trip1 = new Trip("MT", 2, person1);
Trip ia1Trip2 = new Trip("IA", 1, person2);
// more than one trip by a person goes to the same state
Trip mn2Trip1 = new Trip("MN", 2, person1);
Trip ia2Trip2 = new Trip("IA", 2, person2);
Trip mn1Trip2 = new Trip("MN", 1, person2);

person1.setTrips(Arrays.asList(mn1Trip1, wi1Trip1, nd2Trip1, mt2Trip1, mn2Trip1));
person2.setTrips(Arrays.asList(ia1Trip2, ia2Trip2, mn1Trip2));

em.getTransaction().begin();
em.persist(person1);
em.persist(person2);
em.getTransaction().commit();

最后,我们可以查询数据库以查看架构以及数据的填充方式:

Person table
============
id
--
 1
 7

Trip table
===========
id trip_no person_id state
-- ------- --------- -----
  9       2 IA            7
  4       2 ND            1
  8       1 IA            7
  6       2 MN            1
  2       1 MN            1
 10       1 MN            7
  3       1 WI            1
  5       2 MT            1

注意:ID 不同,但架构已正确创建并且数据已按预期填充。

至于下面评论中的问题:

// Person class have only trips associated with that person
String jpql1 = "SELECT p.trips FROM Person p WHERE p.id = 1";
List<Trip> trips = em.createQuery(jpql1, Trip.class)
                     .getResultList();

// the code can look at the Trips to find out the trip_no
String jpql2 = "SELECT t.trip_no FROM Trip t JOIN Person p " +
               "WHERE p.id = :id AND t.state = :state";
List<Long> trip_nos = em.createQuery(jpql2, Long.class)
                        .setParameter("id", 1)
                        .setParameter("state", "MN")
                        .getResultList();

关于第二个问题:

没有 JPA 2 也可以吗?

您可以尝试使用普通的旧 JDBC 并使用 java.sql.Statement 插入 SQL 语句。

【讨论】:

  • 使用 TripId 匹配数据库架构,但不匹配 OP 想要的集合映射。 OP 想要映射 List&lt;Map&lt;String, Trip&gt;&gt; 这是不可能的。
  • Person 类中的 Map 是否只有与该人相关的行程?并且可以看一下Trips的代码来找出trip_no?如果一个人不止一次旅行去同一个州怎么办?
  • @mpkorstanje:这是不可能的。我完全同意。这就是为什么我们试图提供一个至少适合 Welshbard 模式的实体模型,不是吗?
  • @Welshbard:现在我明白你的意思了。为了满足所有条件,我不得不稍微改变一下我的方法(顺便简化了实体模型)。您上述问题的答案与更新后的代码 sn-ps 一起放置(您可以轻松找到 trip_no,它仅取决于查询的输入条件)。
【解决方案2】:

我不确定,因为之前没有测试过,也没有经历过完全相同的事情,但我认为使用Map&lt;K,V&gt;s 的List 作为关联字段会有点复杂,您可以使用@987654323 @rel 代表 Trip 实体中的 person,然后是 One-to-Many 代表 Trips 个人实体:

@Entity
@Table(name = "TRIP")
public class Trip {    
    /*
    code
    */
    @ManyToOne(cascade=CascadeType.ALL)
    private Person person;

    /*
    code
    */
}

并且在Person 实体中不要使用List&lt;Map&lt;String, Trip&gt;&gt; 而不是为Map&lt;String, Trip&gt; 使用@MapKey 注释:

@Entity
@Table(name = "PERSON")
public class Person {
    @Id
    @GeneratedValue
    private long id;

    @OneToMany(mappedBy = "person")
    @MapKey(name = "state")
    private Map<String, Trip> trips; 
}

不过,您需要查看更多内容,希望对您有所帮助。

【讨论】:

  • 如何处理不唯一的 TRIP_NO 以制作列表?还是我必须@PostLoad 来处理地图以获取旅行地图列表?让我在问题中添加一些内容以使这一点更清楚。
  • 如果一个人不止一次旅行到同一个州怎么办?
  • 哦,很抱歉延迟回复您的 cmets,我已经有几天没有上 Stackoverflow 了。你的场景有点复杂,需要更多的审查,我很抱歉目前没有专注和时间(我可能几天后),我希望你能尽快修复它,如果你也可以编辑或完成我的答案很有用,祝你好运
【解决方案3】:

除非你改变你的数据库架构,否则你想要的是不可能的。请注意 Hibernate 文档如何忽略集合类型作为集合的有效内容。

Hibernate 自然也允许持久化集合。这些 持久性集合可以包含几乎任何其他 Hibernate 类型, 包括:基本类型、自定义类型、组件和引用 其他实体。 From Hibernate Collection Mapping

你不能用 hibernate 来做这件事,因为你的数据库设计很不稳定。基本上,您的 TRIP 表中包含太多信息,因为它存储了行程和目的地。

理想情况下,您有旅行和目的地。

Trip 由 id、person_id 和 trip_no 组成,用于通过@OrderBy 在列表中进行排序。

目的地包括一个id、一个trip_id、供@MapKey使用的state_id以及目的地的其他信息。

Java 代码是这样的。没有检查任何错误。只是给你一个想法。

@Entity
@Table(name = "PERSON")
public class Person {
    @Id
    @GeneratedValue
    private long id;

    @OneToMany(mappedBy = "person")
    @OrderBy(clause=trip_no)
    private List<Trip> trips; 
}

@Entity
@Table(name = "TRIP")
public class Trip {
    @Id
    @GeneratedValue
    private long id;

    @Column(name = "PERSON_ID")
    private Person person;

    @Column(name = "TRIP_NO")
    private int trip_no;

    @OneToMany(mappedBy = "trip")
    @MapKey(name = "state")
    private Map<String,Destination> destinations
}

@Entity
@Table(name = "DESTINATION")
public class Destination {

    private Trip trip;

    @Column(name = "STATE")
    private String state;

    ...
}

【讨论】:

    猜你喜欢
    • 2013-11-25
    • 2010-10-22
    • 1970-01-01
    • 2011-01-20
    • 1970-01-01
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多