Reputation: 135
Suppose we have bidirectional OneToOne
relationship.
There are User
and Address
entity. User
has many Address
es.
CREATE SEQUENCE IF NOT EXISTS hibernate_sequence;
CREATE TABLE users (
id BIGINT PRIMARY KEY
);
CREATE TABLE addresses (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL UNIQUE CONSTRAINT fk_addresses_user_id REFERENCES users(id) ON DELETE CASCADE,
);
and
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
//mappings
private Address address;
}
@Table(name = "addresses")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
//mapings
private User user;
}
I'm using OneToOne
as an example but the question is about relations in general so the right approach is valid for ManyToMany
as well.
TL;DR - the question is about using mappedBy<->relation owner<->cascades<->managing other side in setters
in general
User
or Address
) / (Owner or Inverse)?
Address
should have mappedByPost
(User
in my example) have mappedBy to PostDetails
(Address
in my example)User
owns the relation, and therefore Address
should have mappedBy (CustomerRecord
in JavaDoc)Phone
(Person
in my example) has mappedBy to PhoneDetails
(Address
)User
or Address
) / (mappedBy or inverse-side) the @OneToOne(cascade = {CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
should be placed? public void addAddress(Address address) {
if (address != null) {
address.addUser(this);
}
this.addresses.add(address);
}
public void removeAddress(Address address) {
if (address != null) {
address.removeUser(this);
}
this.addresses.remove(address);
}
public Set<Address> getAddresses() {
return Collections.unmodifiableSet(this.addresses);
}
the (User
or Address
) / (mappedBy or inverse-side)?
User
in my example, manages the relation)Moreover - look at theese two links:
Thank you in advance
PS. Previous version of my question was using OneToMany
as an example, but because inverse - ManyToOne
does not have mapped by I changed it to OneToOne
which better shows the problem
Upvotes: 4
Views: 6010
Reputation: 153700
In a relational database system, there are three types of table relationships:
So, a one-to-many
table relationship looks like this:
Note that the relationship is based on the post_id
Foreign Key column in the child post_comment
table.
So, the Foreign Key column is the single source of truth when it comes to managing a one-to-many
table relationship.
Now, let's take a bidirectional JPA entity relationship that maps the one-to-many
table relationship we saw previously:
If you take a look at the diagram above, you can see that there are two ways to manage this relationship.
In the Post
entity, you have the comments
collection:
@OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
And, in the PostComment
, the post
association is mapped as follows:
@ManyToOne(
fetch = FetchType.LAZY
)
@JoinColumn(name = "post_id")
private Post post;
So, you have two sides that can change the entity association:
comments
child collection, a new post_comment
row should be associated with the parent post
entity via its post_id
column.post
property of the PostComment
entity, the post_id
column should be updated as well.Because there are two ways to represent the Foreign Key column, you must define which is the source of truth when it comes to translating the association state change into its equivalent Foreign Key column value modification.
The mappedBy
attribute tells that the @ManyToOne
side is in charge of managing the Foreign Key column, and the collection is used only to fetch the child entities and to cascade parent entity state changes to children (e.g., removing the parent should also remove the child entities).
It's called the inverse side because it references the child entity property that manages this table relationship.
Now, even if you defined the mappedBy
attribute and the child-side @ManyToOne
association manages the Foreign Key column, you still need to synchronize both sides of the bidirectional association.
The best way to do that is to add these two utility methods:
public void addComment(PostComment comment) {
comments.add(comment);
comment.setPost(this);
}
public void removeComment(PostComment comment) {
comments.remove(comment);
comment.setPost(null);
}
The addComment
and removeComment
methods ensure that both sides are synchronized. So, if we add a child entity, the child entity needs to point to the parent and the parent entity should have the child contained in the child collection.
Upvotes: 6
Reputation: 41
to make an association the owner, you must mark the other end as being mapped by the other. I think the following link is the best for you question: How to Define Association Mappings between Entities
Upvotes: 0
Reputation: 135
This is what I have learnt so far.
If I'm mistaken to any of the points - please let me know in comments, and I edit the answer.
`According to this answer
Basically, the owner is address, as it's the owner who holds the reference.
So the "owner
" of relation in database is entity with foreign key - Address
in case of OneToOne/OneToMany.
Moreover - it seems that in the same time "owner
" of the database relation is the Child
in Hibernate relation - but I need confirmation for this
According to this article
The Post entity is the parent, while the PostDetails is the child association because the Foreign Key is located in the post_details database table
So User
is the Parent
and Address
is the Child
here (because Address
holds foreign key)
According to this answer
The mappedBy attribute marks the side of a bidirectional association which does not own the association. Usually, that's the side which does not have the Foreign Key.
So mappedBy
should be placed in User
, because foreign key
is in Address
.
In seems that in @OneToOne
mappedBy
should be always placed in Parent
(entity not holding the foreign key)
It seems that JoinColumn
in OneToOne
must be always placed in Child
(since it holds foreign key)
According to this answer
cascading entity state transitions only makes sense from parents to child entities.
So cascades
in OneToOne
should be always done in Parent
, so - according to previous links - User
in this example
I'm not sure but it seems that utility setter should be placed in User
.
Is it always placed in the Parent
?
CREATE SEQUENCE IF NOT EXISTS hibernate_sequence;
CREATE TABLE users (
id BIGINT PRIMARY KEY
);
CREATE TABLE addresses (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL UNIQUE CONSTRAINT fk_addresses_user_id REFERENCES users(id) ON DELETE CASCADE,
);
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false, orphanRemoval = true)
private Address address;
public void setAddress(Address address) {
if (address != null) {
address.setUser(this);
}
this.address = address;
}
}
@Table(name = "addresses")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
It seems to be the same as with OneToOne
, so User
is the Parent
and Address
is the Child
(because it has foreign key), but I'm not 100% sure...
In this article mappedBy
is placed in Parent
(but I'm not sure if this is the rule of thumb though)
It seems that JoinColumn
in OneToMany
must be always placed in Child
(since it holds foreign key)
In above article cascades
are also placed in Parent
(but I'm not sure if this is the rule of thumb as well)
In above article utility methods
are also placed in Parent
(but I'm not sure if this is the rule of thumb as well)
CREATE SEQUENCE IF NOT EXISTS hibernate_sequence;
CREATE TABLE users (
id BIGINT PRIMARY KEY
);
CREATE TABLE addresses (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL CONSTRAINT fk_addresses_user_id REFERENCES users(id) ON DELETE CASCADE,
);
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private List<Address> addresses = new ArrayList<>();
public void addAddress(Address address) {
if (address != null) {
address.setUser(this);
}
this.addresses.add(address);
}
public void removeAddress(Address address) {
this.addresses.remove(address);
if (address != null) {
address.setUser(null);
}
}
public Set<Address> getAddresses() {
return Collections.unmodifiableSet(this.addresses);
}
}
@Table(name = "addresses")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
According to this article
A typical many-to-many database association includes two parent tables
So both User
and Address
are Parents
In the same article Vlad wrote:
The mappedBy attribute of the posts association in the Tag entity marks that, in this bidirectional relationship, the Post entity own the association
Therefore here is the eception, because we don't have one Parent
.
It is not clear to me what Vlad means exactly by "own", but if User
"owns" the relation, mappedBy
must be placed in Address
,
It seems that JoinTable
must be always placed in entity that "owns" the relation, so User
in this case.
Am I right?
According to the same article cascades
should be always placed in "owner" defined above, so User
in this case.
Also important thing to notice is that we can not use REMOVE
cascade
It seems that add/remove utility methods
should be placed in User
.
Is this the rule of thumb, that utility methods
should be alway placed in entity that "owns" the relation?
CREATE SEQUENCE IF NOT EXISTS hibernate_sequence;
CREATE TABLE users (
id BIGINT PRIMARY KEY
);
CREATE TABLE addresses (
id BIGINT PRIMARY KEY,
);
CREATE TABLE users_addresses (
user_id BIGINT NOT NULL CONSTRAINT fk_users_addresses_user_id REFERENCES users(id) ON DELETE CASCADE,
address_id BIGINT NOT NULL CONSTRAINT fk_users_addresses_address_id REFERENCES addresses(id) ON DELETE CASCADE,
PRIMARY KEY(user_id,address_id)
);
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
@JoinTable(name = "post_tag",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "tag_id")
)
private List<Address> addresses = new ArrayList<>();
public void addAddress(Address address) {
addresses.add(address);
address.getUsers().add(this);
}
public void removeAddress(Address address) {
addresses.remove(address);
address.getUsers().remove(this);
}
}
@Table(name = "addresses")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@ManyToMany(mappedBy = "addresses")
private List<User> users = new ArrayList<>();
}
I think this table sums it up: https://i.ibb.co/zNjZ3md/JPA-relations.png
The last thing I don't unserstand is:
mappedBy
in Author
("owner") instead of Book
(especially that it throws exception in my code)mappedBy
is on CustomerRecord
(child) not on Customer
(but here maybe foreign key is located in Customer, so although counter intuitive - it is correct)Upvotes: 4
Reputation: 2118
- In database there should be user_id in address table table, right? Who is the owner of the relation then?
- In entities: which one should have mappedBy - User or Address?
@ManyToOne
doesn't even have the attribute mappedBy. It's present only on xToMany annotations.
Your mapping should be:
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@OneToMany(mappedBy="user", cascade = ...) // atenttion, this refer the attribute name as opposed to the column name
private Set<Address> addresses = new HashSet<>();
}
@Table(name = "addresses")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@ManyToOne
@JoinColumn(name="user_id") // the name is optional if you refer to the PK
private User user;
}
On which side (User or Address?) the @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH}) should be placed?
And last question: which of the entities should manage the relation like this:
In this case, you want to handle User, and then JPA handles Address automatically.
Therefore, the cascade would be on User.
On this side (OneToMany), you probably also want to add orphanRemoval = true
.
Trying to adjust after clarification...
About the support, JPA supports cascading from both sides, ok?!
From where you should use it depends on your domain.
You are supposed to use it on the Entity which you use on entityManager operations, for instance: em.persist(user)
or em.merge(address)
.
Really, depends on how your code is.
In this specify case, it seems it makes more sense to leave on User. I suppose you have an User CRUD, which also includes address.
In this case, it makes sense when you create or remove an User, then its addresses are created/remove together.
On the other side, it wouldn't make sense to cascade from the Address. If you remove an Address, it does NOT mean that the user should be removed too.
Can you see?! You need to check your use case. The side to use it (whether oneToMany or manyToOne or ...) should be thought and decided based on your needs on that moment.
Upvotes: 0