【问题标题】:How to persist two entities with JPA如何使用 JPA 持久化两个实体
【发布时间】:2014-10-06 15:02:49
【问题描述】:

我在我的 web 应用程序中使用 JPA,但我不知道如何保留两个相互关联的新实体。举个例子:

这是两个实体

+-----------------+ +--------------------+ |消费者 | |简介图片 | +-----------------+ +--------------------+ |编号 (PK) |---|消费者 ID (PPK+FK)| |用户名 | |网址 | +-----------------+ +--------------------+

Consumer 有一个id 和一些其他值。 ProfilePicture 使用 Consumerid 作为它自己的主键和外键。 (因为没有消费者,个人资料图片就不会存在,而且并非每个消费者都有个人资料图片)

我使用 NetBeans 生成实体类和会话 bean(外观)。

这就是它们的外观

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创建一个Consumer时,我想我会这样做:

   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,然后持久化图片:

   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 作为外部和主要)..

现在的表格如下所示:

+-----------------+ +--------------------+ |消费者 | |简介图片 | +-----------------+ +--------------------+ |编号 (PK) |_ |身份证 (PK) | |用户名 | \_|消费者 ID (FK) | +-----------------+ |网址 | +--------------------+

然后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 关系,所以每个 Consumer 现在可以拥有许多个人资料图片。这不是我最初的意图,但我可以接受它:) 因此我不能只为消费者设置 ProfilePicture,而是必须将其添加到图片集合中(如上)。

这是我实现的唯一附加方法,现在它可以工作了。再次感谢您的所有帮助!

【问题讨论】:

  • 如果我没看错的话 ProfilePicture 没有自动生成的 ID,所以除非你明确设置一个,否则这应该会失败。分离主键和外键是一种选择吗?
  • @ftr:我只是试图给 ProfilePicture 它自己的主键。最后,我为每个消费者获得了一个Collection 的 ProfilePictures。这还不错——尽管我知道每个消费者最多只能有一张照片。并且在调用profilePicture.setConsumerId(consumer); 时似乎有效

标签: java jpa javabeans


【解决方案1】:

当保持关系的非拥有方的实例(包含“mappedBy”,在您的情况下为消费者)时,您必须始终确保关系的双方都设置为按预期进行级联工作。

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

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

【讨论】:

  • 谢谢,这绝对是正确的方向:) 所以我为ProfilePicture 添加了一个主键,因为“ftr”告诉我,并按照你的建议向Consumer 添加了一个addProfilePicture() 方法。现在我对这个解决方案感到满意。我创建了ConsumerProfilePicture,然后调用consumer.addProfilePicture(profilePicture),这两个实体都按原样插入!
【解决方案2】:

您一次可以持久化一个对象及其子对象。所以我认为这应该可行:

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
  • 好的,你的问题。您可以为 @Column(name = "consumerId") private Integer consumerId; 删除 ProfilePicture 中的 @NotNull;并尝试一下。由于 NotNull 将为此检查 consumerId。这个链接可能会帮助你EJBException
  • 感谢您的回答。我尝试删除 @NotNull 但不幸的是错误仍然存​​在。我认为这是因为我的数据库中的consumerId 列设置为NOT NULL,因为它是该表的键。但是当我将表索引更改为提到的“ftr”时,JPA 似乎能够理解两个表之间的关系。
  • 好的,太好了。很高兴听到。谢谢。
猜你喜欢
  • 2013-06-16
  • 1970-01-01
  • 2011-07-16
  • 2017-06-22
  • 1970-01-01
  • 1970-01-01
  • 2011-08-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多