I have tow classes, the "Article" which contains a #ManyToOne reference to a "SurchargeGroup" which specifies the surcharge for that article.
#Entity
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode(doNotUseGetters = true)
#Audited
public final class Article {
#Id
#GeneratedValue(generator = "increment")
#GenericGenerator(name = "increment", strategy = "increment")
#Getter(onMethod_ = {#Key(PermissionKey.ARTICLE_ID_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.ARTICLE_ID_WRITE)})
private int id;
#JoinColumn(nullable = false)
#ManyToOne
#Getter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_WRITE)})
private SurchargeGroup surchargeGroup;
}
The other class "SurchargeGroup" contains a parent object reference which can inherit the surcharge to the "SurchargeGroup" if it isn't set the case that no surcharge is provided by any parent is not possible.
#Table
#Entity
#EqualsAndHashCode(doNotUseGetters = true)
#Audited
public class SurchargeGroup implements Serializable, Cloneable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
#Getter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_ID_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_ID_WRITE)})
private int id;
#Column
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SURCHARGE_WRITE)})
private Double surcharge;
#Column
#Getter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_NAME_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_NAME_WRITE)})
private String name;
#JoinColumn
#ManyToOne(fetch = FetchType.EAGER)
#Getter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_WRITE)})
private SurchargeGroup parent;
public double getSurcharge() {
if (surcharge == null) {
return parent == null
? supplier == null
? Setting.SURCHARGE_DEFAULT.getDoubleValue()
: supplier.getDefaultSurcharge()
: parent.getSurcharge();
} else return surcharge;
}
#JoinColumn
#ManyToOne
#Getter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_WRITE)})
private Supplier supplier;
}
My problem is now that if I call the "getSurcharge()" method I get this exception which I cannot explain to myself because I marked the surcharge group to fetch eager
Exception in thread "AWT-EventQueue-0" org.hibernate.LazyInitializationException: could not initialize proxy [kernbeisser.DBEntities.SurchargeGroup#1046] - the owning Session was closed
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:172)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:309)
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
at kernbeisser.DBEntities.SurchargeGroup$HibernateProxy$cdTAuBkS.getSurcharge(Unknown Source)
I asked myself if this could get caused by the #Audited annotation? Any ideas? Thanks a lot!
Note: the #Key annotations have no effect to this scenario.
Here is what the debugger shows (Sorry for the German toString() functions):
Hibernate needs to stop eagerly fetching associations at some point, otherwise it would need to join an infinite number of times the SurchargeGroup entity (since it references itself).
The depth these fetches can be controlled application wide using the hibernate.max_fetch_depth property.
The source of the error was the AuditReader it doesn't fetch all eager properties even if they are annotated as Fetch.EAGER
It looks like the AuditReader only fetches one level of eager relations:
Article -> SurchargeGroup -> SurchargeGroup -> ...
(fetched) (fetched) (not fetched)
Related
Mandatory background info:
As part of my studies to learn Spring, I built my usual app - a little tool that saves questions and later creates randomized quizzes using them.
Each subject can have any number of topics, which in turn may have any number of questions, which once again in turn may have any number of answers.
Now, the problem proper:
I keep getting LazyInitializationExceptions.
What I tried last:
I changed almost each and every collection type used to Sets.
Also felt tempted to set the enable_lazy_load_no_trans property to true, but I've consistently read this is an antipattern to avoid.
The entities proper: (only fields shown to avoid wall of code-induced fatigue)
Subject:
#Entity
#Table(name = Resources.TABLE_SUBJECTS)
public class Subject implements DomainObject
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = Resources.ID_SUBJECT)
private int subjectId;
#Column(name="subject_name", nullable = false)
private String name;
#OneToMany(
mappedBy = Resources.ENTITY_SUBJECT,
fetch = FetchType.EAGER
)
private Set<Topic> topics;
}
Topic:
#Entity
#Table(name = Resources.TABLE_TOPICS)
public class Topic implements DomainObject
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "topic_id")
private int topicId;
#Column(name = "name")
private String name;
#OneToMany(
mappedBy = Resources.ENTITY_TOPIC,
orphanRemoval = true,
cascade = CascadeType.MERGE,
fetch = FetchType.EAGER
)
private Set<Question> questions;
#ManyToOne(
fetch = FetchType.LAZY
)
private Subject subject;
}
Question:
#Entity
#Table(name = Resources.TABLE_QUESTIONS)
public class Question implements DomainObject
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = Resources.ID_QUESTION)
private int questionId;
#Column(name = "statement")
private String statement;
#OneToMany(
mappedBy = Resources.ENTITY_QUESTION,
orphanRemoval = true,
cascade = CascadeType.MERGE
)
private Set<Answer> answers;
#ManyToOne(
fetch = FetchType.LAZY
)
private Topic topic;
}
Answer:
#Entity
#Table(name = Resources.TABLE_ANSWERS)
public class Answer implements DomainObject
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = Resources.ID_ANSWER)
private int answerId;
#Column(name = "answer_text", nullable = false)
private String text;
#Column(name = "is_correct", nullable = false)
private Boolean isCorrect;
#ManyToOne(
fetch = FetchType.LAZY
)
private Question question;
}
I'm using interfaces extending JpaRepository to perform CRUD operations. I tried this to fetch stuff, without luck:
public interface SubjectRepository extends JpaRepository<Subject, Integer>
{
#Query
Optional<Subject> findByName(String name);
#Query(value = "SELECT DISTINCT s FROM Subject s " +
"LEFT JOIN FETCH s.topics AS t " +
"JOIN FETCH t.questions AS q " +
"JOIN FETCH q.answers as a")
List<Subject> getSubjects();
}
Now, the big chunk of text Spring Boot deigns to throw at me - the stack trace:
Caused by: org.hibernate.LazyInitializationException: could not initialize proxy [org.callisto.quizmaker.domain.Subject#1] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:322) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
at org.callisto.quizmaker.domain.Subject$HibernateProxy$B8rwBfBD.getTopics(Unknown Source) ~[main/:na]
at org.callisto.quizmaker.service.QuizMakerService.activeSubjectHasTopics(QuizMakerService.java:122) ~[main/:na]
at org.callisto.quizmaker.QuizMaker.checkIfActiveSubjectHasTopics(QuizMaker.java:307) ~[main/:na]
at org.callisto.quizmaker.QuizMaker.createNewQuestion(QuizMaker.java:117) ~[main/:na]
at org.callisto.quizmaker.QuizMaker.prepareMainMenu(QuizMaker.java:88) ~[main/:na]
at org.callisto.quizmaker.QuizMaker.run(QuizMaker.java:65) ~[main/:na]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:769) ~[spring-boot-2.6.3.jar:2.6.3]
This exception happens when I call this line of code:
boolean output = service.activeSubjectHasTopics();
Which, in turn, calls this method on a service class:
public boolean activeSubjectHasTopics()
{
if (activeSubject == null)
{
throw new NullPointerException(Resources.EXCEPTION_SUBJECT_NULL);
}
return !activeSubject.getTopics().isEmpty();
}
The activeSubjectHasTopics method gets called in this context:
private void createNewQuestion(View view, QuizMakerService service)
{
int subjectId = chooseOrAddSubject(view, service);
service.setActiveSubject(subjectId);
if (checkIfActiveSubjectHasTopics(view, service))
{
chooseOrAddTopic(view, service, subjectId);
}
do
{
createQuestion(view, service);
createAnswers(view, service);
}
while(view.askToCreateAnotherQuestion());
service.saveDataToFile();
prepareMainMenu(view, service);
}
private boolean checkIfActiveSubjectHasTopics(View view, QuizMakerService service)
{
boolean output = service.activeSubjectHasTopics();
if (!output)
{
view.printNoTopicsWarning(service.getActiveSubjectName());
String topicName = readTopicName(view);
createNewTopic(service, topicName);
}
return output;
}
Not helpful if you change your structure to set. If you need to get the entities you need to explicitly include FETCH clause in your hql queries. You'll work your way by checking out the Hibernate documentation:
https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/performance.html#performance-fetching
I was able to track down the cause of the issue thanks to a comment from Christian Beikov - to quote:
Where do you get this activeSubject object from? If you don't load it
as part of the transaction within activeSubjectHasTopics, then this
won't work as the object is already detached at this point, since it
was loaded through a different transaction.
The activeSubject object was defined as part of the service class containing the activeSubjectHasTopics method, and was initialized by a different transaction as he pointed out.
I was able to fix the problem by annotating that service class as #Transactional and storing the IDs of the objects I need instead of the objects themselves.
I have data being persisted in Spring of employees and "personal development plans". Employee is the dominant class, so to speak. It looks like this:
#Entity(name = "employee")
#Table(name = "EMPLOYEE")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "EMPLOYEE_ID")
private int id;
// etc...
}
Personal Development Plan looks like this:
#Entity(name = "pdp")
#Table(name = "PDP")
public class PersonalDevelopmentPlan implements Serializable {
#Id
#GeneratedValue
#Column(name = "PDP_ID")
private int id;
#ManyToOne(optional = false)
#JoinColumn(name = "EMPLOYEE_ID")
private Employee employee;
// etc..
}
In the database it is stored as a foreign key reference from PDP -> Employee.
I want to be able to load a PDP as it is in the database, with only employee id, but i always get the whole Employee object with all attributes. How do i do this?
I tried #ManyToOne(fetch = FetchType.Lazy) but this gives me the following error when fetching:
Type definition error: [simple type, class
org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]; nested
exception is
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No
serializer found for class
org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no
properties discovered to create BeanSerializer (to avoid exception,
disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference
chain:
java.util.ArrayList[0]->nl.kars.lms.model.pdp.PersonalDevelopmentPlan["employee"]->nl.kars.lms.model.Employee$HibernateProxy$AAwzPX4I["hibernateLazyInitializer"])
What am i doing wrong?
If you want only the ID, why not map it without the relationship ?
#Entity(name = "pdp")
#Table(name = "PDP")
public class PersonalDevelopmentPlan implements Serializable {
#Id
#GeneratedValue
#Column(name = "PDP_ID")
private int id;
#Column(name = "EMPLOYEE_ID")
private Long employeeId;
// etc..
}
It is actually how an ORM works... mapping table(relational side) to entities(object side). And mapping between entities is not done via ids but by entity references.
So either just persist the id (so remove strong relation) or use a projection query to just get back the employee Id.
You can create getter:
public Long getEmployeeId(){
return this.employee.getId();
}
Or you can change mapping to value private Long employeeId.
I do have two entities called School and Level with a Many-To-Many relationship.
#Data
#Entity
public class School {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "school_generator")
#SequenceGenerator(name="school_generator", sequenceName = "school_seq", allocationSize=1)
#Column(name = "school_id")
private Long id;
#NotBlank
private String name;
#NotBlank
private String city;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(
name = "school_level",
joinColumns = #JoinColumn(name = "school_id"),
inverseJoinColumns = #JoinColumn(name = "level_id"))
private Set<Level> levels = new HashSet<>();
}
#Data
#Entity
public class Level implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "level_generator")
#SequenceGenerator(name = "level_generator", sequenceName = "level_seq", allocationSize = 1)
#Column(name = "level_id")
private Long id;
#NotBlank
#Column(nullable = false, unique = true)
private String name;
}
My SchoolController, Rest controller, has a create method which takes a SchoolDTO (for now it has the same structure as the entity).
When I post the DTO, the nested child list Levels are populated only with existing Levels ids (since Levels are created previously).
When I try to save through the JPARepository save method...An exception with this message is thrown:
"detached entity passed to persist: com.salimrahmani.adawat.domain.Level; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.salimrahmani.adawat.domain.Level",
"trace": "org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist:
The problem is solved by iterating through each Level Id ...get the references by using the JPA Repository method getOne (which calls entityManager.getReference)
and then loads the right model and then persist successfully.
#PostMapping("/{id}/grades")
public ResponseEntity<SchoolDTO> addLevelToPackage(#PathVariable Long id, #RequestBody #Valid LevelDTO levelDTO) {
return schoolService.findById(id)
.map(school -> {
Level level = levelMapper.map(levelDTO);
if(level.getId() != null) {
level = levelService.getOne(level.getId());
school.getLevels().add(level);
} // else error
School saved = schoolService.save(school);
return ResponseEntity.ok(schoolMapper.map(saved));
}).orElseGet(() -> ResponseEntity.notFound().build());
}
My question is: Is this the "only" correct way to posting association in Spring Data? Or, is there any better way?
In the addLevelToPackage function Why using the map schoolService.findById(id).map.. if as I think the schoolService will return only one instance of the entity School with the given id.
I think replacing the map with the following would be enough:
...
School theSchool = schoolService.findById(id);
Level theLevel = levelService.getOne(levelDTO.getId());
theSchool.getLevels().add(theLevel);
School saved = schoolService.save(theSchool);
return ResponseEntity.ok(schoolMapper.map(saved));
...
I created an instance of A, defined name, with a blank collection of entities B and save it into DB. This is revision #1. Now I use the following statement to get all initial revision of class A
//Get revisions
A a = auditReader.find(A.class, aId, revisions.get(0));
I am getting an exception
could not resolve property: aId_id of: .B_AUDIT [select e__ from B_AUDIT e__ where e__.aId_id = :a_id and e__.originalId.REV.id <= :revision and REVTYPE != :delrevisiontype and (e__.REVEND.id > :revision or e__.REVEND is null)]
Following are my class details
#Table(name = "A")
#Audited
public class A{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
....
#OneToMany(mappedBy = "aId")
#AuditMappedBy(mappedBy = "aId")
private List<B> b;
}
which has #oneToMany relationship with B
#Entity
#Table(name = "B")
#Audited
public class B{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private int aId;
...
}
Hibernate Enver Version : 5.1.4.Final
Thank you for your support.
If I had to wager a guess, I believe it is likely because of how you decided to map the oppsite side of the #OneToMany relationship inside entity B. You mapped it directly to the primary key value rather than to the entity type itself.
In other words, Envers likely expected this mapping instead:
#Entity
#Table(name = "B")
#Audited
public class B {
// other stuff removed for breavity
#ManyToOne
private A a;
}
I am getting an exception when I am updating Parent record in spring data jpa.
This is my code:
ParentEntity
#Entity
#Table(name = "CAMP")
#Getter
#Setter
public class Parent extends AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tkeygenerator")
#GenericGenerator(name = "tkeygenerator", strategy = "com.custom.TKeyGenerator",
parameters = {#org.hibernate.annotations.Parameter(name = "sequence", value = "TKEY_SEQ")})
#Column(name = "TKEY", nullable = false)
private String id;
#ManyToOne
#JoinColumn(name = "SUB_CAT_TYPE_CODE", referencedColumnName = "SUB_CAT_TYPE_CODE")
private Child child;
#Column(name = "DATE")
#Basic
private LocalDate date;
}
Child Entity
#Entity
#Table(name = "SUB_CAT_TYPE")
#AttributeOverrides({
#AttributeOverride(name = "code",
column = #Column(name = "SUB_CAT_TYPE_CODE", length = 30)),
#AttributeOverride(name = "description",
column = #Column(name = "SUB_CAT_TYPE_DESC", length = 255))})
#EqualsAndHashCode(callSuper = true)
public class Child extends AbstractTypeDesc {}
TestCode
public Parent update(#PathVariable("id") String id, #Valid #RequestBody UpdateDto dto) {
Parent parentObj = parentRepository.findById(id);
mapper.map(dto, parentObj); // Dozer to map incoming dto to domain
childRepository.findByCode(dto.child().getCode())
.map(child -> {
parentObj.setChild(child);
return child;
});
return parentRepository.save(parentObj); //Exception occurs here
}
I am getting an exception while trying to update code variable of child entity in parent entity as fk. It says can't alter code from X to Y.
Any suggestion?
I figured out what went wrong in above code block for update operation.Although not much clear why its happening.Dozer mapping which maps dto to Domain was causing issue it was changing the value of child entity and then again when i was trying to set child entity through setter method It was causing "Id alter exception", Though i thats the same thing i dont know why it was taking it differently. below is the working Working code.
Test code should be like
public Parent update(#PathVariable("id") String id, #Valid #RequestBody UpdateDto dto) {
Parent parentObj = parentRepository.findById(id);
childRepository.findByCode(dto.child().getCode())
.map(child -> {
parentObj.setChild(child);
return child;
});
mapper.map(dto, parentObj); // Dozer dto to domain mapping was causing problem
return parentRepository.save(parentObj);
}