Now as I mentioned in previous posts, the main reason I found myself developing - well, debugging at first - on the iPhone platform was to investigate why a SQLite based feature of our application wasn't functioning as well as the original Java proof-of-concept. Fairly soon I was immersed in the world of SQLite's C/C++ interface - JDBC this was not. From a Java point of view it's low-level: Error conditions signaled by return value on almost every function and pointers aplenty. What I wanted was JDBC - or rather OBJCDBC - but in fact I wanted much more because in the JDBC world I had also become used to:
- Excellent connection and prepared statement pooling with the likes of DBCP.
- Concise utility methods that allowed me to avoid JDBC boiler-plate in the form of DbUtil.
I Googled a while to see if such a thing existed in the iPhone domain and thankfully found many likely candidates on the SQL Wiki (see: Objective-C section) ranging from simple wrappers to ORM frameworks. However, the simple wrappers were not as clean and object orientated as I was used to with JDBC and the ORM frameworks would not give me enough control to write some of the highly optimized SQL queries that our application demanded. I set about writing my own wrapper. One could argue that I was reinventing the wheel - but I'm always happy to learn more about wheels. My requirements were as follows:
- Option to pool connections and statements
- Check for every error condition that SQLite could possibly set and convert these into exceptions so we can adopt a try/catch/finally approach when accessing the database.
- Provide a clean and simple programmatic interface.
- Move all direct interactions with SQLite into a few sensible classes - C based SQLite code had previously been spread liberally throughout the application.
My core classes were to be as follows:
- PooledDataSource - A connection data source that also pools connections. Calling close on a connection actually returns it to the pool.
- Connection - Encapsulates a SQLite database handle - can also pool statements that have been prepared from this connection. Calling prepare on a connection might actually fetch and reuse a pooled PreparedStatement rather than creating a new instance. rovides methods to manage transactions.
- PreparedStatement - Prepares transient and non-transient statements, binds parameter values to statements, and executes statements. Returns ResultSets for SELECT queries. Calling close on a prepared statement returns it to the statement pool if it is not transient.
- ResultSet - An interface for stepping through a cursor and retrieving values from the row.
With these classes in place I could write some typical Data Access Object code:
PreparedStatement statement;
ResultSet results;
NSNumber maxId = nil;
@try {
stmt = [con prepare:@"SELECT MAX(id) FROM foo WHERE bar = ?"];
[con begin]; // OK - so we don't need this for a SELECT
results = [stmt executeWithValues:@"dog", nil];
if ([results next]) {
maxId = [results getInt:1];
}
[con commit]; // for illustration only
@catch (NSException* sqlX) {
[con rollback]; // for illustration only
// handle error
} @finally {
[DatabaseCommons closeQuietly:results, statement, con];
}
I'll get more into the actual class implementation details in part 2.
No comments:
Post a Comment