This sunday, I was looking for some geek-ish stuff to do before heading back to a long and difficult week in academia where I know that I am definitely not going to write a single line of code (LaTeX does not count as code, right!). I also wanted a good excuse to test a Wordpress plugin for highlighting code that I had just installed…
I decided to play with Oracle Berkeley DB Java Edition. For those of you who don’t know what a Berkeley DB is, then think about a giant hash map which features concurrent isolation, long-lived persistence and transactions. If you are already using a relationnal database management system and all you do with it is loading/storing through an object-relationnal mapper, then I strongly urge you to have a look at Berkeley DB, especially as it features what they call Direct Persistence Layer (DPL). Briefly, you can decorate your Java classes with Java 5 annotations and the DPL will handle the rest.
To make it short: Berkeley DB + DPL is equivalent to using a relationnal database system (think PostgreSQL) + an ORM (think Hibernate), but without a network and query layer. So if your application uses SQL only to map with Java objects, this type of approach may be way simpler!
The hack that I have designed is as stupid as it is useless. Really. (remember I was geeking)
I have designed a contact entity Java class which surprisingly stores a name, email and website.
@Entity public class Contact { @PrimaryKey private String name; @SecondaryKey(relate = ONE_TO_ONE) private String email; @SecondaryKey(relate = MANY_TO_ONE) private String website; public Contact(String name, String email, String website) { this.name = name; this.email = email; this.website = website; } /* (...) boilerplate getters, setters, toString, equals and the likes. */ }
The class is decorated with @Entity so that the DPL knows it should handle it. Then we need a primary key on the name: like in traditionnal RDBMS, it identifies the entity. The case of secondary keys is actually funnier. In RDBMS terminology, they are used for foreign keys, i.e., to reference a primary key in another table and maintain referential integrity. With Berkeley DB, they can be used to do that by refering to an external entity in the annotation, but in this sample it doesn’t. So what are they for then? Simple: they are just plain indexes. The rule is the following: if you need to lookup objects by more than their primary keys, then define secondary keys.
As for the cardinalities defined through the relate annotation attributes, they mean that a given email can be used in only entity, and that a given website can be reused in several other entities.
We now create a quick database helper class:
public class MyDatabase { private Environment env; public MyDatabase() throws DatabaseException { EnvironmentConfig cfg = new EnvironmentConfig(); cfg.setAllowCreate(true); cfg.setTransactional(true); this.env = new Environment(new File("db"), cfg); } public void close() throws DatabaseException { env.close(); } public Environment getEnv() { return env; } }
Nothing impressive here, Berkeley DB Java edition is quite simple to use.
We now provide an entity store helper:
public class ContactEntityStore { private MyDatabase db; private EntityStore store; private PrimaryIndex<string,> contactByNameIndex; private SecondaryIndex<string,> contactsByEmailIndex; private SecondaryIndex<string,> contactsByWebsiteIndex; @Inject public ContactEntityStore(MyDatabase db) throws DatabaseException { this.db = db; StoreConfig cfg = new StoreConfig(); cfg.setAllowCreate(true); cfg.setTransactional(true); store = new EntityStore(this.db.getEnv(), "ContactStore", cfg); contactByNameIndex = store.getPrimaryIndex(String.class, Contact.class); contactsByEmailIndex = store.getSecondaryIndex(contactByNameIndex, String.class, "email"); contactsByWebsiteIndex = store.getSecondaryIndex(contactByNameIndex, String.class, "website"); } public void close() throws DatabaseException { store.close(); } public PrimaryIndex<string,> getContactByNameIndex() { return contactByNameIndex; } public SecondaryIndex<string,> getContactsByEmailIndex() { return contactsByEmailIndex; } public SecondaryIndex<string,> getContactsByWebsiteIndex() { return contactsByWebsiteIndex; } } </string,></string,></string,></string,></string,></string,>
Client code can use this class to get the indexes from which entities can be retrieved, updated, added and deleted. Every such operation can be done in an auto-commit fashion, or a transaction manager can be used. Of course transactions can be nested if need be.
I was a bit lazzy and I wanted a completely wrong reason to play with Google Guice, here is the module that will be used by the injector. It could have been completely empty, but I decided to enforce a singleton database object.
public class GuiceModule extends AbstractModule { protected void configure() { try { bind(MyDatabase.class).toInstance(new MyDatabase()); } catch (DatabaseException e) { e.printStackTrace(); } } }
An finally here is the class to play with it all:
public class GuicedAssemblyRunner { @Inject private MyDatabase db; @Inject private ContactEntityStore entityStore; public void run() { try { Contact me = new Contact("Somebody", "somebody@gmail.com", "http://jpz-log.info/"); entityStore.getContactByNameIndex().putNoOverwrite(me); me = new Contact("Me", "me@home.mail.me", "http://www.izforge.com/"); entityStore.getContactByNameIndex().putNoOverwrite(me); System.out.println(entityStore.getContactsByEmailIndex().get("somebody@gmail.com")); entityStore.close(); db.close(); } catch (DatabaseException e) { e.printStackTrace(); } } public static void main(String[] args) { Injector injector = Guice.createInjector(new GuiceModule()); GuicedAssemblyRunner runner = injector.getInstance(GuicedAssemblyRunner.class); runner.run(); } }
As you can see there is no boring boilerplate SQL and in a real application, the code would not be much bigger and yet you could have 100 different applications (standalone or webapps) that would be using the database for reading and writing at the same time.
Along the way I had a lot of fun discovery DPL which I didn’t knew about. Quite frankly I will look at it in the future when I know that I won’t really have a need for SQL queries other than for ORM. The example here does not show the power of Guice (I like it very much!), so I can only advice to have a look at some references:
- the user guide
- the Binder interface Javadoc which contains a hell lot of useful examples to understand the Guice capabilities.