StackOverflowError when retriving ManyToMany join table with Java EJB 3 - java

I have the following entity mapings for my EJB3 application that map a many-to-many relationship:
#Entity
Crawl{
#OneToMany(fetch = FetchType.EAGER, mappedBy = "pk.crawl")
public List<Change> changes;
}
#Entity
Change{
#EmbeddedId
ChangePK pk;
#Temporal(javax.persistence.TemporalType.DATE)
Date changeDate;
}
#Embeddable
ChangePK{
#ManyToOne
Crawl crawl;
#ManyToOne
Page page;
}
#Entity
Page{
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.page")
List<Change> changes;
}
I am trying to get all of the changes that are related to a crawl and order them by date using:
this.entityManager
.createQuery("SELECT c FROM Change c WHERE
c.pk.crawl.id = :id
ORDER BY c.changeDate DESC")
.setParameter("id", crawl.getId());
This is giving me a stack overflow error. I belive the eager fetch may have something to do with it but in every other occurence I want the changes loaded with a crawl and it will cause a lot of problems in the rest of my application if I change the fetch type to lazy.
I have overriden hashCode and equals methods for each class.
Edit:
hashcode and equals code:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
return result;
}
#Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Crawl other = (Crawl) obj;
if (id != other.id)
return false;
return true;
}
These are generated by Eclipse and I have selected the primary keys to use in them, the other classes all use the same thing.

If the whole tree of object tree is big, there's no way* to avoid the stackoverflow as hibernate resolves the dependencies recursively, which is ok for 99.9% of the cases (in 8 years of using hibernate, this is the first time that I've seen this error).
One alternative to fix this, is to increase the stack size, but that will increase the size on all the application threads (which might not be something good). so for example you can add the option -Xss1m when you run the JVM and you'll get a 1mb stack size. (the default stack size varies from platform to platform, but I think it's usually 512k)
Another alternative is to change the mapping, but I think all include denormalising the table a bit.
One option is to flatten the tree, so given a specific Crawl, you can retrieve all the children of the crawl with one query. In this case, the collection Crawl.changes contains all the children, grandsons, etc of the crawl.
*there is always a way

Related

Why Hibernate requires us to implement equals/hashcode methods when I have a private id field?

First, consider the snippet,
public class Employee
{
private Integer id;
private String firstname;
private String lastName;
private String department;
// public getters and setters here, i said PUBLIC
}
I create 2 objects with same ids and rest of all the fields are also same.
Employee e1 = new Employee();
Employee e2 = new Employee();
e1.setId(100);
e2.setId(100);
//Prints false in console
System.out.println(e1.equals(e2));
The whole problem starts here
In a real time application, this must return true.
Consequently, everyone knows a solution exists (to implement equals() and hashcode())
public boolean equals(Object o) {
if(o == null)
{
return false;
}
if (o == this)
{
return true;
}
if (getClass() != o.getClass())
{
return false;
}
Employee e = (Employee) o;
return (this.getId() == e.getId());
}
#Override
public int hashCode()
{
final int PRIME = 31;
int result = 1;
result = PRIME * result + getId();
return result;
}
Now, as usual:
Employee e1 = new Employee();
Employee e2 = new Employee();
e1.setId(100);
e2.setId(100);
//Prints 'true' now
System.out.println(e1.equals(e2));
Set<Employee> employees = new HashSet<Employee>();
employees.add(e1);
employees.add(e2);
//Prints ofcourse one objects(which was a requirement)
System.out.println(employees);
I am going through this excellent article Don't Let Hibernate Steal Your Identity. But one thing I have failed to understand completely. The whole problem and its solution discussed above and the linked article were dealing the problems when the 2 Employee object ids were same.
Consider when we have a private setter for id field with the id field generated by the generator class provided in hbm.xml. As soon as i start to persist the Employee objects(and in no way i would be able to change the id), i find no need to implement equals and hashcode methods. I am sure i am missing something, since my intuition says when a particular concept is too much rotated over the web, it must have always been laid in front of you for the sake of avoiding some common errors ? Do i still have to implement those 2 methods when i have a private setter for id field?
If the entity defines a natural business key, then you should use that for equals and hashCode. The natural identifier or business key is consistent across all entity state transitions, hence the hashCode will not change when the JPA entity state changes (e.g. from New to Managed to Detached).
In your example, you are using the assigned identifier, which doesn't change when you persist your entity.
However, if you don't have a natural identifier and you have a generated PRIMARY KEY (e.g., IDENTITY, SEQUENCE), then you can implement equals and hashCode like this:
#Entity
public class Book implements Identifiable<Long> {
#Id
#GeneratedValue
private Long id;
private String title;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Book))
return false;
Book other = (Book) o;
return id != null &&
id.equals(other.getId());
}
#Override
public int hashCode() {
return getClass().hashCode();
}
//Getters and setters omitted for brevity
}
The entity identifier can be used for equals and hashCode, but only if the hashCode returns the same value all the time. This might sound like a terrible thing to do since it defeats the purpose of using multiple buckets in a HashSet or HashMap.
However, for performance reasons, you should always limit the number of entities that are stored in a collection. You should never fetch thousands of entities in a #OneToMany Set because the performance penalty on the database side is multiple orders of magnitude higher than using a single hashed bucket.
The reason why this version of equals and hashCode works is that the hashCode value does not change from one entity state to another, and the identifier is checked only when it's not null.

