Hibernate CollectionOfElements EAGER fetch duplicates elements - java

I have a class called SynonymMapping which has a collection of values mapped as a CollectionOfElements
#Entity(name = "synonymmapping")
public class SynonymMapping {
#Id private String keyId;
//#CollectionOfElements(fetch = FetchType.EAGER)
#CollectionOfElements
#JoinTable(name="synonymmappingvalues", joinColumns={#JoinColumn(name="keyId")})
#Column(name="value", nullable=false)
#Sort(type=SortType.NATURAL)
private SortedSet<String> values;
public SynonymMapping() {
values = new TreeSet<String>();
}
public SynonymMapping(String key, SortedSet<String> values) {
this();
this.keyId = key;
this.values = values;
}
public String getKeyId() {
return keyId;
}
public Set<String> getValues() {
return values;
}
}
I have a test where I store two SynonymMapping objects to the database and then ask the database to return all saved SynonymMapping objects, expecting to receive the two objects I stored.
When I change the mapping of values to be eager (as shown in in the code by the commented out line) and run the test again, I receive four matches.
I have cleared out the database between runs and I can duplicate this problem swapping between eager and lazy.
I think it has to do with the joins that hibernate creates underneath but I can't find a definite answer online.
Can anyone tell me why an eager fetch is duplicating the objects?
Thanks.

I stepped into the same problem - when you set the FetchType.EAGER for a #CollectionOfElements, the Hibernate tries to get everything in one shot, i.e. using one single query for each entry of element linked to a "master" object. This problem can be successfully solved at a cost of N+1 query, if you add the #Fetch (FetchMode.SELECT) annotation to your collection.
In my case I wanted to have a MediaObject entity with a collection of its metadata items (video codec, audio codec, sizes, etc.). The mapping for a metadataItems collection looks as follows:
#CollectionOfElements (targetElement = String.class, fetch = FetchType.EAGER)
#JoinTable(name = "mo_metadata_item", joinColumns = #JoinColumn(name = "media_object_id"))
#MapKey(columns = #Column(name = "name"))
#Column (name = "value")
#Fetch (FetchMode.SELECT)
private Map<String, String> metadataItems = new HashMap<String, String>();

It's generally not a good idea to enforce eager fetching in the mapping - it's better to specify eager joins in appropriate queries (unless you're 100% sure that under any and all circumstances your object won't make sense / be valid without that collection being populated).
The reason you're getting duplicates is because Hibernate internally joins your root and collection tables. Note that they really are duplicates, e.g. for 2 SynonymMappings with 3 collection elements each you would get 6 results (2x3), 3 copies of each SynonymMapping entity. So the easiest workaround is to wrap results in a Set thereby ensuring they're unique.

I have faced this problem and I solved it using
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
This clears out the duplicates which are caused by the join made to the child tables.

You could use a SELECT DISTINCT (Hibernate Query Language) clause as follows
SELECT DISTINCT synonym FROM SynonymMapping synonym LEFT JOIN FETCH synonym.values
DISTINCT clause removes duplicate references in Hibernate.
Although both component and value-type collection has its lifecycle bound to the owning entity class, you should declare them in select clause in order to retrieve them. (LEFT JOIN FETCH synonym.values)
ChssPly76's answer is another approach, but does not forget override equals and hashcode method according to Set semantic
regards,

Instead of FetchMode.SELECT with N+1 queries it is better using BatchSize e.q. #BatchSize(size = 200).
DISTINCT and Criteria.DISTINCT_ROOT_ENTITY doesn't help, if you have to fetch more than 1 association. For this case see other solutions: https://stackoverflow.com/a/46013654/548473

I have achieved it via simply add
session.createCriteria(ModelClass.class).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
This help to remove duplicate.

Related

Fetch ManytoOne based on certain condition

I am using Hibernate 3.5.4 version as Orm I have two tables which have many to one relationship , Like Table 'Book' can have many 'Authors' associated with It.
#OneToMany(mappedBy = "key.bookId", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public Set<BookAuthor> getAuthors() {
return authors;
}
But we use soft delete for deleting the association (we maintain a column named isDeleted) , i want to fetch the entity based on isDeleted check if its 1 it should not be loaded , else if 0 load it.
Is it possible by modifying this current fetching strategy to provide above support or there is another better solution that can be applied please let me know.
Have a look at the #Filter or #Where Annotation.
As far as I know this is the usual way to restrict collection fetching.

NamedEntityGraph - JPA / Hibernate throwing org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags

