Why Spring JPA does not initialize LAZY property MyChildEntity.myParentEntity (all fields are null)?
I tried to use Hibernate.initialize and #Transactional, but that doesn't help.
my service:
#Service
#Transactional
public class MyService {
#Resource
private MyChildEntityRepository myChildEntityRepository;
#Resource
private MyParentEntityRepository myParentEntityRepository;
#PostConstruct
public void init() {
MyParentEntity p = myParentEntityRepository.save(new MyParentEntity("my name"));
myChildEntityRepository.save(new MyChildEntity(p, "first value"));
myChildEntityRepository.save(new MyChildEntity(new MyParentEntity(1L, "another name"), "another value"));
// At this point both MyChildEntity's are in database and have correct foreign key value
List<MyChildEntity> result = myChildEntityRepository.findAll();
//even this doesn't help, myParentEntity property still has all fields equals to null
Hibernate.initialize(result.get(0).getMyParentEntity());
MyParentEntity p2 = result.get(0).getMyParentEntity();
//trigger proxy's method to initialize lazy field
System.out.print(p2.getName()); // null
System.out.println(p2.getId()); // null
// PROBLEM: p2 has all fields equals null
// the same for result.get(1)
// BUT, this works correct - returns (1L, "my name") entity
myParentEntityRepository.findAll();
}
}
child entity:
#Entity
public class MyChildEntity {
#Id
#SequenceGenerator(sequenceName = "CHILD_SEQ", name = "ChildSeq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ChildSeq")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "my_parent_entity_id", referencedColumnName = "id")
private MyParentEntity myParentEntity;
#Column
private String value;
// constructors, getters, setters...
parent entity:
#Entity
public class MyParentEntity {
#Id
#SequenceGenerator(sequenceName = "WORKFLOW_SEQ", name = "WorkflowSeq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "WorkflowSeq")
private Long id;
#Column
private String name;
//constructors, getters, setters...
The fetch attribute indicates when the related entities should be retrieved from the
database using the javax.persistence.FetchType enum. FetchType.EAGER means that the JPA
provider must retrieve the values when the entity is retrieved. On the other hand, FetchType.LAZY
serves as a hint to the JPA provider that it can wait and fetch the values only when the property
is first accessed (which may be never, thus saving a trip to the database). However, JPA providers
are not required to support lazy loading, so these values may be loaded eagerly anyway.
Source: Professional Java for Web Applications by Nicholas S Williams
edit:
I really apologize I took this long. Here is what I think is wrong. I don't see an instance of child entity in parent entity. It should look like this:
public class MyParentEntity {
... //other fields
#OneToMany(fetch = FetchType.LAZY, mappedBy = "myParentEntity")
private Set<MyChildEntity> myChildEntities = new HashSet<MyChildEntity>;
... //other fields or constructors or getters or setters
...
}
I hope this works. If not, then in your MyChildEntity class, there is a weird annotation inside #JoinColumn called referencedColumnName. I don't know what that is. Please remove it.
Thanks
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 have generated master tables using liquibase. I have created the corresponding models in spring boot now I want to maintain a relation ship between those models.
I have one table called Vehicle_Type, it is already pre-populated using liquibase.
#Data
#Entity
#Table(name="VEHCILE_TYPE")
public class VehicleType {
#Id
private int id;
#Column(name="DISPLAY_NAME")
private String displayName;
#Column(name="TYPE")
private String type;
#Column(name="CREATED_DATE")
private LocalDateTime createdDate;
#Column(name="UPDATED_DATE")
private LocalDateTime updateDate;
}
now what I want to achieve is, I have one child entity, I have refer the VehicleType instance inside that entity as depicted below
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
#Table(name = "NON_MSIL_VEHICLE_LAYOUT")
public class NonMsilVehicleLayout extends BaseImagesAndLayout {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "NMV_SEQ")
#SequenceGenerator(sequenceName = "NON_MSIL_VEH_SEQUENCE", allocationSize = 1, name = "NMV_SEQ")
private int id;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
public interface VehType {
String getVehType();
}
}
The problem is when I tries to save entity NonMsilVehicleLayout, then it tries to first insert the data in VEHICLE_TYPE table also. which should not going to be happen.
I don't want that, I want JPA will pick the correct ID from VEHICLE_TYPE table and place it inside the corresponding table for NonMsilVehicleLayout, because the id of VEHICLE_TYPE table is act as foreign key in Non_Msil_Vehicle_Layout table.
log.info("Inside saveLayout::Start preparing entity to persist");
String resourceUri = null;
NonMsilVehicleLayout vehicleLayout = new NonMsilVehicleLayout();
VehicleType vehicleType=new VehicleType();
vehicleType.setType(modelCode);
vehicleLayout.setVehicleType(modelCode);
vehicleLayout.setFileName(FilenameUtils.removeExtension(FilenameUtils.getName(object.key())));
vehicleLayout.setS3BucketKey(object.key());
I know I missed something, but unable to figure it out.
You are creating a new VehicleType instance setting only the type field and set the vehicleType field of NonMsilVehicleLayout to that new instance. Since you specified CascadeType.ALL on NonMsilVehicleLayout#vehicleType, this means to Hibernate, that it has to persist the given VehicleType, because the instance has no primary key set.
I guess what you rather want is this code:
vehicleLayout.setVehicleType(
entitManager.createQuery("from VehicleType vt where vt.type = :type", VehicleType.class)
.setParameter("type", typeCode)
.getSingleResult()
);
This will load the VehicleType object by type and set that object on NonMsilVehicleLayout#vehicleType, which will then cause the foreign key column to be properly set to the primary key value.
Finally, after some workaround, I got the mistake, the column name attribute was incorrect, so I made it correct and remove the referencedColumn and Cascading.
Incorrect:
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
Correct:
#OneToOne
#JoinColumn(name = "VEHICLE_TYPE")
private VehicleType vehicleTypes;
also I have added the annotation #Column in the referende entity VehicleImage
public class VehicleType {
#Id
#Column(name = "ID") // added this one
private int id;
}
That bit workaround solved my problem, now I have achieved what I exactly looking for.
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 an Evaluation entity that has an associated list of EvaluationEvaluator. I need to explicitly create that entity because it required an extra column "STATUS". Before I continue evaluation. I do: evaluation.setEvaluationEvaluator(listEvaluator) where listEvaluator is a list of EvaluationEvaluator type. Then persist(evaluation). When I run this, it does not throw any kind of exception. But in the database, it inserts in the Evaluation table, and not inserted into the EvaluationEvaluator table.
Below my Evaluation entity.
#Entity
public class Evaluation implements Serializable{
#Id
#GeneratedValue
private Long id;
//MORE FIELDS
#OneToMany(mappedBy="evaluation")
private List<EvaluationEvaluator> evaluators;
//CONSTRUCTORS
//GETTER AND SETTERS
}
This is my EvalutionEvaluator Entity:
#Entity
#Table(name= "EVALUATION_EVALUATOR")
#IdClass(EvaluationEvaluatorId.class)
public class EvaluationEvaluator implements Serializable{
#Id
#Column(name="EMPLOYEE_ID", insertable=false , updatable=false)
private Long EmployeeID;
#Id
#Column(name="EVALUATION_ID", insertable=false, updatable=false)
private Long EvaluationID;
#ManyToOne
#JoinColumn(name"EMPLOYEE_ID")
private Employee employee;
#ManyToOne
#JoinColumn(name"EVALUATION_ID")
private Evaluation evaluation;
#NotNull
private String status;
//CONSTRUCTORS
//GETTER AND SETTERS
}
This is my EvaluationEvaluatorId class
public class EvaluationEvaluatorId implements Serializable{
private Long employeeID;
private Long evaluationID;
//CONSTRUCTOR
//GETTER AND SETTERS
}
And finally, this is my EvaluationBean class
#Stateful
#Named
#LocalBean
#ConversationScoped
public class EvaluationBean {
#PersistentContext(type= PersistenceContextType.EXTENDED)
private EntityManager em;
#Inject
Conversation conversation;
private Evaluation evaluation;
//IN MY WEBPAGE I IMPLEMENT PRIMEFACES PICKLIST AND IT REQUIRE DUALIST TO HANDLE
private DualListModel<Employe> evaluators;
private EvaluationEvaluator evaluationEvaluator;
private List<EvaluationEvaluator> listEvaluators;
#Inject
private EmployeeList employeeList;
//GETTER AND SETTERS
public String begin(){
if (conversation.isTransient()){
converstaion.begin();
}
evaluationEvaluator = new EvaluationEvaluator();
listEvaluators = new ArrayList<EvaluationEvaluator>();
evaluation = new Evaluation();
List<Employee> source = employeeList.findAll();
target = new ArrayList<Employee>();
evaluators = new DualListModel<Employee>(source, target);
return "/evalution/evaluationAsig.xhtml"
}
public String save(){
Iterator<Employee> iterator = evaluators.getTarget().iterator();
while (iterator.hasNext()){
EvaluationEvaluator ev = new EvaluationEvaluator();
ev.setEmployee(iterator.next());
listEvaluators.add(ev);
}
evalution.setEvaluationEvaluators(listEvaluators);
if(evaluation.getId()==null){
em.persist(evalution);
} else{
em.merge(evalution);
}
if(!conversation.isTransient()){
convesation.end();
}
return "/evalution/evaluationsAsig.xhtml"
}
}
When I debug my application,apparently everything is correct, but I mentioned above, doesn't persist in EvaluationEvaluator table.
Your #OneToMany association is missing cascading configuration.
Add cascade = CascadeType.ALL or cascade = {CascadeType.PERSIST, CascadeType.MERGE} to the #OneToMany annotation. JPA assumes no cascading by default so you would need to persist each EvaluationEvaluator by yourself explicitely otherwise.
UPDATE
There is another thing wrong with the code - the Ids of EvaluationEvaluators are never assigned. You have a complex key made of two Long columns. Both are marked not insertable nor updatable which tells to JPA that the id is going to be somehow generated on database level and it should not care about it. There is however no sequence configured explicitely in your entity (although it is not necessarily required) and also from your comment:
I did what you recommended but it throws the following exception. "A different object with same identifier was already associated with the session"
I assume that this is not the case and both id column values default to null or zero and are same for all EvaluationEvaluators you are trying to persist. If you'd like the database to generate the id for you automatically use #GeneratedValue - Configure JPA to let PostgreSQL generate the primary key value - here you can find explanation how to do this (the database part is database dependent, this is for PostgreSQL). The most common use case however, is to configure the sequence but let hibernate pick the next value, instructions here - Hibernate sequence on oracle, #GeneratedValue(strategy = GenerationType.AUTO)
I am working on a web app and I am using JSF and JPA(EclipseLink). I have the tables story and story_translate, which are mapped as follows:
#Entity
#Table(name = "story")
public class Story{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
private String title;
private String description;
#OneToMany(mappedBy = "story", cascade=CascadeType.ALL)
private List<StoryTranslate> translateList;
//getters and setters
}
#Entity
#Table(name = "story_translate")
public class StoryTranslate{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
#Column(name="STORY_ID")
private Integer storyId;
#ManyToOne
#JoinColumn(name="story_id", referencedColumnName="id", updatable=false, insertable=false)
private Story story;
//some other fields, getters and setters
}
In a ManagedBean I am doing the following:
StoryTranslate translate = new StoryTranslate(null, sessionController.getEntity().getId(), getAuthUser().getId(), language,
title, description, new Date(), false);
EntityTransaction transaction = TransactionSingleton.getActiveInstance();
Story story = storyService.read(sessionController.getEntity().getId());
if (story != null){
if (story.getTranslateList() == null){
story.setTranslateList(new ArrayList<StoryTranslate>());
}
story.getTranslateList().add(translate);
translate.setStory(story);
}
transaction.commit();
When I try to create a new StoryTranslate, I get a DatabaseException, saying the story_id cannot be null.
I have managed relationships before, but I have never seen this error.
Where is the problem?
EDIT: I am sorry, but I have forgotten about another part of the mapping(must be the lack of sleep).
The problem is that your declare the storyId property in the StoryTranslate class for the STORY_ID column but when adding a new StoryTranslate , you do not set any value to its storyId property and I believe STORY_ID column has a NOT NULL constraint and that why you get the exception saying that story_id cannot be null.
The problem should be fixed once you set the storyId property of the StoryTranslate instance before committing the transaction .
But it is strange that you map the STORY_ID column to two different properties ( storyId and story) of the StoryTranslate class . Actually you do not need to declare storyId property as this value can be retrieved from the story instance . I suggest you change the mapping of StoryTranslate to the following and your code should work fine without any changes.
#Entity
#Table(name = "story_translate")
public class StoryTranslate{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
#ManyToOne
#JoinColumn(name="story_id")
private Story story;
//some other fields, getters and setters
}