Hibernate: Persist tree like structure

I have a tree like structure in a Collection. I have ensured that all nodes in the collection do not make extraneous references and are topologically sorted such that the root node at the head of the collection and all leaves are near the end of it.
My primary abstract node class is something like this:
#Entity
public abstract class Node {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public long ID;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "parent_id", insertable = false, updatable = false)
Node parent;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "root_id", insertable = false, updatable = false)
Node root;
}
I do not maintain children list, but each node points to its parent. The root reference is a convenience field to refer to the root node of the tree. For example, it will be easier for deleting entire trees. Now I have many descendants from Node such as A, B C, etc.
The problem:
When trying to persist the entire tree, I use the following code.
// Check for extraneous references, and sort them topologically.
Session s = hibernate.openSession();
Transaction tx = s.beginTransaction();
try {
int i = 0;
for (Node p: objects) {
if (p.parent == null) {
throw new IOException("Parent is `null`.");
}
s.persist(p);
if (i % batchSize == 0) {
s.flush();
s.clear();
}
i++;
}
tx.commit();
}
catch (Throwable t) {
log.error(t.getMessage(), t);
tx.rollback();
throw new IOException(t);
}
This method doesn't persist objects correctly. If the batch size is too small, I get a PersistentObjectException with a message:
org.hibernate.PersistentObjectException: detached entity passed to persist: com.example.Node
If the batch size is at least as large as total number of objects I can persist, but PARENT_ID and ROOT_ID in database is all set to null. I am using H2 while testing. Note, class A is always the root node, all other objects can appear at any level below A. I tried s.merge()ing too but that didn't work either. I have implemented equals() and hashCode() according to my business keys.
Is it a problem with my equals/hashCode method? Or is it the way I'm attempting to persist? I don't know what's wrong with my code. Somehow I feel this is a trivial error and that I'm overlooking fundamental aspect. Could someone please help me fix it? I tried reading through different blogs that talk about hierarchical representation using Hibernate, but nothing helped.
Try removing s.clear().
It is basically detaching the objects from the persistence context, which may be causing the exception detached entity passed to persist

Lazy loading datatable shows duplicate results with FetchType.EAGER

