如何使用JPA持久化两个实体 [英] How to persist two entities with JPA

查看:88
本文介绍了如何使用JPA持久化两个实体的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在我的webapp中使用JPA,我无法弄清楚如何持久保存两个彼此相关的新实体。这里有一个例子:



这是两个实体



 
+ --- -------------- + + -------------------- +
|消费者| | ProfilePicture |
+ ----------------- + + -------------------- +
| id(PK)| --- | consumerId(PPK + FK)|
| userName | |网址|
+ ----------------- + + -------------------- +

消费者 id 和其他一些值。 ProfilePicture 使用消费者 id 作为自己的主键和外键。 (由于没有消费者而且不是每个消费者都有ProfilePicture,ProfilePicture不会存在)



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

这就是他们的简短表示



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(可选= false)
@NotNull
@Size(min = 1,max = 50)
@Column(name =userName)
private String userName;

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

/ *和所有基本的getter和setter * /
(...)
}

ProfilePicture.java

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

@Id
@Basic(可选= false)
@NotNull
@Column(name =consumerId)
private Integer consumerId;

@Basic(可选= 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;

/ *和所有基本的getter和setter * /
(...)
}

因此,当我想用​​他的 ProfilePicture 创建一个消费者时,我想我会这样做:

  ProfilePicture profilePicture = new ProfilePicture(http://www.url.to/picture.jpg); //创建图片对象
消费者消费者=新消费者(John Doe); //创建消费者对象

profilePicture.setConsumer(消费者); //在图片中设置消费者(因此JPA可以关注
consumerFacade.create(消费者); //外观类来保持消费者
profilePictureFacade.create(profilePicture); //当消费者被持久化(并且有一个id)时,持续存在图片



我的问题



我尝试了几乎所有组合中的所有内容,但JPA似乎无法自己链接这两个实体。大多数时候我收到这样的错误:

  EJB5184:在EJB ConsumerFacade上调用期间发生系统异常,方法:public void com.me.db.resources.bean.ConsumerFacade。 create(com.mintano.backendclientserver.db.resources.entity.Consumer)
(...)
在回调事件上执行自动Bean验证时违反了Bean验证约束:'prePersist'。请有关详细信息,请参阅嵌入式ConstraintViolations。

据我所知,这是因为 ProfilePicture 不知道消费者的ID,因此实体无法持久存在。



唯一有效的方法是首先坚持消费者,将其ID设置为 ProfilePicture 然后坚持图片:

  ProfilePicture profilePicture = new ProfilePicture(http://www.url.to/picture.jpg ); //创建图片对象
消费者消费者=新消费者(John Doe); //创建消费者对象

consumerFacade.create(消费者); //用于持久化消费者的外观类
profilePicture.setConsumerId(consumer.getId()); //在图片中设置消费者的新ID

profilePictureFacade.create(profilePicture); //当持久化消费者(并且有一个id)时,持久化图片

然而这两个表只是一个例子,自然数据库要复杂得多,手动设置ID似乎非常不灵活,我害怕过于复杂化。特别是因为我不能在一个事务中持久化所有实体(这看起来非常低效)。



我做得对吗?或者是否有另一种更标准的方式?



编辑:我的解决方案



作为 FTR 建议,一个问题是 ProfilePicture缺少 id 表(我使用 Consumer.id 作为外国和主要)..



表现在看起来像这样:

 
+ ----------------- + + ---------- ---------- +
|消费者| | ProfilePicture |
+ ----------------- + + -------------------- +
| id(PK)| _ | id(PK)|
| userName | \_ | consumerId(FK)|
+ ----------------- + |网址|
+ -------------------- +

然后< a Href =https://stackoverflow.com/users/1356423/alan-hay> 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 关系,因此每个消费者现在可以拥有许多个人资料图片。这不是我最初想要的,但我可以用它生活:)因此我不能只为消费者设置一个ProfilePicture,但必须将它添加到图片集合(如上所述)。



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

解决方案

当持久存在关系的非拥有方的实例时(包含'mappedBy'和您的情况下消费者)那么您必须始终确保关系的两边都设置为按预期进行级联工作。



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

 消费者c =新消费者(); 
ProfilePicure p = new ProfilePicture();
c.setProfilePicture(p); //见实现
//持久c

Consumer.java

  @Entity 
@Table(name =Consumer )
@NamedQueries({...})
public class Consumer实现Serializable {

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

@Basic(可选= 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 THE THE RELATIONSHIP SIDES
this.profilePicture = profilePicture;
profilePicture.setConsumer(this);
}
}

始终将添加/删除封装到关系中,然后就可以了确保正确性:

  public class Parent {
private Set< Child>儿童;

public Set< Child> getChildren(){
return Collections.unmodifiableSet(children); //没有直接访问:强制客户端使用添加/删除方法
}

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

公共类Child(){
私人父母;
}


I am using the JPA in my webapp and I can't figure out how to persist two new entities that relate to each other. Here an example:

These are the two entities

+-----------------+   +--------------------+
|     Consumer    |   |   ProfilePicture   |
+-----------------+   +--------------------+
| id    (PK)      |---| consumerId (PPK+FK)|
| userName        |   | url                |
+-----------------+   +--------------------+

The Consumer has an id and some other values. The ProfilePicture uses the Consumer's id as it's own primary key and as foreign key. (Since a ProfilePicture will not exist without a Consumer and not every Consumer has a ProfilePicture)

I used NetBeans to generate the entity classes and the session beans (facades).

This is how they look like in short

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 */
    (...)
}

So when I want to create a Consumer with his ProfilePicture I thought I would do it like this:

   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

My Problem

I tried almost everything in every combination but JPA doesn't seem to be able to link the two entities on it's own. Most of the time I am getting errors like this:

 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.

As far as I understand the problem, it is because the ProfilePicture doesn't know the id of the Consumer and thus, the entities cannot persist.

The only way it ever worked, was when persisting the Consumer first, setting it's id to the ProfilePicture and then persisting the picture:

   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

However these two tables are just an example and naturally the database is much more complex and setting the ids manually like this seems very inflexible and I am afraid of over complicating things. Especially because I can't persist all entities in one transaction (which seems very inefficient).

Am I doing it right? Or is there another, more standard way?

Edit: my solution

As FTR suggested, one problem was the missing id for the ProfilePicture table (I used the Consumer.id as foreign and primary)..

The tables look like this now:

+-----------------+   +--------------------+
|     Consumer    |   |   ProfilePicture   |
+-----------------+   +--------------------+
| id    (PK)      |_  | id (PK)            |
| userName        | \_| consumerId (FK)    |
+-----------------+   | url                |
                      +--------------------+

Then Alan Hay told me to Always encapsulate add/remove to relationships and then you can ensure correctness, which I did:

Consumer.java

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

Since ProfilePicture has it's own id now, it became a OneToMany relationship, so each Consumer can now have many profile pictures. That's not what I intended at first, but I can life with it :) Therefore I can't just set a ProfilePicture to the Consumer but have to add it to a collection of Pictures (as above).

This was the only additional method I implemented and now it works. Thanks again for all your help!

解决方案

When persisting an instance of the non-owning side of the relationship (that which contains the 'mappedBy' and in your case Consumer) then you must always ensure both sides of the relationship are set to have cascading work as expected.

You should of course always do this anyway to ensure your domain model is correct.

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

Always encapsulate add/remove to relationships and then you can ensure correctness:

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

这篇关于如何使用JPA持久化两个实体的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