Avoid Instantiation of IndirectList in Eclipselink - java

I have a simple OneToMany Relation between a Parent and a Child.
Parent:
#OneToMany(mappedBy = "parent", orphanRemoval = true, cascade = CascadeType.ALL)
private List<Child> children = new ArrayList<>();
Child:
#ManyToOne(optional = false)
#JoinColumn(name = "PARENT_ID", nullable = false)
private Parent parent;
Because a parent can have a big amount of children I wanted to take advantage of Lazy Instantiation of Indirect Collections:
IndirectList and IndirectSet can be configured not to instantiate the list from the database when you add and remove from them. IndirectList defaults to this behavior. When Set to true, the collection associated with this TransparentIndirection will be setup so as not to instantiate for adds and removes. The weakness of this setting for an IndirectSet is that when the set is not instantiated, if a duplicate element is added, it will not be detected until commit time.
As the default FetchType of OneToMany is LAZY and I am using a List for my Collection, loading a parent from the database causes an IndirectList to be used for the relation. As soon as I add another child to that parent I can see that a select query for the children of that parent is executed.
How can I change that?
I am using Eclipselink 2.6.4 (org.eclipse.persistence:eclipselink:2.6.4).
I also tried to use a DescriptorCustomizer to call org.eclipse.persistence.mappings.CollectionMapping.setUseLazyInstantiationForIndirectCollection(Boolean) on my relation, but this seemed to have absolutely no effect.
After debugging into the Method org.eclipse.persistence.indirection.IndirectList.add(E), I was able to see that the Method call to org.eclipse.persistence.indirection.IndirectList.shouldAvoidInstantiation() at line 206 returned false, because org.eclipse.persistence.indirection.IndirectList._persistence_getPropertyChangeListener() at line 1007 returns null and null is not instanceof AttributeChangeListener. Because of this the relation is then instantiated by org.eclipse.persistence.indirection.IndirectList.getDelegate() in line 216.
To me this seems like a bug, but I don't know enough about this implementation to be sure.

Change tracking is required to support not instantiating lazy collections when making modifications. Change tracking is enabled when using weaving as described here: https://www.eclipse.org/eclipselink/documentation/2.5/concepts/app_dev007.htm

Related

How to prevent hibernate from creating a proxy

I have a tricky problem with hibernate using more queries than necessary for a simple findAll call. In my model there is two entities Parent and Child with oneToMany association;
Parent
class Parent{
#id
private long id;
//unique
private String code;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<OperatorAttribute> children;
}
Child
class Child{
#id
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_code", referencedColumnName = "code")
#LazyToOne(LazyToOneOption.NO_PROXY) // here i'm trying to tell hibernate to create no proxy and just ignore the field but no luck :/
public Parent parent;
}
The problem is that whenever I try to fetch the list of child using childRepository.findAll() from the database, hibernate make N+1 select query, why ?
I think this may be the explanation for that: IMHO when Hibernate populate the child object, he tries to create a proxy for the parent field,
and for that he needs the id of the parent row, which should normally be the foreign key in the child table, but in my case the #fk isn't binded to the primary key of the Parent table but to a unique column (plz don't ask me why) so in order to populate the id he needs to do an additional select query just to initialize the proxy of the parent field.
So my question is how to prevent Hibernate from creating a proxy for the parent field.
Thanks.
You are right. The proxy needs the #Id of the proxied entity (this way it could be made sure that it could be find). As soon you define the LazyToOneOption.NO_PROXY it tells the system to give back the real object. And this is what happens here. What you get mapped on the result is not a proxy, because with this annotation you explicitly disabled it so you have to get the real object.
Based on the mapping provided you cannot ignore the field because You'll loose the information what was is the Parent on the Child. So with this kind of setup you'll always need to read the parent.
If this field is not needed at all in a specific area, you can create some other mappings to the same table. But be careful! This could introduce a load of other cache related problems.

Hibernate: mapping parent to child entity with 2 references to one column in child?

I am trying to have a cascade delete within my entities. But I think that it is being stopped by the fact that I have 2 references to the one column in my child entity.
In my child Dog entity I originally had the following field:
#Column(name = "KENNEL_ID", insertable = false, updatable = false)
private String kennelId;
I then added this because I wanted to get a list of all child entities related to the parent:
#ManyToOne
#JoinColumn(name = "KENNEL_ID" )
private Kennel kennel;
In my parent Kennel entity I also added this to refer to the field in the child I added:
#OneToMany(mappedBy = "kennel",cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<Dog> dogList= new ArrayList<Dog>();
Before I added the 2nd child reference and the parent references, cascade delete worked for all of my entities. However since I have added them it does not.
How can I fix this?
It is not a problem of mapping parent and child to the same class.The problem is that you need to maintain both ends of the bi-directional-relationship by hand.
child.setParent(parent)
parent.addChild(child)
BTW: Setting it only on one side (the one which is responsible to store the relationship in the database), store and reload the entity will work in some cases too. (And you will find this dirty trick in many old tutorials). But in my opinion it is bad practice. (In your test case, it would require to clean the cache before you reload the parent after the child is saved.)
public void setDogList(List<Dog> dogList) {
this.dogList.clear();
this.dogList.addAll(dogList);
}

Automatically hibernate save relationship

