intra-mart Accel Platform TERASOLUNA Server Framework for Java (5.x) プログラミングガイド 第15版 2019-08-01

データベース

概要

TERASOLUNA Server Framework for Java (5.x) (intra-mart Accel Platform 2015 Spring(Juno) 以降) では、永続化フレームワークとしてMyBatis3を使用します。
  • MyBatis3(3.2)
当項ではMyBatis3を使用したデータベースプログラミングの方法を説明します。

MyBatis3について

MyBatis3はデータベースで管理されているレコードとオブジェクトをマッピングするという考え方ではなく、SQLとオブジェクトをマッピングという考え方で開発されたO/Rマッピングフレームワークです。
複雑な結合条件、細かいクエリチューニングを行う必要がある場合などに向いています。
MyBatis3の詳細については下記を参照してください。

注意

MyBatisは2.x系と3.x系で実装が異なります。
TERASOLUNA Server Framework for Java (5.x) では、MyBatis3.2系を利用して実装します。

プログラミング方法

MyBatis-Springの設定

Springの設定にMyBatis3の設定を追加します。
juggling project の <classes/META-INF/spring/applicationContext-im_tgfw_mybatis3.xml> に以下の設定を追加、修正します。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
        http://mybatis.org/schema/mybatis-spring
        http://mybatis.org/schema/mybatis-spring.xsd">

    <!-- DIコンポーネントの対象とする要素のトップレベルパッケージ -->
    <context:component-scan base-package="my.terasoluna.domain" />

    <bean id="sqlSessionFactory"
        class="org.mybatis.spring.SqlSessionFactoryBean">  <!-- ① -->
        <property name="dataSource" ref="dataSource" />  <!-- ② -->
        <property name="configLocation"
            value="classpath:/META-INF/mybatis/mybatis-config.xml" />  <!-- ③ -->
    </bean>

    <mybatis:scan base-package="my.terasoluna.domain.repository" />  <!-- ④ -->

</beans>
  1. SqlSessionFactory を生成するためのコンポーネントとして、SqlSessionFactoryBean をbean定義します。
  2. dataSource プロパティに、設定済みのデータソースのbeanを指定します。
    MyBatis3の処理の中でSQLを発行する際は、ここで指定したデータソースからコネクションが取得されます。
  3. configLocation プロパティに、MyBatis設定ファイルのパスを指定します。
    ここで指定したファイルがSqlSessionFactory を生成する時に読み込まれます。
  4. リポジトリインタフェースをスキャンするためにmybatis:scan要素を定義し、base-package属性には、リポジトリインタフェースが格納されている基底パッケージを指定します。

TypeAliasの設定

MyBatis3の機能を利用するにあたり、マッピングファイルで指定するJavaクラスに対して、エイリアス名(短縮名)を割り当てる事ができます。
TypeAliasを使用しない場合、マッピングファイルで指定する属性にJavaクラスの完全修飾クラス名を指定する必要があるため、マッピングファイルの記述効率の低下、記述ミスの増加などが懸念されます。
juggling project の <classes/META-INF/mybatis/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>
    <typeAliases>
        <package name="my.terasoluna.domain.model" />
    </typeAliases>
</configuration>
package要素のname属性に、エイリアスを設定するクラスが格納されているパッケージ名を指定します。
指定したパッケージ配下に格納されているクラスは、パッケージの部分が除去された部分がエイリアス名として割り当てられます。
上記例だと、my.terasoluna.domain.model.MyCompanyクラスのエイリアス名は、mycompanyです。
  • クラス単位で指定する例
<?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>
    <typeAliases>
        <typeAlias alias="MyCompany" type="my.terasoluna.domain.model.MyCompany" />
    </typeAliases>
</configuration>
typeAlias要素のtype属性に、エイリアスを設定するJavaクラスの完全修飾クラス名を指定します。
指定したクラスの、 alias属性に設定した値がエイリアス名として割り当てられます。
上記例だと、my.terasoluna.domain.model.MyCompanyクラスのエイリアス名は、MyCompanyです。

