如何使用JPA持久化两个实体对象

7
我在我的Web应用程序中使用JPA,但无法弄清如何持久化彼此相关的两个新实体。这是一个例子:
这是两个实体:
消费者(Consumer):具有id和其他一些值。
ProfilePicture:将消费者(Consumer)的id用作自己的主键和外键。(因为没有消费者就不会有ProfilePicture,并且并非每个消费者都有ProfilePicture)
我使用NetBeans生成了实体类和会话bean(facade)。

Consumer.java

@Entity
@Table(name = "Consumer")
@NamedQueries({...})
public class Consumer implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "id")
    private Integer id;

    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 50)
    @Column(name = "userName")
    private String userName;     

    @OneToOne(cascade = CascadeType.ALL, mappedBy = "consumer")
    private ProfilePicture profilePicture;

    /* and all the basic getters and setters */
    (...)
}

ProfilePicture.java

@Entity
@Table(name = "ProfilePicture")
@XmlRootElement
@NamedQueries({...})
public class ProfilePicture implements Serializable {

    @Id
    @Basic(optional = false)
    @NotNull
    @Column(name = "consumerId")
    private Integer consumerId;

    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 255)
    @Column(name = "url")
    private String url;

    @JoinColumn(name = "consumerId", referencedColumnName = "id", insertable = false, updatable = false)
    @OneToOne(optional = false)
    private Consumer consumer;

    /* and all the basic getters and setters */
    (...)
}

所以当我想创建一个具有其个人资料图片的消费者时,我认为应该像这样做:
   ProfilePicture profilePicture = new ProfilePicture("http://www.url.to/picture.jpg");  // create the picture object
   Consumer consumer = new Consumer("John Doe"); // create the consumer object

   profilePicture.setConsumer(consumer);        // set the consumer in the picture (so JPA can take care about the relation      
   consumerFacade.create(consumer);             // the facade classes to persist the consumer
   profilePictureFacade.create(profilePicture);  // and when the consumer is persisted (and has an id) persist the picture

我的问题

我尝试了几乎所有的组合,但JPA似乎无法自动关联这两个实体。大多数情况下,我会遇到以下错误:

 EJB5184:A system exception occurred during an invocation on EJB ConsumerFacade, method: public void com.me.db.resources.bean.ConsumerFacade.create(com.mintano.backendclientserver.db.resources.entity.Consumer)
 (...) 
Bean Validation constraint(s) violated while executing Automatic Bean Validation on callback event:'prePersist'. Please refer to embedded ConstraintViolations for details.

据我所了解的问题,是由于 ProfilePicture 不知道 Consumer 的 id,因此实体无法持久化。
唯一有效的方法是先持久化 Consumer,将其id设置为 ProfilePicture 的id,然后再持久化图片:
   ProfilePicture profilePicture = new ProfilePicture("http://www.url.to/picture.jpg");  // create the picture object
   Consumer consumer = new Consumer("John Doe"); // create the consumer object

   consumerFacade.create(consumer);             // the facade classes to persist the consumer
   profilePicture.setConsumerId(consumer.getId()); // set the consumer's new id in the picture     

   profilePictureFacade.create(profilePicture);  // and when the consumer is persisted (and has an id) persist the picture

然而这两个表只是一个例子,数据库自然更加复杂,手动设置id似乎非常不灵活,我担心会使事情变得过于复杂。特别是因为我不能在一个事务中持久化所有实体(这似乎非常低效)。
我做得对吗?还是有另一种更标准的方法?
编辑:我的解决方案
正如FTR建议的那样,一个问题是ProfilePicture表缺少id(我使用Consumer.id作为外键和主键)。
现在表格看起来像这样:
+-----------------+ +--------------------+ | Consumer | | ProfilePicture | +-----------------+ +--------------------+ | id (PK) |_ | id (PK) | | userName | \_| consumerId (FK) | +-----------------+ | url | +--------------------+
然后Alan Hay告诉我始终将添加/删除关系封装起来,然后可以确保正确性,我也这样做了。

Consumer.java

public void addProfilePicture(ProfilePicture profilePicture) {
    profilePicture.setConsumerId(this);  
    if (profilePictureCollection == null) {
        this.profilePictureCollection = new ArrayList<>();
    }
    this.profilePictureCollection.add(profilePicture);
}

