如何使用注释在 Hibernate 中映射一个映射列表?
How do I map a List of Maps in Hibernate using annotations?
我正在尝试使用 Hibernate 注释将映射列表映射到 class。
我希望我的最终 class 看起来像这样:
@Entity
@Table(name = "PERSON")
public class Person {
@Id
@GeneratedValue
private long id;
@OneToMany(mappedBy = "person")
private List<Map<String, Trip>> trips;
}
行程 table 有一个行程编号,每个行程都有多行,其中包含行程经过的州。
@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 table 有这些列:
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 ...
所以在Maps的List中,TRIP_NO是List的index,STATE是Map的key。人 1 进行了两次旅行,第一次去明尼苏达州和威斯康星州,第二次去北达科他州和蒙大拿州。第 2 个人去了爱荷华州 1 次。
我在浏览以查找如何指定此配置时遇到问题。
第二个问题:没有JPA 2可以吗?
我不确定,因为没有测试过,也没有经历过完全相同的事情,但我认为使用 Map<K,V>
的 List
作为关联字段会有点复杂,你可以在 Trip
实体中有 person
的 Many-to-One
关系,然后在个人实体中有 Trips
的 One-to-Many
:
@Entity
@Table(name = "TRIP")
public class Trip {
/*
code
*/
@ManyToOne(cascade=CascadeType.ALL)
private Person person;
/*
code
*/
}
并且在 Person
实体中不要使用 List<Map<String, Trip>>
而是使用 @MapKey 注释 Map<String, Trip>
:
@Entity
@Table(name = "PERSON")
public class Person {
@Id
@GeneratedValue
private long id;
@OneToMany(mappedBy = "person")
@MapKey(name = "state")
private Map<String, Trip> trips;
}
但是你还需要复习更多,我希望这会有所帮助。
除非你改变你的数据库模式,否则你想要的是不可能的。请注意 Hibernate 文档如何忽略集合类型作为集合的有效内容。
Naturally Hibernate also allows to persist collections. These
persistent collections can contain almost any other Hibernate type,
including: basic types, custom types, components and references to
other entities.
From Hibernate Collection Mapping
你不能用 hibernate 做到这一点,因为你的数据库设计很奇怪。基本上,您的 TRIP table 包含太多信息,因为它同时存储了行程和目的地。
理想情况下,您有行程和目的地。
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;
...
}
这是我的方法:
// 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
}
编辑:在 Welshbard 从下面的评论中提出问题后,我不得不更新实体模型。变化是:
Person
实体:Map<String, Trip> trips;
替换为 List<Trip> trips;
Trip
实体:添加 String state;
字段作为 Person
实体 中 trips
键的替换
- 用于填充数据/输出模式的构造函数/setters/代码片段已相应更新
@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();
关于第二个问题:
Can it be done without JPA 2?
您可以尝试使用普通的旧 JDBC 并使用 java.sql.Statement
插入 SQL 语句。
我正在尝试使用 Hibernate 注释将映射列表映射到 class。
我希望我的最终 class 看起来像这样:
@Entity
@Table(name = "PERSON")
public class Person {
@Id
@GeneratedValue
private long id;
@OneToMany(mappedBy = "person")
private List<Map<String, Trip>> trips;
}
行程 table 有一个行程编号,每个行程都有多行,其中包含行程经过的州。
@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 table 有这些列:
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 ...
所以在Maps的List中,TRIP_NO是List的index,STATE是Map的key。人 1 进行了两次旅行,第一次去明尼苏达州和威斯康星州,第二次去北达科他州和蒙大拿州。第 2 个人去了爱荷华州 1 次。
我在浏览以查找如何指定此配置时遇到问题。
第二个问题:没有JPA 2可以吗?
我不确定,因为没有测试过,也没有经历过完全相同的事情,但我认为使用 Map<K,V>
的 List
作为关联字段会有点复杂,你可以在 Trip
实体中有 person
的 Many-to-One
关系,然后在个人实体中有 Trips
的 One-to-Many
:
@Entity
@Table(name = "TRIP")
public class Trip {
/*
code
*/
@ManyToOne(cascade=CascadeType.ALL)
private Person person;
/*
code
*/
}
并且在 Person
实体中不要使用 List<Map<String, Trip>>
而是使用 @MapKey 注释 Map<String, Trip>
:
@Entity
@Table(name = "PERSON")
public class Person {
@Id
@GeneratedValue
private long id;
@OneToMany(mappedBy = "person")
@MapKey(name = "state")
private Map<String, Trip> trips;
}
但是你还需要复习更多,我希望这会有所帮助。
除非你改变你的数据库模式,否则你想要的是不可能的。请注意 Hibernate 文档如何忽略集合类型作为集合的有效内容。
Naturally Hibernate also allows to persist collections. These persistent collections can contain almost any other Hibernate type, including: basic types, custom types, components and references to other entities. From Hibernate Collection Mapping
你不能用 hibernate 做到这一点,因为你的数据库设计很奇怪。基本上,您的 TRIP table 包含太多信息,因为它同时存储了行程和目的地。
理想情况下,您有行程和目的地。
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;
...
}
这是我的方法:
// 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
}
编辑:在 Welshbard 从下面的评论中提出问题后,我不得不更新实体模型。变化是:
Person
实体:Map<String, Trip> trips;
替换为List<Trip> trips;
Trip
实体:添加String state;
字段作为Person
实体 中 - 用于填充数据/输出模式的构造函数/setters/代码片段已相应更新
trips
键的替换
@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();
关于第二个问题:
Can it be done without JPA 2?
您可以尝试使用普通的旧 JDBC 并使用 java.sql.Statement
插入 SQL 语句。