JDBIでOneToMany

一応 [stackoverflow](http://stackoverflow.com/questions/24337100/how-to-create-a-one-to-many-relationship-with-jdbi-sql-object-api) にサンプルはある。

この例だと、

- もともとのクエリのメソッドを`_getAccountById`にして、新しく作った`getAccountById`を呼び出す
- `getAccountById`では1件だけ返すように調整。ない場合はnullを返す

のようにしている。

まとめると、Mapperが全行について呼ばれてしまう（`_getAccountById`の戻り値の型がリストになっていることに注目）ため、別のメソッドを生やして1件だけ返すようにしている。


しかし、上記の例だと、one側のidが1つの場合しか対応出来ないので、自分で別の例を書いてみた。

---

`order`と`order_detail`があり、1対nで対応しているとする。

```sql 
-- create
create table `order` (id bigint);
create table order_detail (id bigint, order_id bigint);

-- input data
insert into `order` values (1);
insert into `order` values (2);
insert into order_detail values (1, 1);
insert into order_detail values (2, 1);
insert into order_detail values (3, 2);
```

```groovy
// Groovy Version: 2.4.5 JVM: 1.8.0_77 Vendor: Oracle Corporation OS: Mac OS X
@GrabConfig(systemClassLoader = true)
@Grab('mysql:mysql-connector-java:5.1.31')
@Grab('org.jdbi:jdbi:2.78')

import groovy.transform.ToString
import org.skife.jdbi.v2.DBI
import org.skife.jdbi.v2.StatementContext
import org.skife.jdbi.v2.sqlobject.Bind
import org.skife.jdbi.v2.sqlobject.SqlQuery
import org.skife.jdbi.v2.sqlobject.customizers.Mapper
import org.skife.jdbi.v2.tweak.ResultSetMapper

import java.sql.ResultSet
import java.sql.SQLException
import java.util.concurrent.ConcurrentHashMap
import java.util.stream.Collectors

abstract class OrderDAO {
    @Mapper(OrderMapper.class)
    @SqlQuery("select o.id, od.id, od.order_id from `order` o join order_detail od on (o.id = od.order_id) where o.id = :id")
    abstract List<Order> _findOne(@Bind("id") long id)

    @Mapper(OrderMapper.class)
    @SqlQuery("select o.id, od.id, od.order_id from `order` o join order_detail od on (o.id = od.order_id)")
    abstract List<Order> _findAll()

    Order findOne(long id) {
        List<Order> orders = _findOne(id)
        return orders.isEmpty() ? null : orders.get(0)
    }

    List<Order> findAll() {
        List<Order> orders = _findAll()
        // 重複判定はもう少しちゃんとした方がよさそう
        return orders.stream().distinct().collect(Collectors.toList())
    }
}

@ToString
class Order {
    long id
    List<OrderDetail> details = new ArrayList<>()
}

@ToString
class OrderDetail {
    long id
    long orderId
}

class OrderMapper implements ResultSetMapper<Order> {
    private Map<Long, Order> orderMap = new ConcurrentHashMap<>()

    @Override
    Order map(int index, ResultSet r, StatementContext ctx) throws SQLException {
        long id = r.getLong("o.id")
        Order order = orderMap.get(id)
        if (order == null) {
            order = new Order()
            order.id = r.getLong("o.id")
        }
        OrderDetail detail = new OrderDetail()
        detail.id = r.getLong("od.id")
        detail.orderId = r.getLong("od.order_id")
        order.details.add(detail)
        orderMap.put(id, order)

        return order
    }
}

DBI dbi = new DBI("jdbc:mysql://localhost/sandbox", "root", "")

OrderDAO dao = dbi.onDemand(OrderDAO.class)
println dao.findOne(1L)
println dao.findOne(9L)
println dao.findAll()
```

結果

```
Order(1, [OrderDetail(1, 1), OrderDetail(2, 1)])
null
[Order(1, [OrderDetail(1, 1), OrderDetail(2, 1)]), Order(2, [OrderDetail(3, 2)])]
```

### ps.

Mapperが実際に呼び出されている深いところまでは追っていないが、一応スレッドセーフにはなってるっぽい。
GParsで同時実行しながら、Mapperの中でランダムな文字列吐いて確認しても、全ての呼び出しで同じ文字が出てくる。が、結果は正しかった。
cglibがJDBI内にあるので、1つだけ生成して、あとは呼び出し時にそれを雛形に複製して割り当てているのかしら？？？