» tagged pages
» logout
Hibernate
Return to Hibernate

Hibernate BAG vs IDBAG Performance Comparison

(or Cancel)

(Editing anonymously: to be credited for your changes, login or register a new account)

other page actions:

Tags Applied to this Topic

1 person has tagged this page:

Compare performance of bag and idbag mappings.(Created before October 1st, 2006)

Hibernate is well known for its ability to help developers to persist collections of dependent elements, but that comes at a price. What is the the cost?

Hibernate provides us with numerous mapping options, but let’s focus on two: bag, and idbag. On the application code level the usage of bag and idbag tags is exactly the same. The differences are hidden in the Hibernate mapping files and in the database schema. Let’s look at the DB schema:

create-db.sql

1     
2    CREATE TABLE bag_main_objects ( 
3      id varchar(40) PRIMARY KEY, 
4      name varchar(100) 
5    ); 
6     
7    CREATE TABLE idbag_dependent_objects ( 
8      id varchar(40) PRIMARY KEY, 
9      main_id varchar(40), 
10     val char(255) 
11   ); 
12    
13   CREATE TABLE bag_dependent_objects ( 
14     main_id varchar(40), 
15     val char(255) 
16   ); 
17   

Here we create do tables for dependent objects: table bag_dependent_object does not have a surrogate key, and table idbag_dependent_objects that has surrogate key. For simplicity we will just have values in the dependent objects table, but we as well could store keys to another table that will allow us to implements many-to-many relationships.

Then we will populate those tables with 100 main object and with 20 dependent objects per main object.

DBSetterImpl.java

27    
28     public void generateTestData( Connection c ) throws SQLException{ 
29       for( int main_id = 0; main_id < MAIN_OBJ_ID_LIMIT; main_id++){ 
30         exec( c, "INSERT INTO bag_main_objects (id, name ) VALUES ( '"+ main_id +"','main-obj-" 
                                 + main_id + "')"); 
31         for( int j = 0; j < DEPENDENT_OBJ_LIMIT; j++ ){ 
32           exec( c, "INSERT INTO idbag_dependent_objects(id, main_id, val) VALUES " + 
33               "( 'm"+ main_id + "d"+ j +"','" + main_id + "','value of " +main_id + "d"+ j +"')"); 
34           exec( c, "INSERT INTO bag_dependent_objects(main_id, val) VALUES " + 
35               "('" + main_id + "','value of " +main_id + "d"+ j 
                +"kjhdsafjhsjhdfkhgskjhdfkjhsgkdjfhgkjshdfgk asjfhdg akksddg aslkdkhfg alsidf')"); 
36         } 
37       } 
38     }

BAG mapping is the example of un-optimized collection handling, it sure looks nice in the code: we simply add, update, or delete objects from the collection and Hibernate stores our changes. However for bag Hibernate uses a rather brutal force strategy: it deletes all the relevant rows from decent objects table and then inserts all the current collection content back into the database.

For example, lets assume that we have 20 dependent objects in a collection of dependent objects. If we delete just one object from the collection then Hibernate will execute 1 delete statement to delete all 20 rows, and then 19 insert statements to insert remaining 19 objects back into the database.

Here is how the mapping file looks:

bag.hbm.xml

5     
6    <hibernate-mapping package="com.sourcelabs.hibernate.bhw.bags" > 
7     
8    <class  table="bag_main_objects" name="BagContainerObj"  lazy="false" > 
9        <id name="id" > 
10         <generator  class="uuid"/> 
11       </id> 
12      <property name="name" /> 
13     <bag name="dependentObjects" table="bag_dependent_objects" lazy="false"> 
14       <key column="main_id" /> 
15       <element type="java.lang.String" column="val"/> 
16     </bag> 
17   </class> 
18   </hibernate-mapping>

IDBAG mapping is much smarter than BAG but requires ID column in the table of dependent objects. When we change collection content or update its members Hibernate will issue only necessary SQL update statements. If we delete one object from the collection, then Hibernate will execute only one SQL delete statement. IDBAG mapping looks as the following:

idbag.hbm.xml

<hibernate-mapping package="com.sourcelabs.hibernate.bhw.bags" > 
6     
7    <class  table="bag_main_objects" name="BagContainerObj"  lazy="false" > 
8        <id name="id" > 
9          <generator  class="uuid"/> 
10       </id> 
11      <property name="name" /> 
12     <idbag name="dependentObjects" table="idbag_dependent_objects" lazy="false"> 
13       <collection-id column="id" type="java.lang.String"> 
14         <generator class="uuid"/> 
15       </collection-id> 
16       <key column="main_id" /> 
17       <element type="java.lang.String" column="val"/> 
18     </idbag> 
19   </class> 
20   </hibernate-mapping>

