MyBatisだけでも十分に素敵だけど、Guiceと組み合わせるともう最強って感じ。
せっかくなのでMyBatis+Guiceの魅力をwebappではない、スタンドアローンのプログラムを書いて堪能してみる。
mybatis: ver 3.0.6
mybatis-guice: ver 3.2
guice: ver 1.0
[1] データベースにテーブルを作る
MySQLで。
CREATE TABLE `Friends` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(45) NOT NULL, `memo` varchar(140), PRIMARY KEY (`id`), UNIQUE INDEX `name_index` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
[2] プロジェクトを作る
Mavenで。
(依存ライブラリを探してダウンロード+解凍+パス設定+などなどを、手動でやってた頃が懐かしい。)
MyBatis-GuiceはMyBatisとGuiceをつなぐ架け橋。これがあるおかげで、ものすごく便利な組み合わせになってる。
MyBatisは主要なロギングフレームワークに対応しているので、logback-classicを入れてる。
ロギングフレームワークは自動認識されるので、ライブラリをパスに追加するだけでログを吐くようになる。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd" > <modelVersion>4.0.0</modelVersion> <groupId>com.takumakei.study</groupId> <artifactId>mysql-mybatis-guice</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>mysql-mybatis-guice</name> <url>http://takumakei.blogspot.com/</url> <properties> <project.build.sourceEncoding>utf-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.4</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.0.6</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-guice</artifactId> <version>3.2</version> </dependency> <dependency> <groupId>com.google.code.guice</groupId> <artifactId>guice</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.0.0</version> <scope>runtime</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.18</version> <scope>runtime</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>6</source> <target>6</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project>
[3] レコードのモデルクラスを作る
POJOで書いてるけれど、BeanでもOK。
package com.takumakei.study.model; public class Friend { public int id; public String name; public String memo; public Friend() { // 引数なしのコンストラクタがないとエラーになる } public Friend(String name, String memo) { this.name = name; this.memo = memo; } @Override public String toString() { return String.format("%s[%d/%s/%s]", super.toString(), id, name, memo); } }
[4] マッパーインターフェースを作る
このインターフェースを、MyBatis-Friends.xmlでmapperにマッピングする。
package com.takumakei.study.mapper; import java.util.List; import java.util.Map; import org.apache.ibatis.annotations.MapKey; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.session.RowBounds; import com.takumakei.study.model.Friend; public interface FriendsMapper { public void insert(Friend friend); // DELETEしたレコードの数を返す仕様みたい public int deleteById(int id); // UPDATEしたレコードの数を返す仕様みたい // 複数のパラメータを指定している。mybatis-friends.xmlでは#{0}とか#{1}で参照することになる // こういう指定方法も可能ではあるけれど、保守性低下するから使わない方が良さそう public int updateMemo(int id, String memo); public Friend findById(int id); // org.apache.ibatis.session.RowBoundsのパラメータの扱いは特殊。 // mybatis-friends.xmlではこのパラメータを明示していないけれど // MyBatisは期待通りにコード数を制限してくれる。 public List<Friend> list(RowBounds bounds); // Mapで取り出す例。 // 複数のパラメータを指定する際に、それぞれのパラメータに名前を付けて参照できるようにする例でもある。 // (ただし、offsetとlimitはRowBoundsを使うべきところ) @MapKey("id") public Map<Integer, Friend> map(@Param("offset") int offset, @Param("limit") int limit); public int count(); }
[5] データベース操作用のクラスを作る
@Injectを指定して、friendsフィールドをGuiceに生成してもらう。
無駄に@Transactionalを使っているが、トランザクションの扱いも非常に簡単。
package com.takumakei.study.db; import java.sql.SQLIntegrityConstraintViolationException; import java.util.List; import java.util.Map; import org.apache.ibatis.exceptions.PersistenceException; import org.apache.ibatis.session.RowBounds; import org.mybatis.guice.transactional.Transactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.inject.Inject; import com.takumakei.study.mapper.FriendsMapper; import com.takumakei.study.model.Friend; public class Friends { static final Logger logger = LoggerFactory.getLogger(Friends.class); // Guiceにインスタンスを生成してもらう @Inject protected FriendsMapper friends; // 無駄に@Transactionalを指定してみた。 // org.mybatis.guiceのログレベルをDEBUGにすると、 // insertの前後でTransaction開始/終了したようなログが出力される @Transactional public Friend insert(String name, String memo) { Friend friend = new Friend(name, memo); try { // friendのidはAUTO_INCREMENT // insertに成功するとfriendのidを更新して返してくれる // mybatis-friends.xmlのuseGeneratedKeysとkeyPropertyのおかげかな? logger.info("before:{}", friend); friends.insert(friend); logger.info(" after:{}", friend); return friend; } catch (PersistenceException e) { Throwable cause = e.getCause(); if (cause instanceof SQLIntegrityConstraintViolationException) { // 制約に違反してinsert失敗した時にはnullを返すようにしてみた。 // ここでわざわざ例外を握りつぶす必要性は全くない。例示のための実装。 logger.warn("INSERT FAILED:{}", cause.getMessage()); return null; } throw e; } } public boolean deleteById(int id) { return 1 == friends.deleteById(id); } public boolean updateMemo(int id, String memo) { return 1 == friends.updateMemo(id, memo); } public Friend findById(int id) { return friends.findById(id); } public List<Friend> list(int offset, int limit) { return friends.list(new RowBounds(offset, limit)); } public Map<Integer, Friend> map(int offset, int limit) { return friends.map(offset, limit); } public int count() { return friends.count(); } }
[6] SQLを書く
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.takumakei.study.mapper.FriendsMapper"> <sql id="friend">id, name, memo</sql> <sql id="#memo">#{memo, javaType=String, jdbcType=VARCHAR}</sql> <insert id="insert" parameterType="Friend" useGeneratedKeys="true" keyProperty="id"> INSERT INTO Friends (name, memo) VALUES(#{name}, <include refid="#memo"/>); </insert> <delete id="deleteById" parameterType="int"> DELETE FROM Friends WHERE id = #{id} </delete> <update id="updateMemo"> UPDATE Friends SET memo = #{1, javaType=string, jdbcType=VARCHAR} WHERE id = #{0, javaType=int, jdbcType=INTEGER} </update> <select id="findById" parameterType="int" resultType="Friend"> SELECT <include refid="friend"/> FROM Friends WHERE id = #{id} </select> <select id="list" resultType="Friend"> SELECT <include refid="friend"/> FROM Friends ORDER BY id </select> <select id="map" resultType="Map"> SELECT <include refid="friend"/> FROM Friends ORDER BY id DESC LIMIT #{offset}, #{limit} </select> <select id="count" resultType="int"> SELECT COUNT(*) FROM Friends </select> </mapper>
[7] mybatisの設定ファイルを作る
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <properties> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test" /> <property name="username" value="change*me" /> <property name="password" value="change*me" /> </properties> <typeAliases> <typeAlias alias="Friend" type="com.takumakei.study.model.Friend" /> <typeAlias alias="FriendsMapper" type="com.takumakei.study.mapper.FriendsMapper" /> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="${driver}" /> <property name="url" value="${url}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> </dataSource> </environment> </environments> <mappers> <mapper resource="mybatis-friends.xml" /> </mappers> </configuration>
[8] logbackの設定ファイルを作る
設定ファイルは後で書いた方が、大量のDEBUGログを見れるので良いかもしれない。
<?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} %-5level [%thread] %logger{136} - %msg%n</pattern> </encoder> </appender> <logger name="org.apache.ibatis" level="INFO" /> <logger name="org.mybatis.guice" level="INFO" /> <logger name="java.sql.Connection" level="INFO" /> <logger name="java.sql.Statement" level="INFO" /> <logger name="java.sql.PreparedStatement" level="INFO" /> <logger name="java.sql.ResultSet" level="INFO" /> <root level="DEBUG"> <appender-ref ref="stdout"/> </root> </configuration>
[9] 動かす
package com.takumakei.study; import java.sql.SQLRecoverableException; import java.util.Date; import java.util.Map; import org.apache.ibatis.exceptions.PersistenceException; import org.mybatis.guice.XMLMyBatisModule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.inject.Guice; import com.google.inject.Injector; import com.takumakei.study.db.Friends; import com.takumakei.study.model.Friend; public class App { static final Logger logger = LoggerFactory.getLogger(App.class); public static void main(String[] args) { try { logger.info("<start>"); run(); } catch (PersistenceException e) { Throwable cause = e.getCause(); if (cause instanceof SQLRecoverableException) logger.error("database dead? [{}] {}", cause.getClass().getSimpleName(), cause.getMessage()); } catch (Throwable t) { logger.error("something bad", t); } finally { logger.info("<exit>"); } } public static void run() throws Exception { Injector injector = Guice.createInjector(new XMLMyBatisModule() { @Override protected void initialize() { setEnvironmentId("development"); setClassPathResource("mybatis-config.xml"); } }); Friends db = injector.getInstance(Friends.class); logger.info("[list up]"); for (Friend friend : db.list(0, 3)) { logger.info("select:{}", friend); } logger.info("[insert]"); logger.info("insert:{}", db.insert("(1)TAKUMA KEI", "(1)hello world")); logger.info("insert:{}", db.insert("(1)TAKUMA KEI", "(1)same name!!")); logger.info("insert:{}", db.insert("(2)TAKUMA KEI", "(2)hello world")); logger.info("insert:{}", db.insert("(3)TAKUMA KEI", "(3)hello world")); logger.info("insert:{}", db.insert("(4)TAKUMA KEI", "(4)hello world")); Friend insertedFriend = db.insert(new Date().toString(), null); logger.info("insert:{}", insertedFriend); logger.info("[count]"); logger.info("count:{}", db.count()); logger.info("[findById]"); logger.info("findById({}):{}", insertedFriend.id, db.findById(insertedFriend.id)); logger.info("findById(0):{}", db.findById(0)); logger.info("[update]"); logger.info("update:{}", db.updateMemo(insertedFriend.id, "HELLO HELLO HELLO")); logger.info("update:{}", db.updateMemo(1, new Date().toString())); logger.info("findById({}):{}", insertedFriend.id, db.findById(insertedFriend.id)); logger.info("[delete]"); logger.info("delete:{}", db.deleteById(insertedFriend.id)); logger.info("[map]"); Map<Integer, Friend> map = db.map(0, 3); for (Integer id : map.keySet()) { logger.info("map:{} => {}", id, map.get(id)); } } }
0 件のコメント:
コメントを投稿