How to insert in to multiple tables via Hibernate - java

I have two tables I need to insert in to in Hibernate - I have a User and every user belongs is a Member. Therfore when creating a new user I need a new entry in the Member table. I have attempted this via creating a Member object which maps to my member table and then having that as a field in my User object which maps to the user table
#Entity
#Table(name = "USER")
public class User
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "fullName")
private String fullName;
//other fields ommited
#OneToOne
#JoinColumn(name = "id")
private Member member;
My member pojo looks as follows
#Entity
#Table(name = "MEMBER")
public class Member
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "sgpid")
private int sgpid;
#Column(name = "username")
private String username;
Trying to save the object i do as follows;
#Override
public boolean addUser(User user)
{
if (user == null)
{
throw new IllegalArgumentException("Unable to add null user");
}
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
session.save(user);
session.getTransaction().commit();
return true;
}
This gives me the row saved in my user table but the entry is not inserted in to the member table. I think my linking annotations are probably incorrect but I am not too sure - please could someone provide some assistance.
Thanks

Try to set the cascade value of the #OneToOne annotation:
#OneToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "id")
private Member member;

First thing in your user class you should change the joinColumn to member_id.
As mentioned in another answer to persist a related entity you need to set the cascade to persist, i would recommend using cascade All which will involve the related entity in all operations check the doc
https://docs.oracle.com/cd/E19798-01/821-1841/bnbqm/index.html
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "member_id")
private Member member;

Related

Spring oneToOne relationship not setting FK when saving

this is my code:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER, optional = false)
private Employee employee;
public User() {
}
//SETTERS AND GETER
}
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
public Employee() {
}
//SETTERS AND GETER
}
On my service I'm trying somthing like this:
User user = new User();
user.setUsername("my_username");
user.setPassword("12345");
Employee employee = new Employee();
employee.setName("My Name");
employee.setBirth(LocalDate.now());
user.setEmployee(employee);
userService.save(user);
There Are no errors nor any problem on the application, but looking on my database, the user_id column is empty, what is for me to do to have user_id set automatically with User id? Thanks in advance!
As it is stated in the hibernate documentation:
Whenever a bidirectional association is formed, the application developer must make sure both sides are in-sync at all times.
You use bidirectional #OneToOne so, you should synchronize both side of the association:
User user = new User();
user.setUsername("my_username");
user.setPassword("12345");
Employee employee = new Employee();
employee.setName("My Name");
employee.setBirth(LocalDate.now());
// make both side of bidirectional #OneToOne in-sync
user.setEmployee(employee);
employee.setUser(user);
userService.save(user);

Getting Exception while adding new User in Spring Security

I have a user class and role class and user role class . Now every time i am trying to add a new user with a set of role which is already existing it throws a Unique error which is correct . But ideally it should not try to save the new role if it already exists . Below i am adding all my tables and save method .
#Table(name = "t_user")
#Data
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "mobile_number")
private String mobileNumber;
#Column(name = "email")
private String email;
#Column(name = "first_name")
private String firstName;
#Size(max = 100)
#NotBlank(message = "Last name can not be empty")
#Column(name = "last_name")
private String lastName;
#Column(name = "is_archived")
private Boolean isArchived = false;
#Column(name = "qualification")
private String qualification;
#JsonIgnore
#Column(name="password")
private String password;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "t_user_role", joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {
#JoinColumn(name = "role_id", referencedColumnName = "id")})
private Set<Role> roles = new HashSet<>();
}
#Data
#Table(name = "m_role")
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name="name")
private String name;
}
#Data
#Table(name = "t_user_role")
#Entity
public class UserRole {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "role_id")
private Role role;
}
method where i am saving the user:
User newUser = new User();
newUser.setFirstName(user.getFirstName());
newUser.setLastName(user.getLastName());
newUser.setEmail(user.getEmail());
newUser.setMobileNumber(user.getPassword());
newUser.setPassword(bcryptEncoder.encode(user.getPassword()));
newUser.setRoles(user.getRoles());
return userRepository.save(newUser);
}
and below is the post request format to create the user:
{
"firstName":"first",
"lastName":"name",
"email":"email#gmail.com",
"mobileNumber":"1110122223",
"password":"1234567890",
"roles":[{
"name":"ADMIN"
}]
}
I do not want to insert the role if present which should be ideal . But this is the standard way i find while implementing spring security with roles
Your request is missing id of the role. As id is not present. Spring try to add a new role in role table.
{
"firstName":"first",
"lastName":"name",
"email":"email#gmail.com",
"mobileNumber":"1110122223",
"password":"1234567890",
"roles":[{
"id" : "" // ID must be present here.
"name":"ADMIN"
}]
}
Or from the role -> name, you can fetch Role entity/obj from the role table and set it in User object.
[Update 2]:
#ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#ToString.Exclude
private Set<Role> roles = new HashSet<>();
You need to change the cascade type from 'ALL' to 'DETACH'. See: ALL means if you save USER, ROLE will also get saved, if you delete USER, role should also get delete. This is not what we want. You only need to use 'ROLE', not manipulate the 'ROLE' tables record in any way.
On behalf of what I understand, your requirements are:
User entity
Role entity, with each user having multiple roles
If role is passed from client, you want to save the role only if it does not exist in your database, else you want to use the existing role (UPDATE: which as per comments and my opinion, is never an ideal thing to do)
In your case, I would suggest let Spring take care of the User->Roles relationship as follows:
public class User {
... all fields
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#ToString.Exclude
private Set<Role> roles = new HashSet<>();
}
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
}
In Role repository, you would want a method: Optional<Role> findByName(String Name);
In between the layers (preferably in the service layer), try this:
public Map<String, Object> addUser(User user) {
// perform validations
user.getRoles().forEach(role -> {
Role existing = roleRepository.findByName(role.getName())
.orElse(new Role(role.getName())); // using optional to create new role with name passed in from the client
if (existing.getId() != null) user.setRole(existing); // UPDATE: NOT IDEAL
});
... other tasks
userRepository.save(user); // this all saves the correct role
return yourResponseMap;
}
Other notes:
We generally prefer to keep fetches Lazy, instead of Eager. But there are cases when you may need Eager retrieval so it depends on you.
Letting Spring Data JPA handle third tables is better in terms of convenience in my opinion.
org.hibernate.PersistentObjectException: detached entity passed to persist occurs when you're trying to save the role passed in from client directly without loading it on your application (see the service layer method for 'add user').
Check this link, you might find it helpful.

