I am facing lazy inizialization issue when I added Lombok project into my hibernate project and used its #Getter and #Setter on the entity class.
Entity classes are annotated with #Entity of Javax.persistence as I am using hibernate 5.
Issue stacktrace :-
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:146)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:259)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:73)
at com.capehenry.domain.user.User_$$_jvst52e_9.getId(User_$$_jvst52e_9.java)
at com.capehenry.business.rs.course.SeatRequestResource.validateSeatRequestCancel(SeatRequestResource.java:338)
at com.capehenry.business.rs.course.SeatRequestResource.cancel(SeatRequestResource.java:220)
Everything was working fine with below code
#Entity
#Audited
#Table(name = "seat_request")
public class SeatRequest extends BaseEntity {
private CourseSchedule courseSchedule;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "courseScheduleId", nullable = false)
public CourseSchedule getCourseSchedule() {
return courseSchedule;
}
public void setCourseSchedule(CourseSchedule courseSchedule) {
this.courseSchedule = courseSchedule;
}
When I do searRequest.getCourseSchedule().getId() it works at rest layer means outside the transaction.
As soon as I change the code to below (add lombok), searRequest.getCourseSchedule().getId() at rest layer starts throwing lazyInitializationException :-
#Entity
#Audited
#Table(name = "seat_request")
#Setter
public class SeatRequest extends BaseEntity {
#ManyToOne(fetch = FetchType.LAZY, optional=false)
#JoinColumn(name = "courseScheduleId", nullable = false)
private CourseSchedule courseSchedule;
NOTE :-
1) I have to compulsory use Lombok project
2) I have to use searRequest.getCourseSchedule().getId() outside Sevrice and trasaction
Please suggest the solution, Thanks in advance!!
I have to use searRequest.getCourseSchedule().getId() outside Service and transaction
I have noticed this just now... If you are outside the service and transaction you will always have that exception. Try to use FetchType.EAGER and it should work.
When you are out of transaction your entities are detached, this means that all collections that you marked as lazy won't be loaded. So you have two options: the first is to perform all calls to collections getters inside the transaction, the second one is to mark as eager your collection, so when Hibernate loads the entity it will also load referenced collection immediately. Alternatively you could map to a DTO your Entity inside your transaction. As long as you are in the transaction the getters of lazy loaded field will always work, so a mapper to the DTO would access all informations. Once that the DTO is out of the transaction you will have access to all fields you have mapped, and than do whatever you want.
Here is how I solved the issue finally!
I thought the issue started after integration with Lombok project but the issue started when the annotations were moved to field level from method (property) level.
Bear with me for the long answer.
Here foreign is refering to the database level foreign tables.
To access any column from foreign table's outside the transaction you need to either use FetchType.Eager (which is default in hibernate for any foreign object) or need to join/subquery that table.
But if you just want to fetch the foreign key(column) with which 2 tables are joined (in our case the ID) and want to keep FetchType.LAZY then you can do it in 2 ways :-
1) Keep annotations (manyToOne, JoinColumn etc) on getter methods
2) If annotations has to be kept on field level then write one more annotation on foreign key field in parent table which is - #Access(AccessType.PROPERTY)
So in above code to solve I added this annotation on id field of course Schedule
#Entity
#Audited
#Table(name = "course_schedule")
#Getter
#Setter
public class CourseSchedule{
#Id
#GenericGenerator(name = "autoincr", strategy = "native")
#GeneratedValue(generator = "autoincr")
#Column(name = "id", unique = true, nullable = false)
#Access(AccessType.PROPERTY)
protected Long id;
..........
}
So no change was required in Seat Request.
Related
I have a JpaRepository persisting newly created entity in Spring MVC app. This entity looks like this (very simplified):
#Entity
public class Translation {
.....
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToOne(fetch = FetchType.LAZY)
private Version version;
....
}
and Version entity:
#Entity
public class Version {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
#Column(name = "version_code")
private long code;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "version", cascade = {CascadeType.ALL}, orphanRemoval = true)
private Set<Translation> translations;
}
I create a translation object like this
TranslationDTO t = new TranslationDTO();
t.setText(translationText);
ClientVersionDTO version = new ClientVersionDTO();
version.setId(11);
t.setVersion(version);
where 11 is a version that exists in the database already from the very beginning. Please notice that I do not set values for name and code of ClientVersionDTO.
Then I have a service that persists new object (I use dozer library to convert DTO to entities)
#Service
#Transactional
public class TranslationsServiceImpl implements TranslationsService {
#Override
public Long create(TranslationDTO translationDTO) {
Translation translation = translationsConverter.unconvert(translationDTO);
Translation t = translationRepository.saveAndFlush(translation);
Translation t2 = translationRepository.findOne(t.getId());
// !!!! t2.getVersion() returns version where no values are set to 'code' and 'name'
return t2.getId();
}
}
Please notice my comment "t2.getVersion() returns version where no values are set to 'code' and 'name'" - I was expecting so that when I fetch the data from the database, I would get a Version object right from the database with code and name values set. However they are not set. So basically what I get as a t2.getVersion() object is the same object as in input argument translationDTO.getVersion(). How can they I re-invalidate the Version object?
UPDATE tried moving #Transactional to JpaRepository, but still the same result.
If you are using Hibernate, this is the expected result. When you call translationRepository.saveAndFlush(translation) and translationRepository.findOne(t.getId()) one after the other, they hit the same Hibernate session which maintains a cache of all objects that it has worked on. Therefore, the second call simply returns the object passed to the first. There is nothing in those two lines that would have forced Hibernate to fire a SELECT query on the database for the Version entity.
Now, the JPA spec does have a refresh method on the EntityManager interface. Unfortunately, Spring Data JPA does not expose this method using its JpaRepository interface. If this method was available, you could have done t = translationRepository.saveAndFlush(translation) and then versionRepository.refresh(t.getVersion()) to force the JPA provider to synchronize the version entity with the database.
Implementing this method is not difficult. Just extend SimpleJpaRepository class from Spring Data JPA and implement the method yourself. For details see adding custom behaviour to all Spring Data JPA repositories.
An alternate would be to load the version entity as versionRepository.findOne(version.getId()) before setting it on the translation. Since you can hard-code version id in your code, your versions seem to be static. You can therefore mark your Version entity as #Immutable and #Cacheable (the former is a Hibernate-specific annotation). That way, versionRepository.findOne(version.getId()) should not hit the database every time it is called.
I try to manually validate a graph of entities. I didn't have issues doing it when using Hibernate SessionFactory. Since I switched to Hibernate JPA the nested entities are not validated anymore. Why?
The Hibernate event-based validation is run with the default group and works at pre-persiste/pre-update/pre-remove phases, but a manual validation doesn't detect validation error in nested entities.
The whole entity graph is eagerly loaded so I assume the TraversableResolver is not the issue here. Anyway I still declared a custom TraversableResolver that always request the navigation to nested entities.
If I create a fresh entity graph in a unit test, outside of the persistence context, the validation error is found. Yet, if I detach the parent entity from the persistence context the validation error is still not found.
Any help understanding this issue would be greatly appreciated.
I'm using org.springframework.boot:spring-boot-starter-data-jpa (Spring Boot 2.1.7.RELEASE), packaged with Hibernate 5.3.10.Final. I'm also using Lombok.
Here is my code. Should the alwaysAnError field be present on the parent class a validation error will be found. If this field is nested in an #Valid child no error is found.
Parent.java
#Data #Builder #NoArgsConstructor #AllArgsConstructor
#Entity
#Table(name = "parent")
public class Parent {
[...]
#Valid
#Builder.Default
#OneToMany(mappedBy = "file", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#Fetch(value = FetchMode.SUBSELECT)
private List<#Valid Child> children = new ArrayList<>();
}
Child.java
#Data #Builder #NoArgsConstructor #AllArgsConstructor
#Entity
#Table(name = "child")
public class Child {
[...]
#ManyToOne
#JoinColumn(name = "file_id")
private File file;
#NotNull
#Transient
private String alwaysAnError = null;
}
ValidationService.java
Validator validator = Validation.byDefaultProvider()
.configure()
.traversableResolver(new TraversableResolver() {
#Override
public boolean isReachable(Object traversableObject, Path.Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType) {
return true;
}
#Override
public boolean isCascadable(Object traversableObject, Path.Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType) {
return true;
}
})
.buildValidatorFactory()
.getValidator();
Set<ConstraintViolation<Declaration>> constraintViolations = validator.validate(fileInstance, Default.class);
if (!constraintViolations.isEmpty()) {
throw new RuntimeException(constraintViolations);
}
Related questions:
HibernateValidator 5 seems to not cascade validation on JPA entities
I found the reason. The parent-child relation is declared with FetchType.EAGER but in my code the parent entity was already lazily loaded before reaching the validation step. Since Hibernate did already cache the entity it will retrieve the proxy and not an eagerly loaded instance.
From this post.
Hibernate does everything it can to have one and only one instance of
an entity in the session.
The parent entity was already loaded from another relation.
#Entity
#Table(name = "lazy_child")
public class LazyChild {
[...]
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "file_id")
private File file;
}
But that's not all of it. This is not a vanilla lazy-loading otherwise the TraversableResolver would have solved the issues and it does not explain why embedded children can't be validated either.
Looking at the debugger it seems some kind of byte code enhancement is made on the parent entity.
Every field appears as null but can be accessed upon request (as long it's not the bean validation that does the request).
The entity is not just proxied but also modified in some way.
The relationships can't be traversed even with a yes-only TraversableResolver.
The embedded objects are ignored by the validator.
The solution is to not use Lombok but declare getters and annotate them with #Valid.
When lazy loaded associations are supposed to be validated it is
recommended to place the constraint on the getter of the association.
See the Bean Validation documentation for the quote and this post for further references.
I created an issue in the Hibernate tracker and opened a second SO question requesting more explanation.
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
I'm working with JPA 2 + Hibernate 4 and I'm implementing some CRUD operations on model entities.
Now I need to prevent a certain entity (EntityB) to be deleted when a related entity (EntityA) exists in database:
#Entity
public class EntityA {
#Id
private int id;
#OneToOne(mappedBy = "entityA", optional = false, fetch = FetchType.LAZY)
private EntityB entityB;
//...
}
#Entity
public class EntityB {
#Id
private int id;
#OneToOne
#JoinColumn(name = "id")
private EntityA entityA;
//...
}
Is there any way to achieve this using relationship options or should I check EntityA existence in my dao/repository before removing EntityB?
NOTE I need this also for #ManyToOne relationships.
If you want to prevent that in your code, than simply do not delete that entity (by checking that manually). There is no possibility to do that with annotations.
On the other side, this sounds to me rather like a need for a DB constraint. If those entities are already related, then simply add a foreign key constraint (if none is existent). If not, than think of adding one.
PS: if you already have a relationship, check the CascadeType.REMOVE setting.
I don't think you can solve this with annotations. You should manally check related-entity existence before.
I have two tables: t_promo_program and t_promo_program_param.
They are represented by the following JPA entities:
#Entity
#Table(name = "t_promo_program")
public class PromoProgram {
#Id
#Column(name = "promo_program_id")
private Long id;
#OneToMany(cascade = {CascadeType.REMOVE})
#JoinColumn(name = "promo_program_id")
private List<PromoProgramParam> params;
}
#Entity
#Table(name = "t_promo_program_param")
public class PromoProgramParam {
#Id
#Column(name = "promo_program_param_id")
private Long id;
//#NotNull // This is a Hibernate annotation so that my test db gets created with the NOT NULL attribute, I'm not married to this annotation.
#ManyToOne
#JoinColumn(name = "PROMO_PROGRAM_ID", referencedColumnName = "promo_program_id")
private PromoProgram promoProgram;
}
When I delete a PromoProgram, Hibernate hits my database with:
update
T_PROMO_PROGRAM_PARAM
set
promo_program_id=null
where
promo_program_id=?
delete
from
t_promo_program
where
promo_program_id=?
and last_change=?
I'm at a loss for where to start looking for the source of the problem.
Oh crud, it was a missing "mappedBy" field in PromoProgram.
Double-check whether you're maintaining bidirectional association consistency. That is; make sure that all PromoProgramParam entities that link to a PromoProgram as its parent are also contained in said parent's params list. It's a good idea to make sure this happens regardless of which side "initiates" the association if you will; if setPromoProgram is called on a PromoProgramParam, have the setter automatically add itself to the PromoProgram's params list. Vice versa, when calling addPromoProgramParam on a PromoProgram, have it set itself as the param's parent.
I've encountered this problem before as well, and it was due to not maintaining bidirectional consistency. I debugged around into Hibernate and found that it was unable to cascade the delete operation to the children because they weren't in the list. However, they most certainly were present in the database, and caused FK exceptions as Hibernate tried to delete only the parent without first deleting its children (which you've likely also encountered with the #NonNull in place).
FYI, I believe the proper "EJB 3.0"-way of making the PromoProgramParam.promoProgram field (say that a 100 times) non-nullable is to set the optional=false attribute on the #ManyToOne annotation.