Playing with JDBM (II)

I'm still doing some research on lightweight persistence for my desktop applications. I've been exploring JDBM in further detail: I'm able to do m:n relationships without too much hassle.

Since I don't like dependencies (not even with JDBM ;-)), I've built a tiny abstraction layer (sort of "DAOs for the desktop") that allows me to plug different persistence mechanisms. Just in case JDBM doesn't fit my needs.

Why I need M:N relationships

I'm not satisified at all with the way Mozilla (Internet Explorer) handles my bookmarks. The user interface is just too dumb. I'd like to store my bookmarks in some other different way. I'd like to assign a single bookmark to different keywords (tags). And, on the other side, I need that tags can be applied to different bookmarks. And I want to do that with a Swing application that may run locally without a network connection. An online, web-based application doesn't fit my needs either, because I don't have (or I'm not allowed to have) a permanent network connection.

So I have a M:N relationship between my list of bookmarks and my list of tags. That's why I need M:N relationships between these two entities.

And that's the reason why a simple persistence mechanism (such as XML storage or J2SE preferences) doesn't fit my needs. I need to be able to quickly retrieve the list of bookmarks for a given tag, or the list of tags for a given bookmark. Those operations need to be fast. Should be done by an offline application. Swing based (so I can run it from Windows and Linux and Solaris). And I don't want to bundle a whole RDBMS just for that. That's why I'm investigating JDBM (and several other lightweight persistence mechanisms, preferably object-oriented.

DAOs for the desktop

As you may know, the Sun Java Center released the Core J2EE Design Patterns a few years back. Among these patterns we had the Data Access Object pattern.

And I think the pattern fits nicely my desktop application, with some minor changes.

My bookmark manager application will have to CRUD bookmarks and CRUD tags, and must also be able to assign a tag to a bookmark (and the other way round) and deattach a tag from a bookmark (and the other way round) and search all tags for a given bookmark (and the other way round).

So I've defined all those operations in a Java interface. Something like this:

public interface BookmarkDAO
{
   public void insert( Bookmark aBookmark ) throws ...;
   public Iterator<bookmark> findBookmarksByTag( String aTag ) throws ...;
   public Iterator<tag> findTagsByBookmark( URI aBookmarkURI ) throws ...;
   ...
}

And I've delegated the actual implementation to what I call a DAO. So I have a JDBM DAO to insert/update/delete/select bookmarks, and to insert/update/delete/select tags, and for different other operations that involve bookmarks and tags.

public class JDBMBookmarkDAO
  implements BookmarkDAO
{
  private RecordManager recordManager;
  private BTree bookmarks;
  private BTree tags;
  private BTree bookmarkTags;
  private BTree tagBookmarks;
  public void insert( Bookmark aBookmark ) throws ...
  {
    bookmarks.insert( aBookmark.getURI(), aBookmark, true );
    ...
  }
  ...
}

So if I find, later on, that JDBM doesn't fit my needs I may be able to change the DAO implementation to use, say, MySQL or Postgress or Derby, and reduce the impact of the change to just that implementation. So this DAO pattern allows me to change the underlying implementation without having to change a big part of my code. It's a small abstraction layer that absorbs the impacts of change of the persistence mechanism. I think that's a good approach when investigating things.

Resource handling for DAOs and DAO Factories

So, how am I expected to handle "JDBM connections"? Since I use JDBM I'll need to keep a list of B-Trees (that index bookmarks and tags, for instance). But, shall I need JDBM B-Trees "connection pools"?

One of the main differences between a J2EE application and a desktop application is that J2EE applications need to share resources between many different users. That's why you have to handle resources with care: they must be shared between all those users, you should use connection pools for that. So you open and close connections as you go, trying to use them during short periods of time, so that other users may use them meanwhile.

Desktop applications, on the other hand, don't share resources. A lightweight persistence mechanism such as JDBM is used by just one user (that may spawn different threads, though). So there's no need to open and close connections all the time. There's no need to use connection pools. At least not for JDBM.

What I've done is to make a DAO Factory that instantiates (anc caches) a single implementation of each DAO. So this DAO Factory will have just a single implementation of my JDBM DAO (and a single implementation of my MYSQL DAO, if it ever exists). So I write:

