Ok. I've read the Hibernate2 reference document (a few times), the examples and FAQs. They've helped, but now I'm stuck. I've found that for small one-many collections, performance is pretty good. However Hibernate seems to make some unecessary calls that really effect performance on large collections.
In my example:
Person - A collection of Aliases (just a wrapper class)
Alias - Hosts demographic info on a Person
Address - Address info for the Alias.
Person 1 <-> * Alias 1 <-> * Address
Problem: To test and demo Hibernate2 I'm trying to do a few things, but one of them is a stress test on insert and delete performance.
Test1: Insert 1 Person with 1 million generated Aliases, and no Address associations. Fails, out of memory (kind of expected).
Test2: Insert 1 Person with 100,000 generated Aliases, and no Address associations. Fails, out of memory.
Test3: Modified test 2, not to fail. Uses session.evict(). However doesn't work correctly when cascade="all" or cascade="all-delete-orphan" is selected. I like using the all-delete-orphan option since on smaller collections it has exactly the symantics I want. However in Test2 odd update statements are called which hang things up.
Test4: Remove all the Persons created in tests 1, 2, or 3. The problem I'm seeing here is that Persons seem to be getting initialized before they're removed. This also kills performance.
I've think I have the correct lazy and inverse settings, but if someone could follow my code/mapping files, take a look at my output and post some suggestions, I'd really appreciate it.
Really wish the forum had an attachment option.
Hibernate version: 2.1.4
Java version: 1.4.2_03
Database: Postgresql 7.4.1
Mapping Docs:
Person.hbm.xml
Code:
<hibernate-mapping>
<class
name="db.Person"
table="person"
dynamic-update="true"
dynamic-insert="true"
>
<cache usage="nonstrict-read-write" />
<id
name="ID"
column="id"
type="java.lang.Long"
>
<generator class="increment">
<param name="sequence">person_sequence</param>
</generator>
</id>
<set
name="aliases"
table="person_alias"
lazy="true"
inverse="true"
cascade="all-delete-orphan"
sort="unsorted"
>
<cache
usage="nonstrict-read-write"
/>
<key
column="personid"
>
</key>
<one-to-many
class="db.Alias"
/>
</set>
<!--
To add non XDoclet property mappings, create a file named
hibernate-properties-Person.xml
containing the additional properties and place it in your merge dir.
-->
</class>
<query name="person.getPersonByAliasLName"><![CDATA[
select person from db.Person as person, db.Alias as alias where alias.lastName = :lname and alias in elements(person.aliases)
]]></query>
<query name="person.getOrphanedPersons"><![CDATA[
from db.Person as person where person.aliases.size = 0
]]></query>
<query name="person.getAllPersons"><![CDATA[
select person from db.Person as person
]]></query>
</hibernate-mapping>
Alias.hbm.xmlCode:
<hibernate-mapping>
<class
name="db.Alias"
table="person_alias"
dynamic-update="true"
dynamic-insert="true"
>
<cache usage="nonstrict-read-write" />
<id
name="ID"
column="id"
type="java.lang.Long"
>
<generator class="increment">
</generator>
</id>
<property
name="firstName"
type="java.lang.String"
update="true"
insert="true"
access="property"
column="firstName"
length="60"
/>
<property
name="lastName"
type="java.lang.String"
update="true"
insert="true"
access="property"
column="lastName"
length="60"
not-null="true"
/>
<property
name="SSN"
type="java.lang.String"
update="true"
insert="true"
access="property"
column="SSN"
length="9"
/>
<set
name="addresses"
lazy="true"
inverse="true"
cascade="all-delete-orphan"
sort="unsorted"
>
<cache
usage="nonstrict-read-write"
/>
<key
column="aliasid"
>
</key>
<one-to-many
class="db.Address"
/>
</set>
<many-to-one
name="person"
class="db.Person"
cascade="none"
outer-join="auto"
update="true"
insert="true"
access="property"
column="personid"
not-null="true"
/>
<!--
To add non XDoclet property mappings, create a file named
hibernate-properties-Alias.xml
containing the additional properties and place it in your merge dir.
-->
</class>
</hibernate-mapping>
Address.hbm.xmlCode:
<hibernate-mapping>
<class
name="db.Address"
table="address"
dynamic-update="true"
dynamic-insert="true"
>
<cache usage="nonstrict-read-write" />
<id
name="ID"
column="id"
type="java.lang.Long"
>
<generator class="increment">
</generator>
</id>
<many-to-one
name="alias"
class="db.Alias"
cascade="none"
outer-join="auto"
update="true"
insert="true"
access="property"
column="aliasid"
not-null="true"
/>
<property
name="street"
type="java.lang.String"
update="true"
insert="true"
access="property"
column="street"
/>
<property
name="city"
type="java.lang.String"
update="true"
insert="true"
access="property"
column="city"
/>
<property
name="state"
type="java.lang.String"
update="true"
insert="true"
access="property"
column="state"
/>
<property
name="zip"
type="java.lang.String"
update="true"
insert="true"
access="property"
column="zip"
length="5"
/>
<property
name="zipPlusFour"
type="java.lang.String"
update="true"
insert="true"
access="property"
column="zipPlusFour"
length="4"
/>
<!--
To add non XDoclet property mappings, create a file named
hibernate-properties-Address.xml
containing the additional properties and place it in your merge dir.
-->
</class>
</hibernate-mapping>
Problem create code: I've done a number of things to try and get things working. All either have memory problems when the number of aliases are > 80K or there are other issues when Session.evict() is used in conjunction with any cascade option that also does a save-update. The below code will work and insert 30k Aliases. However the delete code has a lot of problems. I'd like the below code to work for > 100k Aliases and the delete code to work efficiently.
Insert CodeCode:
Session s = sessionfactory.openSession();
Transaction tx = s.beginTransaction();
Person p = new Person();
//s.save(p);
//s.flush();
List aliasList = new LinkedList();
for(int i=0; i<30000; i++)
{
Alias a = new Alias();
a.setLastName("Hazen"+i);
a.setPerson(p);
a.setFirstName("Jim");
a.setSSN("555555555");
//s.save(a);
//aliasList.add(a);
p.getAliases().add(a);
//s.save(p);
if(i%10000 == 0)
{
//s.save(p);
//s.flush();
//s.clear();
// for(Iterator iterator = aliasList.iterator(); iterator.hasNext();)
// {
// s.evict((Alias)iterator.next());
// iterator.remove();
// }
//
// //s.close();
// //s = sessionfactory.openSession();
System.out.println("i = "+i);
System.out.println("memory = "+Runtime.getRuntime().freeMemory());
}
}
s.save(p);
//s.flush();
tx.commit();
s.close();
Delete CodeCode:
Session s = sessionfactory.openSession();
Transaction tx = s.beginTransaction();
Query q = s.getNamedQuery("person.getAllPersons"); //select person from db.Person as person
s.delete(q.getQueryString());
// for(Iterator i = q.iterate(); i.hasNext();)
// {
// s.delete((Person)i.next());
// }
tx.commit();
s.close();
OutputCode:
[java] i = 0
[java] memory = 7372008
[java] i = 10000
[java] memory = 4977048
[java] i = 20000
[java] memory = 2170424
18:39:04,996 INFO DriverManagerConnectionProvider:143 - cleaning up connection pool: jdbc:postgresql:hibernate
[java] Hibernate: insert into person (id) values (?)
[java] Hibernate: insert into person_alias (firstName, lastName, SSN, personid, id) values (?, ?, ?, ?, ?)
[java] Hibernate: select person0_.id as id from person person0_
[java] Hibernate: select aliases0_.id as id__, aliases0_.personid as personid__, aliases0_.id as id0_, aliases0_.firstName as firstName0_, aliases0_.lastName as lastName0_, aliases0_.SSN as SSN0_, aliases0_.personid as personid0_ from person_alias aliases0_ where aliases0_.personid=?
[java] Hibernate: select addresses0_.id as id__, addresses0_.aliasid as aliasid__, addresses0_.id as id0_, addresses0_.aliasid as aliasid0_, addresses0_.street as street0_, addresses0_.city as city0_, addresses0_.state as state0_, addresses0_.zip as zip0_, addresses0_.zipPlusFour as zipPlusF7_0_ from address addresses0_ where addresses0_.aliasid=?
... (same message many times)
[java] Hibernate: select addresses0_.id as id__, addresses0_.aliasid as aliasid__, addresses0_.id as id0_, addresses0_.aliasid as aliasid0_, addresses0_.street as street0_, addresses0_.city as city0_, addresses0_.state as state0_, addresses0_.zip as zip0_, addresses0_.zipPlusFour as zipPlusF7_0_ from address addresses0_ where addresses0_.aliasid=?
[java] Hibernate: delete from person_alias where id=?
After which the code hangs, or at least the delete doesn't complete in a reasonable amount of time (I usually let it go for 1-5 minutes, but the same call by hand only takes a second.)
So my questions are, why is Hibernate doing all those selects? You can do the same stuff very simply, and it doesn't take nearly as long. I figure it must be with my mapping, but if I change the mapping file, things don't work right. I get the same FAQ errors that people get when they don't set up their mapping correctly. So how do I get deletes to work in conjunction with cascade=all-delete-orphan without all the initialization.
By hand the statements work much quicker:
Code:
delete from address where aliasid in (select id from person_alias where personid=?);
delete from person_alias where personid=?;
delete from person where id=?;
Also, if I try to use the session.evict() insert code with a cascade that does save-update, I get the following problem. The initial insert goes through, then the next, and then an update. Eventually this grinds things to a hault. I don't see the need for the update, and can't find a way to get both cascade all-delete-orphan symantics and avoid running out of memory on large collections. Sure, you can say that ORM tools aren't really designed for large collections. But being able to demonstrate that there is at least a way, will be critical for my company.
Output with evict codeCode:
[java] Hibernate: insert into person (id) values (?)
[java] Hibernate: insert into person_alias (firstName, lastName, SSN, personid, id) values (?, ?, ?, ?, ?)
[java] i = 0
[java] memory = 7473288
18:59:07,667 INFO DriverManagerConnectionProvider:143 - cleaning up connection pool: jdbc:postgresql:hibernate
[java] Hibernate: insert into person_alias (firstName, lastName, SSN, personid, id) values (?, ?, ?, ?, ?)
[java] Hibernate: update person_alias set firstName=?, lastName=?, SSN=?, personid=? where id=?
[java] i = 10000
[java] memory = 3749384
[java] Hibernate: insert into person_alias (firstName, lastName, SSN, personid, id) values (?, ?, ?, ?, ?)
[java] Hibernate: update person_alias set firstName=?, lastName=?, SSN=?, personid=? where id=?
After which things grind to a hault. I'm sure it's probably because of the collection state, but I'm not sure what to do here. I'd like to batch update these records, so insert 10k, commit and then evict, but haven't found a way to do that all with cascade all-delete-orphan (which it was the program will need most of the time).
Any help would be appreciated. I have a complete src package with ant build/run/db schema export script that I could send if people need to play around on their systems. Let me know.
Thanks in advance,
Jim