Brian's Waste of Time

Thu, 28 Dec 2006

jDBI has come a Long Way!

Just added Spring (2.0) support back into jDBI (2.0). When I started I looked back at the 1.4 codebase to see what I had done in the previous version... The 2.0 is much smaller, lighter, and has less special stuff. Yea!

This has been my first chance to work with Spring 2.0 and I rather like it. The SPI for playing with the transaction manager is pretty much identical, which is cool. The @Transactional annotation stuff works very nicely. I also learned, along the way, that Derby supports nested transactions. Sweet!

Anyway, jDBI has come a helluva long way in two years. The 2.0 codebase is getting the kinks worked out and I really like it.

Anywho, jDBI 2.0pre13 has been cut. I am trusting the codebase, so hopefully will hit a 2.0 final soon. In prerelease form it trumps 1.4.X right now, but I don't want to rule out API tweaks until I've used it anger a bit more.

0 writebacks [/src/java/jdbi] permanent link

Tue, 28 Nov 2006

Oracle's DML Returning and jDBI 2.0pre7

So I broke down and have started adding some database-specific functionality to jDBI. The first bit is a statement customizer which does the downcasts and whatnot to make it convenient to use oracle's dml returning stuff (like "insert into something (id, name) values (something_id_seq.nextval, ?) returning id into ?").

public void testFoo() throws Exception
{
    Handle h = dbi.open();

    OracleReturning<Integer> or = new OracleReturning<Integer>(new ResultSetMapper<Integer>() {
        public Integer map(int index, ResultSet r) throws SQLException
        {
            return r.getInt(1);
        }
    });

    or.registerReturnParam(1, OracleTypes.INTEGER);

    h.createStatement("insert into something (id, name) values (17, 'Brian') returning id into ?")
            .addStatementCustomizer(or)
            .execute();
    List<Integer> ids = or.getReturnedResults();

    assertEquals(1, ids.size());
    assertEquals(Integer.valueOf(17), ids.get(0));
    h.close();
}

This allows for a nice API, without client code downcasts as well, to make use of results returned from DML against Oracle. When I get a chance I'll add one for Postgres as well :-) The code to do it is pretty straightforward. The nice part was that it required no changes to the core of jDBI 2.0 to do this :-)

Pushed 2.0pre7 as well ;-) Have fun!!

0 writebacks [/src/java/jdbi] permanent link

Thu, 10 Aug 2006

O/R Mapping, SQL, Relation Orientation and Convenience

Ted Neward has broken out his commentary on object-relational mapping again. When he first posted this idea I disagreed -- I felt strongly that everyone tackling it, all the options, and various approaches, signaled a strong and healthy solution space. Over the intervening couple of years I have started to come around to Ted's point of view.

Ted sums up the problems very thoroughly, much more so than I want to take the time to do, so... go read his post.

Now, if you do want to use SQL-centric stuff in a convenient way, there are few options that give you the kind of convenience APIs that the O/RM tools provide. To take a trivial example of a one-parameter query:

