I am using PostgreSQL 12.11, JPA 3.1.0, and Hibernate 5.6.10. This might become important because I am doing things that apparently do not work with JPA 2.0.
My goal is to add an attribute to a many-to-many relationship. I found this posting. @Mikko Maunu states that "There is no concept of having additional persistent attribute in relation in JPA (2.0)." To me, this sounds like what I want to do is not possible. However, the answer is rather old and might not be complete anymore.
Beside the time gap and the version gap, this is, in my opinion, a new question because I am doing something that is probably questionable and not part of the original thread.
What I did is this:
- Create a
@ManyToMany
relationship in JPA and specify a@JoinTable
. - Manually define an entity with identical table name to the table specified in 1. For this table, I chose a composite primary key using
@IdClass
. I also added my attribute. - Inside one of the n:m-connected entities, create a
@OneToMany
relationship to the connection-table-entity created in 2. However, I did not create a corresponding@ManyToOne
relationship as that would have created an error.
As a result, I can access the original entities and their relation as many-to-many, but also the relation itself, which is not an entity in the original ERM, but it is for JPA. First tests show this seems to be working.
I am aware, however, that I basically access the same part of the persistence (the PostgreSQL database) through two different ways at the same time.
Now my questions are:
- Is this a valid way to do it? Or will I get in bad trouble at one point?
- Is there a situation where I will need to refresh to prevent trouble?
- Is this something new in JPA > 2.0, or just an extension to the original answer?
CodePudding user response:
This should help.
Here is how I do it:
@Entity
@Table(name = "person", schema = "crm")
public final class Person implements Serializable {
@Id
@Column(name = "id", unique = true, nullable = false, updatable = false, columnDefinition = "bigserial")
private Long id;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "person", orphanRemoval = true)
private Set<PersonEmail> emails = new HashSet<>();
}
@Entity
@Table(name = "email", schema = "crm")
public final class Email implements Serializable {
@Id
@Column(name = "id", unique = true, nullable = false, updatable = false, columnDefinition = "bigserial")
private Long id;
@Column(name = "email", nullable = false, length = 64, columnDefinition = "varchar(64)")
private String localPart;
@Column(name = "domain", nullable = false, length = 255, columnDefinition = "varchar(255)")
private String domain;
}
@Entity
@Table(name = "person_email", schema = "crm")
public final class PersonEmail implements Serializable {
@EmbeddedId
private PersonEmailId id;
// The mapped objects are fetched lazily.
// This is a choice.
@ToString.Exclude
@MapsId("personId")
@ManyToOne(fetch = FetchType.LAZY, optional = false)
private Person person;
@ToString.Exclude
@MapsId("emailId")
@ManyToOne(fetch = FetchType.LAZY, optional = false)
private Email email;
// Here's an extra column.
@Column(name = "type", nullable = false, columnDefinition = "email_type_t")
@Convert(converter = EmailType.EmailTypeConverter.class)
private EmailType type;
public final void setPerson(final Person person) {
this.person = person;
id.setPersonId(this.person.getId());
}
public final void setEmail(final Email email) {
this.email = email;
id.setEmailId(this.email.getId());
}
@Embeddable
public static final class PersonEmailId implements Serializable {
@Column(name = "person_id", nullable = false, insertable = false, updatable = false, columnDefinition = "bigint")
private Long personId;
@Column(name = "email_id", nullable = false, insertable = false, updatable = false, columnDefinition = "bigint")
private Long emailId;
}