BookmarkDAO myBookmarkDAO = DAOFactory.getBookmarkDAO( DAOFactory.JDBM_TYPE );

Since the instance of BookmarkDAO is cached, I don't need to open and close the JDBM database whenever a query is to be done. That's what I wanted.

If I ever want to implement all this stuff with, say, MySQL or Derby, I may want to use a connection pool. Then the actual implementation of the BookmarkDAO ( MySQLBookmarkDAO, for instance ) may use a connection pool to handle JDBC connections, and open and close connections for each query.

So by using the J2EE DAO Pattern I'm still able to handle both behaviours: my DAOs are responsible for choosing an efficient (an appropriate) way of managing persistence resources. JDBM and MySQL can easily coexist with this. Good.

M:N relationships with JDBM (first try, please help!)

So let's go straight to the point. I was talking about M:N relationships with JDBM. Let me explain how I'm doing them and, if you have any comments, then please let me know if I'm doing something wrong (or if there's room for improvement).

  • Bookmarks are stored by URI. I use the URI as a primary key for bookmarks (I had to provide a custom URIComparator to be able to sort the B-Tree). So I have a "bookmarks" BTree that indexes by bookmarks by URI. Bookmarks have other fields, such as the number of visits and the last visit time, a description and some other stuff. If I know the URI I can gain access to all the rest of attributes by knowing the bookmark's URI.

So the Bookmarks B-Tree looks roughly something like:

KeyValue
http://www.antonioshome.netBookmark Java Bean containing info for http://www.antonioshome.net
http://www.java.netBookmark Java Bean containing info for http://www.java.net
  • Tags are stored by the tag itself. Tags are just Strings. So I have a "tags" BTree that indexes the tags.

So the Tags B-Tree looks like this:

KeyValue
JavaEmpty!
SwingEmpty!

But, how can I associate a tag with a bookmark when a M:N relationship exists?

  • bookmarks-tag : I've solved the problem by using another BTree, which I call "bookmarks-tag" that uses an URI and a tag as the primary key. So if I want to associate the URI http://desktop.java.net with the tags "Java" and "Swing" I'll add two entries to this BTree. The first entry will be something like "http://desktop.java.net|Java" and the second entry will be something like "http://desktop.java.net|Swing". Looking like this:
KeyValue
http://desktop.java.net|JavaEmpty!
http://desktop.java.net|SwingEmpty!

This allows me to easily know which tags are associated with the URL http://desktop.java.net: I just have to browse the "bookmarks-tag" (using a JDBM's TupleBrowser) looking for entries bigger than "http://desktop.java.net" and I'm done. I'll extract the tag from the index itself and I'm done. Removing a URI from a tag is just as easy: I just have to remove the entry matching the URI with the tag and I'm done.

  • tag-bookmarks Another BTree (tag-bookmarks) keeps the reverse association between a single tag and different URLs. So it may look like this:
KeyValue
Java|http://desktop.java.netEmpty!
Java|http://java.sun.comEmpty!
Java|http://www.antonioshome.netEmpty!
Java|http://www.java.comEmpty!
Java|http://www.java.netEmpty!
Swing|http://desktop.java.netEmpty!

Of course when removing a bookmark (from the bookmark BTree) I'll have to remove all entries from the "bookmarks-tag" and "tag-bookmarks" BTrees. This is what many commercial RDBMS call "cascade delete": these RDBMS just look for foreign keys and delete entries from those tables automatically. Since I don't want to bundle a full RDBMS with my desktop application I'll have to do that myself. (But, well, this is fun for me!)

The next step: bookmarker

I'm planning to release all this stuff under an open source license a little later on, as soon as there's something useable. The "bookmarker" project will be able to handle a list of bookmarks and index these by tag (several tags ;-)) . While I'm building this stuff I'd appreciate any ideas on features this should have. And feedback about this DAO for the desktop would be welcome too.

Happy JDBM-ing meanwhile, Antonio

P.S.: I've been suffering comment spam for a while. I've just installed some software to (try to) get rid of it. Let's see how well it works!!

blog comments powered by Disqus