OneToOne bidirectional mapping foreign key auto fill - java

I have a relationship one-to-one between two tables, but the foreign key is on the one I don't need to map, the DBA did this in favor of future changes.
Let's imagine we have User and Address, today every user has only one address, and it will be mapped this way, but DBA believe in the future it could be a one to many mapping, so the foreign key of user is on the address, but the application have instances of users, which is important to fetch address automatically.
We did it right, as follow:
#Entity
#Table(name = "user")
class User {
#Id
#Column(name = "user_id")
private Long id;
//...
#OneToOne(cascade = CascadeType.MERGE, mappedBy = "user")
private Address address; // this attribute is crucial
}
#Entity
#Table(name = "address")
class Address {
#Id
#Column(name = "address_id")
private Long id;
#OneToOne
#JoinColumn(name = "user_id")
private User user; // this attribute is not needed for the business at all, but mappedBy requires it
//...
}
Database:
-- SQL:
CREATE TABLE user
(
user_id INT NOT NULL,
-- ...
CONSTRAINT user_pk PRIMARY KEY (user_id)
);
CREATE TABLE address
(
address_id INT NOT NULL,
user_id INT NOT NULL,
-- ...
CONSTRAINT address_pk PRIMARY KEY (address_id),
CONSTRAINT address_user_id_fk FOREIGN KEY (user_id) REFERENCES user (user_id),
CONSTRAINT address_user_id_uk UNIQUE (user_id) -- it says it's a one to one relation for now, if in the future it won't be anymore, just remove this constraint
);
The problem is when save a instance of user with a new instance of address, the user's attribute of address is null, so I was expecting Hibernate was smart enough to set it the value from the user's instance it comes from.
I'd been looking for a solution for a couple of days, but still didn't find how to solve this, meanwhile I'm setting the value manually, but I expect I don't need to do so.

The standard solution is to properly update both sides of the bidirectional association (although only the owning side needs to be updated for the association to be saved to the database). Add to the Address setter in the User class:
public void setAddress(Address address) {
this.address = address;
address.setUser(this);
}
Also, you may want to extend cascading options for the address property to include PERSIST as well, so that it is always persisted together with its user:
#OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "user")
private Address address;
Then you can set an address to a user and persist both:
user.setAddress(address);
session.persist(user);

If "private User user" is not needed, delete it and delete also mappedBy in 'User' entity. Use a uni-directional relation.
In the case of your example, mappedBy means that the owner of the association is the 'Address' entity, so save the instance of Adress and not User. Like :
adress.setUser(user);
session.save(adress);

You need to add CasecadeType.PERSIST to make the creation Address casecade with creation of User.
In your java code, you need to do:
user.setAddress(address);
address.setUser(user);
session.persist(user);
Then your user will be created with an address.
If when you just want to read the Address of a new created User, then you need to do :
// your code to persist a new User and Address
session.flush();
session.refresh(user);
If it's not what you want, then you need to share your own Java code and give a detailed description on what you're expecting.

Related

Hibernate #OneToOne Unidirectional Mapping...Cascade Delete

I'm working on a Spring Boot Application with Hibernate and I'm just trying to understand the correct way to approach a OneToOne mapping when it comes to using cascade delete.
So, we have a User table and a PasswordResetToken table. A user has standard user columns: id, username, password, email.
A password reset token has an id, a FK to userId, and a string for a token.
So, my question now is: how do I correctly model this so we can properly cascade delete?
My thought process is that we have a unidirectional mapping since password reset token has a FK to user, and user does NOT have a FK to password reset token.
So I would think that we would place the #OneToOne on our PasswordResetToken class in Java and not have a reference to PasswordResetToken in our User class, but then the PasswordResetToken class will have a reference to a User object.
But, through some stackoverflowing, I found that people would have the child object (PasswordResetToken) inside the parent object (User) despite the parent object's table not having a reference to the child object's table (since the User table doesn't have a PasswordResetToken in it) which allows for adding the cascade remove to the #OneToOne annotation which means that when a User gets deleted, all children will get deleted as well.
So, which way is the right way to model this relationship?
Thanks for your time
There are many ways to solve your problem. Some are less, some are more efficient.
Bidirectional with foreign key
#Entity
public class PasswordResetToken {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User User;
// other fields
}
#Entity
public class User {
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL,
fetch = FetchType.LAZY, optional = false)
private PasswordResetToken passwordResetToken;
// other fields
}
Bidirectional with principal/parent's primary key as foreign key
Since it's 1-1 relationship, you could use User's ID as a primary key for PasswordResetToken table.
#Entity
public class PasswordResetToken {
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private User User;
// other fields
}
#Entity
public class User {
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL,
fetch = FetchType.LAZY, optional = false)
private PasswordResetToken passwordResetToken;
// other fields
}
Unidirectional
If you want to have unidirectional mapping, and to have PasswordResetToken entity as part of User entity, you'll have to move the foreign key to User table, since #JoinColumn has to be applied on entity owning the foreign key.
#Entity
public class User {
#OneToOne(cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
#JoinColumn("password_reset_token_id") // FK in User table
private PasswordResetToken passwordResetToken;
// other fields
}
As for performance, the most efficient is bidirectional with #MapsId. Bidirectional with #JoinColumn is less efficient, and I'm not sure about unidirectional mapping. One to one mappings are not that common in practice, and I'm not sure how often people use unidirectional mapping. Probably not at all, since the foreign key is usually on dependent side.
I don't know how big the token is, but what is wrong with storing the token in the User entity as simple column? You can abstract some parts by using an #Embeddable but really this should IMO be in the same table. If you are concerned with the amount of data fetched, you should be using DTOs to reduce the amount of data.

