Hibernate tries to persist child twice after setting parent - java

I've got such Hibernate mapping:
Parent:
#OneToMany(mappedBy = "parent")
#Valid
private Set<Assignment> assignments;
Child:
#ManyToOne(cascade = {CascadeType.PERSIST})
#JoinColumn(name = "parent_id")
#Valid
private Parent parent;
I've got such situation: Parent is already persisted in database, I get new Assignments list. I make such thing (using Spring Data JPA):
parent.getAssignments().addAll(newAssignments);
parent.getAssignments().forEach(assignment -> assignment.setParent(parent));
parentRepository.save(parent);
I've got #PrePersist annotated method in base entity (extended by both), with validation that checks if createdDate, which is initialized in #PrePersist is not null. Base entity contains:
#NotNull
#Column(name = "created_date")
protected LocalDateTime createdDate;
#PrePersist
public void prePersist() {
setCreatedDate(LocalDateTime.now());
setModifiedDate(LocalDateTime.now());
validateEntity();
}
private void validateEntity() {
Set<ConstraintViolation<BaseEntity>> violations = Validation.buildDefaultValidatorFactory().getValidator().validate(this);
if(!violations.isEmpty()) {
throw new RuntimeException(new ValidationMessageBuilder().build(violations));
}
}
I've checked the objects under debugger, and for example Assignment is object #17609 and Parent is #17608.
Exception is raised, after invoking save() method.
java.lang.RuntimeException: may not be null => com.foo.bar.model.domain.Assignment.parent.assignments.createdDate.
at com.foo.bar.model.BaseEntity.validateEntity(BaseEntity.java:70)
at com.foo.bar.model.BaseEntity.prePersist(BaseEntity.java:58)
I've debugged and in #PrePersist method of Assignment entity Hibernate invokes it for another object, which is Assignment#17646, which contains Parent#17608, and parent contains collection of children with one element, which is #17609.
Why hibernate tries to persist another object?

these are my observations to your problem:
The Exception being thrown is not from Hibernate... it is being
thrown by your validation method: validateEntity(), because your
child assigments have a null createDate.
You're using EntityListeners for setting your createdDate and
modifiedDate and validating your bean values... However, it seems
(and this is me guessing) that your validation method is iterating
(and validating) over the child assigments long before hibernate
executes the #PrePersist on them!
My Suggestions to fix the problem:
Use this configuration:
Parent:
#OneToMany(mappedBy = "parent", CascadeType = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
private Set<Assignment> assignments;
Child:
#ManyToOne
#JoinColumn(name = "parent_id")
private Parent parent;
BaseClass: Rename your prePersist method as:
#PrePersist
#PreUpdate
public void prePersistOrUpdate() {
setCreatedDate(LocalDateTime.now());
setModifiedDate(LocalDateTime.now());
validateEntity();
}
Check (and modify if required) your base validation method. This
method should only validate the instance variables (as
createdDate) and avoid transverse child objects that will be
modified later for the EntityListener Method:
prePersistOrUpdate() (Beware, I remove the #Valid annotation
because I don't know for what it is used for... may be It is used by
your validation framework).
Modify the business logic (where the newAssigments) are persisted
as this:
parent.getAssignments().addAll(newAssignments);
parent.getAssignments().forEach(assignment -> assignment.setParent(parent));
parentRepository.merge(parent); // This should cascade and persist the new assigments!

Related

One to one mapping is triggering N+1 queries