コラム

記述効率の向上、記述ミスの削減、マッピングファイルの可読性向上などを目的として、TypeAliasを使用することが推奨されています。

リポジトリインタフェースの作成

任意のエンティティに対し、以下のようにリポジトリインタフェースを作成します。
package my.terasoluna.domain.repository;

import my.terasoluna.domain.model.MyCompany;
import java.util.List;
import org.apache.ibatis.session.RowBounds;

public interface MyCompanyRepository {

    public void insert(MyCompany entity); // ①

    public void update(MyCompany entity); // ②

    public void delete(String companyId); // ③

    public MyCompany selectOne(String companyId); // ④

    public List<MyCompany> selectAll(RowBounds rowBounds); // ⑤

    public List<MyCompany> selectLessId(String companyId); // ⑥

    public List<MyCompany> selectLessIdByInt(int companyId); // ⑦

}
  1. マッピングファイルのidの”insert”にマッピングしています。
    引数はクエリの要求するパラメータクラスのオブジェクトをセットします。
  2. マッピングファイルのidの”update”にマッピングしています。
    引数はクエリの要求するパラメータクラスのオブジェクトをセットします。
  3. マッピングファイルのidの”delete”にマッピングしています。
    引数はクエリの要求する文字列をセットします。
  4. マッピングファイルのidの”selectOne”にマッピングしています。
    引数はクエリの要求する文字列をセットします。
  5. マッピングファイルのidの”selectAll”にマッピングしています。
    引数はページネーションするためのオブジェクト(取得範囲の情報(offsetとlimit)を保持するRowBounds)をセットします。
    検索結果から該当範囲のレコードを抽出する処理は、MyBatis3が行うため、SQLで取得範囲のレコードを絞り込む必要はありません。
  6. マッピングファイルのidの”selectLessId”にマッピングしています。
    引数はクエリの要求する文字列をセットします。
  7. マッピングファイルのidの”selectLessIdByInt”にマッピングしています。
    引数はクエリの要求する数値をセットします。

マッピングファイルの作成