Java hibernate OneToOne: which side should be the owner

So i'm learning from these simple examples, there're 2 tables, USERS and USER_DETAILS, simple enough, each user has user_details and it's 1-to-1 relationship. So this sample is like this,
#Entity
#Table(name = "USERS")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USR_ID")
private long id;
#Column(name = "USERNAME", nullable = false, unique = true)
private String username;
#Column(name = "PASSWORD")
private String password;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "user", fetch = FetchType.LAZY)
private UserDetail userDetail;
//Setter and getter methods
}
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USR_DET_ID")
private long id;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "EMAIL")
private String email;
#Column(name = "DBO")
private LocalDate dob;
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "USR_ID")
private User user;
//Setter and Getter methods
}
If you look at mappedBy, it's in the User not UserDetails.
Q1: so USER is the owner, if it calls save(),
USER_DETAILS table will be updated as well ?
Q2: same examples put mappedBy in the USER_DETAILS side,
why people want to do this ?
How to determine which side to put mappedBy ?
Thanks for your help !
Q2: same examples put mappedBy in the USER_DETAILS side,
why people want to do this ?
How to determine which side to put mappedBy ?
In a bidirectional relationship, each entity has a relationship field
or property that refers to the other entity. Through the relationship
field or property, an entity class’s code can access its related
object. If an entity has a related field, the entity is said to “know”
about its related object.
There is a bidirectional one-to-one relationship in your example. Both User and UserDetail entities have a relationship field. #OneToOne annotation specified on both the entities.
For one-to-one bidirectional relationships, the owning side
corresponds to the side that contains the corresponding foreign key.
The owner of the relationship is UserDetail entity. The owner has #JoinColumn annotation to specify foreign key (USR_ID).
Inverse side of relationship (User) has mappedBy attribute.
Q1: so USER is the owner, if it calls save(),
USER_DETAILS table will be updated as well ?
In your example UserDetail is the owner. Therefore the saving process:
User user = new User(); // Ignoring the constructor parameters...
UserDetail userDetail = new UserDetail();
user.setUserDetail(userDetail);
userDetail.setUser(user);
userRepository.save(user);
You only need to save the parent. It will save the child as well.

Hibernate "object references an unsaved transient instance"