RIFE provides a very nice API for accessing relational databases as... relational databases. Hynek recently posted. RIFE uses a builder approach to sql statements, like(to use Hynek's example):

Select select = new Select(datasource)
                        .from("customers")
                        .where("city", "=", city);

DbQueryManager manager = new DbQueryManager(datasource);
List<Customer> r =  manager.executeFetchAllBeans(select, Customer.class);

This style is really nice when your queries are dynamic, such as the ubiquitous "advanced search" form. The example here chains everything, but you can quite easily wrap the where clauses in if statements to build up a statement programmatically rather than via string concatenation.

If you prefer to work directly with SQL, or have mostly static statements, then there are a couple more really good tools. My favorite is, well, one I wrote called jDBI, which lets you do things like:

Handle handle = DBI.open(datasource);
List<Customer> r = handle.createQuery("select * from customers where city = :city")
                                .bind("city", city)
                                .map(Customer.class)
                                .list();

jDBI is focused very tightly on being a JDBC convenience API, not a general framework, such as RIFE. RIFE, on the other hand, provides some higher level functions enabled by taking control over what exactly gets done, such as handling limit and offset even on databases which don't support them (via query munging and rownums on Oracle, or using scrolling cursors and only fetching the correct rows elsewhere).

The grandaddy of JDBC convenience libraries is Apache DbUtils. With DbUtils you would have:

QueryRunner runner = new QueryRunner(datasource);
List r = (List) runner.query("select * from customers where city = ?",
                             city,
                             new BeanListHandler(Customer.class));

DbUtils is a short-and-sweet tool which provides even less high-level functionality, but is extremely well tested and quite flexible. It hasn't been upgraded to take advantage of generics yet, but some people probably prefer that :-)

Spring provides a few convenience API's, mostly centered around the JdbcTemplate. Like much of Spring, it doesn't provide an alternative API for what it helps with, it instead focuses on making the common cases easy and smoothing the sharp edges.


SimpleJdbcTemplate t = new SimpleJdbcTemplate(datasource);
List<Customer> r = t.query("select * from customers where city = ?", 
                                 new ParameterizedRowMapper<Customer>() {
                                     Customer mapRow(ResultSet rs, int index) {
                                         Customer c = new Customer();
                                         // map into the bean
                                         return c;
                                     }
                                 }, city);

This uses the upcoming Spring 2.0 stuff :-) Surprisingly they don't seem to have a generic JavaBean mapping row mapper. Weird, but easy to write.

Finally, this doesn't really exist yet, but JDBC 4 (part of JDK 1.6) will include a spiffy looking convenience API that looks like an annotation based SQL-J. You define an annotated interface and the driver provides an implementation which does the queries.

public interface CustomerDao extends BaseQuery {
    @Select("select * from customers where city = ?1")
    DataSet<Customer> findCustomersFrom(String city);
}

CustomerDao cd = QueryObjectFactory.createQueryObject(CustomerDao.class, datasource);
DataSet<Customer> r = cd.findCustomersFrom(city);

DataSet extends List, so you can treat reasonably! I am quite looking forward to trying this out. There are only a limited set of annotations defined, so you cannot specify things like fetch size, but it is pretty nice looking anyway :-)

Anyway, it hasn't been an in-depth look, but a taste and starting point for folks who want something that tries to embrace the underlying relational database.

update Thu Aug 10 09:01:00 PDT 2006 -- add iBatis

It was kindly pointed out by Steven Grimm that I missed iBatis. D'oh! It definitely should be mentioned. iBatis is very configuration heavy so I don't really have time to post up the trivial example. Will do so later :-)

4 writebacks [/src/java/jdbi] permanent link

Sat, 06 May 2006

jDBI 2.0pre1

Pushed jDBI 2.0pre1 last night. This is very much an ongoing development branch -- not many docs, not all the sugary features of 1.X (Spring TX integration, the magic configuration detection, etc), but it has some major features that just aren't doable under the 1.X design, like much more explicit control over things when you need it, while still providing the higher level functions when you don't -- and the ability to mix them pretty willy-nilly.

Rather than C&P lots of code here, I'll reference the test cases to demonstrate stuff =)

Anyway, have fun! and let us know if things break, could be better, are bass ackwards, or whatnot!

0 writebacks [/src/java/jdbi] permanent link

Thu, 16 Mar 2006

jDBI 2.0 Accidental Feature

I got an accidental feature in the upcoming 2.0 jDBI when I implemented forward-only external iteration (via an iterate() method on Query:

    public void testIteratorBehavior2() throws Exception
    {
        h.insert("insert into something (id, name) values (1, 'keith')");
        h.insert("insert into something (id, name) values (2, 'eric')");

        ResultIterator<Something> i = h.createQuery("select * from something order by id")
                .map(Something.class)
                .iterator();

        Something first = i.next();
        assertEquals("keith", first.getName());
        Something second = i.next();
        assertEquals(2, second.getId());
        assertFalse(i.hasNext());

        i.close();
    }

I realized that providing the iterate method provided all that was needed for Iterable, so we also get...

    public void testIterable() throws Exception
    {
        int count = 0;
        h.insert("insert into something (id, name) values (1, 'keith')");
        h.insert("insert into something (id, name) values (2, 'keith')");

        for (Something s : h.createQuery("select * from something").map(Something.class))
        {
            assertEquals("keith", s.getName());
            count++;
        }
        assertEquals(2, count);
    }

Nifty! Looking forward to getting this release out =)