We have a project where we need to lazily load collections of an entity, but in some cases we need them loaded eagerly. We have added a #NamedEntityGraph annotation to our entity. In our repository methods we add a "javax.persistence.loadgraph" hint to eagerly load 4 of attributes defined in said annotation. When we invoke that query, Hibernate throws org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags.
Funnily, when I redefine all of those collection as eagerly fetched Hibernate does fetch them eagerly with no MultipleBagFetchException.
Here is the distilled code.
Entity:
#Entity
#NamedEntityGraph(name = "Post.Full", attributeNodes = {
#NamedAttributeNode("comments"),
#NamedAttributeNode("plusoners"),
#NamedAttributeNode("sharedWith")
}
)
public class Post {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "postId")
private List<Comment> comments;
#ElementCollection
#CollectionTable(name="post_plusoners")
private List<PostRelatedPerson> plusoners;
#ElementCollection
#CollectionTable(name="post_shared_with")
private List<PostRelatedPerson> sharedWith;
}
Query method (all cramped together to make it postable):
#Override
public Page<Post> findFullPosts(Specification<Post> spec, Pageable pageable) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Post> query = builder.createQuery(Post.class);
Root<Post> post = query.from(Post.class);
Predicate postsPredicate = spec.toPredicate(post, query, builder);
query.where(postsPredicate);
EntityGraph<?> entityGraph = entityManager.createEntityGraph("PlusPost.Full");
TypedQuery<GooglePlusFullPost> typedQuery = entityManager.createQuery(query);
typedQuery.setHint("javax.persistence.loadgraph", entityGraph);
query.setFirstResult(pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
Long total = QueryUtils.executeCountQuery(getPostCountQuery(specification));
List<P> resultList = total > pageable.getOffset() ? query.getResultList() : Collections.<P>emptyList();
return new PageImpl<P>(resultList, pageable, total);
}
Any hints on why is this working with eager fetches on entity level, but not with dynamic entity graphs?
I'm betting the eager fetches you think were working, were actually working incorrectly.
When you eager fetch more than one "bag" (an unorder collection allowing duplicates), the sql used to perform the eager fetch (left outer join) will return multiple results for the joined associations as explained by this SO answer. So while hibernate does not throw the org.hibernate.loader.MultipleBagFetchException when you have more than one List eagerly fetched it would not return accurate results for the reason given above.
However, when you give the query the entity graph hint, hibernate will (rightly) complain. Hibernate developer, Emmanuel Bernard, addresses the reasons for this exception to be thrown:
eager fetching is not the problem per se, using multiple joins in one SQL query is. It's not limited to the static fetching strategy; it has never been supported (property), because it's conceptually not possible.
Emmanuel goes on to say in a different JIRA comment that,
most uses of "non-indexed" List or raw Collection are erroneous and should semantically be Sets.
So bottom line, in order to get the multiple eager fetching to work as you desire:
use a Set rather than a List
persist the List index using JPA 2's #OrderColumn annotation,
if all else fails, fallback to Hibernate specific fetch annotations (FetchMode.SELECT or FetchMode.SUBSELECT)
EDIT
related:
https://stackoverflow.com/a/17567590/225217
https://stackoverflow.com/a/24676806/225217

Duplicates in OneToMany annotated List

I'm working on a Java project using JPA 2 + Hibernate 4.2.6 and I'm getting a strange behaviour.
In my model I have two related entites: Question and Answer
#Entity
public class Question {
// ...
#OneToMany(mappedBy = "question", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Answer> answers;
// ...
}
#Entity
public class Answer {
// ...
#ManyToOne(optional = false)
#JoinColumn(name = "question_id", nullable = false)
private Question question;
// ...
}
This works perfectly: all Answers related to a certain Question are loaded correctly.
But now I need to change the tipe of answers collecton from Set to List. I changed the type and ran the application again and now I get several duplicates in answers...
Why is it possible? I know that List allows duplicates, but there are no duplicate records in my DB, so why I get these?
I read about some similar bugs in previous version of Hibernate, but I expect they are solved in last version... am I wrong?
NOTE I need to change Set into List because I need to keep information about the order for answers and, possibly, to change this order.
You are most likely getting duplicates because when using fetch=FetchType.EAGER, Hibernate uses an outer join to fetch the data in the joined table.
Try removing the eager fetching to confirm. If it is the case, you should either remove the eager fetching, keep a Set instead of a List, or write a JPQL query to retrieve exactly what you need.
From Hibernate doc:
The recommanded approach is to use LAZY on all static fetching
definitions and override this choice dynamically through JP-QL.

Hibernate Query that Orders an Attribute based on a nested path

