Berkeley DB Java Edition (JE) and JRuby Integration, Part 2: Using the Direct Persistence Layer (DPL)
In my previous post (Berkeley DB Java Edition in JRuby), I showed an example of calling JE's base API layer and mentioned that Mark and I had been thinking about how to use the DPL from JRuby. Our ideal is to be able to define classes in Ruby, annotate those class definitions with DPL-like annotations, and have the JE DPL store them. There are a number of technical hurdles to overcome before we can do this. For instance, Ruby classes defined in JRuby do not map directly to underlying Java classes; instead they all appear as generic RubyObjects to a Java method. Granted, it would be possible for the DPL to fish out all of the fields from these classes using reflection, but presently it's just not set up to do that (hence the modification to the DPL that I spoke about in my previous blog entry). Furthermore, unlike Java, Ruby allows classes to change on the fly (add/remote new fields and methods) causing more heartburn for the DPL unless we required that only frozen Ruby classes could be stored persistently.
On thinking about this some more, we realized that there may be a way to use the DPL from JRuby, albeit with some compromises. The key to this is that in JRuby, if a Java instance is passed back to the "Ruby side" (e.g. through a return value or by calling the constructor for a Java class), it remains a Java instance, even when passed around in JRuby (and eventually passed back into the "Java side"). So what if we require all persistent classes to be defined (i.e. annotated) on the Java side? That buys us the standard DPL annotations (effectively the DDL), freezes the classes that the DPL sees, and still lets us benefit from the POJO persistence of the DPL. All of this can be done without modification to JE or the DPL using the currently available release. I cooked up a quick example that builds on the standard "Person" example in the DPL doc and included the code below.
require 'java'
module DPL
require 'date'
# Include all the Java and JE classes that we need.
include_class 'java.io.File'
include_class 'com.sleepycat.je.Environment'
include_class 'com.sleepycat.je.EnvironmentConfig'
include_class 'com.sleepycat.persist.EntityCursor'
include_class 'com.sleepycat.persist.EntityIndex'
include_class 'com.sleepycat.persist.EntityStore'
include_class 'com.sleepycat.persist.PrimaryIndex'
include_class 'com.sleepycat.persist.SecondaryIndex'
include_class 'com.sleepycat.persist.StoreConfig'
include_class 'com.sleepycat.persist.model.Entity'
include_class 'com.sleepycat.persist.model.Persistent'
include_class 'com.sleepycat.persist.model.PrimaryKey'
include_class 'com.sleepycat.persist.model.SecondaryKey'
include_class 'com.sleepycat.persist.model.DeleteAction'
include_class 'persist.Person'
include_class 'persist.PersonExample'
# Create a JE Environment and Database. Make them transactional.
envConf = EnvironmentConfig.new()
envConf.setAllowCreate(true)
envConf.setTransactional(true)
f = File.new('/export/home/cwl/work-jruby/JE')
env = Environment.new(f, envConf);
# Open a transactional entity store.
storeConfig = StoreConfig.new();
storeConfig.setAllowCreate(true);
storeConfig.setTransactional(true);
store = EntityStore.new(env, "PersonStore", storeConfig);
class PersonAccessor
attr_accessor :personBySsn, :personByParentSsn
def init(store)
stringClass = java.lang.Class.forName('java.lang.String')
personClass = java.lang.Class.forName('persist.Person')
@personBySsn = store.getPrimaryIndex(stringClass, personClass)
@personByParentSsn =
store.getSecondaryIndex(@personBySsn, stringClass, "parentSsn");
end
end
dao = PersonAccessor.new(store)
dao.init(store)
personBySsn = dao.personBySsn
person = Person.new('Bob Smith', '111-11-1111', nil)
personBySsn.put(person);
person = Person.new('Mary Smith', '333-33-3333', '111-11-1111')
personBySsn.put(person);
person = Person.new('Jack Smith', '222-22-2222', '111-11-1111')
personBySsn.put(person);
# Get Bob by primary key using the primary index.
bob = personBySsn.get("111-11-1111")
puts "Lookup of Bob => #{bob.name}, #{bob.ssn}"
children = dao.personByParentSsn.subIndex(bob.ssn).entities()
puts "\nRetrieving children of Bob"
while (true) do
child = children.next()
break if child == nil
puts "#{child.name}, #{child.ssn}"
end
children.close()
store.close
env.close
end