brianm/jdbi — pull request #107 by arteam (2014-07-12 20:31:19+00:00)
Title: Support for fluent map queries

Maybe it's a quite a big change to add, but I will give a shot.

The problem this PR addresses is a complexity to perform map queries in JDBI. I think JDBI can do a lot better in this field.

Let's have a simple table in a database
```sql
insert into something(id, name) values (1, 'keith');
insert into something(id, name) values (2, 'brian');
```

Let's imagine we want to load data from this table to **Map**. *Canonical* way to make such queries is using *fold*. Something like that:
```java
h.createQuery("select id, name from something")
.fold(new HashMap(), new Folder2>() {
public Map fold(Map a, ResultSet rs, StatementContext context) throws SQLException {
a.put(rs.getString("name"), rs.getInt("id"));
return a;
}
});
```
Quite cumbersome. A little better with *Guava* and *Java 8*:
```java
h.createQuery("select id, name from something")
.fold(Maps.newHashMap(), (a, rs, context) -> {
a.put(rs.getString("name"), rs.getInt("id"));
return a;
});
```

Another problem is that we don't have a strict mapping between keys and values. So, in case if we already have some mappers for complex types it's quite not natural to use it the *fold* method.

This change proposes a fluent interface to work with map queries. Basic structure like that:
```java
h.createMapQuery("select id, name from something")
.mapKey(new StringMapper("name"))
.mapValue(new IntegerMapper("id"))
.get();
```
I believe it's more concise and more focused on the real task.

Registered mappers can be reused:
```java
h.registerMapper(new SomethingMapper());
h.registerMapper((index, r, ctx) ->
new City(r.getInt("city_id"), r.getString("city_name"), r.getString("state")));
h.createMapQuery("select u.id, u.name, u.city_id, c.name city_name, c.state " +
"from users u inner join cities c on u.city_id = c.id")
.mapKeyTo(Something.class)
.mapValueTo(City.class)
.get();
```

We can set the needed implementation of **Map** if the default **HashMap** is not sufficient.
```java
h.createMapQuery("select id, name from something")
.mapKey(new StringMapper("name"))
.mapValue(new IntegerMapper("id"))
.accumulator(Maps.newHashMapWithExpectedSize(128))
.get();
```

If we want some not standard structure with keys and values, we can use *fold* (I use more simple fold version without changing an accumulator, because it more handy for typical cases).
```java
h.createMapQuery("select u.id, u.name, u.city_id, c.name city_name, c.state " +
"from users u inner join cities c on u.city_id = c.id")
.mapKeyTo(Something.class)
.mapValueTo(City.class)
.fold(ArrayListMultimap.create(), (accumulator, key, value) ->
{accumulator.put(value, key);});
```

I've tried to make everything backward compatible, but could make mistakes. Fell free to correct me.
Thanks in advance.