com.javaranch.db
Class Jenny
java.lang.Object
|
+--com.javaranch.db.Jenny
- public class Jenny
- extends java.lang.Object
Jenny the db code generator.
This program reads a database and generates java source which provides strongly typed access to the database.
In my opinion, there are two major problems with JDBC:
1) There are slight differences in SQL, drivers, app servers, database access, etc. that make it clear that a
facade is needed. The facade would provide access to the database in plain java (not SQL). All of the
implementation specific code is concentrated in one small area instead of spread out all over your application.
The DBFacade class fixes this problem.
2) For any database change, it takes a great deal of discipline to find all of the table names and column
names used throughout your code that now functions differently. Even the slightest lack of discipline
results in a bug that might not be discovered until run time. Strong typing eliminates this problem.
This program (Jenny) provides the strong type checking.
For each table and view in a given database, Jenny will create a java source class file. Each of these classes will provide
a collection of general purpose methods for working with the table/view. They will also provide a list of all the column
names, the table/view name and an inner class called Row that can be used to manipulate a single row in the table.
Many tables have a primary key that is a non-nullable integer called "tablenameID" or sometimes "ID". If Jenny
finds this, she will add extra methods to the generated source for working with the primary key.
Some of my goals:
- Simplicity: Accessing the database needs to be as simple as possible. I decided that this meant using
static methods for the table level methods. All connections, statements, result sets and all other objects
that need closing are generally managed for you so you don't have to worry about it - thus eliminating about
70% of the code required for typical JDBC access.
- Unit Testing: I want to auto-generate a Mock class for every table class to facilitate unit testing.
This means that I need to have something I can override. Since static methods cannot be overridden, I need an
inner class that the static methods use that I can override for my mock classes. This also means that I need
to hide all of the Row constructors so a mock object can be returned. This allows unit testing without having
to provide SQL in the unit tests or to have a database server running during the testing.
- Flexibility: I want to be able to allow alternate connections, or to make several database calls with
one connection, so every database access method allows me to pass in a connection object. I also want to
allow multiple ways to get to the same database; support multiple databases on one database server; support
the use of multiple servers being used from one application.
- Functional Testing: I want to allow for functional testing (sometimes called integration testing -
kinda like unit testing, but with more than one class and sometimes the actual database) so I allow all
classes to replace the connection source at the facade level.
- Clear Division of Labor: I want to work with the idea that the database is managed by a DBA and business
logic is managed by software engineers. So fancy database stuff is done by the DBA in the database (i.e. joins
are handled within views by the DBA). In most big software shops, the software engineer will be accessing
data in an existing database that is managed by a DBA that has far more experience with the database than the
engineer. Anything that cannot be done through the facade probably needs to be done on the database side.
- Complexity Reduction: I want to avoid having complex relationships defined in an XML
document or embedded in the code. Good engineering is making complicated business logic look SIMPLE!
My experiences with object databases, object to relational mapping, CMP and similar tools is that they promise
simplicity, but when you get right down to it, it becomes horribly complicated!
- Self Discipline Not Required! Many solutions similar to Jenny require human engineers to have the
discipline to make sure that any changes in one place are reflected in other places. If your table name in
your database does not match the table name in your java code, you have a problem! With this solution, you
don't need that discipline. Therefore you have more reliable code.
Each generated class will provide some basic methods such as these:
Row getRow()
Row getRow( String column , String searchText )
DBResults search( String column , String searchText , String[] dataColumns )
Row[] getRow( String column , String searchText )
Row[] getAllRows()
DBResults getAllRows( String[] dataColumns )
void update( String column , String searchText , Map data )
void delete( String column , String searchText )
void insert( Map data )
If an ID field is found, some methods like these will also be added:
Row getRow( int id )
void delete( int id )
Every class will have an inner class called Row that can provide strong type checking for every field (column) as
well as methods like:
void update()
void delete()
void insert()
The strong type checking for Row is provided by getters and setters. Suppose you have a table called Employee. Jenny
will generate a class called EmployeeTable that will contain a Row class that might have the following methods:
int getEmployeeID()
void setEmployeeID( int employeeID )
String getLastName()
void setLastName( String lastName )
Here's a sample of a business logic method that uses a Jenny generated class.
// pass in an employee ID and get back the number of tax exemptions that the employee claims
private int getEmployeeExemptions( int employeeID )
{
return EmployeeTable.getRow( employeeID ).getExemptions();
}
This same code using plain JDBC could be 10 to 40 lines long depending on how it would be implemented. You would need
to get a connection, create a statement, build your sql string, execute your statement, wade through the result set,
and each of these things need try/catch/finally blocks! Don't forget to close your connection, statement and result
set!
Using Jenny
From the command line:
Make sure your classpath includes jr.jar and then at the command prompt type:
java com.javaranch.db.Jenny db.properties
Where db.properties is a properties file that describes how Jenny should find your database. There is a sample
properties file complete with in-line documentation inside jr.jar at /src/com/javaranch/db/jenny.properties.
- - - - - - - - - - - - - - - - -
Copyright (c) 2003 Paul Wheaton
You are welcome to do whatever you want to with this source file provided
that you maintain this comment fragment (between the dashed lines).
Modify it, change the package name, change the class name ... personal or business
use ... sell it, share it ... add a copyright for the portions you add ...
My goal in giving this away and maintaining the copyright is to hopefully direct
developers back to JavaRanch.
The original source can be found at JavaRanch
- - - - - - - - - - - - - - - - -
- Author:
- Paul Wheaton, Marilyn de Queiroz
Constructor Summary |
Jenny()
|
Method Summary |
com.javaranch.db.Jenny.TableProcessor |
getTableProcessor(java.sql.DatabaseMetaData metaData,
java.lang.String tableName,
java.lang.String suffix,
java.lang.String idColumnName)
|
static void |
main(java.lang.String[] args)
|
Methods inherited from class java.lang.Object |
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Jenny
public Jenny()
throws java.lang.Exception
getTableProcessor
public com.javaranch.db.Jenny.TableProcessor getTableProcessor(java.sql.DatabaseMetaData metaData,
java.lang.String tableName,
java.lang.String suffix,
java.lang.String idColumnName)
throws java.lang.Exception
java.lang.Exception
main
public static void main(java.lang.String[] args)
throws java.lang.Exception
java.lang.Exception
Copyright ©2004 Paul Wheaton All Rights Reserved