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 をサクッと実行するだけであれば、インタフェースに直接定義できて楽だなーという感触です。
まだいくつかまとめられていないアノテーションがあるので、そのうち追記したいと思います。