Spring oneToOne relationship not setting FK when saving - java

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);

Related

Hibernate One to Many relation- Child table not updating

Here I am adding a list of new orders to a specific user. While adding orders to a specific user, it returns success status, but still my database is empty. Why the data is not getting added...?
Users
#Entity
Class Users{
#Id
private int id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private List<Orders> orders;
}
Orders
#Entity
Class Orders{
#Id
private int id;
private Date orderDate;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
}
Repository
public ResponseEntity<String> addOrder(int userId,List<Orders> orders) throws UserNotFound{
User user =userRepository.findById(userId).orElse(null);
if(user==null) throw new UserNotFound("User Not Found");
for(Orders o:orders){
o.setOrderDate(new Date("....."));
}
user.getOrders().addAll(orders);
userRepository.save(user);
}
Try to add cascade persist to your relation specification:
#Entity
Class Users{
#Id
private int id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade={CascadeType.PERSIST})
private List<Orders> orders;
}

can't find the pattern of setting cascade logic

I have 3 tables which are Person Login and Account.
Person and Login is OneToOne relation and Login has one FK which is connected Person's id column called PERSON_ID.
Person(one) and Account(many) is OneToMany relation and Account has one FK which is connected Person's id column called PERSON_ID as well .
what i want to do is when i delete one data from Account , nothing happen to Person and Login.
if i delete one data from Person which id=1, Login's PERSON_ID=1 data will be deleted , and all of the data PERSON_ID=1 from Account will be deleted as well.
if i delete one data from Login which PERSON_ID=1, Person 's id=1 data will be deleted , and all of the data PERSON_ID=1 from Account will be deleted as well.
how should i set the cascade ?
i've tried dozens of times and still can't find the logic in there, thanks!!
here's my code of all 3 tables without setting cascade:
`
#Entity
#Table(name = "PERSON")
public class Person {
#Id
#Column(name = "ID", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "NAME")
private String name;
#Column(name = "SEX")
private String sex;
#OneToMany(mappedBy = "person",fetch = FetchType.LAZY)
private List<Account> account;
#OneToOne(mappedBy = "person")
private Login login;
#get..
#set..
}
`
#Entity
#Table(name = "ACCOUNT")
public class Account {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "ACCOUNT")
private String account;
#Column(name = "AMOUNT")
private String amount;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PERSON_ID",referencedColumnName = "ID")
public Person person;
#get..
#set..
}
`
#Entity
#Table(name = "LOGIN")
public class Login {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private long id;
#Column(name = "USERNAME")
private String userName;
#Column(name = "PASSWORD")
private String password;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PERSON_ID", referencedColumnName = "ID")
private Person person;
#get..
#set..
}
It's been a while, but if I'm not mistaken you need to use the cascade=REMOVE option on the OneToMany and OneToOne relationships. In the OneToOne I think you need to specify cascade=REMOVE on the side that does NOT own the relationship, that is, the side that also contains the "mappedBy" property.
Finally, I believe JPA will NOT automatically load lazy relationships and then cascade them. I'm thinking you may need to fetch the relationship before you delete the parent entity (otherwise JPA will not know what to delete).

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.

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 !

How to insert in to multiple tables via Hibernate

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;

Categories