Both our mappings map collection of Strings to a list field for the same object, that is right, the same object have two different mappings, they of course cannot be used simultaneously in the same session, therefore we will run our tests sequentially:

BagContainerObj.java

public class BagContainerObj { 
11     String id; 
12     String name; 
13     List dependentObjects; 
14    
15     public String getId(){ 
16       return id; 
17     } 
18    
19     public void setId( String id ){ 
20       this.id = id; 
21     } 
22    
23     public String getName(){ 
24       return name; 
25     } 
26    
27     public void setName( String name ){ 
28       this.name = name; 
29     } 
30    
31     public List getDependentObjects(){ 
32       return dependentObjects; 
33     } 
34    
35     public void setDependentObjects( List dependentObjects ){ 
36       this.dependentObjects = dependentObjects; 
37     } 
38   } 
39   

Running our tests produces the expected results: IDBAG collection is approximately 5 times faster than BAG mapping. And the advantage of IDBAG becomes even more noticeable with increase in the number of objects in the collection.

 run-class:
     [java] 0    [main] WARN  net.sf.ehcache.config.Configurator  
     - No configuration found. Configuring ehcache
 from ehcache -failsafe.xml  found in the classpath: jar:file:
 /home/kosta/.m2/repository/net/sf/ehcache/ehcache
/1.1-1/ehcache-1.1-1.jar!/ehcache-failsafe.xml
     [java] USING BAG
     [java] 613  [main] INFO  org.springframework.aop.framework.DefaultAopProxyFactory  
     - CGLIB2 available: pro
xyTargetClass feature enabled
     [java] 616  [main] INFO  org.springframework.core.CollectionFactory  
     - JDK 1.4+ collections available
     [java] 627  [main] INFO  org.springframework.core.CollectionFactory  
     - Commons Collections 3.x available
     [java] Pass #1
     [java] hibernateSessionFactory = org.hibernate.impl.SessionFactoryImpl@1d95da8
     [java] execAddElement      1716 ms
     [java] Pass #2
     [java] hibernateSessionFactory = org.hibernate.impl.SessionFactoryImpl@1d95da8
     [java] execAddElement      1372 ms
     [java] Pass #3
     [java] hibernateSessionFactory = org.hibernate.impl.SessionFactoryImpl@1d95da8
     [java] execAddElement      1178 ms
     [java] Average::1422.3333333333333 ms
     [java] USING IDBAG
     [java] Pass #1
     [java] hibernateSessionFactory = org.hibernate.impl.SessionFactoryImpl@1a3b359
     [java] execAddElement      485 ms
     [java] Pass #2
     [java] hibernateSessionFactory = org.hibernate.impl.SessionFactoryImpl@1a3b359
     [java] execAddElement      438 ms
     [java] Pass #3
     [java] hibernateSessionFactory = org.hibernate.impl.SessionFactoryImpl@1a3b359
     [java] execAddElement      526 ms
     [java] Average::483.6666666666667 ms
     [java] USING BAG
     [java] Pass #1
     [java] hibernateSessionFactory = org.hibernate.impl.SessionFactoryImpl@1d95da8
     [java] execRemoveElement   1367 ms
     [java] Pass #2
     [java] hibernateSessionFactory = org.hibernate.impl.SessionFactoryImpl@1d95da8
     [java] execRemoveElement   1292 ms
     [java] Pass #3
     [java] hibernateSessionFactory = org.hibernate.impl.SessionFactoryImpl@1d95da8
     [java] execRemoveElement   1353 ms
     [java] Average::1337.3333333333333 ms
     [java] USING IDBAG
     [java] Pass #1
     [java] hibernateSessionFactory = org.hibernate.impl.SessionFactoryImpl@1a3b359
     [java] execRemoveElement   413 ms
     [java] Pass #2
     [java] hibernateSessionFactory = org.hibernate.impl.SessionFactoryImpl@1a3b359
     [java] execRemoveElement   419 ms
     [java] Pass #3
     [java] hibernateSessionFactory = org.hibernate.impl.SessionFactoryImpl@1a3b359
     [java] execRemoveElement   402 ms
     [java] Average::411.6666666666667 ms

The testing environment:

kosta@swan ~/dev/hb-beyond-hw $ java -version
java version "1.5.0_06" 
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_06-b05)
Java HotSpot(TM) Server VM (build 1.5.0_06-b05, mixed mode)
kosta@swan ~/dev/hb-beyond-hw $ uname -a
Linux swan 2.6.14-gentoo-r2 #1 SMP Wed Dec 21 09:41:13 
PST 2005 x86_64 Intel(R) Xeon(TM) CPU 2.80GHz GenuineIntel GNU/Linux

BAGs brute force strategy is surely necessary for update and delete operations, but Hibernate could do a better job optimizing add (insert) operations. There is a little need for deleteing and reinserting all the members of the collection just to add one more record.

Username:
Password:
(or Cancel)