My Users are in Organisations in a ManyToOne relationship, when a user is created with an existing Organisation I am trying to assign it to it without creating a new one.
In my service, here is how I create a user:
#Override
public UserInfo createUser(UserInfo newUser) {
// Check if organisation exists
OrganisationEntity orga = organisationDao.findByName(newUser.getOrganisation());
if (orga != null) {
// Organisation exists, we save it with the correct ID
return mapper.map(userDao.save(mapper.map(newUser, orga.getId())));
} else {
// Organisation does NOT exists, we save it and create a new one
return mapper.map(userDao.save(mapper.map(newUser, (long) -1)));
}
}
With my Mapper (helping me to convert a model to an entity) being:
public UserEntity map(UserInfo userInfo, Long orgaId) {
UserEntity user = new UserEntity();
user.setEmail(userInfo.getEmail());
user.setFirstName(userInfo.getFirstName());
user.setLastName(userInfo.getLastName());
user.setPassword(userInfo.getPassword());
OrganisationEntity orga = new OrganisationEntity();
orga.setName(userInfo.getOrganisation());
// We set the organisation's ID
if (orgaId != -1)
orga.setId(orgaId);
user.setOrganisation(orga);
return user;
}
And here is my UserDao:
#Transactional
public interface UserDao extends CrudRepository<UserEntity, Long> {
UserEntity save(UserEntity user);
}
And finally the relation in my UserEntity:
#ManyToOne(targetEntity = OrganisationEntity.class, cascade = CascadeType.ALL)
#JoinColumn(name = "orga_id")
private OrganisationEntity organisation;
Creating a user with a new Organisation work but when I input an existing one, I get the following:
detached entity passed to persist
From my understanding it is a bidirectional consistency problem, but the answers did not help me so far.
Finally here are my Entity classes:
#Entity
#Table(name = "\"user\"")
public class UserEntity {
#Id
#Column(name = "user_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
private String email;
#NotNull
private String firstName;
#NotNull
private String lastName;
#NotNull
private String password;
#ManyToOne(targetEntity = OrganisationEntity.class, cascade = CascadeType.ALL)
#JoinColumn(name = "orga_id")
private OrganisationEntity organisation;
// Getters & Setters
}
and
#Entity
#Table(name = "organisation")
public class OrganisationEntity {
#Id
#Column(name = "orga_id", unique = true)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#Column(unique = true)
private String name;
// Getters & Setters
}
I have solved my problem,
As you can see in the mapper above, I am creating a new instance of OrganisatonEntity no matter what, even if it already exists !
So a small change in my code solved it:
public UserEntity map(UserInfo userInfo, OrganisationEntity organisationEntity);
instead of
public UserEntity map(UserInfo userInfo, Long orgaId);
When the organisation already exists, I then assign it to my UserEntity like such:
user.setOrganisation(organisationEntity);
instead of instantiating a new object.
Problem solved !

Spring boot - One-to-one relationship not reflected on both tables after updating one entity

I have two entities Employee and Review. I am trying to create a OneToOne relationship Employee <-> Review.
When I update an Employee with a review, the Employee gets updated where the review becomes the corresponding review,
but the Review doesn't get the 'reviewee' column added with the ID of the employee which is what I expect.
What am I doing wrong?
These are my entities:
public class Employee {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private String email;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "reviewee")
private Review review;
}
public class Review {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String body;
private char completed;
#OneToOne(mappedBy = "review")
private Employee reviewee;
}
This is my employeeController update function:
#GetMapping(path="/update")
public #ResponseBody Employee updateEmployee (#RequestParam Integer id,
#RequestParam(value = "name", required=false) String name,
#RequestParam(value = "email", required=false) String email,
#RequestParam() Integer reviewId) {
Employee n = EmployeeRepository.findOne(id);
if(name == null) {
name = n.getName();
}
if(email == null) {
email = n.getEmail();
}
n.setName(name);
n.setEmail(email);
Review r = ReviewRepository.findOne(reviewId);
n.setReview(r);
EmployeeRepository.save(n);
return n;
}
The request:
curl 'localhost:8080/employees/update?id=2&reviewId=1'
Because the owner of the relationship (the one with #JoinColumn) is Employee, you have to create/update/delete the association by saving the Employee object.
This is what you are doing so far. But Hibernate will only update the owner when you save it. You should in addition do this before returning your entity:
r.setReviewee(n);
Notice that the next time you will retrieve the review, it will correctly have an Employee object.
Beware: I smell a Jackson infinite loop there when serializing.
Employee.review -> Review -> Review.reviewee -> Employee -> Employee.review...
EDIT
To prevent the Jackson infinite loop:
1. Ignore the serialization.
Employee.java
public class Employee {
// ...
// Do not serialize this field
#JsonIgnore
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "reviewee")
private Review review;
// ...
}
2. Serialize as ID.
Employee.java
public class Employee {
// ...
// Serialize as a single value with the field "id"
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
// Serialize as told by #JsonIdentityInfo immediately (if false -> on second and further occurrences)
#JsonIdentityReference(alwaysAsId = true)
// Rename to "review_id" (would be "review" otherwise)
#JsonProperty(value = "review_id")
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "reviewee")
private Review review;
// ...
}
3. Alternative to serialize as ID: read-only reference to the foreign key.
Employee.java
public class Employee {
// ...
// Do not serialize this field
#JsonIgnore
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "reviewee")
private Review review;
// Read-only access to the foreign key
#Column(name = "Review_id", insertable = false, updatable = false)
private Integer reviewId;
// ...
}
It's seems to be a configuration mismatch. Please try the below one.
public class Employee {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private String email;
#OneToOne(mappedBy="reviewee",cascade = CascadeType.ALL)
private Review review; }
public class Review {
#Id
#GeneratedValue(generator="gen")
#GenericGenerator(name="gen", strategy="foreign", parameters={#Parameter(name="property",value="reviewee")})
private Integer id;
private String body;
private char completed;
#OneToOne
#PrimaryKeJoinCloumn
private Employee reviewee; }
I hope the above configuration works as you expected.
Please make sure you're calling the save function under Transaction boundary. Otherwise don't forget to call flush() before closing the session.

Categories