Using plain JDBC to access databases involves in a lot repetitive code. On serveral project I worked on I’ve seen some “frameworks” that often required more code than using jdbc direclty. I’ve always thought that a good framework/library is that one that requires to you to write the less code as possible: only the necessary code.
Example, to execute an sql select statement you should specify
- a sql statement
- optionally, the parameters for the query.
example: “select * from mytable where id = ?” - an algorithm to translate resulting records to object(s)
Nothing more.
Instead for insert/update/delete you don’t need to specify the algorithm to read the results, as you want just to know how many records have been affected by the sql statement.
So a convenient interface for a library that access the db would be something like
// for insert/update/delete sql statements
protected int update(String statement, Object[] params)
throws SQLException;
// for select sql statements
protected Object select(String statement, Object[] params,
ResultSetTranslator translator) throws SQLException;
You may want to have also some mechanism to handle transaction. I have mine, but several solutions could be implemented; I leave it to your fantasy.
The resultset translator should have an interface method that takes a java.sql.ResultSet and translates it to a business object or a collection of them:
public interface ResultSetTranslator {
public Object translate(ResultSet rs)
throws SQLException;
}
So, a query that extracts cats by name from a db will look like:
public List findCatByName(String name) throws SQLException {
String sql = "select * from CATS " +
"where NAME like ? order by NAME";
Object[] params = new Object[] { "%" + name + "%" };
ResultSetTranslator catsTrl = new ResultSetTranslator() {
public Object translate(ResultSet rs)
throws SQLException {
List results = new ArrayList();
while (rs.next()) {
Cat cat = new Cat();
cat.setName(rs.getString("NAME"));
cat.setColor(rs.getString("COLOR"));
cat.setBirthDate(rs.getDate("BIRTH_DATE"));
results.add(cat);
}
return results;
}
};
return (List) select(sql, params, catsTrl);
}
You see that the most of the code is written to translate the ResultSet to the desired object(s), in this case a List. But a nice thing is that translator objects can be reused quite frequently. Could be a good idea to declare them as instance variable in data access classes. I bet that someone is already thinking about writing a dynamic translator from xml configuration file (lol), in this case take a look at hibernate. Also the logic to iterate over the ResultSet can be abstracted in an AbstractLoopingTranslator or creating a Loop decorator.
But let’s continue seeing how an update could look like:
public void updateCat(Cat cat) throws SQLException {
String sql = "update CATS " +
"set COLOR = ?, BIRTH_DATE = ? where NAME = ?";
int updated = update(sql, new Object[] { cat.getColor(),
cat.getBirthDate(),
cat.getName() });
}
All the code that handles jdbc classes disappeared: in the previous example you don’s see code for opening and closing Connections, Resultsets, and PreparedStatements; no code that sets parameters into the query, etc. At least the 50% of the code is spared, I believe, and the code written (resultset translator) could be probably be reused.
Ok, seems nice. Where’s the code that implements select() and update methods()? I use a base class for data access classes. Here you find an outline version (you can download the complete source here with explaining javadocs):
protected static interface ResultSetTranslator {
public Object translate(ResultSet rs) throws SQLException;
}
protected Object select(String statement, Object[] params,
ResultSetTranslator translator) throws SQLException {
if (translator == null) {
translator = new ResultSetTranslator() {
public Object translate(ResultSet rs)
throws SQLException {
return rs.next() ? Boolean.TRUE : Boolean.FALSE;
}
};
}
return execute(statement, params, translator);
}
protected int update(String statement, Object[] params)
throws SQLException {
Integer count = (Integer)execute(statement, params, null);
return count.intValue();
}
private Object execute(
String statement, Object[] params,
ResultSetTranslator translator) throws SQLException {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
log(statement, params);
conn = open();
pstmt = conn.prepareStatement(statement);
if (params != null)
for (int i = 0; i < params.length; i++)
if(params[i] == null) // fix oracle bug with nulls
pstmt.setNull(i+1, Types.VARCHAR);
else
pstmt.setObject(i+1, params[i]);
if (translator != null) {
rs = pstmt.executeQuery();
return translator.translate(rs);
} else {
int updatedCount = pstmt.executeUpdate();
return new Integer(updatedCount);
}
} finally {
close(conn, pstmt, rs);
}
}
// logs the query with exploded params
private void log(String statement, Object[] params) {
if (logger.isDebugEnabled()) {
if (params != null && params.length > 0) {
int estimatedLength = statement.length() + (statement.length() >> 2);
StringBuffer buffer = new StringBuffer(estimatedLength);
int currentParam = 0;
for (int i = 0; i < statement.length(); i++) {
char currentChar = statement.charAt(i);
if (currentChar != '?'||
currentParam >= params.length)
buffer.append(currentChar);
else {
Object param = params[currentParam++];
if (param == null )
buffer.append("null");
else if (param instanceof Number)
buffer.append(param);
else
buffer.append("'" + param + "'");
}
}
statement = buffer.toString();
}
logger.debug("executing sql statement: " + statement);
}
}
In the source, you find that select() and update() are front interface for the execute() that actually does the job.
I leaved the log method to show that having a central point for query execution could have also some benefit; logging in this case. There are also other utility methods I made using this approach, one of those is for supporting paginated queries. In this case I needed a way to decorate my queries to support server side pagination for my application (offset, fetch size and totalcount)
protected List fetchList(
String statement, Object[] params,
ResultSetTranslator translator,
Integer offset, Integer fetchSize) throws SQLException {
boolean paged = offset != null && fetchSize != null;
if (! paged) return (List) execute(statement, params, translator);
int totalSize = count(statement, params);
String newStatement =
"select * from (" +
"select rownum x_rownum, x_query.* " +
"from (" + statement + ") x_query) " +
"where " +
"x_rownum between ? and ?";
Object[] newParams = new Object[params.length + 2];
System.arraycopy(params, 0, newParams, 0, params.length);
Integer newOffset = new Integer(offset.intValue() + 1); // Oracle rownum is 1 based
newParams[params.length] = newOffset;
newParams[params.length + 1] = new Integer(offset.intValue() + fetchSize.intValue());
List results = (List) execute(newStatement, newParams, translator);
return PagedList.Instance.create(results, totalSize);
}
protected int count(String statement, Object[] params) throws SQLException {
statement = "select count(*) from (" + statement + ")";
BigDecimal count = (BigDecimal) select(statement, params, new ResultSetTranslator() {
public Object translate(ResultSet rs) throws SQLException {
rs.next();
return rs.getBigDecimal(1);
}
});
return count.intValue();
}
It works wrapping and decorating sql statement.
7 Responses to “Trimming JDBC”
Leave a Reply
Search
Calendar
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
| « Sep | Nov » | |||||
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 | 31 | |||||
Archives
Categories
Tag Cloud
Android API Bash Bios CSS Development Eclipse Encription Error Handling Font Git Google GWT Hardware How-To HTML Installation iPod Java JavaScript Karmic Linux Lucks MacBook Open Source Opinion OSX Pitfalls Pkg Practices Python Resume Security Software Suspend TDD Testing Tools Top Down Tricks Ubuntu Uninstall Wakup On Lan Web Workaround
WP Cumulus Flash tag cloud by Roy Tanck and Luke Morton requires Flash Player 9 or better.
Blog License
Blogs I like
Books on the desk
Friends' Blogs
- Antonio Terreno & Valter Bernardini
- Bruno Bossola
- Daniele Galluccio
- Domenico Ventura
- Ed Schepis
- Fabrizio Gianneschi
- Luca Grulla
- Luigi Zanderighi
- Marcello Teodori
- Mida Boghetich
- Muralidharan Chandrasekaran
- Piero Ricca
- Renzo Borgatti
- Simone Bordet
- Simone Bruno
- Uberto Barbini
- Valvolog
- Webtide blogs (Greg Wilkins & Jan Bartel)
Links





















What does your new JDBC framework provide that the Spring JDBC DAO framework doesn’t? We use it very successfully. …and it handles transactions transparently using AOP.
Okay, it is a project plug, but… You might want to look at jDBI. It does the result set mapping, has decent tx conveniences, and has upcoming support for some serious convenience magic :-)
-Bran
> What does your new JDBC framework provide
> that the Spring JDBC DAO framework doesn’t?
I’ll give a look at Spring JDBC DAO, thankyou for your hint
This codes are just some utility methods I wrote for current project: I would have choosen hibernate if I could, but the buyer asked for plain jdbc access for this app and I wrote this.
I’m working in a context where recent technologies are refused for several (ridiculous) reasons. This is the music here, let’s dance.
Thanks Brian, jDBI sounds good!
Unluckily in current project buyer mandates that we must use jdk 1.3 :-(
(definitely: I’m not working in a vanguard company)
How many higher level APIs around JDBC! How time wasting JDBC API is! ;-)
Have a feeling that spring jdbctemplate is what you looking for, may be you can take a look? http://www.springframework.org/docs/reference/jdbc.html
Have a look at DBUtils from Apache Commons.
http://jakarta.apache.org/commons/dbutils/
Jari
Take a look at O/R Broker.