Hibernate OneToOne mapped entity sets all properties to null - java

Using Hibernate 5, Spring 4
Please consider below codes and mapping between two entities:
User class
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
private TruckOwner truckOwner;
//getter setters below
TruckOwner class
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
//getter setter below
When my code tries to update values inside user class like below code:
UserServiceImpl class
#Override
#Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void resetPassword(Long userId,String newPassword) {
User user = userDAO.findById(userId);
user.setPassword(newPassword);
System.out.println(user.getTruckOwner().getTruckOwnerId());
userDAO.merge(user);
}
When calling userDAO.merge(user); I get below error:
non-transient entity has a null id: com.mymodel.TruckOwner
I am facing this kind of problem in many places in my project, please help me with a proper solution to this problem and why is TruckOwner class has everything null set by hibernate?

We should know the implementation of the userdao merge method but I guess it's called the merge method of hibernate Session interface
In any case the not transient object is the TruckOwner object; hibernat will not fetch the object when you call System.out.println(user.getTruckOwner().getTruckOwnerId()); moreover in that point you are out from hibernate session and if you call any other getter of truckOwner except getTruckOwnerId() you should get the org.hibernate.LazyInitializationException (or similar.. I don't remember correctly)
I guess you have 2 option:
as suggested by staszko032 you should change fetch type to EAGER
When you load the user object by using the userDAO.findById(userId); you should fetch the truckOwner object inside the hibernate session by calling any other method inside the userDAO.findById(userId); implementation and inside the hibernate session
I hope it's useful
Angelo

Try this for the truck class:
#Entity
#Table(name = "truckOwner")
public class TruckOwner{
...
private User user;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "truckOwner", cascade = CascadeType.ALL)
public User getUser() {
return this.user;
}
}
And this for the User class:
#Entity
#Table(name = "user")
public class User{
private TruckOwner truckOwner;
#OneToOne(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn
public TruckOwner getTruckOwner() {
return this.truckOwner ;
}
}

Eager mode is not a solution if you are making a production application. Problem is in your session is already closed when your are trying to getTruckOwner. Try to propagate session to all resetPassword method.

First you should not be using merge here! You should almost never use merge.
Merge should be used when you have an unmanaged entity (serialized or loaded by a previous persistence context) and wish to merge it into the current persistence context, to make it a managed entity. Your entity is already managed since a persistence context was loaded with your DAO inside a container managed transaction. This means you don't have to do even have to call save, any changed to a managed entity will be detected and persisted when the transaction commits.
On the surface JPA looks easy, because a lot of the complexity is not visible on the surface, god knows I banged my head against the wall when I started with TopLink 7 years ago, but after reading about Object life cycles and application versus container managed persistence context, I made a lot more mistakes, and it was much easier to decipher the error messages.

Another solution will be changing the fetch type to EAGER mode in User class. With LAZY mode, hibernate doesn't retrieve TruckOwner connected with user, as it is not explicitly needed in your case. Eventually TruckOwner is null for user, however it has nullable = false option set, and that's why merge fails.

Related

FetchType.LAZY not working for #OneToOne relation

