Trimming JDBC
Published by Luigi October 23rd, 2006 in JavaUsing 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
- January 2010 (2)
- December 2009 (1)
- November 2009 (3)
- September 2009 (2)
- August 2009 (4)
- July 2009 (1)
- June 2009 (2)
- May 2009 (4)
- April 2009 (2)
- March 2009 (7)
- February 2009 (5)
- January 2009 (2)
- December 2008 (1)
- November 2008 (8)
- October 2008 (12)
- September 2008 (3)
- August 2008 (2)
- July 2008 (6)
- June 2008 (16)
- May 2008 (2)
- April 2008 (3)
- March 2008 (6)
- October 2007 (1)
- September 2007 (1)
- August 2007 (5)
- July 2007 (6)
- June 2007 (6)
- May 2007 (1)
- March 2007 (1)
- February 2007 (2)
- January 2007 (1)
- December 2006 (2)
- November 2006 (4)
- October 2006 (7)
- September 2006 (1)
- August 2006 (2)
- July 2006 (6)
- June 2006 (3)
- February 2006 (1)
- January 2006 (1)
- December 2005 (5)
- November 2005 (2)
- October 2005 (2)
- September 2005 (7)
- August 2005 (2)
- July 2005 (8)
- June 2005 (12)
Categories
- Books (7)
- Eclipse (10)
- Errors (2)
- Firefox (7)
- Hardware (14)
- Horror Code (8)
- Internet (17)
- Java (85)
- JavaScript (8)
- Life, universe and everything (29)
- Linux (44)
- Mac (18)
- Software (25)
- Speeches and Conferences (8)
- Web (19)
- Windows (16)
Latest
- Syntactic sugar and Java arrays.
- 3G USB Stick on Ubuntu
- Ipod touch with Linux
- Karmic and Luks: USB drive encryption made (almost) easy
- Suspend/Resume in Karmic /2
- Suspend/Resume problem in Ubuntu Karmic 9.10 running on MacBook Pro 5.1
- MacBook International Keyboard and Linux
- Mighty Mouse: reverse horizontal scrolling workaround on Ubuntu Linux 9.04
- Skype 2.1.0.47 beta released, and amd64 packages available!
- Linux RAM Disks
My open source projects
Blog License
Blogs I like
Friends' Blogs
- Antonio Terreno & Valter Bernardini
- Bruno Bossola
- Daniele Galluccio
- Domenico Ventura
- Ed Schepis
- Fabrizio Gianneschi
- Filippo Diotalevi
- JavaJournal.it Blog
- Luca Grulla
- Luigi Zanderighi
- Marcello Teodori
- Mida Boghetich
- Muralidharan Chandrasekaran
- Piero Ricca
- Renzo Borgatti
- Simone Bordet
- 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.