出会い
vol.002に書いた通り、自分にとっては初のコーディングPJで出会った。
自分が出会った当時は、iBATISと呼ばれていて後に後継としてMyBatisになっている。
MyBatisとは
MyBatis(マイバティス)とは、iBATIS(アイバティス)の後継プロジェクトで、マッピングファイルにSQL文を直接記述し「オブジェクトとSQL実行結果との間」でマッピングを行うという特徴を持つ、Javaおよび.NET Frameworkを対象とするORマッピングライブラリです。オープンソースのフレームワーク/MyBatisとは
- JDBCだとSQLをJavaのクラスに書くことになるため、その点、密結合な状態だった。
- また、ResultSetから取得結果を取り出して、クラスにマッピングする分、手間があった。
- MyBatisだとSQLを外部xmlファイルに記述することで、クラスとSQLを疎結合な状態にできる。
- また、クラスへのマッピングはルールに基づいて記述すれば、自動マッピングしてくれる。
- つまり、JDBCに比べるとMyBatisはちょっと気の利くやつである。
振り返ってみて
2012年頃の経験に基づくが、SQLを外部xmlファイルに記述しているメリットもあったが、デメリットもあったように思う。
【メリット】
使用するSQLが特定のxmlにまとまっているため、SQLのみを一括修正したりするときに楽だった。
当時の現場では、機能単位ごとにxmlを分割していたので、調査・修正の際にSQLにたどり着くのは早かった気がする。
【デメリット】
動的にSQLを生成しないので検索パターンごとにSelectのクエリーを用意することになっていた。
検索条件のパターンが多いものほど不毛さを感じた。※現在、MyBatisでは動的SQLをサポートしている。
当時のPJの先輩が「設計書から自動でSQLの外部ファイル生成」するExcelマクロを用意していたのを思い出した。
静的にSQLをxmlに定義しておいて、それを利用するのがMyBatisの良い使い方だと思う。
「動的にSQLを生成する」ことと「静的にSQLを定義しておく」ことを天秤にかけて、機能要件次第で採用判断になるのかなと思う。
経験上、Webアプリの検索機能でよっぽどシンプルな検索条件でもない限り、多数の検索クエリーに対応することになる。パターンごとにSQLを静的に用意しておくと、SQLが爆裂するのでどうしても動的にSQLを生成することを検討せざるをえない。
個人的には、SQLが外部ファイル化されているメリットってそんなに感じないので、動的SQLに対応していたとしてもMyBatisは使わない気がする。ただ、よっぽど大きなPJで「SQLだけ書くDB寄りのチーム」と「Javaでコーディングするチーム」で分けた方が生産性が高くなるなら採用するのかもしれない。
サンプルで実装してみる。
以下を参考にさせていただきました。
package sample.mybatis.java.dao;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import sample.mybatis.java.dao.mapper.WorkerMapper;
import sample.mybatis.java.model.Worker;
public class SampleDao {
	public static SampleDao instance = new SampleDao();
	private SampleDao () {
	}
	public List<Worker> select() throws SQLException, IOException {
		List<Worker> workerList = null;
		try (var in = Resources.getResourceAsStream("mybatis-config.xml")) {
            var factory = new SqlSessionFactoryBuilder().build(in);
            try (var session = factory.openSession()) {
            	var mapper = session.getMapper(WorkerMapper.class);
                workerList = mapper.selectAll();
            }
        }
        return workerList;
	}
	public void insert() throws SQLException, IOException {
		try (var in = Resources.getResourceAsStream("mybatis-config.xml")) {
            var factory = new SqlSessionFactoryBuilder().build(in);
            try (var session = factory.openSession()) {
            	var mapper = session.getMapper(WorkerMapper.class);
            	var worker = new Worker("0001", "k.jarrett", 73, "music");
            	mapper.insert(worker);
            	session.commit();
            }
        }
	}
	public void update() throws SQLException, IOException {
		try (var in = Resources.getResourceAsStream("mybatis-config.xml")) {
            var factory = new SqlSessionFactoryBuilder().build(in);
            try (var session = factory.openSession()) {
            	var mapper = session.getMapper(WorkerMapper.class);
            	var worker = new Worker("0001", "k.jarrett", 74, "music");
            	mapper.update(worker);
            	session.commit();
            }
        }
	}
	public void delete() throws SQLException, IOException {
		try (var in = Resources.getResourceAsStream("mybatis-config.xml")) {
            var factory = new SqlSessionFactoryBuilder().build(in);
            try (var session = factory.openSession()) {
            	var mapper = session.getMapper(WorkerMapper.class);
            	var worker = new Worker("0001", "k.jarrett", 74, "music");
            	mapper.delete(worker);
            	session.commit();
            }
        }
	}
}
<?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="sample.mybatis.java.dao.mapper.WorkerMapper">
  <resultMap id="workerResultMap" type="sample.mybatis.java.model.Worker">
  	<result property="id" column="id" />
  	<result property="name" column="name" />
 	<result property="age" column="age" />
 	<result property="department" column="department" />
  </resultMap>
  <select id="selectAll" resultMap="workerResultMap">
    select * from worker
  </select>
  <insert id="insert" parameterType="sample.mybatis.java.model.Worker">
    insert into worker
    values (
      #{id},
      #{name},
      #{age},
      #{department}
    )
  </insert>
  <update id="update" parameterType="sample.mybatis.java.model.Worker">
    update worker
    set name = #{name},
        age = #{age},
        department = #{department}
    where id =  #{id}
  </update>
  <delete id="delete" parameterType="sample.mybatis.java.model.Worker">
    delete from worker
    where id = #{id}
  </delete>
</mapper>
package sample.mybatis.java.dao.mapper;
import java.util.List;
import sample.mybatis.java.model.Worker;
public interface WorkerMapper {
	List<Worker> selectAll();
	void insert(Worker worker);
	void update(Worker worker);
	void delete(Worker worker);
}
サンプルプロジェクトごとGitHubにプッシュしています。
https://github.com/TsJazz27Sumin/mybatisSample/tree/master/src
サンプルで実装してみて感想。
- Javaのクラスとxmlを行ったり来たりするのがめんどくさい。
- 一度、設定すれば変更の手間はそんなにかからないのだろうけど、やっぱり設定多い。
- だいぶアノテーションを使って楽にできるように進化しているみたいだけど、これならJPAで良い気がする。(※MyBatis Mapper アノテーションの使い方)
- リフレクションのエラー:java.lang.reflect.InaccessibleObjectException: Unable to make ~にハマッた。
- 
module-info.javaにexports xxx;を追加すればいいだけだけど、すぐ分からなかった。。