I'm using JPA2 and Springboot2X. Trying to define a simple one-to-one relationship in User and Account tables.
User is the parent while Account is the child. The relationship is from parent to child. Followed this.
User table has a column account_no to hold the account number which is the primary key of the Account table. So User holds the foreign key.
#Entity
public class Account extends AbstractEntity implements Serializable{
// fields like account#, balance etc goes here..
#OneToOne (fetch = FetchType.LAZY)
private User user;
}
#Entity
public class User extends AbstractEntity implements Serializable{
// Other fields related to user entity go here ..
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "account_no", referencedColumnName = "account_number")
private Account account;
}
Saving is working properly. But when I fetch a user ( User user = userRp.findByUserID(userid);), I get data from both tables. My question is when I define fetch = FetchType.LAZY why the account table data is fetching? I only want the user data to be in the output as when I run the select query in the User table.
Output :
{
"id": 1,
"userID": "xxxxx",
"firstName": "Dsd",
"llastName": "cccc",
"email": "danqsaa#brown",
"creation_date": "2021-12-19 19:41",
"modified_date": "2021-12-19 19:41",
"account": {
"id": 2,
"accountNo": 6848982326,
"balance": 4000.0,
"creationUser": "xxx",
"creation_date": "2021-12-19 19:41",
"modified_date": "2021-12-19 19:41",
"transactions": []
}
}
DB output
Added below RestController, Service, and Repo details.
#RestController
public class UserController {
#GetMapping("/users/{userID}")
public ResponseEntity<Object> getUser(#PathVariable("userID") String userID){
return new ResponseEntity<>(userServiceImpl.getUserByID(userID), HttpStatus.OK));
}
}
#Service
public class UserService {
// injected user repo
public User getUserByID(String userID){
return userRepo.findByUserID(userID);
}
}
public interface UsersRepository extends CrudRepository<User, String> {
User findByUserID(String userID);
}
I thought I had the answer about this question, thinking it was just Jackson calling the getter during serialization for response object and that had as a result the fetch of the lazy property but it seems that this is not the case!!
This question was a good reason to dig and learn something strange from documentation.
The most common ORM vendor is hibernate. So if you read the documentation on hibernate it clearly states that EAGER type is a must conform for Hibernate, but the LAZY fetch type is just a hint that hibernate may or may not follow !!
From the hibernate doc
fetch - FetchType
(defaults to EAGER) Defines whether this attribute
should be fetched eagerly or lazily. JPA says that EAGER is a
requirement to the provider (Hibernate) that the value should be
fetched when the owner is fetched, while LAZY is merely a hint that
the value is fetched when the attribute is accessed. Hibernate ignores
this setting for basic types unless you are using bytecode
enhancement. See the Bytecode Enhancement for additional information
on fetching and on bytecode enhancement.
Also according to the doc, you have to follow at least for now the bytecode enhancment in order for lazy loading to work
As a hopefully temporary legacy hold-over, it is currently required
that all lazy singular associations (many-to-one and one-to-one) also
include #LazyToOne(LazyToOneOption.NO_PROXY). The plan is to relax
that requirement later.
Also according to hibernate performance enhancement doc you also need a configuration property for hibernate, so in a Spring Boot App, please also include the following in your application.yml or properties file.
hibernate.enhancer.enableLazyInitialization = true
After you include this property, your entity must be updated to
#Entity
public class User extends AbstractEntity implements Serializable{
// Other fields related to user entity go here ..
#LazyToOne(LazyToOneOption.NO_PROXY) <------
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "account_no", referencedColumnName = "account_number")
private Account account;
}
#Entity
public class Account extends AbstractEntity implements Serializable{
// fields like account#, balance etc goes here..
#LazyToOne(LazyToOneOption.NO_PROXY) <-----
#OneToOne (fetch = FetchType.LAZY)
private User user;
}
All these are needed just for hibernate to enable the lazy loading functionality.
Then you would still see it in your response, because Jackson would call the getter method while serializing so it will force hibernate to fetch the value. So you also need to inform jackson to not use this getter during serialization.
So you must also update
#Entity
public class User extends AbstractEntity implements Serializable{
#JsonIgnore <--------
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY) <-------
#LazyToOne(LazyToOneOption.NO_PROXY)
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "account_no", referencedColumnName = "account_number")
private Account account;
Hopefully after all this should you be able to not see the account response because of lazy loading.
It's unfortunate that the question is misleading and it takes many comments to find out what you're asking. Finally I understand - you see the field in JSON and you don't want to. This is a purely Jackson-related question and Hibernate has nothing to do with it.
There are several ways you can take:
Use DTO
There's a good reason we don't use our Entities as part of JSON serialization - exactly because it's rare that they satisfy our JSON requirements.
So instead we create a special DTO class which maps exactly how we need it for a particular endpoint. We convert our Entity to the DTO (and it will not include account field) and then serialize DTO.
Using Jackson Views
Jackson has a notion of a JsonView - it lets you map the same object differently depending on the view that you request. This way you can make account field invisible to that particular endpoint.
Ignore properties
As was already hinted in the other response - you can #JsonIgnore the property or rid of its visibility or making it deserialize-only with WRITE_ONLY.
Jackson Hibernate module
Jackson has "plugins" specific for Hibernate: jackson-datatype-hibernate. They allow to be configured to ignore lazy fields and not initialize them. In such case Jackson will serialize null (HibernateXModule.Feature).
This is probably the worst option of all because you really don't want to tie how you optimize your work with DB and your presentation layer.

