We talked about using databases with RIFE in Chapter 7, Adding database support. We added a simple database layer to the Friends database application, but we really just skimmed the surface there so let's revisit databases again.
In the Friends database, the elements all used a hard coded query manager, FriendManager, by explicitly creating it:
public void processElement() { Template template = getHtmlTemplate("display"); FriendManager manager = new FriendManager(); ... }
This works fine for simple cases, such as when you only use a small subset of database features that work exactly the same on a wide range of servers. It also would be fine when only one database server needs to be supported. Unfortunately, both those cases are quite rare.
The only way to solve this is by writing a separate database driver for each server. A good pattern that helps doing this cleanly and efficiently is to split the code in two parts: one that is common to all databases and one that isn't. The recommended way to do this with RIFE is to specify an interface for the manager, with methods for all database operations, such as install
, remove
, add
, display
like in our example.
Then we write an abstract subclass of DBQueryManager, with methods that matches the interface, but prefixed with an underscore, like _install
, _remove
, _add
, _display
and so on. In those methods, we put the parts that are independent of which database is used, for example checking that parameters are valid, executing statements and processing the results.
After doing this split, writing a driver for a specific database becomes a rather simple task. A driver is just a subclass of the abstract manager class mentioned above, and handles database dependent code like building queries and handling exceptions. This is done in the implementation of the interface, that is the methods install
, remove
and add
, etc. A simple example might look like the following:
Example 10.1. Datasource dependent implementation
public void add(Friend friend) throws TextManagerException { // here we can perform database specific tasks ... // then we let the manager superclass perform the database // independent task try { _add(sAdd, friend); } catch (FriendManagerException e) { // ... handle the exception, which often is database dependent } }
Finally, we'll take a look at the corresponding method in the shared part. The actual implementation is not important here, but notice the general structure, with no database dependent code:
Example 10.2. Datasource independent implementation
protected void _add(Select addQuery, Friend friend) throws TextManagerException { try { DbPreparedStatement ps_add = getPreparedStatement(addQuery); try { ps_add.setBean(friend); ps_add.executeQuery(); } finally { ps_add.close(); } } catch (DatabaseException e) { throw new AddErrorException(friend, e); } }