Hibernate Prototyping with the BeanShell

Just playing around is one of my favorite ways of becoming familiar with software. Let’s see how we can use the BeanShell to prototype Hibernate. I’m partial towards the BeanShell because it’s embedded in my Java Development Environment for Emacs. But if you’re so inclined, then Jython will probably do as well. Just in case there is a lurking configuration gotcha, here’s my current Java software configuration.

I must confess that I wasn’t planning on using the BeanShell. I was planning on adding a simple interpreter with java.io.StreamTokenizer to allow interactive manipulation of Keyword and KeySet objects. And then I realized that by setting the fork attribute of the java task to true separated my application from the console input. And setting it to false terminates the application with an java.lang.ExceptionInInitializerError. The BeanShell was my ticket out.

BeanShell support requires just a handfull of minor changes to our build file. First, the BSH property is added to reference the BeanShell jar and it is prepended to classpath.run to form classpath.bsh. Second, a new bsh target is added in parallel to the run target — note that we use class bsh.Console rather than bsh.Interpreter because we still lose access to console input because of the JVM fork. And Finally, the dist target was modified to include a bsh sub-directory.

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
37
38
39
40
41
42
43
44
45
46
47
48
49
<project default="all">
<!-- ... -->
<property name="LOG4J" location="/Projects/Java/Lib/log4j.jar" />
<property name="BSH" location="/Projects/Java/Lib/bsh.jar" />
<!-- ... -->
<path id="classpath.bsh">
<pathelement location="${BSH}" />
<path refid="classpath.run" />
</path>
<!-- ... -->
<target name="setup-run" depends="clean-run">
<condition property="LOG4J_OUT" value="log4j-to-stdout.properties">
<istrue value="${TALK}"/>
</condition>
<condition property="LOG4J_OUT" value="log4j-to-file.properties">
<isfalse value="${TALK}"/>
</condition>
</target>
<target name="run" depends="compile,setup-run">
<java classname="Main"
fork="true">
<classpath refid="classpath.run" />
<sysproperty key="log4j.configuration" value="${LOG4J_OUT}" />
</java>
</target>
<target name="bsh" depends="compile,setup-run">
<java classname="bsh.Console"
fork="true">
<classpath refid="classpath.bsh" />
<sysproperty key="log4j.configuration" value="${LOG4J_OUT}" />
</java>
</target>
<target name="clean-run">
<delete verbose="${TALK}" file="hibernate.log" />
</target>
<!-- ... -->
<target name="dist">
<zip destfile="distrib.zip"
basedir="."
includes="build.xml,src/**,cfg/**,bsh/**"
excludes="src/prj.el"
/>
</target>
<!-- ... -->
</project>

Next, we’ll create a Scripted Object to simplify our interaction with hibernate. The Hibernate method returns an object that provides easy access to the most common functionality: open, save, find, delete and close.

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import net.sf.hibernate.cfg.Configuration;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Session;
import net.sf.hibernate.Transaction;
import net.sf.hibernate.MappingException;
import net.sf.hibernate.HibernateException;
Hibernate() {
Configuration cfg = new Configuration();
cfg.addClass( hb.Keyword.class );
cfg.addClass( hb.KeySet.class );
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
void open() {
session = factory.openSession();
}
void close() {
session = null;
}
void save( Object o ) {
session.saveOrUpdate( o );
}
void delete( Object o ) {
session.delete( o );
}
Transaction transaction() {
return session.beginTransaction();
}
Object findByName( String className, String objName )
{
java.util.List existing;
if ( objName == null || objName.length()==0 ) {
existing = session.find( "from " + className );
}
else {
existing = session.find( "from " + className +
" as o where o.name='" + objName + "'" );
}
if ( existing.isEmpty() ) {
return null;
}
else {
return existing.get(0);
}
}
return this;
}

