2015-04-13 3 views
3

Я пытаюсь сохранить сущность, которая имеет карту объектов для перечисления в хранилище данных Google App Engine. Классы сущностей аннотируются с использованием JPA.JPA Отношение многих-ко-многим с дополнительным столбцом перечисления

класс Event

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

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 Enum является persistable типа данных по умолчанию. Поэтому я не понимаю, почему я получаю сообщение об ошибке "Response is not a supported property type".
Я подозревал, что проблема связана с классом User. Возможно, ассоциации от Event to Users недостаточно, и у пользователя также должна быть ассоциация с событиями. Поэтому я добавил 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!

ответ

2

Проблема создания двух классов, у которых есть отношение «Много-ко-многим», но таблица реляционных объединений имеет дополнительные данные, является частой проблемой.

Хорошие примеры на эту тему Я нашел в WikiBooks - Java Persistence/Many-To-Many и в статье Mapping a Many-To-Many Join Table with extra column using JPA от Giovanni Gargiulo. Ссылки в официальной документации, которые я нашел много, намного позже: Unowned Entity Relationships in JDO и Unsupported Features of JPA 2.0 in AppEngine.

В этом случае лучшим решением является создание класса, который моделирует таблицу соединений.

Значит, класс EventUserResponse будет создан. Он будет иметь много-к-одному для события и пользователя и атрибут для дополнительных данных. Событие и пользователь будут иметь один-ко-многим для EventUserResponse. К сожалению, мне не удалось сопоставить составной первичный ключ для этого класса. И DataNucleus Enhancer отказался расширять класс сущности без первичного ключа. Поэтому я использовал простой автогенератор.

Результат должен быть, как
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. Это отличная работа, чтобы спасти нас от шаблона.

класс Event

@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" /> 

наконец, что касается кода в вопросе, не разрешается иметь первичный ключ с именем key в AppEngine.

Смежные вопросы