概要
iBatis3を導入する目的で評価用のサンプルコードを書いてみました。
参考にしたサイト
記載は書かれた日時が新しい順です。
Oboe吹きプログラマの黙示録
「iBATIS3 と Google guice」(2010/04/10の記事)
http://blogari.zaq.ne.jp/oboe2uran/
SqlSessionFactoryをシングルトンで使用することの問題についての考察があります。
「iBATIS3入門」(2010/03/14の記事)
http://sourceforge.jp/projects/artery/releases/46311
http://sourceforge.jp/projects/artery/downloads/46311/ibatis3.pdf/
ArteryというORマッパープロジェクト内にある、iBATIS3に関する日本語で書かれた入門書です。
分かりやすい内容でおすすめです。
・xmlのにSQLのIN句を指定する方法
・xmlにSQLを書かずにアノテーションで指定する方法
等々についても説明があります。
公式サイトhttp://ibatis.apache.org/index.html
「User Guide (English)」(2010/02/15づけ)
http://svn.apache.org/repos/asf/ibatis/java/ibatis-3/trunk/doc/en/iBATIS-3-User-Guide.pdf
本家サイトの英語で書かれたガイドです。
最新の内容と思われますが、サンプルコードが不親切です。
CodeZine
「iBATISを使ったO/RマッピングによるDBアクセスの実例」(2007/06/07の記事)
http://codezine.jp/article/detail/1289
バージョン2.3に対しての説明です。
サンプルの内容
DROP TABLE IF EXISTS users; CREATE TABLE users ( user_id INT NOT NULL AUTO_INCREMENT, user_name VARCHAR(10), version INT, PRIMARY KEY (user_id) ) ENGINE=InnoDB;
・idは自動採番
・更新時にはvarsionで楽観的排他制御をおこなう
・varsionはSQLで自動インクリメンタル(Javaでは意識しない)
pom.xml
下記を追加
<dependency> <groupId>org.apache.ibatis</groupId> <artifactId>ibatis-sqlmap</artifactId> <version>3.0-beta-10</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.10</version> </dependency>
サンプルソース構成
src/test/ibatis /configuration /AppSqlSessionFactory.java /iBatisConfiguration.xml /entity /User.java /UserId.java /UserMapper.xml
サンプルソース
AppSqlSessionFactory.java
package test.ibatis.configuration; import java.io.IOException; import java.io.Reader; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; public class AppSqlSessionFactory { private final static String XML_PATH = "test/ibatis/configuration/iBatisConfiguration.xml"; public static SqlSessionFactory get() { try { Reader reader = Resources.getResourceAsReader(XML_PATH); return new SqlSessionFactoryBuilder().build(reader); } catch (IOException e) { throw new IllegalStateException(e); } } }
iBatisConfiguration.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//ibatis.apache.org//DTD Config 3.0//EN" "http://ibatis.apache.org/dtd/ibatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost/iBatisTest" /> <property name="username" value="root" /> <property name="password" value="ponyo" /> </dataSource> </environment> </environments> <mappers> <mapper resource="test/ibatis/entity/UserMapper.xml" /> </mappers> </configuration>
User.java
package test.ibatis.entity; public class User { private long id; private String name; private long version; // 新規追加の場合 public User(String name) { this(-1L, -1L); } // iBatisにより生成される場合 public User(Long id, Long version) { this.id = id; this.version = version; } public long getId() { return id; } public void setName(String name) { this.name = name; } public String getName() { return name; } public long getVersion() { return version; } }
UserId.java
package test.ibatis.entity; public class UserId { private long id; public long get() { return id; } }
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN" "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd"> <mapper namespace="USERS"> <resultMap id="UserMapping" type="test.ibatis.entity.User"> <constructor> <idArg column="user_id" javaType="long" /> <idArg column="version" javaType="long" /> </constructor> <result property="name" column="user_name" /> </resultMap> <select id="findById" parameterType="long" resultType="test.ibatis.entity.User" resultMap="UserMapping" > SELECT user_id, user_name, version FROM users WHERE user_id = #{value} </select> <insert id="insert" parameterType="test.ibatis.entity.User"> INSERT INTO users (user_name, version) VALUES (#{name},1) </insert> <select id="getInsertedId" resultType="test.ibatis.entity.UserId" > SELECT LAST_INSERT_ID() id; </select> <update id="updateById" parameterType="test.ibatis.entity.User"> UPDATE users SET user_name = #{name}, version = version + 1 WHERE user_id = #{id} AND version = #{version} </update> <delete id="deleteById" parameterType="long"> DELETE FROM users WHERE user_id = #{id} </delete> </mapper>
テスト
検証のためさらにUnitTestを追加します。
pom.xmlに追加
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.1</version> <scope>test</scope> </dependency>
UserTest.java
import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.junit.Test; import test.ibatis.configuration.AppSqlSessionFactory; import test.ibatis.entity.User; import test.ibatis.entity.UserId; public class UserTest { @Test public void test() throws Exception { SqlSessionFactory factory = AppSqlSessionFactory.get(); SqlSession session = factory.openSession(); try { // 新規追加 User user = new User("ユーザ名"); user.setName("ユーザ名"); long count = session.insert("USERS.insert", user); assertEquals(1, count);// 追加件数が1件であることを確認 // 採番されたidを取得 UserId userId = (UserId) session.selectOne("USERS.getInsertedId"); long id = userId.get(); // 新規追加したUserを取得 user = (User) session.selectOne("USERS.findById", id); assertEquals(id, user.getId()); assertEquals("ユーザ名", user.getName()); assertEquals(1, user.getVersion()); // 更新 user.setName("ユーザ名改"); count = session.update("USERS.updateById", user); assertEquals(1, count);// 更新件数が1件であることを確認 // 更新結果確認 user = (User) session.selectOne("USERS.findById", id); assertEquals(id, user.getId()); assertEquals("ユーザ名改", user.getName()); assertEquals(2, user.getVersion());//バージョンがインクリメントされたことを確認 session.commit(); } catch (Exception e) { e.printStackTrace(); session.rollback(); fail(e.getMessage()); } finally { session.close(); } } }
結果
テストが全て通過しました。うまく動いているようです。
追加説明
SqlSessionFactoryBuilder
公式サイトのユーザーズガイド(後述)には、SqlSessionFactoryBuilderについて下記のように書かれています。
This class can be instantiated, used and thrown away. There is no need to keep it around once you've created your SqlSessionFactory. Therefore the best scope for instances of SqlSessionFactoryBuilder is method scope (i.e. a local method variable). You can reuse the SqlSessionFactoryBuilder to build multiple SqlSessionFactory instances, but it's still best not to keep it around to ensure that all of the XML parsing resources are freed up for more important things.
SqlSessionFactoryBuilderのベストスコープはメソッドスコープだとありますが、ビジネスロジック(=1トランザクション)毎にnewという解釈でいいのでしょうか?
今回のサンプルでは毎回newしていますが、SqlSessionFactoryをシングルトンで管理できるならそちらの方がよさそうです。