1 writebacks [/src/java/jdbi] permanent link

Sat, 11 Mar 2006

jDBI 1.4.2 Released

Minor release of jDBI to set the target and source versions for compilation explicitely to jdk 1.4. No functionality changes, but folks running jdk 1.4 should be able to use the distributed binaries now =)

Have Fun!

0 writebacks [/src/java/jdbi] permanent link

Wed, 22 Feb 2006

jDBI 1.4.1 Released

Simone Gianni found a vicious little bug in the (lack of) handling of setNull in jDBI 1.4.0. So... jDBI 1.4.1 has been released. Thank you JDK 1.4 and PreparedStatement#getParameterMetaData(). It should degrade to previous behavior if your driver doesn't support parameter metadata, if it doesn't on your platform, please let me know!

Have fun!

2 writebacks [/src/java/jdbi] permanent link

Sat, 18 Feb 2006

jDBI 2.0 In Progress

jDBI has been getting some love here and there, and a recent feature request has lead to a great design change.

The first thing is that the upcoming 2.0 release is going to require JDK 1.5. Why? Because I was beaten with a stick at ApacheCon for not doing something that made total sense to do, but was not very aesthetic in the current codebase. What this really means is that you can do stuff like this in the upcoming release:

    public void testDemo() throws Exception
    {
        Handle h = DBI.open(Tools.getDataSource());
        h.createStatement("insert into something (id, name) values (:id, :name)")
                .setInteger("id", 1)
                .setString("name", "Brian")
                .execute();
        h.insert("insert into something (id, name) values (?, ?)", 2, "Eric");
        h.insert("insert into something (id, name) values (?, ?)", 3, "Erin");

        List<Something> r = h.createQuery("select id, name from something " +
                                          "where name like :name " +
                                          "order by id")
                .setString("name", "Eri%")
                .map(Something.class)
                .list();

        assertEquals(2, r.size());
        assertEquals(2, r.get(0).getId());
        assertEquals(3, r.get(1).getId());

        h.close();
    }

This illustrates a couple of the biggest changes -- there are statement and query abstractions which allow for much more fine tuning of what happens. This opens the door for lazy iteration, setting of isolation levels, etc. All the nice stuff which you need sometimes in JDBC.

The map(Something.class) in there is a fun one -- by default queries still do lists of maps (of String => Object) but bean mapping, custom mapping, etc are much more easily done with the ability to specify a mapper in the query, rather than as an interceptor on the handle. The API is much nicer there.

FInally, what isn't shown above because I haven't finished it, is the statement rewriting system. It is designed to pretty much support first-class macros, with access to the parameters at the time of macro expansion. This allows for some really interesting dynamic SQL scenarios done very cleanly.

I also thought up a byline for jDBI =) "Because SQL shouldn't be such a pain the ass in Java." Not very catchy, but it certainly sums up my thoughts.

0 writebacks [/src/java/jdbi] permanent link

Sat, 19 Nov 2005

Script Locator in jDBI

Just added a pluggable script locator module to jDBI. The script locator is used when you use the handle to execute scripts, so by default you can do:

handle.script("scripts/create-tables.sql");

handle.script("com/example/classpath-scripts.sql");

handle.script("https://example.com/scripts/remote-script.sql");

Of course, you can also write your own implementation of ScriptLocator (define one method) which locates script some other way =)

I pushed two releases with this. The first is a fully backwards compatible 1.3.3 release. The second is a 1.4.0 release which exposes the methods for specifying the statement locator and handle decorator on the IDBI interface (and is a 1.X upgrade as others may have implemented the interface and this would break their implementation).

Have Fun!

1 writebacks [/src/java/jdbi] permanent link

Fri, 18 Nov 2005

SQL Scripts in Java

Devender demonstrated shelling out to SQL*Plus to run a script, figured I had to trackback with a jDBI example that doesn't have to shell out, just for alternatives (and because this is a common mechanism for me to construct test schema =)

Our script, with various comment styles (this was taken from the regression tests for jDBI, btw):

