Hibernate OneToOne relationship - java

I have two persistence entity: User and UserDetail. They have one-to-one relationship. I use hibernate annotations. But I am getting in my database several objects of user information for one same user. Apparently my knowledge of Hibernate annotations are not so good to solve this problem.
User class:
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
#Column(name = "PASSWORD")
private String password;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
private UserDetail userDetail;
// setters and getters
}
UserDetail class:
#Entity
#Table(name = "USER_DETAIL")
public class UserDetail {
#OneToOne
#JoinColumn(name = "USER_ID")
private User user;
// other fields
}
I use this in my code as follows:
UserDetail userDetail = new UserDetail();
userDetail.setInfo(info);
userDetail.setUser(seventhUser);
hibernateTemplate.saveOrUpdate(userDetail);
And everything works properly. Here's what my table USER_DETAIL:
But when I try to change user information, I get an incorrect behavior. I get following table after I again set user information:
UserDetail newUserDetail = new UserDetail();
newUserDetail.setInfo(newInfo);
newUserDetail.setUser(seventhUser);
hibernateTemplate.saveOrUpdate(newUserDetail);
Why the same two objects of information correspond to one user?? I have One-To-One relationship. How can I avoid this? What am I doing wrong?

If you want to modify an existing UserDetail, then you must set its ID, or get it from the session and modify it. Else, Hibernate thinks it's a new one that must be saved, since it doesn't have any ID.
UserDetail existingUserDetail = session.get(UserDetail.class, theUserDetailId);
existingUserDetail.setInfo(newInfo);
To make sure you don't save two UserDetail instances for the same user, you should add a unique constraint on the USER_ID column of the UserDetail database table.

Related

Automatically persisting reverse relationship in JPA

I have an entity User, that can have exactly one Company. I have a Company, that can be assigned to multiple User objects.
Currently if I want to persist a User, I need to get the Company (as it may exist without any User being assigned to it) and assign it. Further more I have to add the User manually to the Company using Company#addUser. Afterwards I save run CompanyRepository.save(company) (which should suffice to persist the User, too, I think, because I am using cascade = CascadeType.PERSIST).
Is there a way to say, that if I get the User and assign a Company to it, the "back-reference" is dealt with automatically? Or do I always have to get the Company and use Company#addUser to add that reference?
My entities look like this (I omitted more properties and reduced it to the most important properties and methods):
Company.java
package com.portal.user.persistence;
(imports omitted)
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder (toBuilder = true)
#Entity
#Table (name = "companies")
public class Company {
#Id
#GeneratedValue (generator = "uuid")
#GenericGenerator (name = "uuid", strategy = "uuid2")
#Column (name = "id")
private String id;
#Column (name = "ucid")
private String ucid;
#OneToMany (fetch = FetchType.LAZY, mappedBy = "company", cascade = CascadeType.PERSIST)
private List<User> users;
public void addUser(#NonNull User user) {
if (users == null) {
users = new ArrayList<>();
}
users.add(user);
}
public void removeUser(#NonNull User user) {
users.remove(user);
}
}
User.java
package com.portal.user.persistence;
(imports omitted)
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder (toBuilder = true)
#Entity
#Table (name = "users")
public class User {
#Id
#GeneratedValue (generator = "uuid")
#GenericGenerator (name = "uuid", strategy = "uuid2")
#Column (name = "id")
private String id;
#ManyToOne (cascade = CascadeType.PERSIST)
private Company company;
}
There are a lot of answers to your question, based on the implementation you would like to achieve.
The first way is to remove the #OneToMany relation in Company and the user list. In this way you would only have to manage one side of the relation, and when you need to search for all users in a company you could use a custom query performing a left join on users and companies tables.
The second way, keeping both side of the relation, is to implement a method 'setCompany' inside the User class like the following:
public void setCompany(Company c){
c.addUser(this);
this.company = c;
}
However in my experience, the first solution fits better since less relations will lead to a lot less work to do later on, especially regarding DTO conversion and deletion of elements from the DB.

Hibernate clone entity without lazy fields

