Hello,
I am using Hibernate 2.1 with j2sdk1.4.2_02 atop of Oracle 9i. Everything is working as advertised however I am having trouble performance tuning (reducing the number of SQL statements generated) using left outer joins. I have included all source, the mapping files and the log file output with debug on, at the end of this post
Here is the scenario:
ClassA has an attribute which is a list of ClassB objects. Each ClassB object has an attribute which is a list of ClassC objects. I have successfully used Hibernate to populate 3 ClassA objects, each of which contains 2 ClassB objects which in turn each contain 3 class C objects When I issue the HQL statement
String findString =
"from ClassA as classA " +
"join fetch classA.listOfClassB as listOfClassB " +
"join fetch listOfClassB.listOfClassC as listOfClassC";
List molList = s.find(findString);
Hibernate produces a single SQL statement that contains all the columns required to realise the graph of objects EXCEPT (seemingly) the index column for the collection of ClassB objects in ClassA. Hibernate then issues an additional SQL statement for each ClassA object but this additional query seems to be getting the attributes of the ClassB instances contained within ClassA. Again (seemingly) the only column in these subsequent queries that is not present in the initial query is the index column for ClassB.
So the questions I have are:
1. Is it possible to have Hibernate realize a collection of objects as described above using a single SQL statement (or at least not issuing additional SQL statements equivalent to the the number of ClassA objects returned)
2. If it is possible to have Hibernate do this what do I need to change to make it happen? I have searched through the forums, the documentation and the web. I have tried several different query approaches (HQL and Criteria) but cannot seem to deduce why the additional SQL statements per object in the list of ClassA should be necessary?
The obvious ramification of not being able to solve this issue is that as soon as the list of ClassA objects returned gets even moderately large (>100) performance is going to grind to a halt
Thanks in advance for any help (code, mappings etc below)
Cheers,
James
Code:
package eager;
import java.util.*;
public class ClassA extends Persistent {
private List listOfClassB;
public ClassA() {
}
public void setListOfClassB(List aList) {
listOfClassB = aList;
}
public List getListOfClassB() {
return listOfClassB;
}
}
Code:
package eager;
import java.util.*;
public class ClassB extends Persistent {
private List listOfClassC;
public ClassB() {
}
public void setListOfClassC(List aList) {
listOfClassC = aList;
}
public List getListOfClassC() {
return listOfClassC;
}
}
Code:
package eager;
public class ClassC extends Persistent {
public ClassC() {
}
}
Code:
package eager;
public class Persistent {
private Long id;
private String someName;
private String someDesc;
public void setId(Long anId) {
id = anId;
}
public Long getId() {
return id;
}
public Persistent() {
}
public void setSomeName(String aName) {
someName = aName;
}
public String getSomeName() {
return someName;
}
public void setSomeDesc(String aDesc) {
someDesc = aDesc;
}
public String getSomeDesc() {
return someDesc;
}
}
Code:
package eager;
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.*;
import net.sf.hibernate.expression.*;
import java.io.*;
import java.util.*;
public class Main {
private SessionFactory factory;
public Main() {
}
public void loadExampleClassAInstances() throws Exception {
Session s = factory.openSession();
Transaction tx = s.beginTransaction();
ClassA aClassA;
ClassB aClassB;
ClassC aClassC;
// Create 3 ClassA instances each of which
// contain a list of 2 classB instances which in turn
// contain a list of 3 classC instances...
List listOfClassB;
List listOfClassC;
for (int i = 0; i < 3; i++) {
aClassA = new ClassA();
aClassA.setSomeName("" + i);
listOfClassB = new ArrayList();
for (int j = 0; j < 2; j++) {
aClassB = new ClassB();
aClassB.setSomeName("" + i + j);
listOfClassB.add(aClassB);
listOfClassC = new ArrayList();
for (int k = 0; k < 3; k++) {
aClassC = new ClassC();
aClassC.setSomeName("" + i + j + k);
listOfClassC.add(aClassC);
s.save(aClassC);
}
aClassB.setListOfClassC(listOfClassC);
s.save(aClassB);
}
aClassA.setListOfClassB(listOfClassB);
s.save(aClassA);
}
tx.commit();
s.close();
}
public void retrieveExampleClassAInstances() throws Exception {
Session s = factory.openSession();
Transaction tx = s.beginTransaction();
String findString =
// First try
// "from ClassA as classA, ClassB as classB " +
// "left join fetch classA.listOfClassB " +
// "left join fetch classB.listOfClassC";
// second try
"from ClassA as classA " +
"join fetch classA.listOfClassB as listOfClassB " +
"join fetch listOfClassB.listOfClassC as listOfClassC";
// third try
// "from ClassA as classA " +
// "join fetch classA.listOfClassB";
List molList = null;
molList = s.find(findString);
System.out.println("Number of ClassA's is: " + molList.size());
// Put the results in a set to remove the duplicates that come from
// the efficiency of a single SQL statement to retrieve Class As list
// of Bs and then each Bs list of Cs through joins
Set molSet = new HashSet(molList);
// molList = s.createCriteria(ClassA.class)
// .setFetchMode("listOfClassB", FetchMode.EAGER)
ClassA aClassA;
List listOfClassB;
ClassB aClassB;
List listOfClassC;
ClassC aClassC;
for (Iterator iter = molSet.iterator(); iter.hasNext(); ) {
aClassA = (ClassA)iter.next();
listOfClassB = aClassA.getListOfClassB();
System.out.println("Got a class A instance with name " +
aClassA.getSomeName() + " and a list of class b instances: " +
listOfClassB);
for (Iterator bInstIter = listOfClassB.iterator(); bInstIter.hasNext(); ) {
aClassB = (ClassB)bInstIter.next();
listOfClassC = aClassB.getListOfClassC();
System.out.println("Got a class B instance with name " +
aClassB.getSomeName() + " and a list of class c instances: " +
listOfClassC);
}
}
tx.commit();
s.close();
}
public static void main(String[] args) throws Exception {
final Main test = new Main();
// This should cause hibernate to try and create the tables
// that will underly the clases for which the hibernate mapping
// files exist
Configuration cfg = new Configuration()
.addClass(ClassA.class)
.addClass(ClassB.class)
.addClass(ClassC.class)
.setProperty(Environment.HBM2DDL_AUTO, "create");
test.factory=cfg.buildSessionFactory();
test.loadExampleClassAInstances();
test.retrieveExampleClassAInstances();
test.factory.close();
}
}
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping package="eager">
<class name="ClassA" table="Molecule_Example" proxy="ClassA">
<id name="id">
<generator class="native"/>
</id>
<property name="someName" not-null="true"/>
<property name="someDesc"/>
<list name="listOfClassB">
<key column="mol_id"/>
<index column="tt_idx"/>
<one-to-many class="ClassB"/>
</list>
</class>
</hibernate-mapping>
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping package="eager">
<class name="ClassB" table="Top_Tor_Example" proxy="ClassB">
<id name="id">
<generator class="native"/>
</id>
<property name="someName" not-null="true"/>
<property name="someDesc"/>
<list name="listOfClassC">
<key column="tt_id"/>
<index column="tt_val_idx"/>
<one-to-many class="ClassC"/>
</list>
</class>
</hibernate-mapping>
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping package="eager">
<class name="ClassC" table="Top_Tor_Val_Example" proxy="ClassC">
<id name="id">
<generator class="native"/>
</id>
<property name="someName" not-null="true"/>
<property name="someDesc"/>
</class>
</hibernate-mapping>
The log file is as follows (notice the 3 additional queries):
09:36:29,632 INFO Environment:462 - Hibernate 2.1.2
09:36:29,822 INFO Environment:496 - loaded properties from resource hibernate.properties: {hibernate.connection.driver_class=oracle.jdbc.driver.OracleDriver, hibernate.cglib.use_reflection_optimizer=true, hibernate.cache.provider_class=net.sf.hibernate.cache.HashtableCacheProvider, hibernate.cache.use_query_cache=true, hibernate.max_fetch_depth=1, hibernate.dialect=net.sf.hibernate.dialect.OracleDialect, hibernate.jdbc.use_streams_for_binary=true, hibernate.jdbc.batch_size=0, hibernate.query.substitutions=true 1, false 0, yes 'Y', no 'N', hibernate.proxool.pool_alias=pool1, hibernate.connection.username=myusername, hibernate.connection.url=jdbc:oracle:thin:@MY_HOST:MY_PORT:MY_INSTANCE, hibernate.show_sql=true, hibernate.connection.password=mypassword, hibernate.connection.pool_size=1}
09:36:29,862 INFO Environment:518 - using java.io streams to persist binary types
09:36:29,872 INFO Environment:519 - using CGLIB reflection optimizer
09:36:29,882 INFO Configuration:329 - Mapping resource: eager/ClassA.hbm.xml
09:36:30,403 INFO Binder:229 - Mapping class: eager.ClassA -> Molecule_Example
09:36:30,613 INFO Configuration:329 - Mapping resource: eager/ClassB.hbm.xml
09:36:30,694 INFO Binder:229 - Mapping class: eager.ClassB -> Top_Tor_Example
09:36:30,704 INFO Configuration:329 - Mapping resource: eager/ClassC.hbm.xml
09:36:30,734 INFO Binder:229 - Mapping class: eager.ClassC -> Top_Tor_Val_Example
09:36:30,734 INFO Configuration:595 - processing one-to-many association mappings
09:36:30,784 INFO Binder:1154 - Mapping collection: eager.ClassA.listOfClassB -> Top_Tor_Example
09:36:30,794 INFO Binder:1154 - Mapping collection: eager.ClassB.listOfClassC -> Top_Tor_Val_Example
09:36:30,794 INFO Configuration:604 - processing one-to-one association property references
09:36:30,794 INFO Configuration:629 - processing foreign key constraints
09:36:30,904 INFO Dialect:82 - Using dialect: net.sf.hibernate.dialect.OracleDialect
09:36:30,904 INFO SettingsFactory:58 - Maximim outer join fetch depth: 1
09:36:30,904 INFO SettingsFactory:62 - Use outer join fetching: true
09:36:30,914 INFO DriverManagerConnectionProvider:41 - Using Hibernate built-in connection pool (not for production use!)
09:36:30,914 INFO DriverManagerConnectionProvider:42 - Hibernate connection pool size: 1
09:36:31,264 INFO DriverManagerConnectionProvider:71 - using driver: oracle.jdbc.driver.OracleDriver at URL: jdbc:oracle:thin:@MY_HOST:MY_PORT:MY_INSTANCE
09:36:31,264 INFO DriverManagerConnectionProvider:72 - connection properties: {user=myusername, password=mypassword}
09:36:31,274 INFO TransactionManagerLookupFactory:33 - No TransactionManagerLookup configured (in JTA environment, use of process level read-write cache is not recommended)
09:36:32,706 INFO SettingsFactory:102 - Use scrollable result sets: true
09:36:32,706 INFO SettingsFactory:105 - Use JDBC3 getGeneratedKeys(): false
09:36:32,706 INFO SettingsFactory:108 - Optimize cache for minimal puts: false
09:36:32,706 INFO SettingsFactory:114 - echoing all SQL to stdout
09:36:32,706 INFO SettingsFactory:117 - Query language substitutions: {no='N', true=1, yes='Y', false=0}
09:36:32,716 INFO SettingsFactory:128 - cache provider: net.sf.hibernate.cache.HashtableCacheProvider
09:36:32,716 INFO Configuration:1080 - instantiating and configuring caches
09:36:33,037 INFO SessionFactoryImpl:119 - building session factory
09:36:34,098 INFO SessionFactoryObjectFactory:82 - no JNDI name configured
09:36:34,128 INFO Dialect:82 - Using dialect: net.sf.hibernate.dialect.OracleDialect
09:36:34,128 INFO DriverManagerConnectionProvider:41 - Using Hibernate built-in connection pool (not for production use!)
09:36:34,128 INFO DriverManagerConnectionProvider:42 - Hibernate connection pool size: 1
09:36:34,139 INFO DriverManagerConnectionProvider:71 - using driver: oracle.jdbc.driver.OracleDriver at URL: jdbc:oracle:thin:@MY_HOST:MY_PORT:MY_INSTANCE
09:36:34,169 INFO DriverManagerConnectionProvider:72 - connection properties: {user=myusername, password=mypassword}
09:36:34,169 INFO SchemaUpdate:102 - Running hbm2ddl schema update
09:36:34,169 INFO SchemaUpdate:110 - fetching database metadata
09:36:34,379 INFO SchemaUpdate:124 - updating schema
09:36:34,379 INFO Configuration:595 - processing one-to-many association mappings
09:36:34,379 INFO Configuration:604 - processing one-to-one association property references
09:36:34,379 INFO Configuration:629 - processing foreign key constraints
09:36:37,974 INFO SchemaUpdate:143 - schema update complete
09:36:37,984 INFO DriverManagerConnectionProvider:137 - cleaning up connection pool: jdbc:oracle:thin:@MY_HOST:MY_PORT:MY_INSTANCE
09:36:38,004 INFO UpdateTimestampsCache:35 - starting update timestamps cache at region: net.sf.hibernate.cache.UpdateTimestampsCache
09:36:38,004 INFO QueryCache:39 - starting query cache at region: net.sf.hibernate.cache.QueryCache
Hibernate: select classa0_.id as id0_, listofclas1_.id as id1_, listofclas2_.id as id2_, classa0_.someName as someName0_, classa0_.someDesc as someDesc0_, listofclas1_.someName as someName1_, listofclas1_.someDesc as someDesc1_, listofclas2_.someName as someName2_, listofclas2_.someDesc as someDesc2_, listofclas2_.id as id__, listofclas2_.tt_id as tt_id__, listofclas2_.tt_val_idx as tt_val_idx__ from Molecule_Example classa0_, Top_Tor_Example listofclas1_, Top_Tor_Val_Example listofclas2_ where classa0_.id=listofclas1_.mol_id and listofclas1_.id=listofclas2_.tt_id
Hibernate: select listofclas0_.id as id__, listofclas0_.mol_id as mol_id__, listofclas0_.tt_idx as tt_idx__, listofclas0_.id as id0_, listofclas0_.someName as someName0_, listofclas0_.someDesc as someDesc0_ from Top_Tor_Example listofclas0_ where listofclas0_.mol_id=?
Hibernate: select listofclas0_.id as id__, listofclas0_.mol_id as mol_id__, listofclas0_.tt_idx as tt_idx__, listofclas0_.id as id0_, listofclas0_.someName as someName0_, listofclas0_.someDesc as someDesc0_ from Top_Tor_Example listofclas0_ where listofclas0_.mol_id=?
Hibernate: select listofclas0_.id as id__, listofclas0_.mol_id as mol_id__, listofclas0_.tt_idx as tt_idx__, listofclas0_.id as id0_, listofclas0_.someName as someName0_, listofclas0_.someDesc as someDesc0_ from Top_Tor_Example listofclas0_ where listofclas0_.mol_id=?
Number of ClassA's is: 18
Got a class A instance with name 0 and a list of class b instances: [eager.ClassB@126f827, eager.ClassB@1690ab]
Got a class B instance with name 00 and a list of class c instances: [eager.ClassC@16dfa45, eager.ClassC@1dc0e7a, eager.ClassC@3a9bba]
Got a class B instance with name 01 and a list of class c instances: [eager.ClassC@173ec72, eager.ClassC@1a85d38, eager.ClassC@8046f4]
Got a class A instance with name 2 and a list of class b instances: [eager.ClassB@ba4211, eager.ClassB@9ffe3f]
Got a class B instance with name 20 and a list of class c instances: [eager.ClassC@47a0d4, eager.ClassC@8ff4cf, eager.ClassC@1114460]
Got a class B instance with name 21 and a list of class c instances: [eager.ClassC@1ed1b0b, eager.ClassC@18a8ce2, eager.ClassC@12789d2]
Got a class A instance with name 1 and a list of class b instances: [eager.ClassB@d05c13, eager.ClassB@15dd910]
Got a class B instance with name 10 and a list of class c instances: [eager.ClassC@14d1d41, eager.ClassC@1bbd7b2, eager.ClassC@14df764]
Got a class B instance with name 11 and a list of class c instances: [eager.ClassC@55bb93, eager.ClassC@8de462, eager.ClassC@18ce14a]
09:36:39,296 INFO SessionFactoryImpl:531 - closing
09:36:39,296 INFO DriverManagerConnectionProvider:137 - cleaning up connection pool: jdbc:oracle:thin:@MY_HOST:MY_PORT:MY_INSTANCE