「MyBatisを使ってみる」の版間の差分
(→まとめ) |
(→まとめ) |
||
501行: | 501行: | ||
以上から、これだけでも、JDBCで直に書くよりかなり省力化が期待できると思います。 | 以上から、これだけでも、JDBCで直に書くよりかなり省力化が期待できると思います。 | ||
+ | |||
+ | |||
MyBatis の機能は、実はこれで既に3割くらいです。かなり薄い機能のライブラリと言えます。 | MyBatis の機能は、実はこれで既に3割くらいです。かなり薄い機能のライブラリと言えます。 |
2016年11月16日 (水) 11:45時点における版
メインページ>コンピュータの部屋#Java>MyBatis Tips
これは MyBatis のごく簡単なチュートリアルです。MyBatis を使ってある単独のテーブルのレコードを読む簡単な例を示します。
目次
処理内容
MySQL flashcardデータベース上の cards テーブルの内容を読みます。
テーブルの形はこれです。
CREATE TABLE `cards` ( `userid` varchar(256) NOT NULL, `english` varchar(256) NOT NULL, `japanese` varchar(256) NOT NULL, `english_japanese_pass_count` int(11) NOT NULL, `japanese_english_pass_count` int(11) NOT NULL, `level` int(11) NOT NULL, `last_review_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`english`,`userid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='フラッシュカードの単語帳';
このテーブルは、所謂単語帳で、利用者別に英単語と日本語が収録されていると考えてください。 成績や学習日時なども記録されています。
内容はこんな感じです。
準備
簡単に始めるなら Maven を使うのがよいでしょう。Eclipse(現在の版は 4.6Neon)のPleiades All in one には Maven が含まれておりますので Pleiades(Neon, All in One)をインストールされていれば準備完了です。Java は 今時ですから 1.8 にしてください。
プロジェクトの設定
Pleades で Mavenプロジェクトを作成したら、pom.xml の dependencies を以下のように設定します。
<dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>[5.1.40]</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.7</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.1.7</version> </dependency> </dependencies>
上の
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>[5.1.40]</version> </dependency>
の部分は MySQL を使うためです。他のデータベースを使うなら適宜変更してください。
また、Javaコンパイラーのバージョンとソースコードのエンコーディングの指定も忘れずに。
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <maven.compiler.target>${java.version}</maven.compiler.target> <maven.compiler.source>${java.version}</maven.compiler.source> </properties>
pom.xml の設定が終わったら、Maven の「プロジェクトの更新」を実行します。
プロジェクトの src/main/resouces には、ログ出力のための logback 用の設定ファイルを置きます。
名称は logback.xml 内容は以下のような感じでよいでしょう。
<?xml version="1.0" encoding="UTF-8" ?> <!--suppress ALL --> <configuration> <!--変数の定義を行います。2行目は外部のプロパティファイルの内容から定義します。--> <property name="LOG_DIR" value="c:/logback_logs" /> <!-- 標準出力に出力する設定です。--> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!--アペンダでのレベル設定--> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>TRACE</level> </filter> <target>System.out</target> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n</pattern> </encoder> <withJasi>true</withJasi> </appender> <!--ログファイルを出力するシンプルな設定です。--> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <!--アペンダでのレベル設定--> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>TRACE</level> </filter> <!--<file>${LOG_DIR}/Trace.log</file>--> <file>log/Trace.log</file> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n</pattern> </encoder> </appender> <!--ログファイルをアーカイブする設定です。アーカイブは1時間毎に行います。--> <appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!--アペンダでのレベル設定--> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>INFO</level> </filter> <!--<file>${LOG_DIR}/Rolling.log</file>--> <file>log/Rolling.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!--<fileNamePattern>${LOG_DIR}/Rolling_%d{yyyy-MM-dd_HH}.log</fileNamePattern>--> <fileNamePattern>log/Rolling_%d{yyyy-MM-dd_HH}.log</fileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n</pattern> </encoder> </appender> <!-- ルートから全てのログをアペンダに流し、アペンダでフィルタリングを行う方式とします。--> <root level="TRACE"> <appender-ref ref="STDOUT"/> <appender-ref ref="FILE"/> <appender-ref ref="ROLLING"/> </root> </configuration>
この辺慣れている方は、ログ周りの dependencies の設定や 設定ファイルの内容はお好きなようにしてください。 log4j などを使いたい人もいるでしょう。
MyBatisの設定
決まり切った設定ですが、プロジェクトの src/main/resources に mybatis-config.xml というファイルを作り 中身はこんな感じにします。
<?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> <settings> <!-- アンダースコアによって単語を分けている名前がCamel形式にマッチするように変更 --> <setting name="mapUnderscoreToCamelCase" value="true" /> </settings> <typeAliases> <typeAlias alias="Card" type="info.nakamuri.app.spike.mybatisspike001.Card" /> </typeAliases> <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:3306/flashcard" /> <property name="username" value="MySQLのユーザ名" /> <property name="password" value="MySQLのパスワード" /> </dataSource> </environment> </environments> <mappers> <mapper resource="CardMapper.xml" /> </mappers> </configuration>
この例では MySQL の flashcard というデータベースの cards というテーブルを読みます。
info.nakamuri.app.spike.mybatisspike001.Card
というクラスは、cardテーブルのレコードの内容を受け取るクラスで、typeAlisa タグでは 後で別の設定ファイルの中でこのクラスを指定する時、クラス名を FQCNで指定するのは長いので 短い名前 Card を定義しています。
environmentタグの中はおなじみの JDBC の接続パラメータで、
<transactionManager type="JDBC" />
は、トランザクションの制御をプログラムで直に制御することを表します。
<dataSource type="POOLED">
はデータベースコネクションがプールされることを示しています。
mappersタグは今回の主役 mapperインターフェースとそのステートメント(メソッド)を定義する ファイルがどこにあるかを示しています。
以上までが、一回書けばほとんど変更しないファイルです。一度作ってひな形としておけば、 わずかな修正で使いまわせるでしょう。
Mapperの定義
さて、ここからが本題です。
Mapperとはテーブル等の読み書きに使うインターフェースです。 select, update, insert, delete 用の SQL を実行するメソッド(ステートメント)を定義します。 この例では select の例を紹介します。
mybatis-config.xml に記述した通り、mapperの定義ファイルは CardMapper.xml ですが これは CardMapper.xml をクラスパス、つまり src/main/resources に置くことを示します。
中身はこんな感じです。
<?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="info.nakamuri.app.spike.mybatisspike001.CardMapper"> <select id="findAllCards" resultType="Card"> select * from cards </select> <select id="findByKey" resultType="Card"> select * from cards where userid=#{param1} and english=#{param2} </select> </mapper>
2つのメソッド findAllCards と finfByKey が定義されており、 findAllCards メソッドは無引数でテーブル上の全てのレコードを返します。findByKeyは 指定ユーザの指定英単語を持つレコードを1個だけ返します。
いずれも Card 型を返すように指定されていますが、実際には、戻り値は Card か Cardの コレクション型 になります。これは Java側のMapperインターフェースの定義で決まります。
SQL の中身は解説不要と思いますが、#{param1}と#{param2}は OGNLという記法で、メソッドの第1引数と第2引数を表します。
Cardの定義
CardはただのJavaBean で、cardsテーブルのレコードの内容そのままになっています。 コードは以下の通りです。Eclipseのウィザードなどで作成するのが簡単でしょう。
package info.nakamuri.app.spike.mybatisspike001; import java.sql.Timestamp; /** * 単語カードを表すクラス. */ public class Card implements Comparable<Card> { /**ユーザ名*/ private String userId = null; /**英語*/ private String english = null; /**日本語*/ private String japanese = null; /**現レベルでの英語→日本語復習合格回数*/ private int englishJapanesePassCount=0; /**現レベルでの日本語→英語復習合格回数*/ private int japaneseEnglishPassCount=0; /**現在のレベル*/ private int level = 1; /**最終復習日時*/ private Timestamp lastReviewDate = null; public String getUserId() { return userId; } public String getEnglish() { return english; } public String getJapanese() { return japanese; } public int getEnglishJapanesePassCount() { return englishJapanesePassCount; } public int getJapaneseEnglishPassCount() { return japaneseEnglishPassCount; } public int getLevel() { return level; } public Timestamp getLastReviewDate() { return lastReviewDate; } public void setUserId(String userId) { this.userId = userId; } public void setEnglish(String english) { this.english = english; } public void setJapanese(String japanese) { this.japanese = japanese; } public void setEnglishJapanesePassCount(int englishJapanesePassCount) { this.englishJapanesePassCount = englishJapanesePassCount; } public void setJapaneseEnglishPassCount(int japaneseEnglishPassCount) { this.japaneseEnglishPassCount = japaneseEnglishPassCount; } public void setLevel(int level) { this.level = level; } public void setLastReviewDate(Timestamp lastReviewDate) { this.lastReviewDate = lastReviewDate; } }
Mapperインターフェースの作成
最後に Cardsテーブル用の Mapperインターフェースを作成します。基本的に CardMapper.xml で定義した通りのものを作ります。
package info.nakamuri.app.spike.mybatisspike001; import java.util.List; /** * カードマッパーインターフェース. */ public interface CardMapper { /** * 全カードの取得. * @return 全カード */ List<Card> findAllCards(); /** * キーでカードを取得. * @param card カード * @return カード */ Card findByKey(String userid, String english); }
findAllCards は List で受けていることに注意してください。このようにインターフェースを定義すると MyBatisは ArrayList で Card を返してきます。
Mapperインターフェースを使う
Mapperインターフェースは以下のように使います。
まず、セッションファクトリ用のシングルトンを作成します。 これが mybatis-config.xml の内容を読み込みます。 JDBCのコネクションに対応する SqlSession オブジェクトはこのシングルトンから 作成するようにします。データソース毎に用意するのが良いでしょう。
package info.nakamuri.app.spike.mybatisspike001; import java.io.IOException; import java.io.InputStream; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; /** * セッションファクトリ. */ public final class MyBatisSqlSessionFactory { private static SqlSessionFactory sqlSessionFactory; public static SqlSessionFactory getSqlSessionFactory() { if (sqlSessionFactory == null) { InputStream inputStream; try { inputStream = Resources.getResourceAsStream("mybatis-config.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { throw new RuntimeException(e.getCause()); } } return sqlSessionFactory; } public static SqlSession openSession() { return getSqlSessionFactory().openSession(); } }
このシングルトンを使って Mapperインターフェースを呼び出します。
findAllCardsの使用例
SqlSession sqlSession = MyBatisSqlSessionFactory.openSession(); try { CardMapper cardMapper = sqlSession.getMapper(CardMapper.class); List<Card> card = cardMapper.findAllCards(); // 何か処理 } finally { sqlSession.close(); }
findByKeyの使用例
SqlSession sqlSession = MyBatisSqlSessionFactory.openSession(); try { CardMapper cardMapper = sqlSession.getMapper(CardMapper.class); Card card cardMapper.findByKey("nakamuri", "take"); // 何か処理 } finally { sqlSession.close(); }
これでちゃんと cardsテーブルの内容を、列を漏らさずに読むことができます。
お疲れさまでした。
補足事項
以上ですが、実は最も肝心なところを説明していません。
JDBC で同じ処理を行うには
- コネクションの確立と切断
- select(SQL)の実行
- select結果をカーソルから読み取って Cardオブジェクトに変換する
の3つの仕事が必要ですが、MyBatisではどうなっているのでしょうか?
コネクションの確立は MyBatisSqlSessionFactory が mybatis-config.xml の情報を使って行っています。 SQL の実行は Mapperインターフェースのステートメント(メソッド)の中で CardMapper.xml に記述された SQL で実行されます。
では、select結果を Cardオブジェクトへどのようにして変換しているのでしょうか?
実はこの例では、MyBatis が変換を自動的に行っているのです。
MyBatis は レコードに対応する Cardオブジェクトを生成し、プロパティを設定します。 selectしたデータの列名と同じ名前のプロパティが Cardオブジェクトあれば、そのプロパティに列値がセットされます。
名前が同じかどうかの判断ではアルファベットの文字の大小は無視されます。
では englishJapanesePassCount プロパティにはなぜ english_japanese_pass_count 列の内容が 入るのでしょうか?
これは、実は mybtis-config.xml の
<settings> <setting name="mapUnderscoreToCamelCase" value="true" /> </settings>
という設定が関係しています。この設定 mapUnderscoreToCamelCase が true に設定されていると、 アンダースコアで区切られた列名とキャメル形式のプロパティ名がマッチし、列値がマッチしたプロパティにセットされるのです。
以上から、Cardオブジェクトの全てのプロパティには自動的にテーブルの全ての列の値がマップされ代入されます。
いつもこううまくゆくわけではアリアませんが、列名とプロパティ名が合わないなら、SQLで列の別名を 使えばよいので問題ありません。MyBatisでは ResultMap という機能を使えば任意の列を任意のプロパティに マップすることもできますが、これは手間がちょっとかかります。 これはもっと ResultMap の凝った機能を使う時に使います。
まとめ
以上ですが、たかがテーブルを1個読むのにたくさんの設定とコードが必要なのに驚いたのではないかと思います。
但し、大部分はプロジェクトにそれぞれ1個だけ必要な定型的なものなので、この記事からコピペすれば すぐに作れます。
Cardなどの JavaBeanはEclipse等のウィザードを使えば作るのはそれほど大変ではありません。 テーブルから自動的にクラスを生成するツールもあります。そもそもこれは MyBatisとは関係なく 必要なものです。
もっとも肝心な Mapperインターフェースは記述量が少なくなるように工夫されています。
以上から、これだけでも、JDBCで直に書くよりかなり省力化が期待できると思います。
MyBatis の機能は、実はこれで既に3割くらいです。かなり薄い機能のライブラリと言えます。 この他に、ResultMapの高度な機能、更新系のSQLを実行するステートメントの定義、 動的SQLのための便利機能、アノテーション、springとの連携などが有ります。 恐らく全てを習得するのに、JDBCでDAOを書きなれた方であれば、数日有れば十分でしょう。