MyBatis では Mapper XML ではなく、アノテーションによって Mapper を定義することができます。
今回は、そのアノテーションの使い方についてまとめます。
環境
- MyBatis 3.4.6
@Insert, @Update, @Select, @Delete
メソッド対して指定可能で、それぞれ実行対象の SQL に対応しています。
各アノテーションは、文字列か文字列の配列を引数にとり、実行したい SQL を渡すことができます。
配列が指定された場合は半角スペース区切りで文字列が結合されます。
XML のときと同様に、 #{hoge} で値を埋め込むことができ、動的 SQL を利用したい場合は、全体を <script> で囲む必要があるようです。
@Select("SELECT id, title FROM table WHERE id=#{id}")
Todo findById(Integer id);
@Select({
  "<script>",
  "SELECT id, title FROM table",
  "<where>",
  "<if test=\"id != null\">AND id=#{id}</if>",
  "<if test=\"title != null\">AND title=#{title}</if>",
  "</where>",
  "</script>"
})
List<Hoge> find(@Param("id") Integer id, @Param("title") String title);
ちなみに、XML の場合は resultTypeという属性を指定していましたが、メソッドの戻り値の型が自動的に適用されるので、指定する必要はありません。
@InsertProvider, @UpdateProvider, @SelectProvider, @DeleteProvider
メソッドに対して指定可能で、それぞれ実行対象の SQL に対応しています。
各アノテーションは、type属性にクラスオブジェクト、method属性には文字列でメソッド名を指定します。
これらによって指定されたメソッドから返却される文字列を SQL として実行します。
SQL を作成するメソッドは、単に文字列を組み立てることでも実現できますが、MyBatis が提供している SQL ビルダーを利用して組み立てることもできます。
また、Java の制御構文を用いることができるため、動的な SQL を作成することができます。
http://www.mybatis.org/mybatis-3/ja/statement-builders.html
SQL に直接値を設定することができてしまいますが、インジェクションの危険性があるので、#{hoge}の形式にしたほうがいいです。
@SelectProvider(type = TodoSQLProvider.class, method = "select")
List<Todo> find2(@Param("id") Integer id, @Param("title") String title);
public class TodoSQLProvider {
  public String select(@Param("id") Integer id, @Param("title") String title) {
    return new SQL() {
      {
        SELECT("*");
        FROM("todo");
        if (id != null) {
          WHERE("id = #{id}");
          // WHERE("id = " + id); これはインジェクションの危険性あり
        }
        if (title != null) {
          WHERE("title = #{title}");
        }
      }
    }.toString();
  }
}
@Option
メソッドに対して指定可能で、XML の <select>や<insert>などで指定することができた以下のオプションを設定することができます。
- useCache
- flushCache
- resultSetType
- statementType
- fetchSize
- timeout
- useGeneratedKeys
- keyProperty
- keyColumn
- resultSets
@Results, @Result
@Reusltsは XML の <resultMap>、@Resultは<result>に該当します。
@Resultsはメソッドに対して指定可能で、id属性に文字列、value属性に @Resultの配列を指定します。
@Resultはid, column, property, javaType, jdbcType, typeHandler, one, manyを属性として指定可能です。
概ね XML の<result>タグで指定可能な属性と同じなので、差分のみ説明します。
| 属性 | 説明 | 
|---|---|
| id | 真偽値で指定します。XML の場合は、 <result>と<id>を使い分けて主キーを指定していましたが、@Resultではこの属性を使用します。 | 
| one | @Oneを指定します。@Oneは XML の<association>に該当します。 | 
| many | @Manyを指定します。@Manyは XML の<collection>に該当します。 | 
@Select("SELECT id, title FROM todo")
@Results(id = "hoge", value = {
  @Result(id = true, column = "id", property = "i"),
  @Result(id = true, column = "title", property = "t"),
})
List<Hoge> findAll();
@Data
public class Hoge {
  private Integer i;
  private String t;
}
@One, @Many
上述の通り、<association>、<collection>に該当します。どちらも、fetchType、selectを属性として指定可能です。
fetchTypeには、FetchType.DEFAULT、FetchType.EAGER、FetchType.LAZYのいずれかを指定します。指定した場合の動作は XML の fetchType と同様なので割愛します。
selectには、Mapper メソッドの完全修飾名を指定します。(@Select、@SelectProviderが付与されたメソッド)
なお、パッケージ等を含んだ完全修飾名ではなく、メソッド名だけを指定すると、同一 Mapper インタフェース内のメソッドを指定したことになります。
動作としては XML で select 属性を指定した場合と同様です。
public interface TodoMapper {
  @Select("SELECT * FROM todo WHERE id=#{id}")
  @Results(id = "todo", value = {
      @Result(column = "author", property = "author", one = @One(select = "com.example.mybatis.UsersMapper.findById", fetchType = FetchType.EAGER)) })
  Todo findById(Integer id);
}
public interface UsersMapper {
  @Select("SELECT * FROM users WHERE id=#{id}")
  User findById(Integer id);
}
XML では select を利用せず、以下のようにテーブルを結合して取得し、その結果を利用してマッピングが可能でした。
<resultMap id="TodoMap" type="Todo">
  <id column="todoId" property="id" />
  <result column="title" property="title" />
  <association property="author" javaType="User">
    <id column="userId" property="id" />
    <result column="name" property="name" />
  </association>