I'm trying to make an #OneToOne mapping following the https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/ the mapping itself works but its triggering an N+1 query problem.
The query is being made on the parent entity service and its triggering N+1 queries.
How can I improve this code to only make 1 query? We don't need to access the ParentDetails in this case.
EDIT: I've tried using JPQL and LEFT JOIN FETCH ParentDetails and didn't work either.
EDIT2: Just to try to add more information. I've put a breakpoint on the getParentDetails just to make sure I was not calling the getter anywhere and I'm not calling and double-checked and it seems a join problem on the repo call.
Let's go to the code:
Parent
#Entity
#DynamicUpdate
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
mappedBy = "parent")
private ParentDetails parentDetails;
// Getters, setters, etc omitted for brevity
}
ParentDetails
#Entity
public class ParentDetails {
#Id
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private Parent parent;
// Getters, setters, etc omitted for brevity
ParentDetailsRepository
#Repository
public interface ParentRepository extends JpaRepository<Parent, Long> {
Page<Parent>findByNameOrderByName(#Param("name") final String name,final Pageable pageable);
}
Hibernate executes the additional queries because the Parent entity doesn't map the foreign key column. Hibernate doesn't support lazy fetching for that end of the association. When Hibernate instantiates a Parent object, it needs to check if it needs to initialize the association with a proxy or a null object. And at some point, the team decided that they would fetch the associated entity if they are forced to perform a query anyways. I explained that in more detail here: https://thorben-janssen.com/hibernate-tip-lazy-loading-one-to-one
If you want to avoid the additional queries, you need to model a unidirectional association between ParentDetails and Parent. In your example, that would mean that you need to remove the parentDetails attribute from your Parent entity.
#Entity
#DynamicUpdate
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// Getters, setters, etc omitted for brevity
}
Because your ParentDetails entity uses the same id value as the Parent entity, you don't need a bidirectional association. If you want to get the ParentDetails entity for a Parent entity, you can get it with a call of the em.find(...) method
Parent p = // fetch the parent object ...
ParentDetails pd = em.find(p.getId(), ParentDetails.class);

Hibernate: #OneToMany: delete entity from "Many" side causes EntityNotFoundException

I have the following entities:
#Entity
public static class Parent {
#Id
#GeneratedValue
private Long id;
String st;
#OneToMany(mappedBy = "parent")
Set<Child> children = new HashSet<>();
// get,set
}
#Entity
public static class Child {
#Id
#GeneratedValue
private Long id;
String st;
#ManyToOne()
private Parent parent;
//get,set
}
Note, that there is no Cascade on #OneToMany side.
And I want the following:
I have one Parent with one Child in Detached state.
Now I want to remove child by some condition, so I'm accesing all children, find necessary and remove it directly via em.remove(child). + I remove it from Parent's collection.
After that I want to change some property of Parent and save it also.
And I'm getting EntityNotFound exception.
I performed some debug, and found that children collection is PersistentSet which remembered it's state in storedSnapshot. So, when I'm merging Parent to context - Hibernate do something with that stored snapshot and tries to load child it from DB. Of course, there is no such entity and exception is thrown.
So, there are couple of things I could do:
Map collection with #NotFound(action = NotFoundAction.IGNORE)
During removing from children collection - cast to PersistentSet and clear it also.
But it seems like a hack.
So,
1. What I'm doing wrong? It seems, that it's correct to remove child entity directly
2. Is there more elegant way to handle this?
Reproducible example:
#Autowired
PrentCrud parentDao;
#Autowired
ChiildCrud childDao;
#PostConstruct
public void doSomething() {
LogManager.getLogger("org.hibernate.SQL").setLevel(Level.DEBUG);
Parent p = new Parent();
p.setSt("1");
Child e = new Child();
e.setParent(p);
e.setSt("c");
p.getChildren().add(e);
Parent save = parentDao.save(p);
e.setParent(save);
childDao.save(e);
Parent next = parentDao.findAll().iterator().next();
next.setSt("2");
next.getChildren().size();
childDao.deleteAll();
next.getChildren().clear();
if (next.getChildren() instanceof PersistentSet) { // this is hack, not working without
((Map)((PersistentSet) next.getChildren()).getStoredSnapshot()).clear();
}
parentDao.save(next); // exception is thrwn here without hack
System.out.println("Success");
}
have you tried changing fetch type to eager? defaults for relations
OneToMany: LAZY
ManyToOne: EAGER
ManyToMany: LAZY
OneToOne: EAGER
maybe it gets cached because of fetch method
You can use next.setChildren(new HashSet<>()); instead of next.getChildren().clear(); to get rid of the getStoredSnapshot()).clear()
But it would be more elegant to use cascade and orphanRemoval.
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
Set<Child> children = new HashSet<>();
public void doSomething() {
...
next.setSt("2");
next.setChildren(new HashSet<>());
parentDao.save(next);
System.out.println("Success");
}