由于ProfilePicture现在有自己的ID,它变成了OneToMany关系,因此每个消费者现在可以拥有许多个头像。这不是我最初想要的,但我可以接受 :) 因此,我不能仅将头像设置为消费者,而必须将其添加到图片集合中(如上所示)。
这是我实现的唯一附加方法,现在它可以工作了。再次感谢您的所有帮助!

1
如果我看得正确,ProfilePicture 没有自动生成的 ID,所以除非你明确设置一个,否则这应该会失败。将主键和外键分开是否是一个选项? - ftr
@ftr:我刚试着给ProfilePicture设置它自己的主键。结果我为每个消费者得到了一个ProfilePictures的集合,虽然我知道每个消费者最多只会有一张图片,但这也不是很糟糕。当我调用"profilePicture.setConsumerId(consumer);"时,它似乎可以正常工作。 - GameDroids
2个回答

2

当持久化非拥有关系的实例(包含“mappedBy”且在您的情况下为Consumer)时,您必须始终确保关系的两侧都设置为按预期进行级联操作。

当然,您应该始终这样做以确保您的领域模型是正确的。

Consumer c = new Consumer();
ProfilePicure p = new ProfilePicture();
c.setProfilePicture(p);//see implementation
//persist c

Consumer.java

    @Entity
    @Table(name = "Consumer")
    @NamedQueries({...})
    public class Consumer implements Serializable {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Basic(optional = false)
        @Column(name = "id")
        private Integer id;

        @Basic(optional = false)
        @NotNull
        @Size(min = 1, max = 50)
        @Column(name = "userName")
        private String userName;     

        @OneToOne(cascade = CascadeType.ALL, mappedBy = "consumer")
        private ProfilePicture profilePicture;

        public void setProfilePicture(ProfilePicture profilePicture){
            //SET BOTH SIDES OF THE RELATIONSHIP
            this.profilePicture = profilePicture;
            profilePicture.setConsumer(this);
        }
}

始终封装与关系相关的添加/删除操作,这样可以确保正确性:

public class Parent{
private Set<Child> children;

public Set<Child> getChildren(){
    return Collections.unmodifiableSet(children); //no direct access:force clients to use add/remove methods
}

public void addChild(Child child){
    child.setParent(this); 
    children.add(child);
}

public class Child(){
    private Parent parent;
}

谢谢,这绝对是正确的方向 :) 所以我像"ftr"告诉我的那样为ProfilePicture添加了一个主键,并像你建议的那样在Consumer中添加了一个addProfilePicture()方法。现在我对这个解决方案感到很舒适。我创建了ConsumerProfilePicture,只需调用consumer.addProfilePicture(profilePicture),两个实体就会按照应该的方式插入! - GameDroids

0

每次只能持久化一个对象及其子对象。所以我认为这个应该可以工作:

ProfilePicture profilePicture = new ProfilePicture("http://www.url.to/picture.jpg");  // create the picture object
Consumer consumer = new Consumer("John Doe"); // create the consumer object
consumer.setProfilePicture(profilePicture); 
consumerFacade.create(consumer);

我刚测试了一下,但是却只收到了 javax.ejb.EJBException 的异常信息:...while executing Automatic Bean Validation on callback event:'prePersist'。并且很奇怪的是:Consumer 没有被插入到数据库中。你知道为什么会触发 prePersist 吗?我的意思是它甚至已经设置为 cascade = CascadeType.ALL 了。 - GameDroids
好的,我收到你的问题。你可以在ProfilePicture类中的@Column(name = "consumerId") private Integer consumerId; 中删除@NotNull注释然后尝试。因为NotNull会检查consumerId是否为空。 这个链接可能对你有帮助EJBException - Sushant Tambare
谢谢您的回答。我尝试删除@NotNull,但不幸的是错误仍然存在。我认为这是因为我的数据库中的consumerId列被设置为NOT NULL,因为它是该表的键。但是当我像“ftr”所提到的那样更改表索引时,JPA似乎能够理解两个表之间的关系。 - GameDroids
好的,太棒了。很高兴听到这个消息。谢谢。 - Sushant Tambare

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接