I'm trying to avoid hibernate's lazy-loading mechanism and I've created this list object with the Eager FetchType, which I would suppose would do it:
#JsonIgnore
#CollectionTable(name = "nav", joinColumns = #JoinColumn(name="conn"))
#ElementCollection(fetch = FetchType.EAGER)
#IndexColumn(name="filter") private List<String> filters = Lists.newArrayList();
But I'm still reading a PersistentList, instead of java.util.List. Any idea on what may be wrong ?
I'm not using xml configurations.
A Persistent List is a non-lazy collection that Hibernate uses to correctly store and retrieve data from the databases. It is not a case that when you define your persistable entities you cannot use Java collection implementations, such as ArrayList or LinkedList, but only interfaces. In fact, you are asking for a List and you are getting PersistentList, which totally respect the contract of the interface.
If you remove the eagerness and debug your class, you will see a lazy collection appearing, and this is typically a CGLIB proxy.
To avoid lazy loading you have to set lazy = true and instead of FetchType.EAGER make it FetchType.JOIN.
for annotations based mapping refer to the below link:
[http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/#entity-hibspec-singleassoc-fetching]
let me know if it helps you.
Related
I have mapped an 1:N relation with a #OneToMany List, but when I access the list, the results are duplicated due to an OUTER JOIN.
This is how the mapping looks like:
#Entity
public class Programmer
#ElementCollection(fetch=FetchType.EAGER)
#CollectionTable(name="emails", joinColumns=#JoinColumn(name="id", nullable=false))
#Column(name="email", nullable=false)
protected Set<String> emails = new HashSet<String>();
#OneToMany(mappedBy="programmer", fetch=FetchType.EAGER)
private List <Game> games = new ArrayList<Game>();
When I get the attribute with prog.getGames(), the results comes duplicated because the Hibernate SQL makes an OUTER JOIN:
from programmer
left outer join emails on programmer.id=emails.id
left outer join game on programmer.id=game.id
where programmer.id=?
Is there any solution without transforming the List into a Set? I need to get the games with prog.getGames(), can not use a custom HQL or Criteria.
While the use of Set<> fundamentally resolves your issue, I'd argue that is simply a bandaid to get the expected results you're after but it doesn't technically address the underlying problem.
You should ultimately be using the default lazy fetch strategy because I'm of the opinion that eagerly loading any associations, particularly collection-based ones, are specific to a query and therefore should be toggled when you construct specific queries and not influenced as a part of your entity mapping model as you're doing.
Consider the future where you add a new query but you're only interestesd in attributes from the aggregate root entity. Your mapping model will still impose eagerly fetching those associations, you'll consume additional resources to by having a larger persistence context which means more memory consumption and impose unnecessary database joins for something which you aren't going to use.
If there are multiple collections that you need to hydrate, I would instead recommend you consider using FetchMode.SUBSELECT instead.
If we assume your query has 10 entities being returned, the default lazy strategy with 2 collections would issue 21 queries (1 for the base result set and 2 for each loaded entity).
The benefit of SUBSELECT is that Hibernate will actually only issue 3 queries (1 for the base result set and 1 for each collection to load all collection elements for all entities). And obviously, depending on certain queries, breaking one query with left-joins into 3 queries could actually perform better at the database level too.
Ive resolved this problem with #Fetch(FetchMode.SUBSELECT)
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#Fetch(FetchMode.SUBSELECT)
private List<CompanyUserEntity> companyUserRelations;
I had the same problem. companyUserRelations had duplicate objects (I mean the same pointers to the same object, not duplicated data)
So after reading #dimitry response, I added #Fetch(FetchMode.SUBSELECT) and it worked
One of our model object in our application has many fields configured to be eagerly fetched like so:
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "field")
public Field getField() {
return this.field;
}
However I sometime do not need these information, which slow down my queries for nothing. I cannot change the behaviour and use FetchType.LAZY instead as I've no idea what will be the impact on the whole application (legacy...). Is there a way to simply tell hibernate to fetch nothing, except if it is specified in the query?
Last time I checked there was no proper solution provided by hibernate, so I ended up with this solution:
Configured the problematic references as LAZY.
All affected service methods (that used these models) got an overloaded version with boolean forceEager
by default all existing functions were refactored to call the new ones with forceEager=true
and here comes the trick: as a means of "forcing the eager fetching" I found nothing better than actually accessing the proxied (lazy-fetched) objects. In case for example a lazily referenced list doing list.size() will force Hibernate to load the full list, hence the service returns with fully fetched object.
In case of more than one layer in your objectstructure is affected, you need to traverse through the whole hierarchy and access every lazily loaded object from top to bottom.
This is a bit error-prone solution, so you need to handle it with care.
If its possible to switch to Criteria for this query, you could use FetchMode.SELECT for the field property
crit.setFetchMode("field", FetchMode.SELECT);
I'm quite new to JPA and Hibernate (I'm studying hard though!) and I am struggling with a problem that I can't seem to find a trivial solution for, so here it is.
I have an entity that looks kinda like the following:
#Entity
#Table(name = "mytable1")
public class EntityOne {
// surrogate key, database generated
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
// business key
#Column(name = "identifier", nullable = false, unique = true)
private String identifier;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumn(name = "twoId", nullable = false)
private EntityTwo two;
#OneToMany(mappedBy = "entityOne", fetch = FetchType.EAGER,
cascade = {CascadeType.ALL}, orphanRemoval = true)
private Set<EntityThree> resources = new HashSet<>();
// getters/setters omitted
#Override
public int hashCode() {
// the business key should always be defined (through constructor/query)
// if this is null the class violates the general hashcode contract
// that the integer value returned must always be the same
Assert.notNull(identifier);
// a dirty alternative would be:
// if(identifier==null) return 0;
return identifier.hashCode();
}
#Override
public boolean equals(Object o) {
return o instanceof ResourceGroup
&& ((ResourceGroup) o).identifier.equals(identifier);
}
}
My project is set up with Spring JPA, so I have my CrudRepository<EntityOne,Long> injected in a Service class that has a few #Transactional methods and I scan my domain/service packages for JPA and transactions respectively.
One of the service methods calls the repository's findAll() method and returns a list of EntityOnes. Everything works fine unless I try to access the getter for two, which obviously throws:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
I thought it might be useful to have this object initialized, so I switched the fetching type from lazy to eager. However, if I do that I get the following:
java.lang.IllegalArgumentException: [Assertion failed] - this argument is required; it must not be null
at org.springframework.util.Assert.notNull(Assert.java:112)
at org.springframework.util.Assert.notNull(Assert.java:123)
at my.pkg.domain.EntityOne.hashCode(ResourceGroup.java:74)
at java.util.HashMap.hash(HashMap.java:351)
at java.util.HashMap.put(HashMap.java:471)
at java.util.HashSet.add(HashSet.java:217)
at java.util.AbstractCollection.addAll(AbstractCollection.java:334)
at org.hibernate.collection.internal.PersistentSet.endRead(PersistentSet.java:346)
at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollection(CollectionLoadContext.java:243)
at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:233)
at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:209)
at org.hibernate.loader.Loader.endCollectionLoad(Loader.java:1149)
//...
I briefly looked at Hibernate's source code and it looks like it's trying to put my EntityOne objects in a set before their business key is initialized. Is my interpretation correct? Is there a way around this? Am I doing something incredibly dumb?
I appreciate your help
EDIT: I just want to clarify that what I'm trying to understand here is what the best practices are specifically with respect to JPA and Hibernate. If this was a plain POJO I could make the identifier field final (I would actually make the whole class immutable) and be safe. I can't do this because I'm using JPA. So the questions: do you violate the hashCode contract and in which way? How does Hibernate deal with this violation? What's the JPA recommended way of doing this in general? Should I get rid of hash based collections altogether and use lists instead?
Giovanni
No, you're not doing anything dumb. Implementing equals and hashCode on a JPA entity is a matter of much heated debate, and all of the approaches I know about have significant drawbacks. There's no obvious, trivial solution that you're just missing.
You have, however, hit on a case which is not discussed very much for some reason. The hibernate wiki recommends using a business key as you are doing, and "Java Persistence with Hibernate" (Bauer / King, 2007, widely regarded as the standard Hibernate reference work) on page 398 recommends the same thing. But in some situations, as you observe, Hibernate can add an entity into a Set before its fields are initialized, so the business-key-based hashCode doesn't work, just as you point out. See Hibernate issue HHH-3799 for discussion of this case. There is an expected-to-fail test case in the Hibernate source code demonstrating the issue, added in 2010, so at least one Hibernate developer considers it to be a bug and wants to fix it, but there hasn't been any activity since 2010. Please consider voting for that issue.
One solution you might consider is to expand the scope of your session so that all your access to entities happens within the same session. Then you can make your Set<EntityThree> be lazy-fetched instead of eager-fetched, and you'll avoid the eager-fetching problem in HHH-3799. Most applications I've worked on make only sparing use of objects in the detached state. It sounds like you're loading your entity and then using it for a while after the session ends; that's a pattern I'd recommend against. If you're writing a web application, see the "open session in view" pattern and Spring's OpenSessionInViewFilter for ideas on how to do this.
Incidentally, I like how you throw an exception when the business key is not initialized; that way you can catch coding errors quickly. Our application has a nasty bug due to HHH-3799 which we might have caught in development if we had used your not-null assertion.
Your interpretation is correct. As a first first step code your hashCode() and equals() with your id field - the one you are telling Hibernate that is your id.
As a second step implement a correct hashCode() and equals() to save you from future trouble. There are plenty of resources if you google it. Here is one on this site
I believe I actually found a way to make this work a little better, i.e., forcing Hibernate (or whatever JPA provider) to have the key available before sticking objects in the collection. In this scenario, the object will be properly initialized and we can be sure that the business key won't be null.
For example, here's how the class EntityTwo would have to look:
#Entity
#Table(name = "mytable2")
public class EntityTwo {
// other code omitted ...
#OneToMany(mappedBy = "entityTwo", fetch = FetchType.EAGER,
cascade = {CascadeType.ALL}, orphanRemoval = true)
#MapKey(name = "identifier")
private Map<String, EntityOne> entityOnes = new HashMap<>();
}
I haven't tested this specific code but I have other working examples and it should work fine according to the JPA docs. In this case, the JPA provider is cornered: it must know the value of identifier before it can put the object in the collection. Besides, the object's hashCode and equals are not even called because the mapping is explicitly handled by the JPA provider.
This is a case in which explicitly forcing the tool to understand the way things are modeled and related to each other leads to great benefit.
I have an entity with the following field:
#ManyToMany(cascade = { CascadeType.ALL }, targetEntity = Comment.class)
#JoinTable(name = "program_to_comment")
#OrderBy("position")
private Set<Comment> comments = new HashSet<Comment>();
but I have the problem that whenever I persist it using:
Program p = entityManager.persist(entity);
the field comes with the objects sorted as it was sorted in the entity object.
Suppose the entity object is configured as following: Program(comments:[Comment(position:15), Comment(position:10)], ...), persisting the entity (entityManager.persist), it will store both comments and the program entity itself to the database. But the resulted entity from the persist method invocation is an object as follows: Program(comments:[Comment(position:15), Comment(position:10)], ...), in the same order gave to the persist method.
From my point of view at this point the resulted entity should present the values following the specified #OrderBy rule, or am I missing something?
Additional information:
JPA2
Hibernate 4.2.0.Final
OrderBy simply add an order by clause to the query used to load the comments of a program. Nothing more. The rest is under your responsibility. So if you want the comments sorted by position when adding comments and persisting them, you have to take care of this by yourself.
I have personally never found this annotation to be really useful. I have also found it not to work in every case, particularly when using a query to fetch programs with their comments, with an order by clause already present in the query. I generally prefer not to use theis annotation, and provide a getSortedComments() method which returns a sorted set or list of comments, using a comparator.
Is it possible to have following collection mapping in JPA / hibernate
#OneToMany(cascade={CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE},
fetch=FetchType.LAZY,mappedBy="parent")
private Deque<Child> childrens;
It throws error
Illegal attempt to map a non collection as a #OneToMany, #ManyToMany or #CollectionOfElements
I am using JPA 2.0 with Hibernate 3
No, JPA does not support Deque. In JPA 2.0 specification this is explained following way:
Collection-valued persistent fields and properties must be defined in
terms of one of the following collection-valued interfaces regardless
of whether the entity class otherwise adheres to the JavaBeans method
conventions noted above and whether field or property access is used:
java.util.Collection, java.util.Set, java.util.List[3], java.util.Map. The collection implementa- tion type may be used by
the application to initialize fields or properties before the entity
is made persistent. Once the entity becomes managed (or detached),
subsequent access must be through the interface type.
I would suggest to add to entity methods that provide needed Deque functionality (or expose view as Deque to persisted list). Other possibility is custom collection as suggested in comments (Thor84no).
While JPA does not support Deque as mentioned by Mikko, you could simply update your code to be an ArrayDeque and you should be good to go.
#OneToMany(cascade={CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE},
fetch=FetchType.LAZY,mappedBy="parent")
private ArrayDeque<Child> childrens;