I know there's a lot of questions about this but none of the solutions helped me.
I'm using PrimeFaces to build a lazy loadind datatable. That means that this datatable list is a LazyDataModel list, and I had to develop a LazyDataModel implementation where I overrode the load method. All of this can be learned from PrimeFaces showcase, and it works fine for most of cases, when the datable uses just one table from the database (which is not my case).
Now, I have two entities:
#Entity
#Table(name="UNIVERSITY")
public class University {
#Id
#Column(name="ID_UNIVERSITY")
#GeneratedValue(strategy = GenerationType.AUTO ,generator="SQ_UNIVERSITY")
#SequenceGenerator(name="SQ_UNIVERSITY", sequenceName="SQ_UNIVERSITY")
private Long idUniversity;
#Column(name="NAME")
private String name;
#ManyToOne
#JoinColumn(name="ID_DIRECTOR")
private Director director;
#OneToMany(fetch=FetchType.EAGER, mappedBy = "university")
private Set<Students> students = new HashSet<Students>(0);
...
public int getStudentsQty(){
return this.students.size();
}
...
Where I'll use the getStudentsQty() method to fill one column from my datatable. And here's the Students entity:
#Entity
#Table(name="STUDENTS")
public class Students
{
#Id
#Column(name="ID_STUDENTS")
#GeneratedValue(strategy = GenerationType.AUTO ,generator="SQ_STUDENTS")
#SequenceGenerator(name="SQ_STUDENTS", sequenceName="SQ_STUDENTS")
private Long idStudent;
#ManyToOne
#JoinColumn(name="ID_UNIVERSITY")
private University student;
#Column(name="NAME")
private String name;
...
Here is the search method that my load implementation will use:
public List<University> find(int startingAt, int maxPerPage,
final String sortField, final SortOrder sortOrder, Map<String, String> filters) {
session = HibernateUtil.getSession();
Criteria criteria =session.createCriteria(University.class);
List<String> aliases = new ArrayList<String>();
if(maxPerPage > 0){
criteria.setMaxResults(maxPerPage);
}
criteria.setFirstResult(startingAt);
addFiltersToCriteria(filters, criteria, aliases);
Order order = Order.desc("name");
if(sortField != null && !sortField.isEmpty()){
if(sortField.contains(".")){
String first = (sortField.split("\\."))[0];
if(!aliases.contains(first)){
criteria.createAlias(first, first);
aliases.add(first);
}
}
if(sortOrder.equals(SortOrder.ASCENDING)){
order = Order.asc(sortField);
}
else if(sortOrder.equals(SortOrder.DESCENDING)){
order = Order.desc(sortField);
}
}
criteria.addOrder(order);
return (List<University>) criteria.list();
}
And now, my problem. If I use FetchType.LAZY, everything works fine, but the performance is terrible, so I wish to use EAGER. If I use EAGER the results will come duplicated as expected and explained here. I tried to implement equals() and hashCode() methods in the University entity to use a LinkedHashSet as suggested in the last link but it didn't worked I don't know how. I also tried to use DISTINCT with Criteria but it doesn't work because of the addOrder that I use, where it asks for joins.
So, I found this other suggestion which worked perfectly. Basically the solution is to do a second Criteria query, searching only for Universities with ID included in the original search. Like this:
private List<University> removeDuplicates(Order order,
List<University> universities) {
Criteria criteria;
List<University> distinct = null;
if(universities.size() > 0){
Set<Long> idlist = new HashSet<Long>();
for(University univ: universities){
idlist.add(univ.getIdUniversity());
}
criteria = session.createCriteria(University.class);
criteria.add(Restrictions.in("id", idlist)) ;
distinct = (List<University>) criteria.list();
return distinct;
}
else{
return universities;
}
}
So it will bring, say, the first 100 lines for my lazy loadind pagination datatable. In the first Criteria search they will be sorted for the first page, and the same 100 correct rows will be present after my second Criteria search, but now they will be unsorted. It's the correct rows for the first page, but unsorted inside the first page. I cant use "addOder" in the second Criteria or else they will come duplicated.
And the strangest thing: if I try to sort the results with Collections.sort the results will be duplicated!!! How?? How can I order my result after all?
Thanks!!
EDIT: the students count is just an example, I'll need in another scenarios get information inside each associated entity.
If I understand correctly you are outputting a table listing universities and you want to show the number of students for each university. If so, loading x000 student records into memory just to get a count is crazy (regardless of whether you do it eagerly or lazily).
Try one of the following:
One
rather than loading the associated students to get the count use Hibernates #Formula functionality and add a derived property to you University entity.
#Formula(value = "select count(*) from students where university_id = ?")
private int studentCount;
Two
Create a a database view say university_summary_data which includes this count and create an Entity mapped to this view (works just like a table) then in University:
#OneToOne
private UniversitySummaryData data;
Three
Look into Hibernate's #LazyCollection(LazyCollectionOption.EXTRA) which will allow you to call size() on the mapped collection without loading all Students.
All much simpler solutions that what you have.
You say that you want to switch to EAGER loading because the performance with Lazy loading is terrible, but with EAGER loading your performance will be the same. You will still get the select n + 1 problem explained for example here and here with solution.
For performance to improve you need to modify the query that Hibernate will generate. Usually I do a left outer join in Hibernate to obtain this, e.g.
sessionFactory.getCurrentSession().createCriteria(University.class)
.createAlias("students", "students_alias", JoinType.LEFT_OUTER_JOIN)
.list();
And it's best to keep the Hibernate default of lazy loading.

How to deal with computed fields in Entities

I have a question about the best way to deal with computed fields, namely count, in my Entities. I have an Establishment entity that contains reviews, and I don't want to load the reviews until I have to, but I do want to get the number of (count) of reviews when I load the entity object. What's the best way to handle this? I've tried creating a numReviews field and and annotating it #Transient, but am having some problems with the session being closed when I called getReviews().size(). I want to know if this is the correct approach to take or if there is a better pattern to follow?
#Entity
#Table(name="ESTABLISHMENT")
public class Establishment {
...
private Set<Review> reviews = new HashSet<Review>();
...
//Make this extra lazy so we can do a count without loading all the things
#OneToMany(fetch = FetchType.LAZY, mappedBy = "establishment")
#LazyCollection(LazyCollectionOption.EXTRA)
public Set<Review> getReviews() {
return reviews;
}
public void setReviews(Set<Review> reviews) {
this.reviews = reviews;
}
#Transient
public int getNumReviews(){
if(this.numReviews == null){
numReviews = this.getReviews().size();
}
return numReviews;
}
Correct may be opinable. My first idea would be moving all your hibernate operations to a DAO layer and ensuring there, when retrieving the object, that the data is loaded, but it may be heavy
Alternatively, I would use FetchType.EAGER.
If that still does not suit you, use HQL to do (sorry is there is some mistake, I am more used to JPQL)
SELECT est, COUNT(rev) FROM Establishment est inner join est.reviews rev ....
And when retrieving the values set each object retrieved its totals.

JPA : how to manage id (or business-id) ? Still the same pb of equals/hashcode

i'm a beginner with Hibernate, Spring, JPA frameworks. For moment, i'm trying to make a simple architecture with Spring 3.1.1 - JPA with Hibernate 4 Implementation.
For the moment, in order to not be dependant of my database, i created some ids with TableGenerator :
#Id
#Column(name = "AIR_ID", unique = true, nullable = false)
#TableGenerator(name="aircraftSeqStore",
table="T_S_APP_SEQ_STORE_AST",
pkColumnName="AST_SEQ_NAME",
valueColumnName = "AST_SEQ_VALUE",
pkColumnValue = "T_R_AIRCRAFT_AIR.AIR_ID",
allocationSize=1)
#GeneratedValue(strategy=GenerationType.TABLE,
generator="aircraftSeqStore")
private Integer id;
After my research, and after reading "dont-let-hibernate-steal-your-identity" article, i don't really understood how to manage my ids.
Should i modify my entities to replace them with an assigned value (how to do it in JPA ?) and should i generate an UUID to affect the ids directly at the creation of the transient object ?
In many tables, i have some easy datas (id, name). I thought i could manage the hashcode and equals methods on name properties which are unique, but not affected at the creation of the object too....(so i think same pb with id which is null ?).
For information, i have an entity which represent a multi join table (3 FK in this join table).
So what do you advice to me ?
Is it not bad of generate UUID for performance ?
EDIT :
Is this entity viable ?
#Id
#Column(name = "AIR_ID", unique = true, nullable = false)
#TableGenerator(name="aircraftSeqStore",
table="T_S_APP_SEQ_STORE_AST",
pkColumnName="AST_SEQ_NAME",
valueColumnName = "AST_SEQ_VALUE",
pkColumnValue = "T_R_AIRCRAFT_AIR.AIR_ID",
allocationSize=1)
#GeneratedValue(strategy=GenerationType.TABLE,
generator="aircraftSeqStore")
private Integer id;
#Column(name = "AIR_BUSINESS_ID", unique = true, nullable = false)
private String uuid = IdGenerator.createId();
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof Aircraft))
return false;
Aircraft other = (Aircraft)o;
if (uuid == null) return false;
return uuid .equals(other.getUuid ());
}
public int hashCode() {
if (uuid != null) {
return uuid .hashCode();
} else {
return super.hashCode();
}
}
Thank you.
As with every question the full, but seldom usefull, answer is: It depends.
The more helpful variant is:
I use GenerationType.Auto most of the time, and do NOT implement equals and hashcode.
The result is:
you are fine comparing entity objects as long as they live in the same session, since hibernate will ensure that each database row is represented by a single instance per session.
equals and hashcode are stable over time, so you can put your objects in HashSets, change the objects and still get them out again.
If you want to work with objects from different Sessions you have to explicitly compare ids or ids + hashcode or some business key, possibly by implementing a Comparator. The extra effort put in deciding what to use and to implement it will remind you that you are actually doing something going against the grain of Hibernate.
About performance: Depending on the database and the use case UUIDs migh cost performance because they are rather large, or gain performance, because they can get created on the client thus saving a database roundtrip. Most of the times other glitches in the application (especially when using Hibernate) are way bigger then any effect of the ID Generation.
Usually i use:
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id
and let the persistence provider to chose the right one.
Hope this help you
I recently asked a question that explores an alternative to the usual pattern: Are there any gotchas with this JPA "cached hashCode" pattern?
I included an example of what I usually do with #Entity classes - generating UUIDs on construction. The probability of a UUID collision is so small that you'd be best worrying about cosmic rays. Some people don't like UUIDs because they feel there is a performance penalty. I've not seen any change in performance versus Integer, but I think the chance of an Integer collision is small enough to make it a concern.
#Id
private UUID id = UUID.randomUUID();
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof MY_CLASS) || id == null)
return false;
MY_CLASS other = (MY_CLASS) obj;
return id.equals(other.id);
}
#Override
public int hashCode() {
Preconditions.checkNotNull(id, "id must be set before #Entity.hashCode can be called");
return id.hashCode();
}
Sometimes I want to check if the actual data itself matches, in which case I create a method like this:
public boolean hasSameProperties(Note other) {
Preconditions.checkNotNull(other);
if (this == other)
return true;
return Objects.equal(source, other.source)
&& Objects.equal(title, other.title)
&& Objects.equal(tags, other.tags)
&& Objects.equal(contents, other.contents);
}

Categories

Resources