How to trigger foreign key relation without adding a reference #Entity in hibernate?

The following relationship creates a foreign key mapping
#Entity
public class Department {
#Id
private String name;
//some more fields
}
#Entity
public class Employee {
#Id
private long id;
private String name;
private String designation;
#ManyToOne
#JoinColumn(name = "fk_department_id", foreignKey = #ForeignKey(name="fk_department"))
private Department department;
}
generates:
...CONSTRAINT fk_department FOREIGN KEY (fk_department_id) REFERENCES department (name)
Question: how can I trigger this constraint creation in hibernate without having to create the Department entity?
Eg just adding the foreign key #Id field without an explicit entity reference. But still trigger the fk on initial creation. The following is of course invalid:
#ManyToOne
#JoinColumn(name = "fk_department_id", foreignKey = #ForeignKey(name="fk_department"))
private String department;
You get the intention. Is that possible?
(sidenote: I'm not interested in creating that foreign key link by startup ddl/sql statements).
You'll have to drop #ManyToOne at least, since that's for entities.
The following should work by overriding the column definition to include the foreign key while creating it
#Column(name = "department_id", columnDefinition = "VARCHAR(255), foreign key (department_id) REFERENCES department(name)")
private String department;
Now there's only a column and a constraint defined, but no relation (as far as Hibernate knows) defined between entities.
Essentially copied from Hibernate and JPA: how to make a foreign key constraint on a String but that was darn hard to find, so I'm not just going to close this as a duplicate! ;)

Can Hibernate orphanRemoval work with unique constraints?

I have 2 entities: Role and Privilege. One role has many privileges. The entities look like this:
#Entity
public class Role {
private Integer id;
private String code;
#OneToMany(mappedBy = "role", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Privilege> privileges;
}
#Entity
public class Privilege {
private Integer id;
private String code;
#ManyToOne
#JoinColumn(name = "role_id")
private Role role;
}
The privilege table has the unique constraint U__ROLE_ID__CODE__PRIVILEGE on the role_id and code columns.
I have a REST endpoint which updates roles. The update includes also changing privileges assigned to a role:
private static void setPrivileges(Set<Privilege> existing, Set<Privilege> privileges) {
existing.clear();
existing.addAll(privileges);
}
By some reason, when I do the update of a role, Hibernate first inserts new privileges into the privilege table, and only then it removes the orphaned privileges. As a result, the update fails with the U__ROLE_ID__CODE__PRIVILEGE constraint violation in case when the new list of privileges contains at least one privilege from the old list.
Without the constraint everything works fine. However, removing the constraint does not look like a perfect solution.
Is it possible to change the order the Hibernate handles the role-privilege relationship update so that first the orphaned privileges are removed and only then the new ones are inserted?
The reproduction project is available here.
You may want to revisit the CascadeType. If you are looking to propagate deletes upon removal of the Role you will want to use CasadeType.REMOVE
Because you have it set to CascadeType.ALL you'll notice that each update/persistence attempt on Role will propagate to the Privilege. Thus conflicting with the the unique constraint on these tables.
// You can flush:
private static void setPrivileges(Set<Privilege> existing, Set<Privilege> privileges) {
existing.clear();
repository.flush(); // queues delete statements before subsequent operations
existing.addAll(privileges);
}

JPA: delete parent with related children

I have following entities:
#Entity
#Table(name = "chat",
uniqueConstraints = {
#UniqueConstraint(columnNames = {"user_1", "user_2"})
})
public class Chat {
#ManyToOne
#JoinColumn(name = "user_1")
#OnDelete(action = OnDeleteAction.CASCADE)
private User user1;
#ManyToOne
#JoinColumn(name = "user_2")
#OnDelete(action = OnDeleteAction.CASCADE)
private User user2;
}
User class:
#Entity
#Table(name="users",
uniqueConstraints={
#UniqueConstraint(columnNames={"company_id", "username"})
}
)
public class User {
#Id
#GenericGenerator(name = "uuid-gen", strategy = "uuid2")
#GeneratedValue(generator = "uuid-gen",strategy=GenerationType.IDENTITY)
private String id;
// there is no field/reference to Chat entity
}
And User entity without any references to Chat entity. I need to remove user with it's chats. Problem is that user id (that I want to remove) could be either in user1 or user2 field. For example, I have user A and user B. They have chat C. And if I try to remove, for example, user A, it should remove user A and chat C. But with provided configuration, I have following error:
Cannot delete or update a parent row: a foreign key constraint fails
(`mydb`.`chat`, CONSTRAINT `FKqslncg7pcc89gvjjpp9jypbha`
FOREIGN KEY (`user_2`) REFERENCES `users` (`id`))
As possible solution I used this answer. But using of
entityManager.remove(user);
entityManager.clear();
does not help. Also, I checked ddl code and there is no any mention of Cascade actions.
How to fix this?
I found a solution. The first thing that you need is to add the human-readable ForeignKey constraint and remove #OnDelete. Now my code looks like this:
#ManyToOne
#JoinColumn(name = "user_1", foreignKey = #ForeignKey(name = "user_1_fk"))
private User user1;
#ManyToOne
#JoinColumn(name = "user_2", foreignKey = #ForeignKey(name = "user_2_fk"))
private User user2;
Then I dropped this table, launched an application to allow hibernate re-create this table with proper FK names provided in annotations. Then I opened MySQL workbench and modified foreign keys for this table using following SQL:
ALTER TABLE chat DROP FOREIGN KEY `user_1_fk`;
ALTER TABLE chat DROP FOREIGN KEY `user_2_fk`;
ALTER TABLE chat
ADD CONSTRAINT `user_1_fk`
FOREIGN KEY (`user_1` )
REFERENCES `users` (`id` )
ON DELETE CASCADE;
ALTER TABLE chat
ADD CONSTRAINT `user_2_fk`
FOREIGN KEY (`user_2` )
REFERENCES `users` (`id` )
ON DELETE CASCADE;
That's all.

