いろいろ備忘録日記

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

iBatis.NET奮闘記-002 (基本的な操作(1テーブルに対するSelect)) (Mapper, ISqlMapper, QueryForList, QueryForObject)

今回は、基本的なデータ取得の方法をやってみます。
つまり、1テーブルに対してのSelect文の発行です。


IBatis.NETにて、特定のSQLを実行するために通常作成するファイルは以下の通りです。

  1. SQL定義ファイル(SqlMapファイル) (必須)
  2. モデルクラス (オプション)
  3. SqlMap.configへのSQL定義ファイルの登録 (必須)


IBatisにて、処理を行なう際、ポイントとなるのはSQL定義ファイルの書き方です。
要は、定義ファイルの書き方を覚える事がIBatisを覚える事になります。


今回は、モデルクラスも作成する方向でやってみます。実際IBatisには、結果をDictionaryオブジェクトで
取得するやり方もあります。ですが、通常は特定のデータクラスを作成し、そのクラスに結果をマッピングします。


今回、対象とするテーブルは、Categoriesテーブルです。
テーブル構造については、第1回目に画像で貼り付けているデータベースダイアグラムを参照願います。


Categoriesテーブルには、予め以下のデータが登録されているとします。

CategoryId CategoryName
1 C#
2 VisualBasic2005
3 Java
4 Python
5 Ruby
6 C
7 C++
8 Ajax
9 日々のメモ


まず、以下のデータクラスを定義します。
このクラスは、javaでいうPOJOとなります。


[Category.cs]

using System;

namespace Gsf.Samples.IBatisNet.Models {

    [Serializable]
    public class Category {

        int    _categoryId = -1;
        string _categoryName;

        public int CategoryId{
            get{
                return _categoryId;
            }
            protected set{
                _categoryId = value;
            }
        }

        public string CategoryName{
            get{
                return _categoryName;
            }
            set{
                _categoryName = value;
            }
        }

        public override string ToString() {
            return string.Format("id:{0}, name:{1}", CategoryId, CategoryName);
        }
    }
}

CategoryIdプロパティのsetの部分を、外からアクセスできないようにしているのは、データベース上
CategoryIdカラムはIDENTITYであり、自動採番されるデータであるからです。IBatis.NETを利用する場合
IBatisはプロパティの値をリフレクションを用いて取得するため、スコープが何に設定されていても大丈夫です。


次に、以下のSQL定義ファイル(SqlMapファイル)を作成します。
作成するXMLファイル名に規則はないですが、私はXXXXX.ibatis.xmlとするようにしています。
(Javaの場合もこのようにしていたので、その名残です。)
また、このファイルは、上記のCategory.csと同じ場所においています。
(これも実際は、どこに置いても問題ありません。)


[Category.ibatis.xml]

<?xml version="1.0" encoding="utf-8" ?>
<sqlMap namespace="Categories" 
        xmlns="http://ibatis.apache.org/mapping" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <alias>
        <typeAlias type="Gsf.Samples.IBatisNet.Models.Category" alias="Category"/>
    </alias>

    <statements>

        <sql id="CommonCategoryQuery">
            <![CDATA[
                select
                     CategoryId
                    ,CategoryName
                from
                    Categories            
            ]]>
        </sql>
        
        <select id="FindAll" resultClass="Category">
            <include refid="CommonCategoryQuery"/>
            <![CDATA[
                order by
                    CategoryId
            ]]>
        </select>

        <select id="FindByCategoryId" parameterClass="int" resultClass="Category">
            <include refid="CommonCategoryQuery"/>
            <![CDATA[
                where
                    CategoryId = #value#
                order by
                    CategoryId
            ]]>            
        </select>
    </statements>
</sqlMap>


IBatisの便利な機能として、同じSQLの部分をまとめるということが出来ます。
上記にて、sql要素を使用している箇所がその部分です。ここに共通する部分を定義し、
各select要素内にて、includeして利用するのが一般的な使い方です。もちろん各select要素
内に、直接SQL全体を記述することもできます。


クエリ名FindByCategoryIdの部分にて、whereの箇所で#value#としている部分ですが、この部分には
parameterClass属性で指定したデータがセットされます。parameterClass属性で指定されたデータは
実際にコード上にて、検索データとして指定されたデータの事です。parameterClassの部分にCategoryの
ように独自クラスが指定されている場合は、以下のように#の中にプロパティ名を記述するとその値がセットされます。

parameterClass="Category"とセットされている場合

where
    CategoryId   = #CategoryId#
    and
    CategoryName = #CategoryName#

と記述できる。

なお、このあたりの決まりごとはjavaの方と全く同じです。


最後に、作成したSQL定義ファイルをSqlMap.configファイルに定義します。
SqlMap.configファイルのsqlMaps要素の中に定義します。


[SqlMap.config]

    <sqlMaps>
        <sqlMap embedded="Gsf.Samples.IBatisNet.Models.Category.ibatis.xml, Sample002"/>
    </sqlMaps>


今回は、SQL定義ファイルを埋め込みリソースとしているので上記のように指定しています。
それ以外の場合の指定の仕方については、ドキュメントを参照してください。


これで、実行するまでの準備は整いました。
後は実行するだけです。


IBatis.NETを利用するには、まず以下のオブジェクトを取得します。

IBatisNet.DataMapper.ISqlMapper

実際には、ISqlMapperはインターフェースなので、実装クラスであるSqlMapperオブジェクトを取得します。
このオブジェクトは、その名前のままですが、マッピング定義を表すオブジェクトです。


このオブジェクトを取得するには、以下のようにします。

IBatisNet.DataMapper.Mapper.Instance()

Mapperクラスには、ISqlMapperオブジェクトを取得するためのシングルトンメソッドが定義されています。
SqlMapperオブジェクトはスレッドセーフであり、通常はアプリケーションに一つあれば良い為、このようになっています。
つまり、どの場所からでも上記のシングルトンメソッドを呼び出せば、処理を行なえます。


以下、処理サンプルになりますが、今回使用しているメソッドは以下の通りです。

public List<T> QueryForList<T>(string queryName, object data)
public T QueryForObject<T>(string queryName, object data)

どちらも名前の通りですね。クエリを実行した結果をListとして返却してくれるのがQueryForList、単一の結果として取得する場合は、QueryForObjectです。


以下サンプルです。

using System;
using System.Collections.Generic;

using NUnit.Framework;

using Gsf.Samples.IBatisNet.Models;

using IBatisNet.DataMapper;

namespace Gsf.Samples.IBatisNet {

    [TestFixture]
    public class IBatisNetSample002 {

        [Test]
        public void 全カテゴリを出力してみる(){

            foreach(Category aCategory in Mapper.Instance().QueryForList<Category>("Categories.FindAll", null)){
                Console.WriteLine(aCategory);
            }

        }

        [Test]
        public void 指定したカテゴリIDのものを取得する(){

            Assert.AreEqual("C#", Mapper.Instance().QueryForObject<Category>("Categories.FindByCategoryId", 1).CategoryName);
            //
            // データが存在しない場合は、nullが返ってくる。
            //
            Assert.IsNull(Mapper.Instance().QueryForObject("Categories.FindByCategoryId", -999));

        }
    }

    class DummyEntryPoint002{
        static void Main(){
            //
            // noop;
            //
        }
    }
}


実行すると以下のように表示されます。

id:1, name:C#
id:2, name:VisualBasic2005
id:3, name:Java
id:4, name:Python
id:5, name:Ruby
id:6, name:C
id:7, name:C++
id:8, name:Ajax
id:9, name:日々のメモ

================================
過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。