有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java JPA具有额外枚举列的多个关系

我正在尝试将一个实体持久化,该实体具有要枚举到Google App Engine数据存储中的对象映射。实体类使用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类中定义对此映射的任何引用。这是一种单向关系

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,默认情况下,Enum是一种持久数据类型。所以我不明白为什么我会收到错误消息说"Response is not a supported property type"
我怀疑问题出在用户类上。也许仅仅从事件到用户的关联是不够的,用户也应该有一个到事件的关联。因此,我向用户添加了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中多对多关系与额外列的示例


共 (1) 个答案

  1. # 1 楼答案

    创建两个具有多对多关系但关系联接表具有附加数据的类是一个常见的问题

    我在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

    In this case the best solution is to create a class that models the join table.

    因此将创建一个EventUserResponse类。它将有一个多对一事件和用户,以及一个用于附加数据的属性。事件和用户将对EventUserResponse进行一对多的访问。不幸的是,我没有设法为这个类映射复合主键。DataNucleus增强器拒绝增强没有主键的实体类。所以我使用了一个简单的自动生成ID

    结果应该是
    ER diagram

    以下是资料来源:

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

    应允许跨组事务处理,以使其工作(what if they aren't?)。将以下属性添加到persistence.xml

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

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