A Persistent Collection with Hibernate

Our series on Hibernate continues with the addition of a Hibernate collection. The collection is a basic building block in programming and storing Keyword objects inside a KeySet collection object gives us an easy way to query and display our persistent objects.

The first step in implementing our KeySet class is deciding whether there will be a one-to-many or a many-to-many relationship between KeySet and Keyword objects. A Keyword object may not be contained in multiple KeySet objects in a one-to-many relationship and it may be contained in multiple KeySet objects in a many-to-many relationship.

This example will use a many-to-many relationship. This requires a new table with two foreign keys tying KeySets and Keywords rows together – a one-to-many relationship would require a foreign key in the Keywords table that referenced the KeySets table. The KeySet.hbm.xml file follows (comments show the SQL used to create the respective tables). It is quite similar to the Keyword.hbm.xml file with the addition of the set tag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<hibernate-mapping>
<!-- CREATE TABLE KEYSETS ( ID IDENTITY, NAME VARCHAR(25) );
-->

<class name="hb.KeySet"
table="keysets">


<id name="id"
type="integer"
column="id"
unsaved-value="-1">

<generator class="identity"/>
</id>

<property name="name"
column="NAME"
not-null="true"
unique="true"
/>


<!-- CREATE TABLE KEYSET_WORD ( SET_ID INTEGER NOT NULL,
KEY_ID INTEGER NOT NULL,
PRIMARY KEY ( SET_ID, KEY_ID ),
FOREIGN KEY ( SET_ID ) references KEYSETS(ID),
FOREIGN KEY ( KEY_ID ) references KEYWORDS(ID) );
-->

<set name="keys"
lazy="false"
table="keyset_word">

<key column="set_id"/>
<many-to-many class="hb.Keyword"
column="key_id"
/>

</set>

</class>
</hibernate-mapping>

We will skip the KeySet.java class as it is primarily boilerplate get/set methods and is quite similar to the Keyword.java class. However, please note that there are some changes to our Keyword implementation:

  1. The parameter definition unsaved-value=”-1” has been added to Keyword.hbm.xml to explicitly declare an unsaved id value. And the static final int unsaved_value = -1 has been added to the Keyword java file and is used to set the initial value of the id member value. The two values need to match and should correspond to an invalid id value.
  2. The default constructor and the setId function are now protected. Hibernate sets the id when an object is loaded or saved and everything else should leave it alone.
  3. A toString() method has been added for easy output.

In addition, the build.xml file has been modified to add a DEBUG property that defines the value of the debug attribute of the javac task. The DEBUG property is set to true and Java source is now compiled with debug information.

Now let’s take a look at some code to load, query and create persistent objects. First, don’t forget to add the class to the configuration object. The error message “net.sf.hibernate.QueryException: unexpected token: as [from KeySet as ks …]” can be a bit perplexing until you’ve encountered it a few times.

1
2
3
4
5
6
Configuration cfg = new Configuration();
cfg.addClass( hb.Keyword.class );
cfg.addClass( hb.KeySet.class );

SessionFactory sessions = cfg.buildSessionFactory();
Session session = sessions.openSession();

Second, let’s query for a KeySet object.

1
2
tx = session.beginTransaction();
List existing = session.find( "from KeySet as ks where ks.name='colors'" );

If we find the KeySet we’re looking for, then we print it. Otherwise, we create a KeySet object, populate it with two Keyword objects and persist all three objects.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if ( ! existing.isEmpty() ) {
ks = (KeySet) existing.get(0);
System.out.println( "Read " + ks );
}
else {
Set set = new HashSet();

kw = new Keyword( "red" );
session.save( kw );
set.add( kw );

kw = new Keyword( "blue" );
session.save( kw );
set.add( kw );

ks = new KeySet( "colors" );
ks.setKeys( set );
session.save( ks );

System.out.println( "Create " + ks );
}
tx.commit();

Now, start with a clean database and execute twice:

1
$ ant run
Buildfile: build.xml

init:

compile:

clean-run:

setup-run:

run:
     [java] Create {0:colors|[1:blue, 0:red]}

BUILD SUCCESSFUL
Total time: 14 seconds
$ ant run
Buildfile: build.xml

init:

compile:

clean-run:
   [delete] Deleting: /Projects/Learn/Hibernate/Simple/hibernate.log

setup-run:

run:
     [java] Read {0:colors|[0:red, 1:blue]}

BUILD SUCCESSFUL
Total time: 13 seconds

Success, we’ve stopped adding persistent objects to the database with every execution. And we’ve stored and retrieved objects. You can download the source for this simple Hibernate collection. As a reminder, here is my hibernate software configuration.

Disclaimer: I don’t claim to be an expert on hibernate. Please send comments and corrections.