JPA Lazy loading is not working in Spring boot

I googled a lot and It is really bizarre that Spring Boot (latest version) may not have the lazy loading is not working. Below are pieces of my code:
My resource:
public ResponseEntity<Page<AirWaybill>> searchAirWaybill(CriteraDto criteriaDto, #PageableDefault(size = 10) Pageable pageable{
airWaybillService.searchAirWaybill(criteriaDto, pageable);
return ResponseEntity.ok().body(result);
}
My service:
#Service
#Transactional
public class AirWaybillService {
//Methods
public Page<AirWaybill> searchAirWaybill(AirWaybillCriteriaDto searchCriteria, Pageable pageable){
//Construct the specification
return airWaybillRepository.findAll(spec, pageable);
}
}
My Entity:
#Entity
#Table(name = "TRACKING_AIR_WAYBILL")
#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#airWaybillId") //to fix Infinite recursion with LoadedAirWaybill class
public class AirWaybill{
//Some attributes
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FK_TRACKING_CORPORATE_BRANCH_ID")
private CorporateBranch corporateBranch;
}
And when debugging, I still getting all lazy loaded attributed loaded. See image below.
One of my questions is could Jackson be involved in such behaviour?
Is there any way that I may have missed to activate the lazy loading?
EDIT
Another question, could the debugger be involved in ruining the lazy loading?
EDIT 2:
For specification build, I have :
public static Specification<AirWaybill> isBranchAirWayBill(long id){
return new Specification<AirWaybill>() {
#Override
public Predicate toPredicate(Root<AirWaybill> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.equal(root.join("corporateBranch",JoinType.LEFT).get("id"),id);
}
};
}
Hibernate Session exists within method with #Transactional.
Passing entity outside Service class is a bad practise because session is being closed after leaving your search method. On the other hand your entity contains lazy initialised collections, which cannot be pulled once session is closed.
The good practise is to map entity onto transport object and return those transport objects from service (not raw entities).
SpringBoot by default has enabled:
spring.jpa.open-in-view = true
That means transaction is always open. Try to disable it.
more information here
Most likely you are debugging while still being inside the service, thus while the transaction is still active and lazy loading can be triggered (any method called on a lazy element triggered the fetch from the database).
The problem is that lazy loading cannot occur while being outside of the transaction. And Jackson is parsing your entity definitely outside the boundaries of one.
You either should fetch all the required dependencies when building your specification or try with the #Transactional on the resource level (but try that as of last resort).
Just so that you know, LAZY fetching strategy is only a hint.. not a mandatory action. Eager is mandatory:
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.
When using a debugger, you are trying to access the value of your variables. So, at the moment you click that little arrow on your screen, the value of the variable in question is (lazily) loaded.
I suppose you are using Hibernate as JPA.
From specification:
The EAGER strategy is a requirement on the persistence provider runtime that data must be eagerly fetched. 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. https://docs.jboss.org/hibernate/jpa/2.2/api/javax/persistence/FetchType.html
Hibernate ignores fetch type specially in OneToOne and ManyToOne relationships from non owning side.
There are few options how to force Hibernate use fetch type LAZY if you really need it.
The simplest one is to fake one-to-many relationship. This will work because lazy loading of collection is much easier then lazy loading of single nullable property but generally this solution is very inconvenient if you use complex JPQL/HQL queries.
The other one is to use build time bytecode instrumentation. For more details please read Hibernate documentation: 19.1.7. Using lazy property fetching. Remember that in this case you have to add #LazyToOne(LazyToOneOption.NO_PROXY) annotation to one-to-one relationship to make it lazy. Setting fetch to LAZY is not enough.
The last solution is to use runtime bytecode instrumentation but it will work only for those who use Hibernate as JPA provider in full-blown JEE environment (in such case setting "hibernate.ejb.use_class_enhancer" to true should do the trick: Entity Manager Configuration) or use Hibernate with Spring configured to do runtime weaving (this might be hard to achieve on some older application servers). In this case #LazyToOne(LazyToOneOption.NO_PROXY) annotation is also required.
For more informations look at this:
http://justonjava.blogspot.com/2010/09/lazy-one-to-one-and-one-to-many.html
Just a guess: you are forcing a fetch while building your specification.
I expect something like
static Specification<AirWaybill> buildSpec() {
return (root, query, criteriaBuilder) -> {
Join<AirWaybill, CorporateBranch> br = (Join) root.fetch("corporateBranch");
return criteriaBuilder.equal(br.get("addressType"), 1);
};
}
If this is the case, try changing root.fetch to root.join
The retrieved data already lazy but you are using debug mode its return value when click to watch a data from a debugger.
You can solve this problem with wit 2 steps with jackson-datatype-hibernate:
kotlin example
Add In build.gradle.kts:
implementation("com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:$jacksonHibernate")
Create #Bean
#Bean
fun hibernate5Module(): Module = Hibernate5Module()
Notice that Module is com.fasterxml.jackson.databind.Module, not java.util.Module
Another consideration is while using Lombok, #Data/#Getter annotation causes to load lazy items without need. So be careful when using Lombok.
This was my case.
I think I might have a solution. You can give this a try. This worked for me after 4 hours of hit and trial -
User Entity :
class User {
#Id
String id;
#JsonManagedReference
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Address> addressDetailVOList = new ArrayList<Address>();
}
Address entity :
class Address {
#JsonBackReference
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "userId")
private User user;
}
Your parent class will use #JsonManagedReference, and child class will use #JsonBackReference. With this, you can avoid the infinite loop of entity objects as response and stack overflow error.
I also faced the same issue with Spring data JPA. I added the below annotation & able to get the customer records for a given ORDER ID
Customer to Order : one to Many
Order to customer is lazy load.
Order.java
#ManyToOne(cascade = CascadeType.ALL,targetEntity = CustomerEntity.class,fetch = FetchType.LAZY)
#Fetch(FetchMode. JOIN)
#JoinColumn(name = "CUSTOMER_ID",referencedColumnName = "CUSTOMER_ID",insertable = false,updatable = false)
#LazyToOne(LazyToOneOption.PROXY)
Private CustomerEntity customer
Customer.java
#Entity
#TabLe(name = "CUSTOMER" ,
uniqueConstraints = #UniqueConstraint(columnNames= {"mobile"}))
public class CustomerEntity {
#GeneratedVaLue(strategy = GenerationType.IDENTITY)
#CoLumn(name = "customer_id" )
private Integer customerld;
private String name;
private String address;
private String city;
private String state;
private Integer zipCode;
private Integer mobileNumber;
#OneToMany(mappedBy = " customer" )
#Fetch(FetchMode.JOIN)
#LazyToOne(LazyToOneOption.PROXY)
private List<OrderEntity> orders;
}