javax.persistence.EntityNotFoundException: Unable to find object with id X

I'm trying to persist an object, after that i want to add 2 lists to that object and then update it (Since i can't persist the object with the lists).
All beign done inside a loop, the first iteration works just fine, from the second i get a EntityNotFoundException saying that the ID wasn't found to do the update.
private Foo foo;
private FooDao dao;
for(int i = 0 ; i<10 ; i++){
foo = new Foo();
foo.setVar(i);
dao.save(foo);
generateLists(); //creates a new list every interaction
foo.setCatList(catList);
foo.setBarList(barList);
dao.update(foo);
}
If i remove the Lists and the update it works fine.
The object:
#Entity
public class Foo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
private Integer id;
#Basic(optional = false)
#NotNull
private String var;
#OneToMany(cascade = CascadeType.ALL)
private List<Bar> barList;
#OneToMany(cascade = CascadeType.ALL)
private List<Cat> catList;
//Getters and Setters
}
The DAO methods:
public void save(Foo foo) {
this.manager.joinTransaction();
this.manager.persist(foo);
//this.manager.flush(); Tried this, but didn't work
}
public void update(Foo foo) {
this.manager.joinTransaction();
this.manager.merge(foo);
}
The error:
ERROR [org.jboss.as.ejb3] (EJB default - 1) javax.ejb.EJBTransactionRolledbackException: Unable to find foo with id 4
ERROR [org.jboss.as.ejb3.invocation] (EJB default - 1) JBAS014134: EJB Invocation failed on component FooDao for method public void FooDao.atualiza(Foo): javax.ejb.EJBTransactionRolledbackException: Unable to find Foo with id 4
ps: Using this generic approach to simplify, if needed i'll post my solution(or the mess that i call solution)
Because you are adding same lists again and again.Since you have OneToMany the second transaction says you already persisted that list.
An workaround would to change relation to ManyToMany
Adding #NotFound(action = NotFoundAction.IGNORE) with #ManyToOne or #OneToOne is helpful because ..ToOne is FetchType.EAGER by default
I believe problem is with the transactions obtain the transaction and commit within the save method in your dAO.
this.manager.getTransaction().commit();
If an error comes it is because there is no active transaction it is why the flush fails. Be sure you are within a transaction, consider use EntityTransaction to begin , commit and end the transaction around your DAO methods.
If you don't have an active TX you won't be able to write entities to the database, and when you try to merge something the entity won't be found then this is the reason for what you are getting EntityNotFoundException
--- UPDATE
When you do the merge, what is the ID that the Foo object have ? I believe is not the correct one , or at least the last id inserted in the database, so what I suggest is find first and then merge.
Use
<T> T find(java.lang.Class<T> entityClass,
java.lang.Object primaryKey)
Using the id, and verify the instance have the correct key
I got similar issue. Fixed by updating #ManyToOne annotation to:
#NotNull
#JoinColumn(nullable = false)
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private ParentEntityClass parent;
added: nullable = false, optional = false, fetch = FetchType.LAZY and, in addition to these - #NotNull (javax.validation.constraints.NotNull - probably not necessary, just to prevent null-ables :) )

JPA Composite key with ManyToOne getting org.hibernate.PropertyAccessException: could not set a field value by reflection setter of

