JPA 多对多关系与额外的枚举列
JPA Many-To-Many relationship with an extra enum column
我正在尝试将具有要枚举的对象映射的实体保存到 Google App Engine 数据存储区中。实体 classes 使用 JPA 注释。
Event class
import com.google.appengine.datanucleus.annotations.Unowned;
import com.google.appengine.api.datastore.Key;
import java.util.Map;
import javax.persistence.*;
import lombok.Builder;
import lombok.Data;
@Entity
@Builder
public @Data class Event {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
// I want a map belonging to event in order to query a particular user whether he confirmed his participation in the event
// All addressees are initially present in this map with response set to UNDEFINED
// If user has received and read notification, than the response is updated to YES, NO, or MAYBE
@Unowned
@ElementCollection
@CollectionTable(name = "user_response")
@MapKeyJoinColumn(name = "user_id")
@Enumerated
@Column(name = "response")
private Map<User, Response> addressees;
}
Response class
public enum Response {
UNDEFINED, YES, NO, MAYBE
}
我没有在 User
class 中定义任何对此地图的引用。这是单向关系。
User class
@Entity
public @Data class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
}
Event.addressees
专栏似乎很棘手。所以我 运行 我的测试来检查是否一切正常。好吧,事实并非如此。当我尝试将 Event
实体保存到数据存储时出现异常:
java.lang.IllegalArgumentException: addressees: Response is not a supported property type.
at com.google.appengine.api.datastore.DataTypeUtils.checkSupportedSingleValue(DataTypeUtils.java:235)
at com.google.appengine.api.datastore.DataTypeUtils.checkSupportedValue(DataTypeUtils.java:199)
at com.google.appengine.api.datastore.DataTypeUtils.checkSupportedValue(DataTypeUtils.java:173)
at com.google.appengine.api.datastore.DataTypeUtils.checkSupportedValue(DataTypeUtils.java:148)
at com.google.appengine.api.datastore.PropertyContainer.setProperty(PropertyContainer.java:101)
根据 DataNucleus 的说法,默认情况下,枚举是一种可持久化的数据类型。所以我不明白为什么我会收到错误消息 "Response is not a supported property type"
.
我怀疑问题出在用户 class 上。可能 Event 到 Users 的关联还不够,User 也应该和 Events 有关联。所以我将 events
字段添加到用户,如下所示:
@Unowned
@ElementCollection
@CollectionTable(name = "user_event_responses")
@ManyToMany(mappedBy="addressees", targetEntity = Event.class)
@MapKeyJoinColumn
@Enumerated
@Column(name = "response")
private Map<Event, Response> events;
反正没用。然后我看了类似的问题,并没有找到快速的答案。
请给我看一个多对多关系的例子,在 DataNucleus / JPA 中有一个额外的列!
创建两个具有多对多关系的 class 的问题,但关系连接 table 有额外的数据,是一个常见的问题。
我在 WikiBooks 上找到了关于这个主题的好例子 - Java Persistence / Many-To-Many and in the article Mapping a Many-To-Many Join Table with extra column using JPA by Giovanni Gargiulo. References in the official documentation I've found much, much later: Unowned Entity Relationships in JDO and Unsupported Features of JPA 2.0 in AppEngine。
In this case the best solution is to create a class that models the join table.
因此将创建一个 EventUserResponse
class。它将具有事件和用户的多对一关系,以及附加数据的属性。事件和用户将一对多到 EventUserResponse
。不幸的是,我没有设法为此 class 映射复合主键。 DataNucleus Enhancer 拒绝增强没有主键的实体 class。所以我使用了一个简单的自动生成的 ID。
结果应该是这样的
来源如下:
EventUserAssociation class
@Entity
@Table(name = "event_user_response")
@NoArgsConstructor
@AllArgsConstructor
@Getter @Setter
@EqualsAndHashCode(callSuper = true, exclude = {"attendee", "event"})
public class EventUserAssociation extends AbstractEntity {
@Unowned
@ManyToOne
@PrimaryKeyJoinColumn(name = "eventId", referencedColumnName = "_id")
private Event event;
@Unowned
@ManyToOne
@PrimaryKeyJoinColumn(name = "attendeeId", referencedColumnName = "_id")
private User attendee;
@Enumerated
private Response response;
}
如果您觉得 Lombok 注释(例如 @NoArgsConstructor
)不熟悉,您可能想看看 ProjectLombok。它很好地将我们从样板代码中拯救出来。
Event class
@Entity
@Builder
@EqualsAndHashCode(callSuper = false)
public @Data class Event extends AbstractEntity {
/* attributes are omitted */
// all addressees are initially present in this map with response set to UNDEFINED
// if user has received and read notification, than the response is updated to YES, NO, or MAYBE
@Singular
@Setter(AccessLevel.PRIVATE)
@OneToMany(mappedBy="event", cascade = CascadeType.ALL)
private List<EventUserAssociation> addressees = new ArrayList<>();
/**
* Add an addressee to the event.
* Create an association object for the relationship and set its data.
*
* @param addressee a user to whom this event notification is addressed
* @param response his response.
*/
public boolean addAddressee(User addressee, Response response) {
EventUserAssociation association = new EventUserAssociation(this, addressee, response);
// Add the association object to this event
return this.addressees.add(association) &&
// Also add the association object to the addressee.
addressee.getEvents().add(association);
}
public List<User> getAddressees() {
List<User> result = new ArrayList<>();
for (EventUserAssociation association : addressees)
result.add(association.getAttendee());
return result;
}
}
User class
@Entity
@NoArgsConstructor
@RequiredArgsConstructor
@Getter @Setter
public class User extends AbstractEntity {
/* non-significant attributes are omitted */
@Setter(AccessLevel.PRIVATE)
@Unowned
@OneToMany(mappedBy="attendee", cascade = CascadeType.ALL)
private List<EventUserAssociation> events = new ArrayList<>();
public static User find(String attribute, EntityManager em) {
/* implementation omitted */
}
}
AbstractEntity class
@MappedSuperclass
@NoArgsConstructor
@EqualsAndHashCode
public abstract class AbstractEntity {
@Id
@Column(name = "_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Getter
protected Key id;
}
EMFService class
public abstract class EMFService {
@Getter
private static final EntityManagerFactory emfInstance = Persistence.createEntityManagerFactory("transactions-optional");
}
用法示例:
EntityManager em = EMFService.getFactory().createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
User fromContact = User.find(fromId, em);
Event event = Event.builder()
/* attributes initialization */
.build();
em.persist(event);
User toUser = User.find(toId, em);
event.addAddressee(toUser, Response.UNDEFINED);
tx.commit();
} finally {
if (tx.isActive()) tx.rollback();
em.close();
}
应该允许跨组交易才能正常工作 ()。将以下 属性 添加到 persistence.xml
:
<property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true" />
最后,关于问题中的代码,在AppEngine中不允许有名称为key
的主键。
我正在尝试将具有要枚举的对象映射的实体保存到 Google App Engine 数据存储区中。实体 classes 使用 JPA 注释。
Event class
import com.google.appengine.datanucleus.annotations.Unowned;
import com.google.appengine.api.datastore.Key;
import java.util.Map;
import javax.persistence.*;
import lombok.Builder;
import lombok.Data;
@Entity
@Builder
public @Data class Event {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
// I want a map belonging to event in order to query a particular user whether he confirmed his participation in the event
// All addressees are initially present in this map with response set to UNDEFINED
// If user has received and read notification, than the response is updated to YES, NO, or MAYBE
@Unowned
@ElementCollection
@CollectionTable(name = "user_response")
@MapKeyJoinColumn(name = "user_id")
@Enumerated
@Column(name = "response")
private Map<User, Response> addressees;
}
Response class
public enum Response {
UNDEFINED, YES, NO, MAYBE
}
我没有在 User
class 中定义任何对此地图的引用。这是单向关系。
User class
@Entity
public @Data class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
}
Event.addressees
专栏似乎很棘手。所以我 运行 我的测试来检查是否一切正常。好吧,事实并非如此。当我尝试将 Event
实体保存到数据存储时出现异常:
java.lang.IllegalArgumentException: addressees: Response is not a supported property type.
at com.google.appengine.api.datastore.DataTypeUtils.checkSupportedSingleValue(DataTypeUtils.java:235)
at com.google.appengine.api.datastore.DataTypeUtils.checkSupportedValue(DataTypeUtils.java:199)
at com.google.appengine.api.datastore.DataTypeUtils.checkSupportedValue(DataTypeUtils.java:173)
at com.google.appengine.api.datastore.DataTypeUtils.checkSupportedValue(DataTypeUtils.java:148)
at com.google.appengine.api.datastore.PropertyContainer.setProperty(PropertyContainer.java:101)
根据 DataNucleus 的说法,默认情况下,枚举是一种可持久化的数据类型。所以我不明白为什么我会收到错误消息 "Response is not a supported property type"
.
我怀疑问题出在用户 class 上。可能 Event 到 Users 的关联还不够,User 也应该和 Events 有关联。所以我将 events
字段添加到用户,如下所示:
@Unowned
@ElementCollection
@CollectionTable(name = "user_event_responses")
@ManyToMany(mappedBy="addressees", targetEntity = Event.class)
@MapKeyJoinColumn
@Enumerated
@Column(name = "response")
private Map<Event, Response> events;
反正没用。然后我看了类似的问题,并没有找到快速的答案。
请给我看一个多对多关系的例子,在 DataNucleus / JPA 中有一个额外的列!
创建两个具有多对多关系的 class 的问题,但关系连接 table 有额外的数据,是一个常见的问题。
我在 WikiBooks 上找到了关于这个主题的好例子 - Java Persistence / Many-To-Many and in the article Mapping a Many-To-Many Join Table with extra column using JPA by Giovanni Gargiulo. References in the official documentation I've found much, much later: Unowned Entity Relationships in JDO and Unsupported Features of JPA 2.0 in AppEngine。
In this case the best solution is to create a class that models the join table.
因此将创建一个 EventUserResponse
class。它将具有事件和用户的多对一关系,以及附加数据的属性。事件和用户将一对多到 EventUserResponse
。不幸的是,我没有设法为此 class 映射复合主键。 DataNucleus Enhancer 拒绝增强没有主键的实体 class。所以我使用了一个简单的自动生成的 ID。
结果应该是这样的
来源如下:
EventUserAssociation class
@Entity
@Table(name = "event_user_response")
@NoArgsConstructor
@AllArgsConstructor
@Getter @Setter
@EqualsAndHashCode(callSuper = true, exclude = {"attendee", "event"})
public class EventUserAssociation extends AbstractEntity {
@Unowned
@ManyToOne
@PrimaryKeyJoinColumn(name = "eventId", referencedColumnName = "_id")
private Event event;
@Unowned
@ManyToOne
@PrimaryKeyJoinColumn(name = "attendeeId", referencedColumnName = "_id")
private User attendee;
@Enumerated
private Response response;
}
如果您觉得 Lombok 注释(例如 @NoArgsConstructor
)不熟悉,您可能想看看 ProjectLombok。它很好地将我们从样板代码中拯救出来。
Event class
@Entity
@Builder
@EqualsAndHashCode(callSuper = false)
public @Data class Event extends AbstractEntity {
/* attributes are omitted */
// all addressees are initially present in this map with response set to UNDEFINED
// if user has received and read notification, than the response is updated to YES, NO, or MAYBE
@Singular
@Setter(AccessLevel.PRIVATE)
@OneToMany(mappedBy="event", cascade = CascadeType.ALL)
private List<EventUserAssociation> addressees = new ArrayList<>();
/**
* Add an addressee to the event.
* Create an association object for the relationship and set its data.
*
* @param addressee a user to whom this event notification is addressed
* @param response his response.
*/
public boolean addAddressee(User addressee, Response response) {
EventUserAssociation association = new EventUserAssociation(this, addressee, response);
// Add the association object to this event
return this.addressees.add(association) &&
// Also add the association object to the addressee.
addressee.getEvents().add(association);
}
public List<User> getAddressees() {
List<User> result = new ArrayList<>();
for (EventUserAssociation association : addressees)
result.add(association.getAttendee());
return result;
}
}
User class
@Entity
@NoArgsConstructor
@RequiredArgsConstructor
@Getter @Setter
public class User extends AbstractEntity {
/* non-significant attributes are omitted */
@Setter(AccessLevel.PRIVATE)
@Unowned
@OneToMany(mappedBy="attendee", cascade = CascadeType.ALL)
private List<EventUserAssociation> events = new ArrayList<>();
public static User find(String attribute, EntityManager em) {
/* implementation omitted */
}
}
AbstractEntity class
@MappedSuperclass
@NoArgsConstructor
@EqualsAndHashCode
public abstract class AbstractEntity {
@Id
@Column(name = "_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Getter
protected Key id;
}
EMFService class
public abstract class EMFService {
@Getter
private static final EntityManagerFactory emfInstance = Persistence.createEntityManagerFactory("transactions-optional");
}
用法示例:
EntityManager em = EMFService.getFactory().createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
User fromContact = User.find(fromId, em);
Event event = Event.builder()
/* attributes initialization */
.build();
em.persist(event);
User toUser = User.find(toId, em);
event.addAddressee(toUser, Response.UNDEFINED);
tx.commit();
} finally {
if (tx.isActive()) tx.rollback();
em.close();
}
应该允许跨组交易才能正常工作 (persistence.xml
:
<property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true" />
最后,关于问题中的代码,在AppEngine中不允许有名称为key
的主键。