つかびーの技術日記

(情報)工学修士, 元SIer SE, 現Web系 SEの技術blogです。Scala, Java, JS, TS, Python, Ruby, AWS, GCPあたりが好きです。

MyBatis GeneratorのExampleクラスなど解説

   

MyBatis Generatorを使うと、MyBatisを利用する時に必要な煩雑な作業(SQL記述、XML作成、Entityクラス作成など)を自動化してくれます。

このMyBatis Generatorですが、デフォルトで****Exampleというクラスまで作成されるようになっています。少し使ってみると、「ああ、and条件とかを表すためのクラスなのね」と分かるのですが、情報不足で詳しいことが分かりません。

勿論公式にExample Class Usage Notesという情報はありますが、英語だし・・・というわけで使い方を調査・解説します。

テーブル

まずは検証用に適当なテーブルとビューを作成します。

/* Create Tables */

-- 生産者
CREATE TABLE PRODUCER
(
    PRODUCER_ID BIGINT UNSIGNED NOT NULL,
    PRODUCER_NAME VARCHAR(30),
    PRIMARY KEY (PRODUCER_ID)
) COMMENT = '生産者';

-- 製品
CREATE TABLE PRODUCT
(
    PRODUCT_ID BIGINT UNSIGNED NOT NULL,
    PRODUCT_NAME VARCHAR(30),
    PRODUCER_ID BIGINT UNSIGNED NOT NULL,
    PRIMARY KEY (PRODUCT_ID)
) COMMENT = '製品';

/* Create Foreign Keys */

ALTER TABLE PRODUCT
    ADD FOREIGN KEY (PRODUCER_ID)
    REFERENCES PRODUCER (PRODUCER_ID)
    ON UPDATE RESTRICT
    ON DELETE RESTRICT
;

/* Create Views */

-- 製品詳細
CREATE VIEW PRODUCT_DETAIL AS SELECT
  PRODUCT.PRODUCT_ID,
  PRODUCT.PRODUCT_NAME,
  PRODUCT.PRODUCER_ID,
  PRODUCER.PRODUCER_NAME
FROM PRODUCT, PRODUCER
WHERE PRODUCT.PRODUCER_ID=PRODUCER.PRODUCER_ID;

Generater起動

上記のテーブルに対してGeneratorを実行します。ここでできたMapperクラスとそのXMLを検証して行きます。

検証対象

Mapperを見るといくつかメソッドが用意されています。例えばProducerMapperは以下のようになっています。 これらを見ていくことにします。

  1. int countByExample(ProducerExample example)
  2. int deleteByExample(ProducerExample example)
  3. int deleteByPrimaryKey(Long producerId)
  4. int insert(Producer record)
  5. int insertSelective(Producer record)
  6. List selectByExample(ProducerExample example)
  7. Producer selectByPrimaryKey(Long producerId)
  8. int updateByExampleSelective(@Param(“record”) Producer record, @Param(“example”) ProducerExample example)
  9. int updateByExample(@Param(“record”) Producer record, @Param(“example”) ProducerExample example)
  10. int updateByPrimaryKeySelective(Producer record)
  11. int updateByPrimaryKey(Producer record)

ByPrimaryKey系

これは単純です。例えばdeleteByPrimaryKeyのXMLは以下のようになっています。

  <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
      This element was generated on Mon Sep 16 01:28:20 JST 2013.
    -->
    delete from producer
    where PRODUCER_ID = #{producerId,jdbcType=BIGINT}
  </delete>

単純に主キーを仮引数にもつ削除メソッドです。

ByExample系

問題のExampleです。まずは公式の情報を翻訳してみることにします。英語苦手ですが・・・。

The example class specifies how to build a dynamic where clause.

Exampleクラスは動的WHERE句を構築する方法を規定します。

The example classes contain an inner static class called Criteria that holds a list of conditions that will be anded together in the where clause.

Exampleクラスは、WHERE句にANDで結合される条件のリストを持つCriteriaという静的内部クラスを含みます。

なるほど、まあようするにExampleクラスはWHEREなわけです。では実際に使っていきます。説明の都合上、ここではDBのVIEWが元となっているProductDetailMapperのメソッドを検証します。あらかじめVIEW(DB)に以下のデータが投入されていることとします。

PRODUCT_ID PRODUCT_NAME PRODUCER_ID PRODUCER_NAME
100 PRODUCT_1 0 PRODUCER_A
101 PRODUCT_2 0 PRODUCER_A
102 PRODUCT_3 0 PRODUCER_A
103 PRODUCT_4 0 PRODUCER_A
104 PRODUCT_5 2 PRODUCER_C
105 PRODUCT_6 2 PRODUCER_C
106 PRODUCT_7 2 PRODUCER_C
107 PRODUCT_8 2 PRODUCER_C
108 PRODUCT_9 3 PRODUCER_D
109 PRODUCT_10 3 PRODUCER_D
110 PRODUCT_11 4 PRODUCER_E