JPA - Mapping OneToMany association between the same table using an intermediate table

I'm creating an application where one large aspect is the ability for users to share content with friends. I'm trying to represent this in the object model and I'm having trouble getting the association to work properly. I'm using a mapping table that records the friender and the friendee, both of which are represented by the primary key (id) of the user. A user can have many friends, and also be referenced by other users. This is what the schema looks like:
Users:
int user_id (PK)
varchar(32) email
varchar(64) password
Users_Map:
int users_map_id (PK)
int friendee_id (FK references users(user_id))
int friender_id (FK references users(user_id))
And this is how I have the User entity set up:
#Data
#Entity
#Table(name = "users")
public class User extends AbstractPersistable<Long> {
#Id
#Column(name = "user_id")
private Long id;
#Column
private String email;
#Column
private String password;
#OneToMany
#JoinTable(name = "users_map",
joinColumns = { #JoinColumn(name = "friender_id") },
inverseJoinColumns = { #JoinColumn(name = "friendee_id") })
private List<User> friends;
}
I run into the following error when deploying the application:
org.hibernate.AnnotationException: A Foreign key refering
com.x.webapp.data.entity.User from
com.x.webapp.data.entity.User has the wrong number of
column. should be 2
I've tried quite a few other configurations, including adding a "referencedColumnName" attribute to each #JoinColumn, but they have also yielded errors. I'm also not entirely sure whether the schema I currently have is the best way to go about mapping users together.
I appreciate any help!
Removing the extension of AbstractPersistable fixed the problem - that contained an #Id reference and clashed with the #Id reference I put inside of User.

Categories

Resources