-- insert 3 lines
insert into something(id, name) values (1, 'one');
# line 2
insert into something (id, name) values (2, 'two');
// line 3
insert into something (id, name) values (3, 'three');

and our code to run the script:

    public void testScriptExample() throws Exception
    {
        Handle h = DBI.open("jdbc:derby:testing");
        assertEquals(0, h.query("select * from something").size());
        h.script("src/test-etc/insert-script-with-comments.sql");
        assertEquals(3, h.query("select * from something").size());
        h.close();
    }

Shelling out to SQL*Plus is always an option I guess =) This did remind me that I need to change jDBI to use a script locator mechanism analogous to its named statement locator, in case you want to store your scripts on a web server, in the database, in JNDI, or... wherever comes to mind. Guess 1.3.3 may be out soon!

1 writebacks [/src/java/jdbi] permanent link

Wed, 16 Nov 2005

jDBI 1.3.2 Release

Just pushed a minor jDBI release (1.3.2) which adds better error reporting on exceptions (it is much easier to get to the underlying SQLException on malformed SQL now).

Have fun!

2 writebacks [/src/java/jdbi] permanent link

Wed, 29 Jun 2005

jDBI 1.3.1 Released and added a Committer =)

Just released jDBI 1.3.1. It fixes a bug which only manifested in JDK 1.5. Annoying things, those platform version dependent bugs =( Thank you Patrick for finding it and figuring out the fix!

Along the way, I managed to force a commit bit onto him, even better!

This should be a drop-in replacement for anyone running 1.3.0.

0 writebacks [/src/java/jdbi] permanent link

Tue, 26 Apr 2005

jDBI 1.3.0 Released

Released a minor API change version of jDBI last night. Significant features include:

  1. Global named parameters which may be set on either a DBI or Handle intance. Handles inherit named params from their parent DBI, but names set on the Handle do not percolate back up. This added two methods to public interfaces, so lead to the 1.3.X instead of 1.2.X. Feature request from John Wilson, aka tug.
  2. Exceptions thrown within the Spring integration system are wrapped in Spring's DataAccessException hierarchy. Thomas Risberg did the heavy lifting for this one (and noticed they needed to do it!). This support will improve with minor releases as I refactor to make things play better with the exception translator.

Global params are easy to show examples of:

public void testParamOnBareFirst() throws Exception
{
    handle.getGlobalParameters().put("name", "Robert");
    handle.execute("insert into something (id, name) values (:id, :name)", 
                   new Something(1, "Robert"));

    Map robbie = handle.first("select * from something where name = :name");
    assertNotNull(robbie);
    assertEquals(new Integer("1"), robbie.get("id"));
}

public void testOnDBI() throws Exception
{
    handle.close();
    DBI dbi = new DBI(Tools.CONN_STRING);
    dbi.getGlobalParameters().put("name", "Robert");
    handle = dbi.open();

    handle.execute("insert into something (id, name) values (:id, :name)", 
                   Args.with("id", new Integer(1)));
    Map robbie = handle.first("select * from something where name = :name");
    assertNotNull(robbie);
    assertEquals(new Integer("1"), robbie.get("id"));
}

So have fun with it!

0 writebacks [/src/java/jdbi] permanent link

Tue, 15 Mar 2005

jDBI 1.2.5 Released

jDBI is a thin wrapper around JDBC, designed to be convenient for the programmer instead of whatever JDBC was designed to be conveninent for =) It uses the Java collections framework for query results, provides a convenient means of externalizing sql statements, and provides named parameter support for any database being used, hooks into the various transaction systems (local, BMT, CMT, Spring, etc), and is hopefully useful.

All 1.2.5 does is use the batch api for handling scripts. This allows for DDL in scripts, which is kinda handy, as a lot of databases don't allow DDL in prepared statements (which the default jDBI statement execution uses).

Go have fun with it!

Coming soon: using the externalized SQL system for scripts instead of the current specialized script handler.

0 writebacks [/src/java/jdbi] permanent link

Mon, 31 Jan 2005

jDBI 1.2.3

Just pushed jDBI 1.2.3. Much thanks to Patrick and Robert for prodding me make a couple big changes, and for prodding me to not make those changes until I found the right way, respectively (for 1.2.2 (transparently handling different transactional contexts) and 1.2.3).

