Result Maps
MyBatisの中で最も重要かつ強力なのがresultMap要素である。resultMapのおかげで、JDBCを使ってResultSetからデータを取得する場合に書かなくてはならないコードの9割を省くことができ、またときにはJDBCがサポートすらしていないことも可能となるのだ。実際、複雑なクエリの結果を結合してマッピングするコードを書く場合、数千行ものコードになってしまう場合もある。
<select id="selectUsers" resultType="map">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
この例の場合、resultTypeで指定された通り、すべての例が自動的にHashMapのキーとしてマップされる。HashMapは良いドメインモデルとは言えない。ほとんどのアプリケーションではドメインモデルとしてJavaBeansやPOJOが使われているだろう。MyBatisはどちらもサポートしている。
package com.someapp.model
public class User {
private int id;
private String username;
private String hashedPassword;
public int getId(){
return id;
}
public void setId(int id){
this.id = id;
}
public String getUsername(){
return username;
}
public void setUsername(String username){
this.username = username;
}
public String getHashedPassword(){
return hashedPassword;
}
public void setHashedPassword(String hashedPassword){
this.hashedPassword = hashedPassword;
}
}
JavaBeanの仕様によれば、上記3つのプロパティはid, username, hashedPasswordを持っている。これらのプロパティは先に挙げたselectステートメントの中で指定されている列名と一致している。このような条件を満たすJavaBeanであれば、HashMapの時同じようにResultSepにマップすることができる。
<select id=”selectUsers” resultType=”com.someapp.model.User”>
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
タイプエイリアスを使えば何度でも完全修飾クラス名を入力せずに済む。
<!-- In Config XML file -->
<typeAlias type="com.someapp.model.User" alias="User" />
<!-- In SQL Mapping XML file -->
<select id="selectUsers" resultType="User">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
こうしたケースでは、名前に基づいて列をJavaBeanにマップするためのResultMapがMyBatisによって自動的に作成される。もし列名が一致しない場合、select文で列に別名をつけることで対応可能。
<select id="selectUsers" resultType="User">
select
user_id as "id",
user_name as "userName",
hashed_password as "hashedPassword",
from some_table
where id = #{id}
</select>
ResultMapの明示的定義
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id">
<result property="username" column="username"/>
<result property="password" column="password"/>
</resultMap>
<!-- 以下resultMap属性を使って定義したResultMapを参照するステートメント -->
<select id="selectUsers" resultMap="UserResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
高度な結果マッピング
MyBatisは「データベースを必ずしも希望通りに定義されている訳ではない」という思想に基づいて設計されている。全てのDBが完全な第三正規形あるいはBCNFなら最高だが、実際そうではない。また、一つで全てのアプリケーションに適合できるようなDBを作成するのが理想だが、そうもいかない。こうした問題に対するMyBatisの答えがResult Mapである。
resultMap要素には多くの子要素や階層構造がある。以下はresultMap要素の構成要素である。
resultMap
- constructor: クエリ結果をコンストラクタに渡してクラスのインスタンスを作成する場合に使用する。
- id:このフィールドまたはプロパティがIDであることを明示する。
- result:フィールドまたはJavaBeanのプロパティにセットされる通常のデータ。
- association:複雑型のアソシエーションを定義する。多くのマッピングはこのタイプに当てはまる。
- collection:複雑型のコレクションを定義する。
- discriminator:結果の値を使ってどのresultMapを使用するか決定する。
ResultMap Attributes
属性 | 説明 |
---|---|
id | このネームスペース内で固有の識別子。resultMapを参照する際に使用 |
type | Javaクラスの完全修飾クラス名またはタイプエイリアスを指定 |
autoMapping | このresultMapに結果をマッピングする際、 自動マッピングを使用するかどうかをtrue/falseで指定 |
ResultMapは段階的に実装するようにすべきである。巨大なresultMapを一度に実装しようとするとミスが発生しやすく、作業も困難である。シンプルなところからスタートして、ユニットテストを書きながら徐々に拡張する。 |
id と result
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
idとresultは結果マッピングの基本となる要素である。どちらも、ある列の値を単純型(String, int, double, Dataなど)のプロパティまたはフィールドにマップするときに使用する。
両者の違いは、idはその結果が識別子プロパティ(オブジェクトのインスタンスを比較する際に使われる)であることを表すという点のみである。
id and result Attributes
属性 | 説明 |
---|---|
property | 結果列の値をマップするフィールドまたはプロパティを指定。名前が一致するJavaBeanプロパティがあればそのプロパティが使われる。なければ、MyBatisは次に名前が一致するフィールドを探す。複雑型のプロパティも指定可能("address.street.number") |
column | DBで定義されている列名、あるいは列の別名を指定。resultSet.getString(columnName)メソッドの引数として渡される文字列と同じ。 |
javaType | Javaクラスの完全修飾クラス名またはタイプエイリアスを指定。通常、JavaBeanにマッピングする場合のJavaTypeはMyBatisによって正しく判別される。ただし、HashMapにマッピングする場合は正しい動作を保証する為適切なjavaTypeを指定するようにする。 |
jdbcType | サポートされているJDBCデータ型を指定。JDBCデータ型の指定が必須となるのは、insert,update,deleteの各ステートメントでnullが許可されている列を指定した場合のみ。(JDBCの仕様) |
typeHandler | デフォルトのタイプハンドラーについては既に説明したが、このプロパティを使うと、デフォルトのタイプハンドラーをオーバーライドすることができる。タイプハンドラーの完全修飾クラス名またはタイプエイリアスを指定。 |
constructor
ほとんどのDTO(Data Transfer Object)やドメインモデルではプロパティを使って値を設定することができる。通常更新されることのない参照用データを含むテーブルなどはイミュータブルクラスに向いている。コンストラクタインジェクションを使うとインスタンス化の際に値を設定することができるので、カプセル化の妨げとなるpublicメソッドの定義が不要となる。constructor要素を使うとコンストラクタインジェクションによって値をマップすることが可能となる。
public class User{
//...
public User(Integer id, String username, int age) {
//...
}
//...
}
コンストラクタ経由で値をマップするためには、指定された引数にマッチするコンストラクタを特定する必要がある。下記ではMyBatisは3つの引数java.lang.Integer,java.lang.String,int
をこの順番で持つコンストラクタを探す。
<constructor>
<idArg column="id" javaType="int" name="id"/>
<arg column="age" javaType="_int" name="age"/>
<arg column="username" javaType="String" name="username"/>
</constructor>