ORM比較(6) - iBATIS

iBATISは、XMLSQLを記述しJavaのBeanに対して自動的に値を設定するORマッピングになっています。どんなSQLになるかは分かりやすいですが、XMLJavaとの同期を取る方法は特には用意されていないので、管理が必要になります。また、このプロジェクトもApacheプロジェクトのうちの1つになっています。同一のプロジェクトで .NETRubyにも対応しているようです。設定が使いまわせるんですかね?
また、新規に開発されている Ver3ではJPAに対応することをうたっていますが、まだstableになっていないので、Ver2を使用しています。
公式ページは http://ibatis.apache.org になります。

動かすために必要なライブラリは、以下の5つだけです。

  • ibatis-2.3.4.726.jar
  • commons-dbcp-1.2.2.jar
  • commons-logging-1.1.jar
  • log4j-1.2.13.jar
  • postgresql-8.3-603.jdbc4.jar

それでは実際のソースを。
Entityは JavaBeanの体裁をとる必要があります。列名とプロパティ名の対応はXMLに記述するため、特に制限はありません。

ユーザマスタ(テーブル名はHUser)のEntity

public class HUser {
	private String userId;
	private String password;
	private String userGroupId;

	public String getUserId() {
		return userId;
	}
	public void setUserId(String userId) {
		this.userId = userId;
	}

	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}

	public String getUserGroupId() {
		return userGroupId;
	}
	public void setUserGroupId(String userGroupId) {
		this.userGroupId = userGroupId;
	}
}

ユーザグループ(テーブル名はHUserGroup)のEntity

public class HUserGroup {
	private String userGroupId;
	private List<HUser> users = new ArrayList<HUser>();

	public String getUserGroupId() {
		return userGroupId;
	}
	public void setUserGroupId(String userGroupId) {
		this.userGroupId = userGroupId;
	}

	public List<HUser> getUsers() {
		return users;
	}
	public void setUsers(List<HUser> users) {
		this.users = users;
	}
}


次に、XMLの設定を読み込む部分をSingltonな実装で書きます。設定ファイル名を定数で記述していますが、別途渡すようにした方がいいかもしれません。

public class SqlConfigFactory {
	private static final String CONFIG_FILE = "SqlMapConfig.xml";
	private static final SqlMapClient sqlMap;
	static {
		try {
			Reader reader = Resources.getResourceAsReader(CONFIG_FILE);
			sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
		} catch (IOException ex) {
			ex.printStackTrace();
			throw new InstantiationError(ex.toString());
		}
	}

	public static SqlMapClient getSqlMapInstance() {
		return sqlMap;
	}
}

さて、続いて設定ファイルのXMLを見ていきます。
まずはメインの接続設定を記述するXMLが、SqlMapConfig.xml となります。だいたい見た通りです。最後の sqlMapタグが重要で、各SQLを記述しているxmlをここに書いておきます。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig
	PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"
	"http://ibatis.apache.org/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
	<settings
		cacheModelsEnabled="true"
		enhancementEnabled="true"
		lazyLoadingEnabled="false"
		maxRequests="32"
		maxSessions="10"
		maxTransactions="5"
		useStatementNamespaces="false"
	/>
	<transactionManager type="JDBC" commitRequired="false">
		<dataSource type="SIMPLE">
			<property name="JDBC.Driver" value="org.postgresql.Driver"/>
			<property name="JDBC.ConnectionURL" value="jdbc:postgresql://192.168.3.20:5432/sample_test"/>
			<property name="JDBC.Username" value="postgres"/>
			<property name="JDBC.Password" value="postgres"/>
		</dataSource>
	</transactionManager>

	<sqlMap resource="ch/jpn/taoe/orm_comp/ibatis/entity/HUserGroup.xml"/>
	<sqlMap resource="ch/jpn/taoe/orm_comp/ibatis/entity/HUser.xml"/>
</sqlMapConfig>