I have two Objects, Entries and Samples. Each entry has an associated set of Samples, and each Sample has a vote associated with it. I need to query the database for all Entries, but for each Entry I need the associated set of Samples sorted according to their vote attribute:
public Class Entry{
Set<Sample> samples;
}
public Class Sample{
int vote;
}
I tried to sort the list of Samples after I had performed the query, but this turned out to be a mess because can't cast between a hibernate set and a java set. Can somebody help me alter my query to have the result I need?
List<Entry> entries = jpaTemplate.find("from Entry");
I found an embarrassingly simple solution to this problem. There is an #OrderBy JPA annotation that works perfectly:
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#OrderBy("votes DESC")
public Set<Sample> getSamples() {
return samples;
}
According to the hibernate faq:
There are three different approaches:
Use a SortedSet or SortedMap, specifying a comparator class in the sort attribute or or . This solution does a sort in memory.
Specify an order-by attribute of , or , naming a list of table columns to sort by. This solution works only in JDK 1.4+.
Use a filter session.createFilter( collection, "order by ...." ).list()

Found shared references to a collection org.hibernate.HibernateException

I got this error message:
error: Found shared references to a collection: Person.relatedPersons
When I tried to execute addToRelatedPersons(anotherPerson):
person.addToRelatedPersons(anotherPerson);
anotherPerson.addToRelatedPersons(person);
anotherPerson.save();
person.save();
My domain:
Person {
static hasMany = [relatedPersons:Person];
}
any idea why this happens ?
Hibernate shows this error when you attempt to persist more than one entity instance sharing the same collection reference (i.e. the collection identity in contrast with collection equality).
Note that it means the same collection, not collection element - in other words relatedPersons on both person and anotherPerson must be the same. Perhaps you're resetting that collection after entities are loaded? Or you've initialized both references with the same collection instance?
I had the same problem. In my case, the issue was that someone used BeanUtils to copy the properties of one entity to another, so we ended up having two entities referencing the same collection.
Given that I spent some time investigating this issue, I would recommend the following checklist:
Look for scenarios like entity1.setCollection(entity2.getCollection()) and getCollection returns the internal reference to the collection (if getCollection() returns a new instance of the collection, then you don't need to worry).
Look if clone() has been implemented correctly.
Look for BeanUtils.copyProperties(entity1, entity2).
Explanation on practice. If you try to save your object, e.g.:
Set<Folder> folders = message.getFolders();
folders.remove(inputFolder);
folders.add(trashFolder);
message.setFiles(folders);
MESSAGESDAO.getMessageDAO().save(message);
you don't need to set updated object to a parent object:
message.setFiles(folders);
Simple save your parent object like:
Set<Folder> folders = message.getFolders();
folders.remove(inputFolder);
folders.add(trashFolder);
// Not set updated object here
MESSAGESDAO.getMessageDAO().save(message);
Reading online the cause of this error can be also an hibernate bug, as workaround that it seems to work, it is to put a:
session.clear()
You must to put the clear after getting data and before commit and close, see example:
//getting data
SrReq sr = (SrReq) crit.uniqueResult();
SrSalesDetailDTO dt=SrSalesDetailMapper.INSTANCE.map(sr);
//CLEAR
session.clear();
//close session
session.getTransaction().commit();
session.close();
return dt;
I use this solution for select to database, for update or insert i don't know if this solution can work or can cause problems.
My problem is equal at 100% of this: http://www.progtown.com/topic128073-hibernate-many-to-many-on-two-tables.html
I have experienced a great example of reproducing such a problem.
Maybe my experience will help someone one day.
Short version
Check that your #Embedded Id of container has no possible collisions.
Long version
When Hibernate instantiates collection wrapper, it searches for already instantiated collection by CollectionKey in internal Map.
For Entity with #Embedded id, CollectionKey wraps EmbeddedComponentType and uses #Embedded Id properties for equality checks and hashCode calculation.
So if you have two entities with equal #Embedded Ids, Hibernate will instantiate and put new collection by the first key and will find same collection for the second key.
So two entities with same #Embedded Id will be populated with same collection.
Example
Suppose you have Account entity which has lazy set of loans.
And Account has #Embedded Id consists of several parts(columns).
#Entity
#Table(schema = "SOME", name = "ACCOUNT")
public class Account {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "account")
private Set<Loan> loans;
#Embedded
private AccountId accountId;
...
}
#Embeddable
public class AccountId {
#Column(name = "X")
private Long x;
#Column(name = "BRANCH")
private String branchId;
#Column(name = "Z")
private String z;
...
}
Then suppose that Account has additional property mapped by #Embedded Id but has relation to other entity Branch.
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "BRANCH")
#MapsId("accountId.branchId")
#NotFound(action = NotFoundAction.IGNORE)//Look at this!
private Branch branch;
It could happen that you have no FK for Account to Brunch relation id DB so Account.BRANCH column can have any value not presented in Branch table.
According to #NotFound(action = NotFoundAction.IGNORE) if value is not present in related table, Hibernate will load null value for the property.
If X and Y columns of two Accounts are same(which is fine), but BRANCH is different and not presented in Branch table, hibernate will load null for both and Embedded Ids will be equal.
So two CollectionKey objects will be equal and will have same hashCode for different Accounts.
result = {CollectionKey#34809} "CollectionKey[Account.loans#Account#43deab74]"
role = "Account.loans"
key = {Account#26451}
keyType = {EmbeddedComponentType#21355}
factory = {SessionFactoryImpl#21356}
hashCode = 1187125168
entityMode = {EntityMode#17415} "pojo"
result = {CollectionKey#35653} "CollectionKey[Account.loans#Account#33470aa]"
role = "Account.loans"
key = {Account#35225}
keyType = {EmbeddedComponentType#21355}
factory = {SessionFactoryImpl#21356}
hashCode = 1187125168
entityMode = {EntityMode#17415} "pojo"
Because of this, Hibernate will load same PesistentSet for two entities.
In my case, I was copying and pasting code from my other classes, so I did not notice that the getter code was bad written:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "credito")
public Set getConceptoses() {
return this.letrases;
}
public void setConceptoses(Set conceptoses) {
this.conceptoses = conceptoses;
}
All references conceptoses but if you look at the get says letrases
I too got the same issue, someone used BeanUtils.copyProperties(source, target). Here both source and target, are using the same collection as attribute.
So i just used the deep copy as below..
How to Clone Collection in Java - Deep copy of ArrayList and HashSet
Consider an entity:
public class Foo{
private<user> user;
/* with getters and setters */
}
And consider an Business Logic class:
class Foo1{
List<User> user = new ArrayList<>();
user = foo.getUser();
}
Here the user and foo.getUser() share the same reference. But saving the two references creates a conflict.
The proper usage should be:
class Foo1 {
List<User> user = new ArrayList<>();
user.addAll(foo.getUser);
}
This avoids the conflict.
I faced similar exception in my application. After looking into the stacktrace it was clear that exception was thrown within a FlushEntityEventListener class.
In Hibernate 4.3.7 the MSLocalSessionFactory bean no longer supports the eventListeners property. Hence, one has to explicitly fetch the service registry from individual Hibernate session beans and then set the required custom event listeners.
In the process of adding custom event listeners we need to make sure the corresponding default event listeners are removed from the respective Hibernate session.
If the default event listener is not removed then the case arises of two event listeners registered against same event. In this case while iterating over these listeners, against first listeners any collections in the session will be flagged as reached and while processing the same collection against second listener would throw this Hibernate exception.
So, make sure that when registering custom listeners corresponding default listeners are removed from registry.
My problem was that I had setup an #ManyToOne relationship. Maybe if the answers above don't fix your problem you might want to check the relationship that was mentioned in the error message.
Posting here because it's taken me over 2 weeks to get to the bottom of this, and I still haven't fully resolved it.
There is a chance, that you're also just running into this bug which has been around since 2017 and hasn't been addressed.
I honestly have no clue how to get around this bug. I'm posting here for my sanity and hopefully to shave a couple weeks of your googling. I'd love any input anyone may have, but my particular "answer" to this problem was not listed in any of the above answers.
I had to replace the following collection initilization:
challenge.setGoals(memberChallenge.getGoals());
with
challenge.setGoals(memberChallenge.getGoals()
.stream()
.map(dmo -> {
final ChallengeGoal goal = new ChallengeGoalImpl();
goal.setMemberChallenge(challenge);
goal.setGoalDate(dmo.getGoalDate());
goal.setGoalValue(dmo.getGoalValue());
return goal;
})
.collect(Collectors.toList()));
I changed
#OneToMany( cascade= CascadeType.ALL)
#JoinColumn(
name = "some_id",
referencedColumnName = "some_id"
)
to
#OneToMany(mappedBy = "some_id", cascade= CascadeType.ALL)
You're using pointers(indirectly), so sometimes you're copying the memory address instead of the object/collection you want. Hibernate checks this and throw that error. Here's what can you do:
Don't copy the object/collection;
Initiate a new empty one;
Make a function to copy it's content and call it;
For example:
public Entity copyEntity(Entity e){
Entity copy = new Entity();
e.copy(name);
e.setCollection2(null);
e.setCollection3(copyCollection(e.getCollection3());
return copy;
}
In a one to many and many to one relationship this error will occur. If you attempt to devote same instance from many to one entity to more than one instance from one to many entity.
For example, each person can have many books but each of these books can be owned by only one person if you consider more than one owner for a book this issue is raised.

Categories

Resources