</resultMap>
<select id="findById" resultMap="TodoMap">
  SELECT t.id as todoId, t.title, u.id as userId, u.name FROM todo t, users u WHERE t.author = u.id
</select>
しかし、@Oneや@Manyでは上記と同じことを実現できず、必ず他の select を実行するしかありません。
select を利用する方法は N+1 問題が起きてしまうのが嫌で、個人的にはあまり使っていなかったので、ここはちょっとデメリットかなと思いました。
@ConstructorArgs, @Arg
@ConstructorArgsは XML の<constructor>、@Argは<arg>に該当します。
@ConstructorArgsは @Argの配列をvalue属性に指定できます。
@Argはid, column, javaType, jdbcType, typeHandler, select, resultMapを属性として指定可能です。
id は @Resultと同様で、主キーの場合はtrueを指定する必要があります。
それ以外は XML の <arg>タグで指定可能な属性と同じです。
@Select("SELECT id, title FROM todo")
@ConstructorArgs({
  @Arg(id = true, column = "id", name = "id"),
  @Arg(id = true, column = "title", name = "title")
})
List<Todo> constructor();
@ResultMap
value属性に利用したい@Resultsのidを指定することで、他で定義されている@Resultsを利用することができます。
@Select("SELECT id, title FROM todo")
@Results(id = "hoge", value = {
  @Result(id = true, column = "id", property = "i"),
  @Result(id = true, column = "title", property = "t")
})
List<Hoge> findAll();
@Select("SELECT id, title FROM todo WHERE id=#{id}")
@ResultMap("hoge")
Hoge findBy(Integer id);
なお、「パッケージ名+インタフェース名+id属性の値」の形式で指定すれば、他のインタフェースに定義された@Resultsも利用することが可能です。
@MapKey
Mapを返却するメソッドにのみ指定できます。
value属性に、Map のキーとするプロパティを指定します。
@Select("SELECT id, title FROM todo")
@MapKey("id")
Map<Integer, Todo> map();
例えば、id が 1 で title が title1、id が 2 で title が title2 のデータが取得できた場合は以下のようなマップになります。
{1=Todo(id=1, title=test1), 2=Todo(id=2, title=test2)}
@ResultType
マッピングするクラスは戻り値の型から取得しています。
しかし、ResultHandler を利用するメソッドの場合、戻り値がvoidとなるため、@ResultTypeによって、マッピングするクラスを指定します。
@Select("SELECT id, title FROM todo")
@ResultType(Todo.class)
void handle(ResultHandler<Todo> handler);
なお、ResultHandler を利用する場合、@ResultMapを利用することもできます。
@SelectKey
XML の<selectKey>に相当する機能を実現することができます。
statement,keyProperty,keyColumn,before,resultType,statementTypeを属性として指定可能です。
statement属性に SQL 文を指定します、beforeは XML のときの orderに該当します。
それ以外は XML のときと同様です。
まとめ
XML と概ね同じことができますが、@One、@Manyには機能差があるのでそこがネックかなと思います。
簡単な SQL をサクッと実行するだけであれば、インタフェースに直接定義できて楽だなーという感触です。
まだいくつかまとめられていないアノテーションがあるので、そのうち追記したいと思います。