Finally, two methods were added each of the KeySet and Keyword classes. The KeySet class gained an add and a remove method that simply provide access to the corresponding methods of the java.util.Set member variable. And the Keyword class gained methods for hashCode and equals — this insures that Keyword objects are equivalent when their String member variables are equivalent.

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
37
38
public class Keyword {
//...
/**
* Hashcode generation
*/
public int hashCode()
{
return ( this.name == null ) ? 0 : this.name.hashCode();
}
/**
* Equality test
* @param other The Object to be checked for equality
*/
public boolean equals(Object other) {
boolean equality = false;
if ( this.getClass() == other.getClass() ) {
Keyword otherWord = (Keyword) other;
if ( this.id != unset_value && this.id == otherWord.id ) {
equality = true;
}
else if ( this.name.equals( otherWord.name) ) {
equality = true;
if ( this.id == unset_value ) {
this.id = otherWord.id;
}
else if ( otherWord.id == unset_value ) {
otherWord.id = this.id;
}
}
}
return equality;
}
// ...
}

And now we’re ready to play with the BeanShell (starting with an empty database). By the way, if you’re anything like me, then the number one thing to remember is use print rather than println.

BeanShell Desktop

We can use the BeanShell to see that we cannot use Hibernate for immutable objects need to be careful when comparing objects from different sessions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1.3b2 - by Pat Niemeyer (pat@pat.net)
bsh % source("bsh/Hibernate.bsh");
bsh % import hb.*;
bsh % hb = Hibernate();
bsh % x = hb.findByName("Keyword","this");
bsh % y = hb.findByName("Keyword","this");
bsh % hb.close();
bsh % hb.open();
bsh % z = hb.findByName("Keyword","this");
bsh % hb.close();
bsh % print( (x==y) ? "equal" : "not-equal" );
equal
bsh % print( (x==z) ? "equal" : "not-equal" );
not-equal
bsh %

We also see that Hibernate performs a shallow save rather than a deep save:

21 May 2004: This behavior is controlled by the cascade attribute.

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
37
38
39
40
41
42
43
44
45
46
47
48
1.3b2 - by Pat Niemeyer (pat@pat.net)
bsh % source("bsh/Hibernate.bsh");
bsh % import hb.*;
bsh % hb = Hibernate();
bsh % tx = hb.transaction();
bsh % a = new Keyword("red");
bsh % s = new KeySet("colors");
bsh % s.add(a);
bsh % print(s);
{-1:colors|[-1:red]}
bsh % hb.save(s);
bsh % tx.commit();
// Error: // Uncaught Exception: Method Invocation tx.commit :
at Line: 1 : in file: <unknown file>
: tx .commit ( )
Target exception: net.sf.hibernate.TransientObjectException:
object references an unsaved transient instance
- save the transient instance before flushing
net.sf.hibernate.TransientObjectException:
object references an unsaved transient instance
- save the transient instance before flushing
at net.sf.hibernate.impl.SessionImpl.getEntityIdentifierIfNotUnsaved (SessionImpl.java:2357)
at net.sf.hibernate.type.EntityType.getIdentifier(EntityType.java:55)
at net.sf.hibernate.type.ManyToOneType.nullSafeSet(ManyToOneType.java:33)
at net.sf.hibernate.collection.CollectionPersister.writeElement(CollectionPersister.java:458)
at net.sf.hibernate.collection.Set.writeTo(Set.java:226)
at net.sf.hibernate.collection.CollectionPersister.recreate(CollectionPersister.java:692)
at net.sf.hibernate.impl.ScheduledCollectionRecreate.execute(ScheduledCollectionRecreate.java:23)
at net.sf.hibernate.impl.SessionImpl.executeAll(SessionImpl.java:2101)
at net.sf.hibernate.impl.SessionImpl.execute(SessionImpl.java:2077)
at net.sf.hibernate.impl.SessionImpl.flush(SessionImpl.java:2017)
at net.sf.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:57)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at bsh.Reflect.invokeOnMethod(Unknown Source)
at bsh.Reflect.invokeObjectMethod(Unknown Source)
at bsh.Name.invokeMethod(Unknown Source)
at bsh.BSHMethodInvocation.eval(Unknown Source)
at bsh.BSHPrimaryExpression.eval(Unknown Source)
at bsh.BSHPrimaryExpression.eval(Unknown Source)
at bsh.Interpreter.run(Unknown Source)
at java.lang.Thread.run(Thread.java:554)
bsh % tx.rollback();
bsh % hb.close();

We’re done for now. You’re ready to download the source to prototype Hibernate with the BeanShell and play.

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