「MyBatisを使ってみる」の版間の差分

提供: tknotebook
移動: 案内検索
(まとめ)
(まとめ)
520行: 520行:
 
==まとめ==
 
==まとめ==
  
 +
===省力化に関して===
 
以上ですが、たかがテーブルを1個読むのにたくさんの設定とコードが必要なのに驚いたのではないかと思います。
 
以上ですが、たかがテーブルを1個読むのにたくさんの設定とコードが必要なのに驚いたのではないかと思います。
  
534行: 535行:
  
  
 
+
===残りの機能===
 
MyBatis の機能は、実はこれで既に3割くらいです。かなり薄い機能のライブラリと言えます。
 
MyBatis の機能は、実はこれで既に3割くらいです。かなり薄い機能のライブラリと言えます。
 
この他に、ResultMapの高度な機能、型変換とカスタムな型変換、更新系のSQLを実行するステートメントの定義、
 
この他に、ResultMapの高度な機能、型変換とカスタムな型変換、更新系のSQLを実行するステートメントの定義、

2016年11月17日 (木) 02:19時点における版

メインページ>コンピュータの部屋#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='フラッシュカードの単語帳';

このテーブルは、所謂単語帳で、利用者別に英単語と日本語が収録されていると考えてください。 成績や学習日時なども記録されています。

内容はこんな感じです。

練習用cardテーブルの内容.png

準備

簡単に始めるなら Maven を使うのがよいでしょう。Eclipse(現在の版は 4.6Neon)のPleiades All in one には Maven が含まれておりますので Pleiades(Neon, All in One)がインストールされていれば準備完了です。Java は 今時ですから 1.8 にしてください。

データベースを読みますから、データベースが必要です。

例では MySQL を使いますので、例をそのまま動かしたいなら ローカルマシン上に MySQL をインストールして flashcard データベースを作成し、cardsテーブルを作ってください。

プロジェクトの設定

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>

この例では MySQLflashcard というデータベースの 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で、データベースプログラミングではおなじみの DTO(Data Transfer Object) です。 この例では cardsテーブル のレコードの内容そのままになっています。 コードは以下の通りです。Eclipseのウィザードなどを使えば、先頭に privateフィールドさえ書けば、後は 自動生成できます。

もし、Javaでデータベースの仕事をする機会の多い部署なら、Excelの仕様書から列名の一覧を読み取って DTOのコードを吐くツールくらいはあるはずです。作るのも容易です。くれぐれも全部手で書こうなんて思わないでください。 間違いの元です。

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;
	}
}

セッションファクトリの作成

MyBatisでデータベースの処理を行うにはセッションが必要になります。

セッションとは JDBCの Connectionオブジェクトのようなものです。

セッションファクトリはセッションを生成するオブジェクトで、アプリケーションに、データソース毎に 一つあればよいので、シングルトンにします。

定型的なコードですが、データソースの数だけクラスを作るのがよいでしょう。

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;

    /**
     * ファクトリのインスタンスの取得.
     * @return ファクトリ
     */
    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;
    }

    /**
     * セッション開始.
     * @return セッション
     */
    public static SqlSession openSession() {
        return getSqlSessionFactory().openSession();
    }
}

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インターフェースを使う

ようやくここまでたどり着きました。以下が使用例です。

Mapperインターフェースは以下のように使います。


このシングルトンを使って Mapperインターフェースを呼び出します。

findAllCardsの使用例

   SqlSession sqlSession = MyBatisSqlSessionFactory.openSession();
   try {
       CardMapper cardMapper = sqlSession.getMapper(CardMapper.class);
       List<Card> cards =  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 で同じ処理を行うには

  1. コネクションの確立と切断
  2. select(SQL)の実行
  3. select結果をカーソルから読み取って Cardオブジェクトに変換する

の3つの仕事が必要ですが、MyBatisではどうなっているのでしょうか?

コネクションの確立はセッション生成の際、 MyBatisSqlSessionFactory が mybatis-config.xml の情報を使って行っています。 SQL の実行は Mapperインターフェースのステートメント(メソッド)の中で CardMapper.xml に記述された SQL で実行されます。

では、select結果を Cardオブジェクトへどのようにして変換しているのでしょうか?

実はこの例では、MyBatis が変換を自動的に行っているのです。JDBCでは最も手間のかかるところです。

MyBatis は レコードに対応する Cardオブジェクトを生成し、プロパティを設定します。 selectしたデータの列名と同じ名前のプロパティが Cardオブジェクトあれば、そのプロパティに列値がセットされます。

名前が同じかどうかの判断ではアルファベットの文字の大小は無視されます。

では englishJapanesePassCount プロパティにはなぜ english_japanese_pass_count 列の内容が 入るのでしょうか?

これは、実は mybtis-config.xml の

   <settings>
       <setting name="mapUnderscoreToCamelCase" value="true" />
   </settings>

という設定が関係しています。この設定 mapUnderscoreToCamelCase が true に設定されていた場合、 MyBatisはアンダースコアで区切られた列名をキャメル形式に変換したものとプロパティ名を比較し、マッチしたプロパティに列値をセットするのです。

以上から、Cardオブジェクトの全てのプロパティには自動的にテーブルの全ての列の値がマップされ代入されます。

いつもこううまくゆくわけではありませんが、列名とプロパティ名が合わないなら、SQLで列の別名を 使えばよいので問題ありません。MyBatisでは ResultMap という機能を使えば任意の列を任意のプロパティに マップすることもできますが、これは少し手間がかかります。 これはもっと ResultMap の凝った機能を使う時に使うのがよいでしょう。

まとめ

省力化に関して

以上ですが、たかがテーブルを1個読むのにたくさんの設定とコードが必要なのに驚いたのではないかと思います。

但し、大部分はプロジェクトにそれぞれ1個だけ必要な定型的なものばかりなので、この記事からコピペすれば すぐに作れます。

Cardなどの JavaBean(DTO:Data Transfer Object)はEclipse等のウィザードを使えば作るのはそれほど大変ではありません。 テーブルから自動的にクラスを生成するツールも MyBatis で用意されています。そもそも DTOは MyBatisとは関係なく 必要なものです。

もっとも肝心な Mapperインターフェースは XML や Java の記述量が非常に少なくなるように工夫されています。

以上から、これだけでも、JDBCで直に書くよりかなり省力化が期待できると思います。


残りの機能

MyBatis の機能は、実はこれで既に3割くらいです。かなり薄い機能のライブラリと言えます。 この他に、ResultMapの高度な機能、型変換とカスタムな型変換、更新系のSQLを実行するステートメントの定義、 動的SQLのための便利機能、アノテーション、springとの連携などが有ります。 恐らく全てを習得するのに、JDBCでDAOを書きなれた方であれば、数日有れば十分でしょう。

JPAのようにちゃんと使おうとすると、何か月も経験を積み。思いもよらぬ振る舞いに悩まされながら 使い込んでようやく使えるようになることを考えれば、手軽に使える MyBatis は選択肢の一つとして 覚えておいて損はないと思います。

特に性能が問題になる場合は、SQLの腕さえあればなんとかなる MyBatis は心強いと思います。