I have two entities:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "age")
private int age;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "person_id")
private Person person;
and
#Entity
#Table(name = "persons")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "number")
private String number;
The person is LAZY. I load one user and detach it.
#Transactional
#Override
public void run(String... args) {
User user = userService.getOne(1L);
userService.detach(user);
System.out.println(user.getName());
System.out.println(user.getAge());
Person person = user.getPerson();
System.out.println(person.getName());
System.out.println(person.getNumber());
}
But when I call user.getPerson() - it does not throw exceptions. I expect exception because I detach entity and try to call LAZY field but it still works.
I want to create a clone of the user, without person and save as a new entity.
User user = userService.getOne(1L);
userService.detach(user);
user.setId(null)//autogenerate id
but when I save user, person clone too. I can set null:
User user = userService.getOne(1L);
userService.detach(user);
user.setId(null);
user.setPerson(null);
But person lazy and it looks like a hack. And what's the point then detach method...
EDIT:
Very interesting thing - If I start example application in debugging with breakpoints - all work fine, but if I deselect all breakpoints I get exception in the console:
Caused by: org.hibernate.LazyInitializationException: could not initialize proxy [com.example.detachexample.User#1] - no Session
If I understand it, you are calling detach on a clone? Well that clone is not of a plain User object, but of the proxy that extends the User object.
You need to get the raw loaded entity first using unproxy.
User olduser = userService.getOne(1L);
User user = org.hibernate.Hibernate.unproxy(olduser);
if (olduser == user) userService.detach(user);
user.setId(null)//autogenerate id
user.getPerson().setId(null); // so you will generate this as well
user.getPerson().setUser(user); // so that it will point to the correct new entity
It seems that at the point of detach the Person has been loaded actually.
This is possible according to FetchType documentation:
The LAZY strategy is a hint to the persistence provider runtime that
data should be fetched lazily when it is first accessed. The
implementation is permitted to eagerly fetch data for which the LAZY
strategy hint has been specified.
So take a look at the Hibernate debug logs and most likely there will be a join to the Person somewhere along with the select of its fields.

One to many doesn't update without a reference when setting only id instead of taking the object from DB first

Iam using Spring Hibernate with JPA. cascade=CascadeType.ALL and #Id #GeneratedValue(strategy = GenerationType.IDENTITY). I have two tables one is User the other is photos when I want to add a new photo do I need to go and take the User from the database and then set it as a user in the photo with photo.setUser(user). Is there a way to just do User user = new User(); user.setId(1) and then put it in the photo with photo.setUser() without a full reference I am getting detached entity passed to persist when I execute repo.save(photo) when I am setting only the id.
What I want to do is:
User user = new User();
user.setId(1);
photo.setUser(user);
repo.save(photo)
Where user is already created in the database and has several photos.
instead of:
User user = repo.findUserById(1);
photo.setUser(user);
repo.save(photo);
my entities:
#Entity
#Table(name = "users")
public class Photo implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#OneToMany(cascade = CascadeType.ALL,mappedBy = "user", fetch = FetchType.EAGER)
private List<Photo> photos = new ArrayList<>();
#Entity
#Table(name = "photos")
public class Photo implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
#JoinColumn(name = "user_id")
private User user;
Use EntityManager.getReference() to get an user object. The nice things of it compared with EntityManager.find() is that it will not trigger additional SQL to get the user object.
The user object is just a proxy with only ID is set. Just make sure you do not access its properties other than ID before save , then no additional SQL will be triggered to get the user which is good for setting up the foreign key to the existing object with the known ID.
User user = entityManager.getReference(1, User.class);
photo.setUser(user);
repo.save(photo)
If you are using spring-data repository , the equivalent method is getOne() which will internally call EntityManager.getReference()

Hibernate and multiple OneToOne without creating new columns

I have the following situations with multiple OneToOne reletanships:
#Table(name = "User")
public class User {
#OneToOne(mappedBy = "settingColumnName")
private Settings setting;
}
#Table(name = "Account")
public class Account {
#OneToOne(mappedBy = "settingColumnName")
private Settings setting;
}
#Table(name = "Settings")
public class Settings{
#OneToOne()
#JoinColumn(name = "userColumnName")
private User user;
#OneToOne()
#JoinColumn(name = "accountColumnName")
private Account account;
}
Now, the issue here is that I have to create and save each model independently, because they are created as a result of StreamEvent capturing. Also, Hibernate will create automatically userColumnName and accountColumnName. What I would really need to do is to have something this:
Is this possible to implement with Hibernate? Could someone provide an example?
Do
#JoinColumn(name="userColumnName", insertable=false,updatable=false),
#JoinColumn(name="accountColumnName", insertable=false,updatable=false),
And Add two more fields in Settings Entity for these tow column and Map with same Column

OneToOne between two tables with shared primary key

I'm trying to set up the following tables using JPA/Hibernate:
User:
userid - PK
name
Validation:
userid - PK, FK(user)
code
There may be many users and every user may have max one validation code or none.
Here's my classes:
public class User
{
#Id
#Column(name = "userid")
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long userId;
#Column(name = "name", length = 50, unique = true, nullable = false)
protected String name;
...
}
public class Validation
{
#Id
#Column(name = "userid")
protected Long userId;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn(name = "userid", referencedColumnName = "userid")
protected User user;
#Column(name = "code", length = 10, unique = true, nullable = false)
protected String code;
...
public void setUser(User user)
{
this.user = user;
this.userId = user.getUserId();
}
...
}
I create a user and then try to add a validation code using the following code:
public void addValidationCode(Long userId)
{
EntityManager em = createEntityManager();
EntityTransaction tx = em.getTransaction();
try
{
tx.begin();
// Fetch the user
User user = retrieveUserByID(userId);
Validation validation = new Validation();
validation.setUser(user);
em.persist(validation);
tx.commit();
}
...
}
When I try to run it I get a org.hibernate.PersistentObjectException: detached entity passed to persist: User
I have also tried to use the following code in my Validation class:
public void setUserId(Long userId)
{
this.userId = userId;
}
and when I create a validation code I simply do:
Validation validation = new Validation();
validation.setUserId(userId);
em.persist(validation);
tx.commit();
But then since User is null I get org.hibernate.PropertyValueException: not-null property references a null or transient value: User.code
Would appreciate any help regarding how to best solve this issue!
I have been able to solve this problem of "OneToOne between two tables with shared primary key" in pure JPA 2.0 way(Thanks to many existing threads on SOF). In fact there are two ways in JPA to handle this. I have used eclipselink as JPA provider and MySql as database. To highlight once again no proprietary eclipselink classes have been used here.
First approach is to use AUTO generation type strategy on the Parent Entity's Identifier field.
Parent Entity must contain the Child Entity Type member in OneToOne relationship(cascade type PERSIST and mappedBy = Parent Entity Type member of Child Entity)
#Entity
#Table(name = "USER_LOGIN")
public class UserLogin implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="USER_ID")
private Integer userId;
#OneToOne(cascade = CascadeType.PERSIST, mappedBy = "userLogin")
private UserDetail userDetail;
// getters & setters
}
Child Entity must not contain an identifier field. It must contain a member of Parent Entity Type with Id, OneToOne and JoinColumn annotations. JoinColumn must specify the ID field name of the DB table.
#Entity
#Table(name = "USER_DETAIL")
public class UserDetail implements Serializable {
#Id
#OneToOne
#JoinColumn(name="USER_ID")
private UserLogin userLogin;
// getters & setters
}
Above approach internally uses a default DB table named SEQUENCE for assigning the values to the identifier field. If not already present, This table needs to be created as below.
DROP TABLE TEST.SEQUENCE ;
CREATE TABLE TEST.SEQUENCE (SEQ_NAME VARCHAR(50), SEQ_COUNT DECIMAL(15));
INSERT INTO TEST.SEQUENCE(SEQ_NAME, SEQ_COUNT) values ('SEQ_GEN', 0);
Second approach is to use customized TABLE generation type strategy and TableGenerator annotation on the Parent Entity's Identifier field.
Except above change in identifier field everything else remains unchanged in Parent Entity.
#Entity
#Table(name = "USER_LOGIN")
public class UserLogin implements Serializable {
#Id
#TableGenerator(name="tablegenerator", table = "APP_SEQ_STORE", pkColumnName = "APP_SEQ_NAME", pkColumnValue = "USER_LOGIN.USER_ID", valueColumnName = "APP_SEQ_VALUE", initialValue = 1, allocationSize = 1 )
#GeneratedValue(strategy = GenerationType.TABLE, generator = "tablegenerator")
#Column(name="USER_ID")
private Integer userId;
#OneToOne(cascade = CascadeType.PERSIST, mappedBy = "userLogin")
private UserDetail userDetail;
// getters & setters
}
There is no change in Child Entity. It remains same as in the first approach.
This table generator approach internally uses a DB table APP_SEQ_STORE for assigning the values to the identifier field. This table needs to be created as below.
DROP TABLE TEST.APP_SEQ_STORE;
CREATE TABLE TEST.APP_SEQ_STORE
(
APP_SEQ_NAME VARCHAR(255) NOT NULL,
APP_SEQ_VALUE BIGINT NOT NULL,
PRIMARY KEY(APP_SEQ_NAME)
);
INSERT INTO TEST.APP_SEQ_STORE VALUES ('USER_LOGIN.USER_ID', 0);
If you use Hibernate you can also use
public class Validation {
private Long validationId;
private User user;
#Id
#GeneratedValue(generator="SharedPrimaryKeyGenerator")
#GenericGenerator(name="SharedPrimaryKeyGenerator",strategy="foreign",parameters = #Parameter(name="property", value="user"))
#Column(name = "VALIDATION_ID", unique = true, nullable = false)
public Long getValidationId(){
return validationId;
}
#OneToOne
#PrimaryKeyJoinColumn
public User getUser() {
return user;
}
}
Hibernate will make sure that the ID of Validation will be the same as the ID of the User entity set.
Are you using JPA or JPA 2.0 ?
If Validation PK is a FK to User, then you do not need the Long userId attribute in validation class, but instead do the #Id annotation alone. It would be:
Public class Validation
{
#Id
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn(name = "userid", referencedColumnName = "userid")
protected User user;
#Column(name = "code", length = 10, unique = true, nullable = false)
protected String code;
...
public void setUser(User user)
{
this.user = user;
this.userId = user.getUserId();
}
...
}
Try with it and tell us your results.
You need to set both userId and user.
If you set just the user, then the id for Validation is 0 and is deemed detached. If you set just the userId, then you need to make the user property nullable, which doesn't make sense here.
To be safe, you can probably set them both in one method call:
#Transient
public void setUserAndId(User user){
this.userId = user.getId();
this.user = user;
}
I marked the method #Transient so that Hibernate will ignore it. Also, so you can still have setUser and setUserId work as expected with out any "side effects."

Categories

Resources