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