An object with an Eager fetch Type property, executes queries for the lazy loaded properties of that property.. why?

I'm using SpringBoot, when a repository is called for the AModel, the repository is executing queries for BModel, CModel and DModel even when I am not calling for either CModel or DModel. any idea why this is happening and how I can prevent it?
#Entity
public class AModel extends Model {
#OneToOne(fetch = FetchType.EAGER)
#JsonIgnore
private BModel;
}
#Entity
public class BModel extends Model {
#OneToOne(fetch = FetchType.LAZY)
private CModel;
#OneToOne(fetch = FetchType.LAZY)
private DModel;
}
#Query("select a from com.project.models.AModel a where a.id = :id")
#Override
Candidate findOne(#Param("id")Long id);
The reason here is that when the entity AModel includes entity BModel, which in turn includes CModel and DModel. It has to fetch CModel and DModel when the fetch for AModel is called, otherwise your queries won't be able to complete if the objects of CModel and DModel are not fetched for, and the entire purpose of making the fetchType as Eager for AModel will be gone.
This happens cause of the oneToOne relations from BModel to CModel and DModel.
When you define a relation with FetchType.LAZY, then hibernate needs to substitute the object with a proxy, so that when you access this the first time, it can load it.
Now with oneToOne relation which is nullable, hibernate has no chance to know, if the relation is null or not without execute a select, cause the table in relation usually use the same primary key.
So if your relations are nonnullable, then define optional = false and no eager fetching is done. If this is not the case you could also use a oneToMany relation instead.
See also this stackoverflow question

Automatically hibernate save relationship

Basically my question is why if I have an Hibernate relationship like this one.
#OneToMany(cascade = {CascadeType.ALL})
#JoinColumn(name = "candidacy_id", nullable = false)
#XmlElement
#JsonIgnore
#Getter
#Setter
private List<EvaluationSelectionCriteria> evaluationSelectionCriterias = new ArrayList<>();
#ManyToOne
#JoinColumn(name = "candidacy_id", nullable = false, insertable = false, updatable = false)
#XmlTransient
#Getter
#Setter
private Candidacy candidacy;
Why if I do this candidacy.setEvaluationSelectionCriteria(list) automatically this list is persisted in database?
I would like to use the EvaluationSelectionCriteria as a repository to render a list of "future" EvaluationSelectionCriteria
Could be because is not Lazy?
More detail explanation
So would be like I call method a, there I´m get from database entity A then I set a list into A and then I return A in the method but I´m not saving A, when I see the value of the list already have ids!!!
If you do not want the list to be saved when the parent entity is saved/merged, you should remove or restrict the cascade setting for the relationship:
#OneToMany
private List<EvaluationSelectionCriteria> evaluationSelectionCriterias
or
#OneToMany(cascade = CascadeType.REMOVE) // or other values from the enum
private List<EvaluationSelectionCriteria> evaluationSelectionCriterias
EDIT: If you want to fetch an entity in a transactional method and modify it, you can restrict the scope of the transaction to the fetching only. Then modify the entity outside the transactional method. Later, you can merge the detached entity if needed.
Since collection attributes are lazy per default you will either need to
access their content while still inside the transactional method - so the collection can be fetched from the DB. Please note that you will have to call a method on the collection that actualy requires it's content to be loaded, like getCriterias().size().
use LEFT JOIN FETCH to load the collection as a side effect of the query.
I would not modify the FlushMode for the session - while this would probably work, it feels like a kludge - it does not communicate your intent very well. Explicitly fetching the collection and modifying it outside the transaction expresses your intent better IMO.
I found the solution, I forgot to say that I´m using Spring, so finally I add the #Transactional(readOnly=true) into my method instead in the service class level.

JPA OneToMany eager fetch does not work

I have a weird problem with two entities with one-to-many relation in JPA. I am using Glassfish 3.1.2.2 with EclipseLink 2.3.2. This is the first entity:
#NamedQueries({
#NamedQuery(name="SampleQueryGroup.findAll", query="SELECT g FROM SampleQueryGroup g")
})
#Entity
public class SampleQueryGroup implements Serializable {
// Simple properties, including id (primary key)
#OneToMany(
mappedBy = "group",
fetch = FetchType.EAGER,
cascade = {CascadeType.REMOVE, CascadeType.MERGE}
)
private List<SampleQuery> sampleQueries;
// Gettes/setters, hashcode/equals
}
And this is the second one:
#Entity
public class SampleQuery implements Serializable {
// Simple properties, including id (primary key)
#ManyToOne(cascade = {CascadeType.PERSIST})
private SampleQueryGroup group;
// Gettes/setters, hashcode/equals
}
I have a stateless session bean which uses an injected EntityManager to run SampleQueryGroup.findAll named query. I also have a CDI managed bean which calls the SSB method and iterates through SampleQueryGroup.getSampleQueries() for each SampleQueryGroup returned by the method. I didn't paste the code as it is pretty straightforward and somehow standard for any Java EE application.
The problem is the eager fetch does not work and getSampleQueries() returns an empty list. However, when I change the fetch type back to FetchType.LAZY, everything works and I get the list correctly populated. I don't understand why this happens. Does it have anything to do with internal caching mechanisms?
My guess is that when you add a new SampleQuery you are not adding it to the SampleQueryGroup sampleQueries, so when you access it, it is not their. When it is LAZY you do not trigger it until you have inserted the SampleQuery, so then it is there.
You need to maintain both sides of your relationships. (you could also disable caching, or refesh the object, but your code would still be broken).
See,
http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Object_corruption.2C_one_side_of_the_relationship_is_not_updated_after_updating_the_other_side

Categories