MyBatisをNoXMLで使う

# 目的

MyBatisは通常XMLで各種定義を行いますが、XMLを使わないことも可能です。

# 手順

## SqlSessionFactoryの取得

### CDI+JNDIの場合

Java EEサーバ上で動かす場合はJNDIルックアップと組み合わせます。JNDI定義は例えば次の記事が参考になります。

https://qiita.com/teruq/items/7705c2f82e17a7e17d12

```
    @Resource(lookup = "jdbc/********") // JNDI名
    private DataSource dataSource;

    @Produces
    public SqlSessionFactory produceSQLSessionFactory() {
        var config = new Configuration();
        config.addMappers("package.to.mappers"); // Mapperクラスが存在するパッケージ
        config.setEnvironment(new Environment("development", new JdbcTransactionFactory(), dataSource));
        config.setLocalCacheScope(LocalCacheScope.STATEMENT); // キャッシュなし
        config.setMapUnderscoreToCamelCase(true); // SNAKEをCAMELに
        var sqlSessionFactory = new SqlSessionFactoryBuilder().build(config);

        return sqlSessionFactory;
    }
```

### 手動で生成する場合

JUnitやスタンドアロンアプリでCDI+JNDIが使えない場合。ただしPGPoolingDataSourceはdeprecatedなので、正式に使う場合はHikari CPなどを挟むとよいでしょう。

```
    public static SqlSessionFactory getSqlSessionFactory() throws Exception {
        //（注）PGPoolingDataSourceの使用は現在推奨されていない
        var dataSource = new PGPoolingDataSource();
        dataSource.setURL("jdbc:postgresql://localhost:5432/********");
        dataSource.setUser("postgres");
        dataSource.setPassword("********");
        dataSource.setProperty("options", "-c lock_timeout=10000");

        var config = new Configuration();
        config.addMappers("package.to.mappers"); // Mapperクラスが存在するパッケージ
        config.setEnvironment(new Environment("development", new JdbcTransactionFactory(), (DataSource) dataSource));
        config.setLocalCacheScope(LocalCacheScope.STATEMENT); // キャッシュなし
        config.setMapUnderscoreToCamelCase(true); // SNAKEをCAMELに
        
        return new SqlSessionFactoryBuilder().build(config);
    }
```

## Mapperクラスの作成

好みの分かれるところですが、SQLはアノテーションで指定できます。Spring Dataを真似して次のようなリポジトリクラス（マッパー）を用意します。このクラスは先ほどaddMappers()で指定したパッケージに配置します。

```
package package.to.mappers;

import java.util.Optional;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.apache.ibatis.cursor.Cursor;

public interface SampleRepository {

    @Select("SELECT id,xxx,yyy_at FROM samples")
    public Cursor<Sample> findAll();

    @Select("SELECT id,xxx,yyy FROM users WHERE id=#{id}")
    public Optional<Sample> findById(int id);

    @Insert("INSERT INTO samples (xxx,yyy) VALUES (#{xxx},#{yyy})")
    public void create(Sample sample);

    @Update("UPDATE samples SET xxx=#{xxx},yyy=#{yyy} WHERE id=#{id}")
    public void update(Sample sample);

    @Delete("DELETE FROM samples WHERE ID=#{id}")
    public void delete(User user);

    @Delete("DELETE FROM samples WHERE ID=#{id}")
    public void deleteById(int id);

}
```

対応するエンティティクラスも用意しておきます。

```
import lombok.Data;

@Data
public class Sample {

    private int id;
    private String xxx;
    private String yyy;

}
```

## 使い方

次のようにリポジトリ（マッパー）クラスを取得し、エンティティの出し入れをします。

```
    @Inject
    private SqlSessionFactory sqlSessionFactory;

    public void createSample() {
        try (var session = sqlSessionFactory.openSession()) {
            var sampleRepository = session.getMapper(SampleRepository.class);
            var sample = new Sample();
            sample.setXxx("aaa");
            sample.setYyy("bbb");
            sampleRepository.create(sample);
            session.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
```

# メリット

* どこでどのSQLを使っているか一目瞭然
* XMLファイルに悩まされなくてよい
* チーム開発で競合しにくい

# デメリット

* アノテーションは改行できないので、SQLが長くなると読みづらくなる
* 素直にSpring Data使えばという気になってくる