I have a composite key ContractServiceLocationPK made out of three id's (contractId, locationId, serviceId) of type long in an embeddable class. The class which uses this composite key, ContractServiceLocation, maps these ids, using #MapsId annotation, to their objects. Here's how it looks like (removed setters/getters and irrelevant properties):
Contract
#Entity
#Table(name = "Contract")
public class Contract implements Serializable {
public Contract() {
}
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy = "contract", cascade = CascadeType.ALL, fetch= FetchType.EAGER)
Collection<ContractServiceLocation> contractServiceLocation;
}
ContractServiceLocationPK
#Embeddable
public class ContractServiceLocationPK implements Serializable {
private long contractId;
private long locationId;
private long serviceId;
}
ContractServiceLocation
#Entity
#Table(name="Contract_Service_Location")
public class ContractServiceLocation implements Serializable {
#EmbeddedId
ContractServiceLocationPK id;
#ManyToOne(cascade = CascadeType.ALL)
#MapsId("contractId")
Contract contract;
#ManyToOne(cascade = CascadeType.ALL)
#MapsId("locationId")
Location location;
#ManyToOne(cascade = CascadeType.ALL)
#MapsId("serviceId")
Service service;
BigDecimal price;
}
When attempting to persist an object of type ContractServiceLocation in any way(directly or throught contract) I get:
Exception in thread "main" javax.persistence.PersistenceException: org.hibernate.PropertyAccessException: could not set a field value by reflection setter of com.test.model.ContractServiceLocationPK.contractId
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1763)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1683)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:1187)
at com.test.MainTest.main(MainTest.java:139)
Caused by: org.hibernate.PropertyAccessException: could not set a field value by reflection setter of com.test.model.ContractServiceLocationPK.contractId
at org.hibernate.property.DirectPropertyAccessor$DirectSetter.set(DirectPropertyAccessor.java:134)
at org.hibernate.mapping.Component$ValueGenerationPlan.execute(Component.java:441)
at org.hibernate.id.CompositeNestedGeneratedValueGenerator.generate(CompositeNestedGeneratedValueGenerator.java:121)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:117)
at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:84)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:206)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:149)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:75)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:811)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:784)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:789)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:1181)
... 1 more
Caused by: java.lang.NullPointerException
at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(Unknown Source)
at sun.reflect.UnsafeLongFieldAccessorImpl.set(Unknown Source)
at java.lang.reflect.Field.set(Unknown Source)
at org.hibernate.property.DirectPropertyAccessor$DirectSetter.set(DirectPropertyAccessor.java:122)
... 12 more
My assumption is that JPA/Hibernate expects a Contract object instead of a long variable, but if I change the variables in embeddable from long to their type then I get The type of the ID mapped by the relationship 'contract' does not agree with the primary key class of the target entity.. If I try using id class instead of embeddable then mappedby in Contract's OneToMany mapping I get In attribute 'contractServiceLocation', the "mapped by" attribute 'contract' has an invalid mapping type for this relationship.. What should I do to make a composite key with multiple ManyToOne mappings?
EDIT: Added a snippet where I try to persist the items:
Service service = new Service();
// Set all service properties
Contract contract = new Contract();
// Set all contract properties
Location location = new Location();
// Set all location properties
ContractServiceLocation csl = new ContractServiceLocation();
csl.setContract(contract);
csl.setLocation(location);
csl.setService(service);
Collection<ContractServiceLocation> cslItems = new ArrayList<>();
cslItems.add(csl);
em.getTransaction().begin();
em.persist(location);
em.persist(service);
em.persist(csl);
em.persist(contract);
em.getTransaction().commit();
The reason it looks like this instead of being in some DAO is because I'm generating the database and testing the items first before I get on with developing the rest of the app.
EDIT 2: I've rewrote my models and now everything seems to work except in Eclipse I get a persistent error. Here's how the things currently look:
Contract - No change (Except removed the Eager loading)
ContractServiceLocationPK - Is now an ID class
public class ContractServiceLocationPK implements Serializable {
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "contract_id")
private Contract contract;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "location_id")
private Location location;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "service_id")
private Service service;
//getters and setters
//overridden equals() and hashCode()
}
ContractServiceLocation
#Entity
#Table(name="Contract_Service_Location")
#IdClass(ContractServiceLocationPK.class)
public class ContractServiceLocation implements Serializable {
#Id
Contract contract;
#Id
Location location;
#Id
Service service;
BigDecimal price;
//getters and setters
//overridden equals() and hashCode()
}
This appears to work correctly for now. It creates a composite key and maintains a many-to-one relationship with all the composite properties. However there is something weird. In Contract eclipse marks mappedBy on the #OneToMany annotation for the ContractServiceLocation collection with the error message In attribute 'contractServiceLocation', the "mapped by" attribute 'contract' has an invalid mapping type for this relationship.. I'm assuming that this is because the Contract property defined in ContractServiceLocation doesn't have a #ManyToOne annotation, but that is defined in the composite class. Did I stumble upon "non-compliant JPA but working with Hibernate" trap or what's going on here?
For your original question (not modified variant):
You have to instatiate "ContractServiceLocationPK id" in your ContractServiceLocation class. Replace line:
#EmbeddedId
ContractServiceLocationPK id;
with this:
#EmbeddedId
ContractServiceLocationPK id = new ContractServiceLocationPK();
Then it should works. Because Hibernate is trying to set properties inside, but fail on NullPointerException.
You need to put the getters and setters in your #Embeddable class as well, your hashCode() and equals() methods will go in to that class which I couldn't see in your class posted here.
In order to save the ContractServiceLocation, following objects needs to be saved first because you are using their ids as composite key for the ContractServiceLocation, right? Here what you are doing is you are creating these as new objects so obviously they won't have their id, because they are not persisted. so you need to persist them first and use the persisted objects and set objects into the ContractServiceLocation.
Service service = new Service();
// Set all service properties
Contract contract = new Contract();
// Set all contract properties
Location location = new Location();
// Set all location properties
When creating a new instance for the joined entity, the #EmbeddedId composite primary key field should be initialized manually as Hibernate would not be able to set the value via reflection
so set values of ContractServiceLocationPK composite class fields in ContractServiceLocation constructor

