LoginSignup
2
1

More than 5 years have passed since last update.

永続化FWを自作する(Java)

Last updated at Posted at 2018-01-22

テンプレートエンジンを駆使すれば、
簡単にMyBatisやDomaみたいな、SQLを外部ファイルに持たせた永続化FWを作れるのでは?
と考え作ってみました。

以下のようなコードだけでuser_mstのデータを取得できるようになりました。

selectById.sql
select *
from user_mst
where id = {{ p.bind(id) }}
user_mst取得するJavaコード
SqlTemplate sqlTemplate = new SqlTemplate();
DbAccess dbAccess = new DbAccess(connection);//java.sql.Connectionです

Map<String, Object> context = new HashMap<>();
context.put("id", id);
QueryBuilder query = sqlTemplate.evaluate( "selectById.sql", context);
UserMst userMst = dbAccess.selectOne( query, UserMst.class );

テンプレートエンジンは今回はPebbleを使います。
が、Apache VelocityでもFreeMarkerでもMustache.javaでも多分できると思うので、
好きなテンプレートエンジンでいいと思います。
要はテンプレートの中でメソッドを呼び出せればいいです。
JDBCを直接扱うのは面倒なので、Apache Commons DBUtilsを使います。
値のセットはリフレクションを駆使して、自動的にセットします。

build.groovy
// https://mvnrepository.com/artifact/com.mitchellbosecke/pebble
compile group: 'com.mitchellbosecke', name: 'pebble', version: '2.4.0'
// https://mvnrepository.com/artifact/commons-dbutils/commons-dbutils
compile group: 'commons-dbutils', name: 'commons-dbutils', version: '1.7'
SQL用テンプレートエンジン
/** SQL用テンプレートエンジン */
import com.mitchellbosecke.pebble.PebbleEngine;
import com.mitchellbosecke.pebble.template.PebbleTemplate;
import java.io.StringWriter;

public class SqlTemplate{
    private static PebbleEngine engine;
    /** テンプレートエンジンを取得します */
    private PebbleEngine getEngine(){
        if( null == engine){
            engine = new PebbleEngine.Builder()
                    .autoEscaping(false)//htmlエスケープは余計なので外す
                    .strictVariables(true)//contextにない変数使おうとすると例外発生させる設定
                    .build();
        }
        return engine;
    }

    /** sqlテンプレート名、テンプレート用の値を元にクエリを取得 */
    public QueryBuilder evaluate(String templateFileName, Map<String, Object> context) throws Exception{
        SqlParametors sqlParametors = new SqlParametors();
        context.put( "p", sqlParametors );
        String result = simpleEvaluateTemplate(templateFileName, context );
        QueryBuilder query = new QueryBuilder( result, sqlParametors.getParams() );
        return query;
    }

    /** テンプレートを評価して、結果の文字列返すだけのメソッド */
    private String simpleEvaluateTemplate(String templateFileName, Map<String, Object> context) throws Exception{
        StringWriter writer = new StringWriter();
        PebbleTemplate template = getEngine().getTemplate(templateFileName);
        template.evaluate(writer, context);
        return writer.toString();
    }
}
sqlパラメータ
import java.util.List;

/** sqlパラメータ格納用クラス.テンプレートエンジン内でアクセスします。 */
public class SqlParametors {
    /** パラメーター */
    private List<Object> params = new ArrayList<>();
    /** パラメーターをバインドします */
    public String bind(Object param){
        params.add(param);
        return "?";
    }
    /** パラメーター取得 */
    public List<Object> getParams(){
        return params;
    }
}
クエリ格納用クラス
import java.util.List;
import java.util.Arrays;

/** クエリ格納用クラス */
public class QueryBuilder {
    /** sql文 */
    private StringBuilder sql = new StringBuilder();
    /** パラメーター */
    private List<Object> params = new ArrayList<>();

    public QueryBuilder(){
    }

    public QueryBuilder(CharSequence sql, List<Object>params){
        this.sql.append(sql);
        this.params.addAll(params);
    }

    public StringBuilder getSql() {
        return sql;
    }

    public void setSql(CharSequence sql) {
        this.sql = new StringBuilder( sql.toString() );
    }

    public Object[] getParams() {
        return params.toArray();
    }

    public void setParams(Object[] params) {
        this.params = Arrays.asList(params);
    }


}
DBアクセス
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/** DBアクセスクラス。QueryBuilderオブジェクトを引数にDB操作する */
class DbAccess{
    /** クエリ実行(dbutils) */
    private QueryRunner queryRunner = new QueryRunner();

    /** コネクション */
    private java.sql.Connection conn;

    /** コンストラクタ */
    public DbAccess(java.sql.Connection conn){
        this.conn = conn;
    }

    /** insert文やupdate文用 */
    public int update(QueryBuilder query ) throws Exception{
        int rows = queryRunner.update(conn, query.getSql().toString(), query.getParams() );
        return rows;
    }

    /** selectしてその結果をリストで取得したいとき用 */
    public <T> List<T>  selectList(QueryBuilder query, Class<T> clazz) throws Exception{
        ResultSetHandler<List<Map<String, Object>>> h = new MapListHandler();
        List<Map<String, Object>> mapList = queryRunner.query(conn, query.getSql().toString(), h, query.getParams() );
        List<T> beanList = convMapList2BeanList(mapList, clazz);
        return beanList;
    }

    /** selectしてその結果を1行だけ取得したいとき用 */
    public <T> T selectOne(QueryBuilder query, Class<T> clazz) throws Exception{
        ResultSetHandler<Map<String, Object>> h = new MapHandler();
        Map<String, Object> map = queryRunner.query(conn, query.getSql().toString(), h ,query.getParams());
        if( null == map )
            return null;
        T bean = convMap2Bean(map, clazz );
        return bean;
    }

    /** selectしてその結果を1列だけ取得したいとき用 */
    public <T> T selectOneColumn(QueryBuilder query, Class<T> clazz) throws Exception{
        Object value = queryRunner.query(conn, query.getSql().toString(), new ScalarHandler<T>(), query.getParams() );
        return clazz.cast(value);
    }

    /**
     * MapオブジェクトリストをBeanリストへセットする.
     * @param mapList Mapオブジェクトのリスト
     * @param clazz セット先Beanクラス
     * @throws Exception
     */
    private <T> List<T> convMapList2BeanList(List<Map<String, Object>> mapList, Class<T> clazz) throws Exception{
        List<T> list = new ArrayList<>();
        for( int i = 0; i < mapList.size(); i++ ){
            list.add( convMap2Bean(mapList.get(i), clazz) );
        }
        return list;
    }

    /**
     * MapオブジェクトをBeanへセットする.
     * @param map Mapオブジェクト
     * @param clazz セット先Beanクラス
     * @throws Exception
     */
    private <T> T convMap2Bean(Map<String, ?> map, Class<T> clazz) throws Exception{
        T bean = clazz.newInstance();
        //フィールド一覧取得
        Field[] fields = clazz.getDeclaredFields();

        for( Field f : fields ){
            f.setAccessible(true);
            f.set(bean, map.get(f.getName()) );//TODO スネークケース=>キャメルへの変換処理入れてもいいですね。
        }
        return bean;
    }
}

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1