I have some model with two relations:
#Entity
#Table(name = "data_model")
public class DataModel {
#Id
#GeneratedValue
#Column(name = "model_id")
private Integer id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "dataModel", cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.MERGE})
private List<OutputField> outputFields;
#OneToMany(mappedBy = "dataModel", cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.MERGE})
private List<Query> queries;
//some another fields
}
I use Spring Data JPA and I want to update entity. I write simple service:
#Service
public class DataModelService {
#Autowired
private DataModelRepository dataModelRepository;
#Transactional
public DataModel createOrUpdate(DataModel dataModel) {
return dataModelRepository.save(dataModel);
}
//another methods
}
I write simple test:
public class DataModelServiceTest {
#Autowired
private DataModelService dataModelService;
#Test
void shouldUpdateDataModel() {
DataModel dataModelBeforeUpdate = dataModelService.getById(1);
dataModelBeforeUpdate.getQueries().get(0).setSqlQuery("SELECT 1");
DataModel updatedModel = dataModelService.createOrUpdate(dataModelBeforeUpdate);
assertThat(updatedModel.getQueries(), notNullValue());
}
}
But, I get error, when I try to call method getQieries():
Unable to evaluate the expression Method threw 'org.hibernate.LazyInitializationException' exception.
In debug I see:
Questions:
Why does this error occur and how can I fix it? How do I make hibernate return all links after an update?
Why is the outputFields field filled in correctly, but the queries field is not?
It happens because you are trying to initialize collection outside a transaction. To fix this add #DataJpaTest and #RunWith(SpringRunner.class) annotations to your test class. By default, data JPA tests are transactional.
Refer here for more details.
Related
In a simple Spring Boot Application, I'm facing with a JpaObjectRetrievalFailureException when I'm trying to save an entity with one-to-many association and client-assigned ids.
Please take a look on these entities and on this simple repository:
#Entity
#Table(name = "cart")
public class Cart {
#Id
#Column(name = "id")
private UUID id;
#Column(name = "name")
private String name;
#OneToMany
#JoinColumn(name = "cart_id")
private List<Item> items;
// constructors, getters, setters, equals and hashCode ommitted
}
#Entity
#Table(name = "item")
public class Item {
#Id
#Column(name = "id")
private UUID id;
#Column(name = "name")
private String name;
// constructors, getters, setters, equals and hashCode ommitted
}
public interface CartRepository extends JpaRepository<Cart, UUID> {
}
I wrote this test:
#DataJpaTest
class CartRepositoryTest {
#Autowired
private CartRepository cartRepository;
#Test
void should_save_cart() {
// GIVEN
final var cart = new Cart(UUID.randomUUID(), "cart");
final var item = new Item(UUID.randomUUID(), "item");
cart.setItems(List.of(item));
// WHEN
final var saved = cartRepository.save(cart);
// THEN
final var fetched = cartRepository.findById(saved.id());
assertThat(fetched).isPresent();
}
}
When I run the test, call to cartRepository.save(cart) fails with:
Unable to find com.example.testjpaonetomany.domain.Item with id f5658508-f3d0-4d9b-a1f0-17b614753356; nested exception is javax.persistence.EntityNotFoundException: Unable to find com.example.testjpaonetomany.domain.Item with id f5658508-f3d0-4d9b-a1f0-17b614753356
org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find com.example.testjpaonetomany.domain.Item with id f5658508-f3d0-4d9b-a1f0-17b614753356; nested exception is javax.persistence.EntityNotFoundException: Unable to find com.example.testjpaonetomany.domain.Item with id f5658508-f3d0-4d9b-a1f0-17b614753356
at app//org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:379)
at app//org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:235)
at app//org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:551)
at app//org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at app//org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at app//org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152)
at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at app//org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174)
at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at app//org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at app//org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at app/jdk.proxy3/jdk.proxy3.$Proxy105.save(Unknown Source)
at app//com.example.testjpaonetomany.repository.CartRepositoryTest.should_save_cart(CartRepositoryTest.java:28)
If I modify my entities by adding #GeneratedValue for ids, and in my test, I replace UUID.randomUUID() by null to delegate to Hibernate the ID generation, the test passes.
How to deal with client-generated ids?
The cause is that you save the parent object only (which is absolutely correct and fine) but still need to explain JPA that the operation should be propagated i.e.
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "cart_id")
private List<Item> items;
As minor improvements I would suggest to put the UUID generation into constructors and establish the relation via the dedicated method i.e.
final var cart = new Cart("cart");
cart.addItem(new Item("item"));
and probably consider using em.persist() instead of repository.save() as it makes a select request first in case of using uuids as #Augusto mentioned
I was trying to implement bi-directional relationships bettwen my entities.
Student
#Table(name = "students")
#Entity
public class Student {
#Id
// #GeneratedValue(strategy = GenerationType.AUTO)
private long album;
#NotNull
private String name;
#NotNull
private String surname;
#OneToMany(mappedBy = "student", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
private List<StudentSection> studentSections;
#Transactional(propagation=Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class)
public void addSection(Section section){
if(this.studentSections == null){
this.studentSections = new ArrayList<>();
}
StudentSection studentSectionToAdd = new StudentSection();
studentSectionToAdd.setStudent(this);
studentSectionToAdd.setSection(section);
this.studentSections.add(studentSectionToAdd); //here
section.addStudentSection(studentSectionToAdd);
}
}
the connecting entity in a ManyToMany relationship
#Table(name = "student_section")
#Entity
public class StudentSection {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Integer grade;
private Date date;
#NotNull
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
#JoinColumn(name = "student_id")
private Student student;
#NotNull
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
#JoinColumn(name = "section_id")
private Section section;
}
and Section
#Table(name = "sections")
#Entity
public class Section {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
private String name;
#NotNull
private Integer sizeOfSection;
#NotNull
private Boolean isActive;
#OneToMany(mappedBy = "section", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
private List<StudentSection> studentSections;
void addStudentSection(StudentSection studentSection){
if(this.studentSections == null){
this.studentSections = new ArrayList<>();
}
this.studentSections.add(studentSection);
}
}
I ran into a problem with the Student.addSection() method. When trying to execute it I got an error on the this.studentSections.add(studentSectionToAdd); line, saying
failed to lazily initialize a collection of role: Student.studentSections, could not initialize proxy - no Session
I read about it and found out that the best way to fix this is to add the #Transactional annotation to the method, however it didnt change anything and I cant get it to work.
I also tried moving the Student.addSection() method to
StudentServiceImpl
#Service
#Primary
public class StudentServiceImpl implements StudentService {
protected StudentRepository studentRepository;
#Autowired
public StudentServiceImpl(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
#Override
#Transactional(propagation = Propagation.REQUIRED, readOnly = true, noRollbackFor = Exception.class)
public void addSection(Student student, Section section) {
if (student.getStudentSections() == null) {
student.setStudentSections(new ArrayList<>());
}
StudentSection studentSectionToAdd = new StudentSection();
studentSectionToAdd.setStudent(student);
studentSectionToAdd.setSection(section);
student.getStudentSections().add(studentSectionToAdd);
//section.addStudentSection(studentSectionToAdd);
}
}
but I still got the error.
I am also using CrudRepository to retrive entities from the database.
#Repository
public interface StudentRepository extends CrudRepository<Student, Long> {
Student findByName(String name);
}
This is where I call the method
#Component
public class DatabaseLoader implements CommandLineRunner {
private final StudentRepository studentRepository;
private final SectionRepository sectionRepository;
private final StudentSectionRepository studentSectionRepository;
private final StudentService studentService;
#Autowired
public DatabaseLoader(StudentRepository studentRepository, SectionRepository sectionRepository, StudentSectionRepository studentSectionRepository,
StudentService studentService) {
this.studentRepository = studentRepository;
this.sectionRepository = sectionRepository;
this.studentSectionRepository = studentSectionRepository;
this.studentService = studentService;
}
#Override
public void run(String... strings) throws Exception {
//Testing entities
Student student = new Student();
student.setAlbum(1L);
student.setName("student");
student.setSurname("test");
this.studentRepository.save(student);
Section section = new Section();
section.setName("section");
section.setSizeOfSection(10);
section.setIsActive(true);
this.sectionRepository.save(section);
//end
//Adding Student to a Section test
Student student1 = studentRepository.findByName("student");
//student1.setStudentSections(this.studentSectionRepository.findAllByStudent(student1));
Section section1 = sectionRepository.findByName("section");
//section1.setStudentSections(this.studentSectionRepository.findAllByStudent(student1));
studentService.addSection(student1, section1);
this.studentRepository.save(student1);
//end test
}
}
Also when I retrive StudentSection lists from the database here and set them im both objects before adding a new relationship it works fine, but this is not really the solution I am going for.
The problem is that every call from the run() method to studentRepository and studentService are separate sessions/transactions.
It's virtually as-if you did this:
...
beginTransaction();
this.studentRepository.save(student);
commit();
...
beginTransaction();
this.sectionRepository.save(section);
commit();
beginTransaction();
Student student1 = studentRepository.findByName("student");
commit();
beginTransaction();
Section section1 = sectionRepository.findByName("section");
commit();
// This does it's own transaction because of #Transactional
studentService.addSection(student1, section1);
beginTransaction();
this.studentRepository.save(student1);
commit();
Since transaction = session here, it means that student1 is detached, and that the lazy-loaded studentSections collection cannot be loaded on-demand outside the session, and hence the code fails.
Inserting a new student and a new section and associating them should really be one transaction, so if a later step fails, it's all rolled back.
Which basically means that you want the entire run() method to be one transaction, so in your case, it is the run() method that should be #Transactional, not the addSection() method.
Generally, in a 3-tiered approach, you would put transaction boundaries on service layer:
Presentation tier. This is #Controller classes, or the run() method for a simple command-line program.
Logic tier. This is #Service classes. This is where you put #Transactional, so each service call is an atomic transaction, i.e. it either succeeds or it fails, as far as the database updates are concerned, no half updates.
Data tier. This is #Repository and #Entity classes.
As such, you should keep the instantiation and initialization of the Student and Section objects in the run() method, but the rest of the code, incl. save(), should be moved to a single method in a #Service class.
About this
#Transactional(propagation=Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class)
public void addSection(Section section){
#Transactional works only for spring-managed beans and Entities are not managed by spring.
You get this exception because you try load a lazy relations outside a session (because your entity is actually in detached-state).
To re-attach --> entityManager.merge(student);
But the best thing to do is to load the relation at query-time. By using EntityGraph for example -->
#EntityGraph(attributePaths="studentSections")
Student findByName(String name);
I'm migrating my Spring Boot REST API from 1.5.4 to 2.0.3.
These are my two entities, a repository for one of them and a controller for accessing them:
Parent.java
#Entity
#Table(name = "PARENT")
public class Parent implements Serializable {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private List<Child> children;
}
Child.java
#Entity
#Table(name = "CHILD")
public class Child implements Serializable {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#Column(name = "PARENT_ID")
private Long parentId;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARENT_ID")
private Parent parent;
#Column(name = "name")
private String name;
}
ParentRepository.java
public interface ParentRepository extends JpaRepository<Parent, Long> {
}
ParentController.java
#RestController
#RequestMapping("/parents")
public class ParentController {
#Autowired
private ParentRepository parentRepository;
#RequestMapping(method = RequestMethod.GET)
public List<Parent> getParents() {
return parentRepository.findAll();
}
}
It appears that there is no longer an active session in the #RestController classes since
parentRepository.findAll().get(0).getChildren().get(0).getName();
now throws a
LazyInitializationException: failed to lazily initialize a collection of role: com.mycompany.myapplication.entity.Parent.children, could not initialize proxy - no Session
This can be fixed by setting a #Transactional annotation on either the controller method or the controller class.
However, the problem I have regards the lazily loaded children.
If I run the example code above, even with the #Transactional annotation, I get the same exception but with a nested
com.fasterxml.jackson.databind.JsonMappingException
This is due to the serialization to JSON happens outside of the controller, hence outside the active session.
There is an ugly fix for this, by reading some data from each child before exiting the method:
#RequestMapping(method = RequestMethod.GET)
public List<Parent> getParents() {
List<Parent> parents = parentRepository.findAll();
parents.stream()
.flatMap(p -> p.getChildren().stream())
.forEach(Child::getName);
return parents;
}
This works, but is terribly ugly and adds a lot of boilerplate.
Another solution would be to map all entities to DTOs before returning them to the client. But this solution adds another layer to my application which I don't want.
Is there a way to make sure that there is an active session during the automagical serialization of the entities?
Soo yeaah...
During migration I had previously set
spring.jpa.open-in-view = false
because I saw a new warning about it in the log. This setting removes the active session I wanted help adding...
Removing this setting and using the default (true) fixed my problem entirely.
Page entity.
#Entity
#Table(name = "pages", schema = "admin")
public class Page implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true)
private Integer id;
#Column(name = "name")
private String name;
#ManyToOne(targetEntity = Partition.class, fetch = FetchType.LAZY)
private Partition partition;
#Column(name = "is_startable")
private Boolean isStartable;
#Column(name = "priority")
private Integer priority;
#Column(name = "prefix_granted_authority")
private String prefixGrantedAuthority;
#OneToMany(mappedBy = "page", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private List<Permission> permissions;
#Column(name = "link", unique = true)
private String link;
PageRepository
List<Page> findByPermissionsGroupsOrderByPartitionNameAscNameAsc(#Param(value = "group") Group group);
PageServiceImpl
#Override
public Collection<Page> getAccessedPages(Group group) {
try {
List<Page> pages = pageRepository.findByPermissionsGroupsOrderByPartitionNameAscNameAsc(group);
return pages;
} catch (Exception ex) {
logger.error("getPage error", ex);
return null;
}
}
getAccessedPages return real List of page entities(not null), but all fields in entities are null.
Why?
I also encounter this problem while ago, it looks like spring data does some kind lazy instantiation.
So if you not access this fields inside of your transaction, they will stay null. Add annotation #Transactional on method where are you calling this request and problem will be solved.
I wanted to expand on #user902383's answer, which ultimately also solved my issue, but it was too long for a comment.
In my case, I had repository method fetching an entity, Helper, called inside a #PostLoad listener that used Helper for calculations for filling a field in another entity, Child. The listener method was already annotated with org.springframework.transaction.annotation.Transactional.
When called by Child's repository it fetched a Helper entity with all fields filled, but when called by the repository of an entity Parent which had a child Child, it fetched an empty Helper object with only the id filled even though it was properly annotated.
The issue was that I was using this hack to access the repository outside of a Spring #Component (I couldn't make the listener a component). I suspect that the Spring magic for detecting when a field is dereferenced in a #Transactional method does not work when the repository was not properly #Autowired. I still do not know why it worked in Child's repository but not in Parent.
My solution to this particular problem was moving the repository call and dereferencing to a #Service, which properly #Autowires the repository, and doing the hackish static call for getting that service instead, which makes for better code structure anyway.
I have two classes as following,
Human.java
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Human implements Serializable {
private long id;
private String name;
....
}
Student.java
#Entity
#DynamicUpdate
public class Student extends MyFactories {
private List<Know> KnowList;
#OneToMany(fetch = FetchType.LAZY)
public List<Know> getKnowlist() {
return knowlist;
}
public void setKnowlist(List<Know> KnowList) {
return Knowlist;
}
}
Know.java
#Entity
public class Know implements Serializable {
private long id;
private Human hu;
private Student st;
....
#ManyToOne
public Person getHu() {
return hu;
}
#ManyToOne
public Client getSt() {
return st;
}
.... setters .....
}
Code
Know kw = new Know();
kw.setSt(studentObject);
kw.setHu(humanObject);
session.save(kw);
tx.commit();
I am able to insert into Know table but hibernate does not insert any record to student_know table which it has created.
I have found this answer but it says I need to use that method if I always want to retrieve all the records. Which I do not (at times, I may just need to retrieve the student class not list of its know)
System.out.println(this.student.getKnowList().size());
When I try to access the list it runs into following exception.
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.myproject.Student.knowList, could not initialize proxy - no Session
for select case change that #OneToMany(fetch = FetchType.LAZY) to #OneToMany(fetch = FetchType.EAGER) so you can get data inside it's list.
and for the insert i need your clarification about where is your relation or getter setter of the private Factory fac;?
you should have at least something like this :
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "YOUR_FACTORY_ID_COLUMN")
private Factory fac;
public Factory getFac(){
return fac;
}
public void setFac(Factory fac){
this.fac=fac;
}
and did factory have any id?
You need to use session.Update(studentObject) as well, to insert a row into student_know table.
Please also be aware that access to a lazy association outside of the context of an open Hibernate session will result in an exception. Link