#iciqlとは?
iciqlはJavaのO/R Mappingで,特徴は軽量なSQL DSL(domain-specific language: ドメイン固有言語)です。とても可読性が高い文法が魅力的なので,紹介します。
##iciql SQL DSLの例
iciql SQL DSLの例を見てみましょう。
Senryu t = new Senryu();
List<Senryu> dbData =
db
.from(t)
.where(t.category).oneOf("09", "15")
.and(t.sql).like("%SELECT%")
.and(t.count).lessThan(1)
.orderByDesc(t.updatedate)
.orderBy(t.idSenryu)
.select();
次のSQLが発行されます。
SELECT *
FROM senryu
WHERE category IN( '09', '15' )
AND sql LIKE '%SELECT%'
AND count < 1
ORDER BY updatedate DESC,
id_senryu
##Fluent Interface
iciqlの文法が懐かしい開発者も多いと思います。これは,2005年にEric EvansとMartin Fowlerが提唱した「流れるようなインタフェース」(fluent interface)が源流で,2007年にSeasarのS2JDBに採用されたり,Ruby on RailsのActive Record,最近ではScalaのSlickなど,Web開発者には馴染みがある文法のためです。
##SQL DSLのメリット
SQL DSLのメリットは,**タイプセーフ(type-safe)**の一言に尽きます。
テーブルが変更された場合に,JavaとSQLとの境界が文字列のORMだと,実行時エラーまで気づかないことがあります。SQL DSLでタイプセーフに記述すると,コンパイルエラーで気づくことができます。
##iciqlのメリット
iciqlのメリットは,jarファイルひとつで動作することです。
- jarファイルひとつで軽量動作(依存なし)
- 設定ファイル不要
- OSS(活発ではない)
- タイプセーフ&流れるようなインタフェース
- DBからモデルを生成するツールもある(公式ページの解説は古い)
流れるようなインタフェースでタイプセーフのJava ORMは,S2JDBCだとSeasar依存,jOOQは有料など,それぞれ偏りがあります。iciqlはバランスが良いです。
#SQL DSL x Java 8 ラムダ式
テーブルモデルのSQL DSLとラムダ式を組み合わせは,コードの相性が良く,可読性が向上します。
Java JDBC SQL周りのリファクタリングに興味を持たれた方は,GitHubに掲載したiciql x Java 8 ラムダ式のソースコードも是非ご参照ください。
public static void main(String[] args) {
Db db = null;
try {
/*==================================================
* Instantiating a Db
*==================================================*/
String url = "jdbc:postgresql://localhost:5432/sqlsenryu";
String user = "sqlsenryu";
String password = "sqlsenryu";
db = Db.open(url, user, password);
/*==================================================
* SQL DSL > Select Statements
*==================================================*/
Senryu t = new Senryu();
List<Senryu> dbData =
db
.from(t)
.where(t.category)
.oneOf(
CommonConst.Category.SQLite,
CommonConst.Category.PostgreSQL)
.and(t.sql).like("%SELECT%")
.and(t.count).lessThan(1)
.orderByDesc(t.updatedate)
.orderBy(t.idSenryu)
.select();
/*==================================================
* Lambda Expression
*==================================================*/
dbData
.stream()
.filter(
x -> x.category
.equals(CommonConst.Category.PostgreSQL))
.forEach(y -> {
print(y);
update(y);
});
dbData
.stream()
.filter(x -> x.category.equals(CommonConst.Category.SQLite))
.forEach(y -> {
print(y);
});
/*==================================================
* SQL DSL > Update Statements
*==================================================*/
db.updateAll(dbData);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (db != null) db.close();
}
}
private static void update(Senryu senryu) {
senryu.updatedate = new Timestamp(System.currentTimeMillis());
senryu.count++;
}
private static void print(Senryu senryu) {
System.out.println(senryu.toString());
}
#参考資料
- iciql, http://iciql.com/
- GitHub, iciql, https://github.com/gitblit/iciql
- Seasar2, 「タイプセーフAPI」, http://s2container.seasar.org/2.4/ja/s2jdbc_typesafe.html
- jOOQ, http://www.jooq.org/
- Slick, http://slick.typesafe.com/
- Martin Fowler's Bliki,「流れるようなインタフェース」, http://capsctrl.que.jp/kdmsnr/wiki/bliki/?FluentInterface
#付録
- iciqlの開発者は,ひとつのwarファイルデプロイでGitサーバを手軽に構築できる"gitblit"を開発したJames Moger氏です。2011年に公開され,最新リリースは2014-11-10のVer.1.5.0です。
- iciqlの読みは「イシキュエル」説。 イシキュエル -> シークェル -> SQLを有力視しています。
- iciqlのテーブルモデルはアノテーションを使用してテーブルとカラムの属性を定義します。設定ファイルレスが良いです。
@IQTable(
create = false,
name = "senryu",
memoryTable = false,
annotationsOnly = true,
inheritColumns = false)
public class Senryu {
@IQColumn(
primaryKey = true,
name = "id_senryu",
trim = false,
scale = 0,
length = 0,
nullable = false,
defaultValue = "",
autoIncrement = false)
public long idSenryu;
@IQColumn(
primaryKey = false,
name = "category",
trim = false,
scale = 0,
length = 2,
nullable = false,
defaultValue = "",
autoIncrement = false)
public String category;
public String toString() {
return String.format(
"%d %s %s",
idSenryu,
category);
}
}
‡SQL DSL以前の世界は・・・
StringBuffer sql = new StringBuffer();
sql.append("SELECT * ");
sql.append("FROM senryu ");
sql.append("WHERE category IN( ?, ? ) ");
sql.append(" AND sql LIKE ? ");
sql.append(" AND count < ? ");
sql.append("ORDER BY updatedate DESC, ");
sql.append(" id_senryu");
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "09");
pstmt.setString(2, "15");
pstmt.setString(3, "%SELECT%");
pstmt.setInt(4, 1);