2013年01月24日

設定ファイル無しで手軽・シンプルに使えるJava O/Rマッパー ICIQLの使い方

Java O/Rマッピング・フレームワークといえばiBATIS、Hibernate、S2JDBCあたりが昔から話題に上がりますが、細々した設定ファイル必要だったりと結局は手続き・設定が面倒になり、さんざん悩んだ挙句にApache/DbUtils(http://commons.apache.org/dbutils/)あたりに落ち着いたりします。

iciql3

でも、やっぱり、ちょっとしたユーティリティを作るような時にはもう少しハイレベルなORMフレームワークを使って短時間でコーディングを終らせたい。

今日は設定ファイル不要・メソッド・チェーンで直感的にDBアクセス・レイヤーを記述できるICIQLフレームワークを使ってコーディングしてみます。

ICIQLの概要と準備


ICIQLライブラリの特徴は次の通り。

  • Smallライブラリ(単一ファイルで完結、約125KBと小さなサイズ)
  • ModelベースでデザインされたJDBCラッパー
  • MySQL、HSQLDB、Derby、PostgreSQLデータベースに対応

スキーマの参照とアノテーションを用いることで最低限のコーディング量でデータにアクセスできるよう工夫されています。
 
また、メソッド・チェーンとSQLライクなSyntaxを組み合わせることで、何をしているコードなのか直感(SQL)的に把握出来るようになります。

必要なファイルはiciql-x.x.x.jarファイルだけ(勿論JDBCドライバは別途必要)。プロジェクト・ページからダウンロード(執筆時点で最新バージョンは1.1)し、クラスパスに追加しましょう。

今回はMySQLにmydbデータベースとITEMSテーブルを作成して評価します。

CREATE TABLE ITEMS (
  ID MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT,
  NAME VARCHAR(255) NOT NULL,
  PRICE INTEGER NOT NULL DEFAULT 0,
  QTY INTEGER NOT NULL DEFAULT 0,
  PRIMARY KEY(id)
);
  
適当なデータも入れておきましょうね。

cicql1


ICIQLの基本的な使い方、JDBCとのコード比較


まず、JDBCだけでitemsテーブルを全レコードを取得してみます。

	public List<Item> selectAllItems() throws InstantiationException,
			IllegalAccessException, ClassNotFoundException, SQLException {
		List<Item> items = new ArrayList<Item>();
		Connection conn = null;
		ResultSet rs = null;
		Statement stmt = null;
		try {
			Class.forName("com.mysql.jdbc.Driver").newInstance();

			conn = DriverManager.getConnection("jdbc:mysql://localhost/mydb",
					"root", "");
			conn.setAutoCommit(false);
			stmt = conn.createStatement();
			rs = stmt.executeQuery("SELECT * from items");
			while (rs.next()) {
				Item item = new Item();
				item.setId(rs.getInt("id"));
				item.setName(rs.getString("name"));
				item.setPrice(rs.getInt("price"));
				item.setQty(rs.getInt("qty"));
				items.add(item);
			}
			conn.commit();
		} finally {
			rs.close();
			stmt.close();
			conn.close();
		}

		return items;
	}

コード中にあるItemクラス(Bean)は省略しますが、大体こんなコードになりますよね。

何が辛いって手続きの多さと検査例外の多さ・・・。 何処かででこいつらをcatchするコードを書くのかと思うと気分も滅入ります。

これをICIQLを使って書いてみます。

まずは、テーブルに相当するモデル・クラスを用意して、

import com.iciql.Iciql.IQColumn;
import com.iciql.Iciql.IQTable;


@IQTable(name="items")
public class Item {

    @IQColumn(primaryKey = true)
    public Integer id;
    
    @IQColumn(length = 254, trim = true)
    public String name;
    
    @IQColumn
    public Integer price;
    
    @IQColumn(name = "QTY")
    public Integer quantity;
}

カラムに相当するフィールドには@IQColumnアノテーションを設定します。

@IQColumnアノテーション中ではPK、実際カラム名、値範囲、DEFAULT値などを定義出来ます(詳しくはこちら)。
※ICIQLはデータベースのスキーマを参照しており嘘を書くとエラーになるので注意しましょう。

これで次のコードで全レコードを参照出来ます。

	public List<Item> selectAllItems() {
		Db mydb = Db.open("jdbc:mysql://localhost/mydb","root", "");
		Item i = new Item();
		List<Item> items = mydb.from(i).select();
		
		return items;
	}

これは簡単で良いですね!、非検査例外(IciqlException)になるので、明示しない限りtry~catch文は必要ありませんし、何をしようとしているのかコードから直感的に判断出来ます。

ご想像の通り、メソッド・チェーンの最後がselect()になるのはこの時点でSQLが確定するからで、select()の代わりにtoSQL()を呼び出すと実行するSQLを確認することも出来ます。

where句を使った条件指定も簡単に書けますよ。

		Db mydb = Db.open("jdbc:mysql://localhost/mydb","root", "");
		Item i = new Item();
		List<Item> items = mydb.from(i).where(i.price).atLeast(200).select();
		for (Item item: items) {
			System.out.println(item.id+","+item.name+","+item.price+","+item.quantity);
		}		
		mydb.close();

実行結果をみると、200円以上のアイテムだけが結果セットに含まれています。

1,りんご,300,10
2,みかん,200,10

INSERT、UPDATEはこうなります。

		// single insert
		mydb.insert(new Item(...));

		// single update
		mydb.update(new Item(...));

		mydb.insertAll(new ArrayList<Item>(
				Arrays.asList(new Item(...), new Item(...))));

		// batch update.
		mydb.updateAll(new ArrayList<Item>(
				Arrays.asList(new Item(...), new Item(...))));


JOINや複雑なSQLはどうするの?


結合するなら、結合後の表に相当するクラスを一つ用意します。基本はテーブルと同じ。

ここではITEMS.IDとITEM_IDで関係付いたORDERSテーブルがある前提で次のようなクラスを用意してみます。

@IQTable
public class OrderItem {

	@IQColumn
	public Integer order_id;
	
	@IQColumn
	public Integer cust_id;
	
	@IQColumn
	public Integer item_id;
	
	@IQColumn
	public Integer num_orders;

	@IQColumn
	public String item_name;
	
	public OrderItem() {
	}
	
	public OrderItem(Integer order_id, Integer cust_id, Integer item_id,
			Integer num_orders, String item_name) {
		this.order_id = order_id;
		this.cust_id = cust_id;
		this.item_id = item_id;
		this.num_orders = num_orders;
		this.item_name = item_name;
	}
}

実際にJOINする場合コードは次のようになります。

		Db mydb = Db.open("jdbc:mysql://localhost/mydb", "root", "");
		final Item i = new Item();
		final Order o = new Order();

		List<OrderItem> orderItems = mydb.from(i).innerJoin(o).on(i.id)
				.is(o.item_id).orderBy(o.quantity).select(new OrderItem() {
					{
						order_id = o.id;
						cust_id = o.cust_id;
						item_id = i.id;
						num_orders = o.quantity;
						item_name = i.name;
					}
				});

		for (OrderItem oi : orderItems) {
			System.out.println(oi.order_id + "," + oi.cust_id + ","
					+ oi.item_name + "," + oi.num_orders);
		}

		mydb.close();

さらに複雑な場合には、JDBCと同じくexecuteQuery, ResultSetパターンを使うことも出来ます。

		Db mydb = Db.open("jdbc:mysql://localhost/mydb","root", "");
		ResultSet rs = mydb.executeQuery("select * from * * *");
		try {
			while (rs.next()) {
			  /* code */
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			mydb.close();
		}


トランザクション管理は?


本格的に使うならやっぱりトランザクション管理は必要。 ICIQLでは・・・無いんです。

えっ、ええええー! と僕も思いましたよ・・・。参照用途ぐらいにしか使えないかと思ったんですが、実はGitHubでトランザクション管理機能を追加したコードが公開されています。

IciqlException & Transactions by bartolomiew ・ Pull Request #4 ・ gitblit/iciql ・ GitHub

こちらのコードを使うか、次期ICIQLバージョン(1.2以上)での正式実装を待つことになりそうです。



Javaデータアクセス実践講座 (DB Magazine SELECTION)
松信 嘉範
翔泳社
売り上げランキング: 197,650

JDBCによるJavaデータベースプログラミング 第2版
ジョージ リース
オライリー・ジャパン
売り上げランキング: 496,668

Posted by netbuffalo at 22:30│Comments(0)TrackBack(0)Java | プログラミング


この記事へのトラックバックURL

http://trackback.blogsys.jp/livedoor/netbuffalo/4350590

コメントする

名前
URL
 
  絵文字