いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

iBatis奮闘記-0003 (1:Nの関連)

てことで、次は1:Nのマッピングについてやってみます。
普通は、1:1のマッピングからスタートだと思いますが
1:1は次の回でやります。(なんでかっていうと、こっちを
先に覚えておかないといけない羽目になったからです ハイw)

複雑なマッピングの場合、以下の点を頭にいれておくといいみたいです。

  • resultClass属性では、無理な場合があるのでその場合はresultMap属性を使用
  • マッピングをするオブジェクトは普通に作成。(1側にListとかでNを保持)
  • 一発で取得しようが、2回に分けて取得しようが自由(SQLを直接かけるのでそこは自由です)

今回は、一回のSQL発行で取得することにします。

まずは、使用するテスト用テーブルを作成します。

create table parent_table (id int primary key, name varchar(100));
create table child_table (
     id        int
    ,parent_id int
    ,name      varchar(100)
    ,primary key (id, parent_id)
    ,foreign key (parent_id) references parent_table(id)
);

当たり前ですが、parent_table側が1で、child_table側がNとなります。

次に、ドメインオブジェクト作成。
今回は、Parent側からChildを取得できる流れのみでいいので、
Child側からParent側への関連は行いません。(親IDのみを保持するだけ
としています)。

// vim:set ts=4 sw=4 et ws is nowrap ft=java fenc=cp932 ff=dos:
package gsf.samples.ibatis.sample002;

import java.io.*;
import java.util.*;

import org.apache.commons.lang.builder.*;

/**
 * PARENTテーブルに対応するドメインオブジェクト.<br/>
 *
 * @author gsf_zero1
 *
 */
public class Parent{

    /** ID */
    private Integer     id;

    /** NAME */
    private String      name;

    /** CHILDテーブルオブジェクトリスト */
    private List<Child> childs;

    /**
     * コンストラクタ.<br/>
     *
     */
    public Parent(){
    }

    /**
     * Get id.
     *
     * @return id as Integer.
     */
    public Integer getId(){
        return this.id;
    }
    
    /**
     * Set id.
     *
     * @param id the value to set.
     */
    public void setId(Integer id){
        this.id = id;
    }
    
    /**
     * Get name.
     *
     * @return name as String.
     */
    public String getName(){
        return this.name;
    }
    
    /**
     * Set name.
     *
     * @param name the value to set.
     */
    public void setName(String name){
        this.name = name;
    }
    
    /**
     * Get childs.
     *
     * @return childs as List.
     */
    public List<Child> getChilds(){
        return this.childs;
    }
    
    /**
     * Set childs.
     *
     * @param childs the value to set.
     */
    public void setChilds(List<Child> childs){
        this.childs = childs;
    }

    /**
     * オブジェクトの文字列表現を返す。<br/>
     *
     * @return 文字列表現
     *
     * @see java.lang.Object#toString()
     *
     */
    @Override
    public String toString(){
        return new ToStringBuilder(this)
                        .append("id",   this.getId())
                        .append("name", this.getName())
                        .toString();
    }
}
// vim:set ts=4 sw=4 et ws is nowrap ft=java fenc=cp932 ff=dos:
package gsf.samples.ibatis.sample002;

import java.io.*;

import org.apache.commons.lang.builder.*;

/**
 * CHILDテーブルに対応するドメインオブジェクト.<br/>
 *
 * @author gsf_zero1
 *
 */
public class Child implements Serializable{

    /** ID */
    private Integer id;

    /** PARENT_ID */
    private Integer parentId;

    /** NAME */
    private String  name;

    /**
     * コンストラクタ.<br/>
     *
     */
    public Child(){
    }
    
    /**
     * Get id.
     *
     * @return id as Integer.
     */
    public Integer getId(){
        return this.id;
    }
    
    /**
     * Set id.
     *
     * @param id the value to set.
     */
    public void setId(Integer id){
        this.id = id;
    }
    
    /**
     * Get parentId.
     *
     * @return parentId as Integer.
     */
    public Integer getParentId(){
        return this.parentId;
    }
    
    /**
     * Set parentId.
     *
     * @param parentId the value to set.
     */
    public void setParentId(Integer parentId){
        this.parentId = parentId;
    }
    
    /**
     * Get name.
     *
     * @return name as String.
     */
    public String getName(){
        return this.name;
    }
    
    /**
     * Set name.
     *
     * @param name the value to set.
     */
    public void setName(String name){
        this.name = name;
    }