この状態で以下のコードを実行します。

  public void run() {
    ProductDetailExample example = new ProductDetailExample();
    example.createCriteria().andProducerNameEqualTo("PRODUCER_A");

    List<ProductDetail> list = mapper.selectByExample(example);
    for (ProductDetail obj : list) {
      System.out.println(obj);
    }
  }

実行結果は以下です。

ProductDetail [Hash = -1390806030, productId=100, productName=PRODUCT_1, producerId=0, producerName=PRODUCER_A, serialVersionUID=1]
ProductDetail [Hash = -1390775278, productId=101, productName=PRODUCT_2, producerId=0, producerName=PRODUCER_A, serialVersionUID=1]
ProductDetail [Hash = -1390744526, productId=102, productName=PRODUCT_3, producerId=0, producerName=PRODUCER_A, serialVersionUID=1]
ProductDetail [Hash = -1390713774, productId=103, productName=PRODUCT_4, producerId=0, producerName=PRODUCER_A, serialVersionUID=1]

なるほど、まずはPrimaryKeyでない条件で検索できました。AND条件1つです。

では次に検索条件をもう一つ追加してみます。

  public void run() {
    ProductDetailExample example = new ProductDetailExample();
    example.createCriteria().andProducerNameEqualTo("PRODUCER_A").andProductNameEqualTo("PRODUCT_1");

    List<ProductDetail> list = mapper.selectByExample(example);
    for (ProductDetail obj : list) {
      System.out.println(obj);
    }
ProductDetail [Hash = -1390806030, productId=100, productName=PRODUCT_1, producerId=0, producerName=PRODUCER_A, serialVersionUID=1]

1レコードに絞れました。AND条件が2つです。

ORもできるようなので、試してみることにします。PRODUCER_AのPRODUCT_1とPRODUCER_CのPRODUCT_5の2レコードを抽出します。orメソッドを利用します。

  public void run() {
    ProductDetailExample example = new ProductDetailExample();
    example.createCriteria().andProducerNameEqualTo("PRODUCER_A").andProductNameEqualTo("PRODUCT_1");
    example.or().andProducerNameEqualTo("PRODUCER_C").andProductNameEqualTo("PRODUCT_5");

    List<ProductDetail> list = mapper.selectByExample(example);
    for (ProductDetail obj : list) {
      System.out.println(obj);
    }
  }
ProductDetail [Hash = -1390806030, productId=100, productName=PRODUCT_1, producerId=0, producerName=PRODUCER_A, serialVersionUID=1]
ProductDetail [Hash = -1390682958, productId=104, productName=PRODUCT_5, producerId=2, producerName=PRODUCER_C, serialVersionUID=1]

抽出できました。

上記のコードではor()メソッドを利用しました。ここでもし、間違えてcreateCriteriaを実行した場合は?上記の引用通り、Exampleクラス内ではCriteriaをリストで持っていますが、生成されたCriteriaはこのリストに追加されないため、コードは意図通り動きません。

上記のコードではcreateCriteriaを利用しましたが、実は1つ目のcreateCriteriaはorでも正しく動作します。orでも内部ではCriteriaを生成します。常にorで良いかもしれません。

Selective系

insertとupdateにSelectiveがあります。これは一体何なんでしょうか。XMLを見ると分かりました。nullの要素のことを言っているようです。Selectiveではパラメータのクラス内のメンバ変数がnullである場合は、それについては関与しない、というもののようです。

例えばupdateしとうとしたときにメンバ変数がnullだとして、果たしてそれはDB上でNULLにしたいのか、それともDB上の値には関与してほしくないのか・・・このSelectiveはDB上の値はNULLにしないということでしょう。逆にSelectiveでない方のメソッドではメンバ変数がnullの場合はDB上の値もNULLにするということでしょう。

実際にコードを実行して確かめてみます。

ここで分かりやすさのために、PRODUCERテーブルのPRODUCER_NAME列のデフォルト値’NONAME’を設けておきます。

まずは以下のコードを実行します。Selectiveでないバージョン。

  public void run() {
    Producer record = new Producer();
    record.setProducerId(900L);
    record.setProducerName(null);
    producerMapper.insert(record);
  }
PRODUCER_ID PRODUCER_NAME
0 PRODUCER_A
1 PRODUCER_B
2 PRODUCER_C
3 PRODUCER_D
4 PRODUCER_E
900 ​(NULL)​

PRODUCER_NAME列がNULLということはDEFAULTは発動していません。これは意図通りと言えるかと思います。SelectiveしないinsertでNULLの値を意図的に入れたわけですから。

では次はSelectiveバージョンを試してみます。

  public void run() {
    Producer record = new Producer();
    record.setProducerId(901L);
    record.setProducerName(null);
    producerMapper.insertSelective(record);
  }
PRODUCER_ID PRODUCER_NAME
0 PRODUCER_A
1 PRODUCER_B
2 PRODUCER_C
3 PRODUCER_D
4 PRODUCER_E
900 ​(NULL)​
901 NONAME

こちらはDEFAULTが発動してNONAMEになりました。

以上で調査と解説は終了です。

 - Java