JPA OneToOne and OneToMany entity instantiation/creation

My application uses a lot of OneToMany and OneToOne references between domain level value-objects, most of them are entities, either being a super class or a subclass of something.I would like to provide my application a consistent(yet easy) way to save those instances and the actual method save() is as such
#Transactional
public void save(Post post){
try{
JPA.em().persist(post);
}catch (EntityExistsException eee){
JPA.em().merge(post);
}catch(ConstraintViolationException cve){
JPA.em().refresh(post);
}
}
The current problem is how to properly instantiate those object and which strategy choose in the cascadeType, i would like to save nested object when saving an object with references with other entities, it works now but only for the first time, after that i get a Unique index or primary key violation given that SQL insert into Utente (passwd, DTYPE, username) values (?, 'Redattore', ?) [23505-168].Clearly my JPA provider (hibernate 3.6.9) fails to not update an existing row, instead it tries to insert a new entry in the DB.Here are some classes i am using:
#Entity
#Table
public class Post extends Domanda {
#Column(nullable = false)
private String nome;
#OneToMany(cascade = CascadeType.ALL)
private List<Commento> commenti;
#OneToMany(cascade = CascadeType.ALL)
private List<Risorsa> risorse;
#OneToOne(cascade = CascadeType.ALL)
private NodoApprendimento nodo;
#Column
private int visibilità;
#Column
private boolean isDraft;
Post is referenced by a few classes among which i there is:
#Entity
public abstract class Partecipante extends Utente{
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
private Set<Post> contributi;
Then i would like to know the proper way to initialize and persisteORupdate those referenced object in the database, thanks in advance.
This isn't a provider problem, but a usage issue. When you call persist, JPA does not require providers to execute an insert immediately - they are usually delayed to flush or commit time. So you will not get an EntityExistsException in most cases. Either way, the transaction state should be marked for rollback - you should not be relying on persist to determine if merge should be called. Either call em.find, or just call em.merge on your entity and allow the JPA provider to determine if it should do an insert or an update for you.

Categories