Basically my question is why if I have an Hibernate relationship like this one.
#OneToMany(cascade = {CascadeType.ALL})
#JoinColumn(name = "candidacy_id", nullable = false)
#XmlElement
#JsonIgnore
#Getter
#Setter
private List<EvaluationSelectionCriteria> evaluationSelectionCriterias = new ArrayList<>();
#ManyToOne
#JoinColumn(name = "candidacy_id", nullable = false, insertable = false, updatable = false)
#XmlTransient
#Getter
#Setter
private Candidacy candidacy;
Why if I do this candidacy.setEvaluationSelectionCriteria(list) automatically this list is persisted in database?
I would like to use the EvaluationSelectionCriteria as a repository to render a list of "future" EvaluationSelectionCriteria
Could be because is not Lazy?
More detail explanation
So would be like I call method a, there I´m get from database entity A then I set a list into A and then I return A in the method but I´m not saving A, when I see the value of the list already have ids!!!
If you do not want the list to be saved when the parent entity is saved/merged, you should remove or restrict the cascade setting for the relationship:
#OneToMany
private List<EvaluationSelectionCriteria> evaluationSelectionCriterias
or
#OneToMany(cascade = CascadeType.REMOVE) // or other values from the enum
private List<EvaluationSelectionCriteria> evaluationSelectionCriterias
EDIT: If you want to fetch an entity in a transactional method and modify it, you can restrict the scope of the transaction to the fetching only. Then modify the entity outside the transactional method. Later, you can merge the detached entity if needed.
Since collection attributes are lazy per default you will either need to
access their content while still inside the transactional method - so the collection can be fetched from the DB. Please note that you will have to call a method on the collection that actualy requires it's content to be loaded, like getCriterias().size().
use LEFT JOIN FETCH to load the collection as a side effect of the query.
I would not modify the FlushMode for the session - while this would probably work, it feels like a kludge - it does not communicate your intent very well. Explicitly fetching the collection and modifying it outside the transaction expresses your intent better IMO.
I found the solution, I forgot to say that I´m using Spring, so finally I add the #Transactional(readOnly=true) into my method instead in the service class level.

cascade = CascadeType.ALL what to expect?

I'm wondering what to expect when I use cascade = CascadeType.ALL as such,
#OneToMany(
mappedBy = "employeeProfile",
cascade = CascadeType.ALL,
orphanRemoval = true)
private List<ProfileEffortAllocation> effortAllocations;
public List<ProfileEffortAllocation> getEffortAllocations() {
if (effortAllocations == null) {
effortAllocations = new ArrayList<>();
}
return effortAllocations;
}
public void setEffortAllocations(List<ProfileEffortAllocation> effortAllocations) {
this.effortAllocations = effortAllocations;
}
I'm finding when I add a new effortAllocation and attempt to save object, but have a validation failure preventing my code from ever reaching session.saveOrUpdate(parentObj), I'm still getting a pk rather than null as if persist is being called on the child OneToMany. Should my parent object call session.saveOrUpdate(parentObj); before I ever see a pk from effortAllocation?
I'd like to point out that the parent object is an existing object and has been loaded from the database with a pk prior to adding a new child record.
When you use CascadeType.ALL, whenever you do any operation on the parent all those operations would also get cascaded to the child.
Yes you should call saveOrUpdate(parent)
In your case as the parent objects are already existing. You could load the existing parent and create a new child and attach the child to parent and when you call saveOrUpdate(parent), it should update the parent and create all those child and relate it to that parent.
Yes it is generating a id for child, because it is trying to create a child due to cascade all and you could have configured it to generate id in #Id.
Enable sql logs using hibernate.show_sql to understand better whats happening.
I assume you would have a #JoinColumn in your child which would map to the parent primary key.
The cause of this issue was do to a lookup query triggering a flush prior to returning it's results. The solution was to set this.session.setFlushMode(FlushMode.COMMIT);
Hibernate tries to ensure that database conents is up-to-date before making any queries.
https://forum.hibernate.org/viewtopic.php?p=2316849

Multiple fetches with EAGER type in Hibernate with JPA

I have an entity which contains:
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "assessment")
#OrderBy(value = "order ASC")
private List<AssessmentPart> assessmentParts = new LinkedList<>();
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "assessment")
private List<AssessmentText> texts = new LinkedList<>();
as you see there are two collections which needs to be eagerly loaded. This is not working and hibernate throws an exception:
Caused by: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
That's because Hibernate can't fetch multiple collections with one go. But if I change the List to Set and the LinkedList to HashSet this part works fine but the other - more annoying issue occurs.
When I am trying to get the entity from the database using:
entityManager.find(entityClass, primaryKey);
It fails with:
org.hibernate.AssertionFailure: null identifier
I am sure that ID I am passing to find method is not null, I've debugged and I am sure of this. It somehow disappears inside Hibernate.
If I change collection types to LAZY everything just works without errors but there are some circumstances where I need to use EAGER.
Does anybody have a solution how to fix it? Either I could have a set but prevent assertion error from occurring or I could have a list but somehow avoid multiple fetch bags error.
I am using:
Hibernate 4.2.2.Final
Tomcat 7
JPA 2.0
JDK 1.7
EDIT
I've just discovered that adding #Fetch(FetchMode.SELECT) fixes the issue and I can use multiple list with EAGER type, but is there anyway to solve this by not using Hibernate specific annotations? And why it fixed the issue in the first place?
The root cause of the problem is that when Hibernate fetches SQL query results there is no simple way to tell which child element belongs to which collection. See this blog entry for more detailed explanation with an example. To summarize you have following workarounds:
Load each collection separately using subselect #Fetch(FetchMode.SELECT)
Force usage of list instead of bag by adding index column #IndexColumn(name="LIST_INDEX")
Use unordered collection like Set.
If you are using Hibernate and you do not care using Hibernate annotations :
Annotate your collection fields with:
#LazyCollection(LazyCollectionOption.FALSE)
Remember to remove the fetchType attribute from the #OneToMany annotation.

Categories