The biggest change is making externalized SQL pluggable. It was something that had been itching for a while (previously it could only pulled named statements from the classpath, though pretty smartly), and Patrick's need for sticking the SQL in the database prodded me to finally support that explicitely. He's not using it, I don't think, but you'd better bet I will before too long =) Maybe stick them in a JNDI or an LDAP instead, or whatever. It's all good.

Speaking of the classpath approach, what I did on the last new thing I used jdbi for was to take advantage of Java's nice resource loading and bundle all the sql into its own jar, under an sql/ directory, so you'd have:

sql/
    find-foo-by-id.sql
    find-foo-by-name.sql
    
...

Map foo = handle.first("sql/find-foo-by-id", Args.with("id", new Long(fooId)));
Map same_foo = handle.first("sql/find-foo-by-name", foo); // gets "name" from map

The hack here is that the named statement is fetched correctly, and if you unzip the sql.jar to tweak the sql, it's in its own dir, not the working dir. This is minor, but I have things that untar (zip|jar) into the working directory =)

Anyway, the release is up on Codehaus. Have fun!

2 writebacks [/src/java/jdbi] permanent link

Wed, 19 Jan 2005

jDBI Releases Galore

I have been lax in announcing jDBI related stuff -- time to make up for that =) First, jDBI made its 14th and 15th (1.1.2 and 1.2.1 -- seperate branches). Technically 1.2 maintains bytecode compatibility with 1.1 (you can drop the jar in and go) but there is a huge seven character difference. DBIException now extends RuntimeException per a bunch of strong arguments by Patrick Burleson. Seven characters, world of difference. Guess I have taken the dive into optional exception checking. Water still feels chilly.

Aside from API changes, hosting has moved to the Haus. The subversion repo isn't there yet, but the rest is. No pretty confluenze site (yet?). We also have mailing lists! I should probably post them on the site sometime soon... In the mean time, its not hard to figure out my email address, or the lists if you look at how Bob lays out projects ;-)

As the last release I announced was 1.0.X series, some other big things in there include really nice Spring integration (factory bean which makes a couple minor changes to play as expected and hooks into the platform transaction system, and DBIUtils to provide transactionally bound handles/access/etc)

Meeting a few feature requests there is a really nice batch and prepared batch system with usages looking something like:

public void testBatchOfTwo() throws Exception
{
    handle.batch()
            .add("insert into something (id, name) values (1, 'one')")
            .add("insert into something (id, name) values (2, 'two')")
            .execute();

    final Collection results = handle.query("select id, name from something order by id");
    assertEquals(2, results.size());
}

public void testPreparedBatch() throws Exception
{
    handle.prepareBatch("insert into something (id, name) values (:id, :name)")
            .add(new Something(1, "one"))
            .add(new Something(2, "two"))
            .add(new Something(3, "three"))
            .add(new Something(4, "four"))
            .execute();
    assertEquals(4, handle.query("select * from something").size());
}

public void testPreparedBatch2() throws Exception
{
    final Something[] things = new Something[]
    {
        new Something(1, "one"),
        new Something(2, "two"),
        new Something(3, "three"),
        new Something(4, "four")
    };
    handle.prepareBatch("insert into something (id, name) values (:id, :name)")
            .addAll(things)
            .execute();
    assertEquals(4, handle.query("select * from something").size());
}

That one is my favorite. I rarely used batched sql when working with jdbc directly as, well, it is bloody inconvenient. Do it all the time now =)

A particularly fun piece is not actually released yet as I am not satisfied that I have caught all the edge cases -- Henri Yandell asked for in-clause expansion. That is, bind a collection or array to a single formal param for an sql in clause, a la, select id, name from something where id in ?, which could have a List<Long> bound and would expand as required to behave correctly (ideally as a prepared statement still). As I said, I have code which does it, but it is buried in subversion and not exposed yet as I had wanted to avoid fulling parsing and transforming the sql. Still, that is powerful stuff, so it'll probably be the next significant feature.

Anyway, enjoy it and have fun! Remember, SQL isn't teh ev1l, JDBC is just inconvenient! Let the code do the work =)

0 writebacks [/src/java/jdbi] permanent link