次に、SQLを定義したXMLです。HUser.xmlに関しては、HUserGroup.xmlの簡易版になるので、ここでは省略します。必要な場合は最後のソースを参照してください。
HUserGroupのエンティティから、関連しているHUserのエンティティを同時に取得するためには、resultClassを指定する普通の selectタグでは対応できません。ネストしたresultMapを用意し、それに対してSQLを実行する必要があります。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap
	PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
	"http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="HUserGroup">
	<typeAlias alias="UserGroup" type="ch.jpn.taoe.orm_comp.ibatis.entity.HUserGroup"/>
	<typeAlias alias="User"      type="ch.jpn.taoe.orm_comp.ibatis.entity.HUser"/>

	<!-- 全件取得SQL -->
	<select id="getAllUserGroup" resultClass="UserGroup">
		SELECT
			user_group_id as userGroupId
		FROM HUSER_GROUP
	</select>
	<!-- 1件取得SQL -->
	<select id="getUserGroup" resultClass="UserGroup">
		SELECT
			user_group_id as userGroupId
		FROM HUSER_GROUP
		WHERE user_group_id = #userGroupId#
	</select>

	<insert id="insertUserGroup">
		INSERT INTO HUSER_GROUP
			(user_group_id)
		VALUES
			(#userGroupId#)
	</insert>
	<update id="updateUserGroup">
		UPDATE HUSER_GROUP SET
			user_group_id = #userGroupId#
		WHERE user_group_id = #userGroupId#
	</update>
	<delete id="deleteUserGroup">
		DELETE HUSER_GROUP SET
		WHERE user_group_id = #userGroupId#
	</delete>


	<!-- 複数のインスタンスにまたがるSQLを実行する場合には、resultMapの定義が必要となる。 -->
	<resultMap id="joinResultMap" class="UserGroup" groupBy="userGroupId">
		<result property="userGroupId" column="user_group_id"/>
		<result property="users" resultMap="HUserGroup.usersResultMap"/>
	</resultMap>
	<resultMap id="usersResultMap" class="User">
		<result property="userId" column="user_id"/>
		<result property="password" column="password"/>
		<result property="userGroupId" column="user_group_id"/>
	</resultMap>
	<!-- 関連するUserのEntityも同時に取得するSQL -->
	<select id="getAllJoinUserGroup" resultMap="joinResultMap">
		SELECT
			g.user_group_id as user_group_id,
			u.user_id as user_id,
			u.password as password
		FROM
			HUSER_GROUP g,
			HUSER u
		WHERE
			g.user_group_id = u.user_group_id
		ORDER BY
			g.user_group_id
	</select>
</sqlMap>


それでは、最後に実際にORMを使う部分のソースです。SqlMapClient#queryForObjectで1件取得、SqlMapClient#queryForListで複数件取得が行えます。その際に引数に、XMLに定義したselectタグのidを文字列で渡します。
データの追加に関しては、SqlMapClient#insert, SqlMapClient#update などのメソッドに、それぞれのタグのidと追加するデータエンティティを渡します。

public class Main {
	public static void createEntities() throws SQLException { /*省略*/ }

	public static void main(String[] args) throws SQLException {
		try {
			if (false) createEntities();
		} catch (SQLException ex) {
			ex.printStackTrace();
		}

		SqlMapClient sqlMap = SqlConfigFactory.getSqlMapInstance();
		List<HUserGroup> list = sqlMap.queryForList("getAllJoinUserGroup");
		for (HUserGroup group: list) {
			System.out.println(group.getUserGroupId() + ":");
			for (HUser user: group.getUsers()) {
				System.out.println("  " + user.getUserId() + "/" + user.getPassword());
			}
		}
	}
}

上記のソースは、Subversionにて http://taoe.jpn.ch:8080/svn/trunk/orm_compare/iBATIS から取得できます。良かったら自分で動かしてみてください*1

*1:PostgreSQLのDBが必要になります