MyBatis3の機能を利用するにあたり、実行するクエリを定義するマッピングファイルを作成する必要があります。
例として、以下のようなXMLファイルを作成し、マッピングされるリポジトリインタフェースとresourcesの同階層に配置します。
%プロジェクト%/src/main/javaの配下 my.terasoluna.domain.repository.MyCompanyRepository.java
%プロジェクト%/src/main/resourcesの配下 my.terasoluna.domain.repository.MyCompanyRepository.xml
<?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="my.terasoluna.domain.repository.MyCompanyRepository">  <!-- ① -->

  <resultMap id="resultMapMyCompnay" type="MyCompany">  <!-- ② -->
    <id property="companyId" column="company_id" />
    <result property="name" column="name" />
  </resultMap>
  
  <insert id="insert" parameterType="MyCompany">  <!-- ③ -->
    INSERT INTO my_company (company_id, name) 
    VALUES(#{companyId}, #{name})
  </insert>
  
  <update id="update" parameterType="MyCompany">  <!-- ④ -->
    UPDATE my_company SET 
    name = #{name}
    WHERE company_id = #{companyId}
  </update>
  
  <delete id="delete" parameterType="string">  <!-- ⑤ -->
    DELETE FROM my_company WHERE company_id = #{companyId}
  </delete>
  
  <select id="selectOne" parameterType="string" resultMap="resultMapMyCompnay">  <!-- ⑥ -->
    SELECT * FROM my_company WHERE company_id = #{companyId}
  </select>
  
  <select id="selectAll" resultMap="resultMapMyCompnay">  <!-- ⑦ -->
    SELECT * FROM my_company
  </select>

  <select id="selectLessId" parameterType="string" resultMap="resultMapMyCompnay">  <!-- ⑧ -->
  <![CDATA[ 
    SELECT * FROM my_company WHERE company_id <  TO_NUMBER(#{companyId}, '999')
  ]]>
  </select>

  <select id="selectLessIdByInt" parameterType="_int" resultMap="resultMapMyCompnay">  <!-- ⑨ -->
  <![CDATA[ 
    SELECT * FROM my_company WHERE company_id <  #{companyId}
  ]]>
  </select>

</mapper>
  1. mapper要素のnamespace属性に、リポジトリインタフェースの完全修飾クラス名を指定します。
  2. resultMap要素に、検索結果(ResultSet)とJavaBeanのマッピング定義を行います。
    id属性にマッピングを識別するためのキーを、type属性にマッピングするJavaBeanのクラス名(またはエイリアス名)を指定します。
    id要素とresult要素は、 どちらも検索結果(ResultSet)のカラムとJavaBeanのプロパティをマッピングするための要素ですが、 ID(PK)カラムに対してマッピングは、id要素を使うことが推奨されます。
    理由としては、ID(PK)カラムに対してid要素を使用してマッピングを行いますと、MyBatis3が提供しているオブジェクトのキャッシュ制御の処理や、 関連オブジェクトへのマッピングの処理のパフォーマンスを、全体的に向上させることが出来るためです。
  3. insert要素の中に、INSERTするSQLを実装します。
    id属性には、リポジトリインタフェースに定義したメソッドのメソッド名を指定します。
    VALUE句にバインドする値は、#{variableName}形式のバインド変数として指定します。
    上記例では、リポジトリインタフェースの引数としてJavaBean(MyCompany)を指定しているため、 バインド変数名にはJavaBeanのプロパティ名を指定します。
  4. update要素の中に、UPDATEするSQLを実装します。
    id属性には、リポジトリインタフェースに定義したメソッドのメソッド名を指定します。
    SET句にバインドする値は、#{variableName}形式のバインド変数として指定します。
    上記例では、リポジトリインタフェースの引数としてJavaBean(MyCompany)を指定しているため、 バインド変数名にはJavaBeanのプロパティ名を指定します。
  5. delete要素の中に、DELETEするSQLを実装します。
    id属性には、リポジトリインタフェースに定義したメソッドのメソッド名を指定します。
    WHERE句に削除条件を指定します。
    削除条件にバインドする値は、#{variableName}形式のバインド変数として指定します。上記例では、 #{companyId}がバインド変数として指定されます。
    Stringのような単純型の場合は、バインド変数名に制約はありませんが、メソッドの引数名と同じ値にしておくことが推奨されます。
  6. select要素の中に、SELECTするSQLを実装します。
    id属性には、リポジトリインタフェースに定義したメソッドのメソッド名を指定します。
    WHERE句に検索条件を指定します。
    検索条件にバインドする値は、#{variableName}形式のバインド変数として指定します。上記例では、 #{companyId}がバインド変数として指定されます。
    select要素のresultMap属性に、適用するマッピング定義のIDを指定します。
  7. select要素の中に、SELECTするSQLを実装します。
    id属性には、リポジトリインタフェースに定義したメソッドのメソッド名を指定します。
    リポジトリインタフェースにて、ページネーションするためのオブジェクト(取得範囲の情報(offsetとlimit)を保持するRowBounds)がセットされていますが、検索結果から該当範囲のレコードを抽出する処理は、MyBatis3が行うため、SQLで取得範囲のレコードを絞り込む必要はありません。
  8. select要素の中に、SELECTするSQLを実装します。
    id属性には、リポジトリインタフェースに定義したメソッドのメソッド名を指定します。
    WHERE句に検索条件を指定します。
    検索条件にバインドする値は、#{variableName}形式のバインド変数として指定します。上記例では、 #{companyId}がバインド変数指定されます。
    SQL内にXMLのエスケープが必要な文字(”<”や”>”など)を指定する場合は、 CDATAセクションを使用すると、SQLの可読性を保つことができます。
    CDATAセクションを使用しない場合は、”&lt;”や”&gt;”といったエンティティ参照文字を指定する必要があり、 SQLの可読性を損なう可能性があります。
  9. select要素の中に、SELECTするSQLを実装します。
    8と同一結果となるSQLですが、parameterType属性に_intを指定しています。
    parameterTypeのstringや_intの他にもdateやcollection等存在します。詳しくはリファレンスを参照してください。

コラム

その他様々なタグや機能がありますので、詳しくは リファレンス を参照してください。

コントローラ

コントローラ(呼び出し)の例です。
@Inject
MyCompanyRepository myCompanyRepository; // ①

public void insert(MyCompanyForm myCompanyForm) { // ②
    MyCompany myCompany = new MyCompany();

...

    myCompanyRepository.insert(myCompany);
}

public void update(MyCompanyForm myCompanyForm) { // ③
    MyCompany myCompany = new MyCompany();

...

    myCompanyRepository.update(myCompany);
}

public void delete(MyCompanyForm myCompanyForm) { // ④
    String deleteCompanyId = "";

...

    myCompanyRepository.delete(deleteCompanyId);
}

public MyCompany selectOne(MyCompanyForm myCompanyForm) { // ⑤
    String companyId = "10";

...

    return myCompanyRepository.selectOne(companyId);
}

public List<MyCompany> selectAll(MyCompanyForm myCompanyForm) { // ⑥
    RowBounds rowBounds = new RowBounds(5, 10);

...

    return myCompanyRepository.selectAll(rowBounds);
}

public List<MyCompany> selectLessId(MyCompanyForm myCompanyForm) { // ⑦
    String companyId = "10";

...

    return myCompanyRepository.selectLessId(companyId);
}

public List<MyCompany> selectLessIdByInt(MyCompanyForm myCompanyForm) { // ⑧
    String companyId = "10";

...

    return myCompanyRepository.selectLessIdByInt(Integer.parseInt(companyId));
}
  1. リポジトリインタフェースをDIします。
  2. リポジトリインタフェースのメソッドを呼び出し、INSERTします。
  3. リポジトリインタフェースのメソッドを呼び出し、UPDATEします。
  4. リポジトリインタフェースのメソッドを呼び出し、DELETEします。
  5. リポジトリインタフェースのメソッドを呼び出し、1件取得します。
  6. リポジトリインタフェースのメソッドを呼び出し、件数を制限して取得します。
    上記の例では、6件目から10件取得します。
    1件目から取得する場合は、第1引数(offset)に0を指定します。
    RowBounds rowBounds = new RowBounds(0, n);
  7. リポジトリインタフェースのメソッドを呼び出し、条件の範囲内を取得します。
  8. リポジトリインタフェースのメソッドを呼び出し、条件の範囲内を取得します。
    検索条件をStringからintに変換して取得します。

エンティティクラスの自動生成

手で作成してもよいのですが、テーブルからエンティティクラスを自動生成するのが簡単です。
Eclipse DTP には、データベースに接続して選択したテーブルからエンティティクラスを生成する機能があります。
詳細については下記ページを参照してください。

トランザクション制御

トランザクション制御の設定は任意のクラス・メソッドに@Transactionalアノテーションを付加して設定します。
例えば、サービスクラスのクラス単位にトランザクション境界を設定する場合、以下のようにクラスに@Transactionalアノテーションを付加します。
import org.springframework.transaction.annotation.Transactional;
...

@Service
@Transactional
public class HogeServiceImpl implements HogeService {
  ...

コラム

@Transactionalはメソッド単位に設定することもできます。

Rollbackする例外を定義する

トランザクション境界内で例外が発生した場合、デフォルトでは、RuntimeExceptionを継承する例外のみロールバックされます。
ロールバックする例外を追加定義する場合、以下のように@TransactionalにrollbackFor属性を付加します。
@Transactional(rollbackFor=java.lang.Exception.class)

コラム

逆に、noRollbackFor属性を設定することで、ロールバックしない例外を設定することもできます。
詳しくは、「Transaction Management」 を参照してください。

明示的トランザクション

アノテーションを用いず、ソースコード内に直接トランザクション処理を記述する場合、以下のようにします。
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

...

@Inject
PlatformTransactionManager transactionManager;// ①

...

 @Override
 public void store(MyCompany entity) {

    DefaultTransactionDefinition def = new DefaultTransactionDefinition();  // ②
    TransactionStatus status = transactionManager.getTransaction(def);      // ③

    try {

      //ビジネスロジック

    } catch (RuntimeException e) {
        transactionManager.rollback(status);  // ④
        throw e;
    }

    transactionManager.commit(status);  // ⑤
  1. PlatformTransactionManagerをフィールド定義します。
    @InjectによりBeanが生成されます。
  2. トランザクション定義情報を設定する
    DefaultTransactionDefinitionクラスを生成します。
  3. transactionManager#getTransactionでトランザクションを開始します。
  4. ロールバックする場合、transactionManager#rollbackを使用します。
  5. コミットする場合、transactionManager#commitを使用します。

シェアードデータベースの利用

applicationContext-im_tgfw_common.xml のsharedDataSourceのコメントアウトを外します。
このとき、connectIdに対象シェアードデータベースの接続IDを指定します。
<bean id="dataSource"   class="jp.co.intra_mart.framework.extension.spring.datasource.TenantDataSource" />

<bean id="sharedDataSource" class="jp.co.intra_mart.framework.extension.spring.datasource.SharedDataSource">
  <constructor-arg name="connectId" value="sharedA" />
</bean>
MyBatis-Springの設定にシェアードデータベースの定義を追加します。
<bean id="sqlSessionFactory"
    class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation"
        value="classpath:/META-INF/mybatis/mybatis-config.xml" />
</bean>

<bean id="sharedSqlSessionFactory"
    class="org.mybatis.spring.SqlSessionFactoryBean">  <!-- ① -->
    <property name="dataSource" ref="sharedDataSource" />  <!-- ② -->
    <property name="configLocation"
        value="classpath:/META-INF/mybatis/mybatis-config.xml" />  <!-- ③ -->
</bean>

<mybatis:scan base-package="my.terasoluna.domain.repository" factory-ref="sqlSessionFactory" />  <!-- ④ -->

<mybatis:scan base-package="my.terasoluna.domain.shared.repository" factory-ref="sharedSqlSessionFactory"/>  <!-- ⑤ -->
  1. SqlSessionFactory を生成するためのコンポーネントとして、SqlSessionFactoryBean をbean定義します。
  2. dataSource プロパティに、設定済みのシェアードデータベースのデータソースのbeanを指定します。
  3. configLocation プロパティに、MyBatis設定ファイルのパスを指定します。
    ここで指定したファイルがシェアードデータベースのSqlSessionFactory を生成する時に読み込まれます。
  4. リポジトリインタフェースをスキャンするためにmybatis:scan要素を定義し、base-package属性には、リポジトリインタフェースが格納されている基底パッケージを指定します。
    SqlSessionFactoryBeanのbeanを複数設定した場合、factory-ref属性の指定が必要です。
  5. リポジトリインタフェースをスキャンするためにmybatis:scan要素を定義し、base-package属性には、リポジトリインタフェースが格納されている基底パッケージを指定します。
    factory-ref属性にsharedSqlSessionFactoryを指定します。

注意

mybatis:scan要素を使用する場合は、リポジトリインタフェースのパッケージをテナントデータベースとシェアードデータベースで分ける必要があります。

コラム

Jugglingプロジェクトの「追加リソースの選択」から TERASOLUNA Server Framework for Java (5.x) (...)設定ファイルを選択すると上記ファイルを含む設定ファイルがJugglingプロジェクト上に配置されます。
チームで開発を行う場合、モジュールプロジェクト上に上記編集ファイルを配置すると他のモジュールプロジェクトに影響を与える可能性があるので、予めJugglingプロジェクト上から設定ファイルの編集を行ってください。