    /**
     * オブジェクトの文字列表現を返す.<br/>
     *
     * @return 文字列表現
     *
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString(){
        return new ReflectionToStringBuilder(this).toString();
    }
}

つぎは、もっとも大事なSQL設定ファイル.

<?xml version="1.0" encoding="Windows-31J"?>
<!-- vim:set ts=4 sw=4 et ws is nowrap ft=xml fenc=cp932 ff=dos: -->
<!DOCTYPE sqlMap
    PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
    "http://www.ibatis.com/dtd/sql-map-2.dtd">

<!--
    1:Nの動作を確認するためのSQL設定ファイル。

    データベーステーブルは、PARENTとCHILDの2つが存在し、
    PARENTに対して、N件のCHILDが紐づく関連となっている。
    • >
<sqlMap namespace="ParentAndChild"> <!-- 別名を付けて、簡略表記できるよう定義。 --> <typeAlias alias="Parent" type="gsf.samples.ibatis.sample002.Parent"/> <typeAlias alias="Child" type="gsf.samples.ibatis.sample002.Child"/> <!-- メインのSELECT要素の結果をオブジェクトにマッピングするための 定義をおこなっている。 内部は、result要素を持ちどのプロパティに対して どの結果カラム名が対応するかを定義しておく。 また、N件の結果をマッピングする(List<Child>)の部分については、 以下に記述してあるように、プロパティに対してカラム名ではなく 別のresultMapを指定して、その別のresultMapのマッピング結果オブジェクトを 追加するように定義する。別のマッピング定義を指定する場合は、必ず名前空間の 指定が必要になるので注意。 groupBy属性を指定するとSQLの結果に対して指定したプロパティでのグループ化を 行ってくれる。今の例でいうと、ひとつのPARENTに対して10のCHILDが紐づいている 場合、SQLの結果は10行になるが、下記のようにgroupByでParentクラスのidプロパティで グループ化を行い、PARENTオブジェクト1つの中に複数のCHILDオブジェクトとなるように 調整している。これを行わないと、結果がおかしくなる(つまり、ひとつのParentオブジェクトに 対して、ひとつのChildオブジェクトしか紐づいていないものが10個出来上がる)ので注意 --> <resultMap id="mainResultMap" class="Parent" groupBy="id"> <result property="id" column="parent_id"/> <result property="name" column="parent_name"/> <result property="childs" resultMap="ParentAndChild.childsResultMap"/> </resultMap> <!-- mainResultMapマッピングの中から呼び出される別のマッピング定義。 こちらは、CHILDテーブルに対応するオブジェクトとカラムのマッピングを 定義している。 --> <resultMap id="childsResultMap" class="Child"> <result property="id" column="child_id"/> <result property="parentId" column="parent_id"/> <result property="name" column="child_name"/> </resultMap> <!-- resultMapを指定しているSELECT要素。 resultClass属性とresultMap属性は、どちらかひとつを 指定すればよい。ジョインも含んだ複雑なマッピングの場合 は、resultMapを指定しないとマッピングできない。 --> <select id="getAllParentAndChild" resultMap="mainResultMap"> <![CDATA[ select p.id as parent_id, p.name as parent_name, c.id as child_id, c.name as child_name from parent_table p, child_table c where p.id = c.parent_id order by parent_id, child_id ] ]> </select> <insert id="insertParent" parameterClass="Parent"> <![CDATA[ insert into parent_table values(#id#, #name#) ] ]> </insert> <insert id="insertChild" parameterClass="Child"> <![CDATA[ insert into child_table values(#id#, #parentId#, #name#) ] ]> </insert> <delete id="deleteParent"> <![CDATA[ delete from parent_table ] ]> </delete> <delete id="deleteChild"> <![CDATA[ delete from child_table ] ]> </delete> </sqlMap>

んで、動作確認といきたいところですが、
SqlMapClient設定ファイル(SqlMapConfig.xml)に
今作成したSQL設定ファイルを定義しておきます。

    <sqlMap resource="gsf/samples/ibatis/sample002/ParentAndChild.ibatis.xml"/>

んで、動作確認クラス

// vim:set ts=4 sw=4 et ws is nowrap ft=java fenc=cp932 ff=dos:
package gsf.samples.ibatis.sample002;

import java.util.*;

import com.ibatis.common.resources.*;
import com.ibatis.sqlmap.client.*;

/**
 * IBatisSample002の動作確認を行うクラス.<br/>
 *
 * @author gsf_zero1
 *
 */
public class IBatisSample002{

    /**
     * アプリケーションエントリーポイント.<br/>
     *
     * @param args 起動時引数
     *
     */
    public static void main(String[] args) throws Exception{

        SqlMapClient sqlMap 
                = SqlMapClientBuilder.buildSqlMapClient(
                        Resources.getResourceAsReader("SqlMapConfig.xml"));

        try{

            sqlMap.startTransaction();

            sqlMap.delete("deleteChild",  null);
            sqlMap.delete("deleteParent", null);

            for(int i = 0; i < 10; i++){
                Parent aParent = new Parent();

                aParent.setId*1;
                aParent.setName("parent_value_" + (i + 1));

                sqlMap.insert("insertParent", aParent);

                for(int j = 0; j < 10; j++){
                    Child aChild = new Child();

                    aChild.setId*2;
                    aChild.setParentId(aParent.getId());
                    aChild.setName("child_value_" + (j + 1));

                    sqlMap.insert("insertChild", aChild);
                }
            }

            sqlMap.commitTransaction();

        }finally{
            sqlMap.endTransaction();
        }

        for(Parent aParent : (List<Parent>) sqlMap.queryForList("getAllParentAndChild", null)){
            System.out.println(aParent);

            for(Child aChild : aParent.getChilds()){
                System.out.printf("\t%s\n", aChild);
            }
        }
    }
}

上記を実行すると、1個のParentオブジェクトに対して、10個のChildオブジェクトが紐づいて
いるのが確認できます。

*1:i + 1

*2:j + 1