How to save two objects with JPA

I use JPA in my webapp and I cannot figure out how to migrate two new entities that are related to each other. Here is an example:

These are two objects.

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

The consumer has id and some other values. ProfilePicture uses the Consumer Identifier as its own primary key and as a foreign key. (Since ProfilePicture will not exist without a Consumer, and not every Consumer has a ProfilePicture)

I used NetBeans to create entity classes and a beans session.

That's how they look 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 */ (...) } 

Therefore, when I want to create a Consumer with its ProfilePicture , I thought that I would do it as follows:

  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 the JPA does not seem to be able to relate these two entities. In most cases, I get 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, this is due to the fact that ProfilePicture does not know the identifier of the Consumer and, therefore, entities cannot be saved.

The only way he ever worked with was to first save Consumer by setting his identifier to ProfilePicture and then saving the image:

  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 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, of course, the database is much more complicated, and setting identifiers manually, as it seems, is very inflexible, and I'm afraid to complicate the situation. Moreover, I can’t save all the objects in one transaction (which seems very inefficient).

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

Edit: my solution

As FTR showed, one of the problems was the lack of id for the ProfilePicture table (I used Consumer.id as foreign and primary).

Now the tables look like this:

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

Then Alan Hay told me to always encapsulate the add / delete in the relationship, and then you can ensure the correctness of what 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 now has its own identifier, it has become OneToMany , so each user can now have many profile images. This is not what I intended at the beginning, but I can live with it :) Therefore, I can’t just install ProfilePicture for the Consumer, but I must add it to the image collection (as indicated above).

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

+5
source share
2 answers

When you save an instance of a non-party relationship (one that contains "mappedBy" and in your case "Consumer"), you should always ensure that both sides of the relationship are set to have cascading operation as expected.

You should, of course, always do this anyway to make sure 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 adding / removing 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; } 
+1
source

You can save one object and its child at the same time. So I think this should work:

 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); 
0
source

Source: https://habr.com/ru/post/1204114/


All Articles