現場で学んだことをまとめようシリーズ(はじめに)
そのまま、現場で学んだ(使用した)ツール等についてまとめていくシリーズです。
自分のためでもあり、それが誰かのためになればいいな、という気持ちを込めて書きます。
間違えや記述方法についての指摘やコメント、お待ちしております!
編集履歴
- 2018/09/30 パラメタの渡し方を追加
- 2019/03/27 書き方Tipsを追加
MyBatisってなんぞや?
XML、またはアノテーションを使用してSQL文とオブジェクトをマッピングするフレームワークです。
通常のCRUD操作はもちろん、
パラメータの状態により動的にSQLを発行したりできます!
何度も使い回すような記述があれば共通化して、記述量を減らしたりもできます。
本記事では主にXMLの記述について解説していきます。
基本的な記述・使い方
何はともあれソースを見ていただいた方が早いので以下をご覧ください。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="src.mapper.MybatisMapper">
  <select id="select" resultType="src.mapper.entity.TableEntity">
    select
        *
    from 
        table_name
  </select>
  <insert id="insert">
    insert into table_name (
      id,
      name,
      age,
      department
    ) values (
      #{id},
      #{name},
      #{age},
      #{department}
    )
  </insert>
</mapper>
package src.mapper
// importは省略
public interface MyBatisMapper {
  TableEntity select();
  void insert(TableEntity entity);
}
package src.mapper.entity
// importは省略
public class TableEntity {
  private String id;
  private String name;
  private Integer age;
  private String department;
  public void setId(String id) {
    this.id = id;
  }
  public String getId() {
    return this.id;
  }
  // 全フィールドのgetter、setter
}
簡単に説明しますと、
- 
<mapper namespane="src.mapper.MybatisMapper">で/src/mapper/MybatisMapper.javaと紐づけます。
- 
<select id="select" resultType="src.mapper.entity.TableEntity"> ~ </select>でMybatisMapper.select()を実行することによりselectが実行され、その結果がTableEntityにマッピングされます。
- パラメタを渡す場合は#{id}のように記述すれば受け取ることができます。上記ではオブジェクトで値を渡していますが、@Paramを使用すればMapperクラスの引数として受け取ることも可能です。
javaクラス、メソッドとの紐づけがとても簡単なので、直感的にわかりやすいです!
パラメタの渡し方
上記のようにデータクラスを用意できるのであれば、フィールド名でマッピングが可能です。(getterがないとダメだったような気がします)
しかし様々な都合で「マッパークラスの引数として渡したい!」という方のために、
いくつか方法をご紹介します。
XMLのパラメタを#{param1}とする
  <select id="select" resultType="src.mapper.entity.TableEntity">
    select
        *
    from 
        table_name
    where
        id = #{param1}
        and name = #{param2}
  </select>
package src.mapper
// importは省略
public interface MyBatisMapper {
  TableEntity select(String id, String name);
}
このように記述すれば#{param1}には第一引数であるid、
#{param2}には第二引数であるnameがマッピングされます!
個人的に引数の順番でマッピングされるというのは把握しにくいのでお勧めしません。
マッパークラスの引数に@Paramを付与する
  <select id="select" resultType="src.mapper.entity.TableEntity">
    select
        *
    from 
        table_name
    where
        id = #{id}
        and name = #{name}
  </select>
package src.mapper
// importは省略
public interface MyBatisMapper {
  TableEntity select(@Param("id") String id, @Param("name") String name);
}
@Paramの引数に文字列でエイリアスを指定することにより、
XML内のパラメタとマッピングを行うことができます。
個人的には一番理解しやすくおすすめです!
mapで渡す
データクラスを作成するように、
key=パラメタ名、value=値としてmapを作成する方法です。
詳しくはこちらをご覧ください。
(これやるならデータクラス作った方が分かりやすい気もしますが。。)
それでは次から動的SQLの記述方法についてご紹介いたします!
動的SQL
動的SQLというのは先述した通り、パラメタの状態によって発行されるSQLを動的に変更できることを指します!
具体的には以下の通りです!
※以後は共通する記述を省きますのでご注意ください!
if/choose/where,set,trim
例えば、参照するカラムは同じなのに条件(WHERE句)が違うから似たようなSELECT文を複数書かないといけない。。というのは億劫ですよね。
これからご紹介するif/choose/where,set,trimは、WHERE句の条件を変えたい場合やUPDATE文を統一したい場合に便利な記述方法です。
if
  <update id="update">
    update table_name 
    set
      <if test="name != null">
        name = #{name},
      </if>
      age = #{age}
    where
      id = #{id}
  </update>
- パラメタ.nameがnullである場合に発行されるSQL…update table_name set age = ? where id = ?
- パラメタ.nameがnullでない場合に発行されるSQL…update table_name set name = ?, age = ? where id = ?
状況によっては二択ではなく、それ以上の条件の中から選択したい!というケースも出てくるかと思います。
そんな場合は**choose(when,otherwise)**をどうぞ。(あんまり見たことないですが)
choose(when,otherwise)
  <select id="select">
    select
      *
    from
      table_name
    where
      <choose>
        <when test="id != null">
          id = #{id}
        </when>
        <when test="name != null">
          name = #{name}
        </when>
        <otherwise>
          age = #{age}
        </otherwise>
      </choose>
  </select>
