テンプレートエンジンを駆使すれば、
簡単に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;
}
}