与额外的枚举列的JPA多对多关系

时间:2015-04-13 15:16:16

标签: java google-app-engine jpa enums

我试图将具有“对象映射”的实体持久保存到Google App Engine数据存储区中。实体类使用JPA注释。

  

活动类

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;

}
  

响应类

public enum Response {
    UNDEFINED, YES, NO, MAYBE
}

我还没有将User类中的任何引用定义到此地图。这是一种单向关系。

  

用户类

@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,默认情况下,枚举是一种可持久的数据类型。因此,我不明白为什么会收到错误消息“{1}}。” 我怀疑问题出在User类上。也许从事件到用户的关联是不够的,用户也应该与事件有关联。所以我已将"Response is not a supported property type"字段添加到用户,如下所示:

events

无论如何它都没有用。然后我读了类似的问题,没有找到快速回答 请给我一个与DataNucleus / JPA中额外列的多对多关系的示例!

1 个答案:

答案 0 :(得分:2)

创建两个具有多对多关系的类的问题,但关系连接表有其他数据,这是一个经常出现的问题。

我在WikiBooks上发现了这个主题的好例子 - Java Persistence / Many-To-Many和Giovanni Gargiulo撰写的文章Mapping a Many-To-Many Join Table with extra column using JPA。我在很久以后发现的官方文档中引用了Unowned Entity Relationships in JDOUnsupported Features of JPA 2.0 in AppEngine

  

在这种情况下,最好的解决方案是创建一个为连接表建模的类。

因此将创建一个EventUserResponse类。它将具有多对一到事件和用户,以及附加数据的属性。事件和用户将拥有EventUserResponse的一对多。不幸的是,我没有管理如何映射此类的复合主键。并且DataNucleus Enhancer拒绝在没有主键的情况下增强实体类。所以我使用了一个简单的自动生成ID。

结果应该像是 ER diagram

以下是来源:

  

EventUserAssociation类

@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;

}

如果您对陌生人注释(例如@NoArgsConstructor)不熟悉,您可能需要查看ProjectLombok。保存我们的样板代码确实很棒。

  

活动类

@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;
    }

}
  

用户类

@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类

@MappedSuperclass
@NoArgsConstructor
@EqualsAndHashCode
public abstract class AbstractEntity {

    @Id 
    @Column(name = "_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Getter
    protected Key id;

}
  

EMFService类

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();
}

应允许跨群组交易才能实现(what if they aren't?)。将以下属性添加到persistence.xml

<property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true" />

最后,关于问题中的代码,不允许在AppEngine中使用名为key的主键。