config XMLを記述しない mybatis を目指して。。。

過去、、いろいろ試行錯誤を書いていた。。
mybatis XML設定を使わない場合 - Oboe吹きプログラマの黙示録

mybatis xml設定ファイル使わない場合の補足 - Oboe吹きプログラマの黙示録

【再興】mybatis XML設定を使わない場合 - Oboe吹きプログラマの黙示録

mybatis config のXMLを書かない - Oboe吹きプログラマの黙示録

XMLMapperBuilder を使ってみる。 - Oboe吹きプログラマの黙示録

改めて、config XMLを記述しない、spring - mybatis に依存しない、
SQL Map XML を書かなかったり、書いた場合でも、Mapper インターフェースクラスのパッケージ階層に
XMLを置いたり、別のリソース クラスパスで読める場所に置いたりする場合にも
対応できるものを作りたかった。ようやく、そういう曖昧さに対応する形を作った。
先日の「XMLMapperBuilder を使ってみる。」 が、かなりヒントになった。
ちゃんと Mapper の XMLディレクトリの深さに関係なく探すようにするのである。
XMLを探す分のパフォーマンスが悪くなるのは覚悟の上である。。。Springが充分に起動が遅いではないか。。)

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import Objects.requireNonNull;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import javax.sql.DataSource;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * SqlWorker
 */
public class SqlWorker{
    private SqlSessionFactory factory;

    public SqlWorker(DataSource dataSource, String path, Class<?>... mapperClasses) {
        Objects.requireNonNull(path);
        Environment environment = new Environment("deployment", new JdbcTransactionFactory(), dataSource);
        Configuration config = new Configuration(environment);
        // snake Case → camel Case
        config.setMapUnderscoreToCamelCase(true);
        for(Class<?> cls: mapperClasses){
            config.addMapper(cls);
        }
        List<File> list = getMapperCanditateFiles(new File(Thread.currentThread().getContextClassLoader().getResource("./"+path).getPath()));
        list.stream().forEach(f->{
            try(InputStream input = new FileInputStream(f)){
               new XMLMapperBuilder(input, config, f.getAbsolutePath(), config.getSqlFragments()).parse();
           }catch(Exception e){
           }
        });
        factory = new SqlSessionFactoryBuilder().build(config);
    }
    private List<File> getMapperCanditateFiles(File file) {
        List<File> list = new ArrayList<>();
        for(File f:file.listFiles()) {
            if (f.isDirectory()){
                searchMapFiles(f, list);
            }else if(f.getAbsolutePath().endsWith(".xml")){
                list.add(f);
            }
        }
        return list;
    }
    private void searchMapFiles(File file, List<File> list){
        for(File f:file.listFiles()) {
            if (f.isDirectory()){
                searchMapFiles(f, list);
            }else if(f.getAbsolutePath().endsWith(".xml")){
                list.add(f);
            }
        }
    }
    public SqlSession getSqlSession(){
        return factory.openSession();
    }
    public void execution(Consumer<SqlSession> consumer) {
        SqlSession session = factory.openSession();
        try(session){
            consumer.accept(session);
            session.commit();
        }catch(Exception e){
            Logger logger = LoggerFactory.getLogger(getClass());
            logger.error(e.getMessage(), e);
            session.rollback();
            throw new RuntimeException(e);
        }
    }
    public void execution(Consumer<SqlSession> consumer, BiConsumer<SqlSession, Throwable> error) {
        SqlSession session = factory.openSession();
        try(session){
            consumer.accept(session);
            session.commit();
        }catch(Exception e){
            Logger logger = LoggerFactory.getLogger(getClass());
            logger.error(e.getMessage(), e);
            session.rollback();
            error.accept(session, e);
            throw new RuntimeException(e);
        }
    }
    public <R> R getObject(Function<SqlSession, R> function) {
        try(SqlSession session = factory.openSession()){
            return function.apply(session);
        }catch(Exception e){
            Logger logger = LoggerFactory.getLogger(getClass());
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }
}

Mapper クラスを SqlWorker のコンストラクタで指定しなくても、resources として
読み込める場所に SQL Map XMLを配置すれば、自動的に読んでくれる。
XMLがなくて Mapper インターフェースクラスだけなら、コンストラクタで指定すれば良い。
コンストラクタで指定する Mapperクラスを書くこと。
resources に置くXMLの場所を問わない。ということである。
DataSource をコンストラクタで渡すようにしている。

開発プロジェクトによって策定される様々なルールで、spring-mybatis で書くあの面倒くさい設定に
つき合わされるのが厭だった。のである。

コンストラクタで指定するDataSource は、
プールを使わないなら、

UnpooledDataSource dataSource = new UnpooledDataSource();
dataSource.setDriver("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/testDB");
dataSource.setUsername("root");
dataSource.setPassword("password");

// sql/ 配下にSQLMap xml がある。 "/" を指定すればツリー全て探してくれる。
SqlWorker worker = new SqlWorker(dataSource, "sql/", SampleMapper.class, ItemMapper.class);
try(SqlSession session = worker.getSqlSession()){
    // TODO
}

プールを使うなら、( org.apache.ibatis.datasource.pooled.PooledDataSource

PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/testDB");
dataSource.setUsername("root");
dataSource.setPassword("password");
dataSource.setPoolMaximumActiveConnections(10);
dataSource.setPoolMaximumCheckoutTime(10000);
dataSource.setPoolMaximumIdleConnections(4);
dataSource.setPoolMaximumLocalBadConnectionTolerance(4);
dataSource.setPoolPingConnectionsNotUsedFor(4);
dataSource.setPoolPingEnabled(true);
dataSource.setPoolPingQuery("SELECT 1");
dataSource.setPoolTimeToWait(10000);

SqlWorker worker = new SqlWorker(dataSource, "sql/", SampleMapper.class, ItemMapper.class);
try(SqlSession session = worker.getSqlSession()){
    // TODO
}

Spring-Boot と合わせて良く使われるプール、HikariCPを使うなら、、
com.zaxxer:HikariCP 5.0.1

<dependency>
  <groupId>com.zaxxer</groupId>
  <artifactId>HikariCP</artifactId>
  <version>5.0.1</version>
</dependency>
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/testDB?serverTimezone=JST");
dataSource.setUsername("root");
dataSource.setPassword("password");
dataSource.setAutoCommit(false);
dataSource.setMaximumPoolSize(10);
dataSource.setMinimumIdle(4);
dataSource.setConnectionTestQuery("SELECT 1");

SqlWorker worker = new SqlWorker(dataSource, "sql/", SampleMapper.class, ItemMapper.class);
try(SqlSession session = worker.getSqlSession()){
    // TODO
}