- パラメタ.idがnullでない場合に発行されるSQL…select * from table_name where id = ?
- パラメタ.idがnullであり、パラメタ.nameがnullでない場合に発行されるSQL…select * from table_name where name = ?
- 上記のいずれにも該当しない場合に発行されるSQL…select * from table_name where age = ?
where,set,trim
where,set,trimは、少し厄介なSQLを操作したい場合に便利です。
例えば以下のようなSQLを見てみましょう。
  <select id="select2" resultType="src.mapper.entity.TableEntity">
    select
      *
    from
      table_name
    where
      <if test="id != null">
        id = #{id}
      </if>
      <if test="name != null">
        and name = #{name}
      </if>
      <if test="age != null">
        and age = #{age}
      </if>
  </update>
もしすべてのifに該当しなかった場合、select * from table_name whereというSQLが発行され構文エラーになってしまいます。
またパラメタ.nameがnullではなく、パラメタ.idとageがnullの場合はselect * from table_name where and name = ?となり、これまた構文エラーになってしまいます。
where
<where> ~ </where>を使用すると、内包するタグのいずれかが結果を返したときだけwhereを挿入してくれます。
またその内包するタグの結果がandまたはorから始まる場合にはそれらを削除してくれます!
なんて便利なんだ。。。
  <select id="select2" resultType="src.mapper.entity.TableEntity">
    select
      *
    from
      table_name
    <where>
      <if test="id != null">
        id = #{id}
      </if>
      <if test="name != null">
        and name = #{name}
      </if>
      <if test="age != null">
        and age = #{age}
      </if>
    </where>
  </select>
※<if> ~ </if>内の要素の末尾にandやorがあっても除去されないので注意してください!
 また非常に細かいですが、and(半角スペース)、or(半角スペース)が除去対象となるため、タブを使用すると正常に動かない場合があります。
set
こちらはwhereのset句版です。
<set> ~ </set>を使用すれば、余計な,を削除してくれます。
  <update id="update">
    update table_name 
    <set>
      <if test="name != null">
        name = #{name},
      </if>
      <if test="age != null">
        age = #{age},
      </if>
      <if test="department != null">
        department = #{department}
      </if>
    </set>
    where
      id = #{id}
  </update>
※whereとは違い、末尾の,を除去するので注意してください!
trim
上記で説明したwhereやsetを<trim> ~ </trim>に定義することによってカスタマイズが可能です。
可読性等を考えると微妙な気もしますが、知っておいて損はないと思います!
- 
<where> ~ </where>をtrimに置き換えた場合
  <select id="select2" resultType="src.mapper.entity.TableEntity">
    select
      *
    from
      table_name
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
      <if test="id != null">
        id = #{id}
      </if>
      <if test="name != null">
        and name = #{name}
      </if>
      <if test="age != null">
        and age = #{age}
      </if>
    </trim>
  </select>
- 
<set> ~ </set>をtrimに置き換えた場合
  <update id="update">
    update table_name 
    <trim prefix="SET" suffixOverrides=",">
      <if test="name != null">
        name = #{name},
      </if>
      <if test="age != null">
        age = #{age},
      </if>
      <if test="department != null">
        department = #{department}
      </if>
    </trim>
    where
      id = #{id}
  </update>
sql,include
例えば取得用SELECT文と更新用SELECT文が必要な場合に、
書いてみたら末尾にfor updateが付くか付かないかの差分しかなかった、みたいなことはありませんか?
そういった時に<sql> ~ <sql>と<include> ~ </include>を使用するとすっきりします!
  <sql id="selectBase">
    select
      *
    from
      table_name
    where
      id = #{id}
  </sql>
  <select id="select" resultType="src.mapper.entity.TableEntity">
    <include refid="selectBase" />
  </select>
  <select id="selectForUpdate" resultType="src.mapper.entity.TableEntity">
    <include refid="selectBase" />
    for update
  </select>
書き方Tips
より実践的な観点でメモをしていきます!
LIKE句を書きたい
MyBatisでは変数の書き方として二種類あります。
- 
#{param}…エスケープし、シングルクォートで囲います。
- 
${param}…エスケープしません。
やりがちなのが以下のような書き方です。(idで前方一致させたい場合)
  <select id="select3" resultType="src.mapper.entity.TableEntity">
    select
      *
    from
      table_name
    where
      id like '${id}%'
  </select>
この書き方でも正しいクエリは発行されますが、
先述した通りエスケープしてくれないのでSQLインジェクションの危険性があります。
各SQLが用意している文字列結合を使用するようにしましょう。
下記はOracleの場合です。
  <select id="select3" resultType="src.mapper.entity.TableEntity">
    select
      *
    from
      table_name
    where
      id like #{id} || '%'
  </select>
不等号を使いたい
MyBatisはXMLで記載するので不等号<>はメタ文字として扱われてしまい、
正常にクエリを読み込むことができません。
そんな時は<![CDATA[...]]>で囲えば使用可能です!
  <select id="select4" resultType="src.mapper.entity.TableEntity">
    <![CDATA[ 
    select
      *
    from
      table_name
    where
      create_at < #{date}
    ]]>
  </select>
ピンポイントで囲うよりもSQL全体を囲ってあげたほうが可読性は高いと思いますので、
個人的には上記のような書き方をお勧めします!
最後に
こうやってアウトプットしてみると、
いかに自分が使えているだけで理解していないかがよく分かりました。
今後も定期的に記事を投稿していこうと思いますのでまた機会がありましたらよろしくお願いいたします。
最後までお読みいただき、ありがとうございました!