ORM比較(6) - iBATIS
iBATISは、XMLにSQLを記述しJavaのBeanに対して自動的に値を設定するORマッピングになっています。どんなSQLになるかは分かりやすいですが、XMLとJavaとの同期を取る方法は特には用意されていないので、管理が必要になります。また、このプロジェクトもApacheプロジェクトのうちの1つになっています。同一のプロジェクトで .NETと Rubyにも対応しているようです。設定が使いまわせるんですかね?
また、新規に開発されている 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が必要になります