今回は、1対Nのデータの取得を行なってみます。
やり方ですが、基本的にはJava版と同じです。
1対NのようなJOIN関連の場合に重要となってくるのが
SQL定義ファイル内に定義するresultMapの定義です。
これが、無いとJOINがうまく出来ません。
実際に、どのように書くのかというと、以下のようにします。
<resultMaps> <resultMap id="FindByCategoryIdResultMap" class="CategoryAndMemos" groupBy="CategoryId"> <result property="CategoryId" column="CategoryId"/> <result property="CategoryName" column="CategoryName"/> <result property="Memos" resultMapping="CategoryAndMemos.FindByCategoryIdResultMap-Memo"/> </resultMap> <resultMap id="FindByCategoryIdResultMap-Memo" class="Memo"> <result property="MemoId" column="MemoId"/> <result property="MemoTitle" column="MemoTitle"/> <result property="MemoData" column="MemoData"/> <result property="MemoAuthor.AuthorId" column="AuthorId"/> <result property="MemoAuthor.AuthorName" column="AuthorName"/> </resultMap> </resultMaps>
上記の定義は、今回のSQL定義ファイルの中から抜き出したものです。
見たら大体お分かりだと、思いますが
<result property="オブジェクトのプロパティ名" column="対応するSQLの結果列の名前"/>
という風に、定義していきます。
てことで、今回のサンプルです。
今回は、以下のデータが予め登録されているとします。
- Categoriesテーブル
- CategoryIdが1で、CategoryNameが"C#"となっているデータが存在する。
- Authorsテーブル
- AuthorIdが10で、Nameが"gsf_zero1"と成っているデータが存在する。
- AuthorIdが11で、Nameが"gsf_zero2"と成っているデータが存在する。
- Memosテーブル
以下のデータが存在するとする。
MemoId | CategoryId | AuthorId | Title | MemoData |
---|---|---|---|---|
1 | 1 | 10 | C#-001 | MemoData-001 |
2 | 1 | 11 | C#-002 | MemoData-002 |
上記の状態で、カテゴリから紐付くメモデータを取得します。
また、メモデータには、対応する作者データを付加します。
まず、データモデルクラスから。
作者データを表すクラスです。
[Author.cs]
using System; using System.Collections.Generic; using System.Text; namespace Gsf.Samples.IBatisNet.Models { [Serializable] public class Author { int _authorId = -1; string _authorName; public int AuthorId{ get{ return _authorId; } protected set{ _authorId = value; } } public string AuthorName{ get{ return _authorName; } set{ _authorName = value; } } } }
次は、メモデータを表すクラスです。
[Memo.cs]
using System; namespace Gsf.Samples.IBatisNet.Models { [Serializable] public class Memo { int _memoId; string _title; string _memoData; /// <summary> /// メモデータの作者を表します。 /// </summary> Author _author; public int MemoId{ get{ return _memoId; } protected set{ _memoId = value; } } public string MemoTitle{ get{ return _title; } set{ _title = value; } } public string MemoData{ get{ return _memoData; } set{ _memoData = value; } } public Author MemoAuthor{ get{ return _author; } set{ _author = value; } } } }
最後に、カテゴリデータとそれに紐付くメモデータを表すクラスです。
[CategoryAndMemos.cs]
using System; using System.Collections.Generic; namespace Gsf.Samples.IBatisNet.Models { [Serializable] public class CategoryAndMemos { int _categoryId = -1; string _categoryName; /// <summary> /// カテゴリに紐付くメモデータのリスト /// </summary> List<Memo> _memos; public int CategoryId{ get{ return _categoryId; } protected set{ _categoryId = value; } } public string CategoryName{ get{ return _categoryName; } set{ _categoryName = value; } } public List<Memo> Memos{ get{ return _memos; } protected set{ _memos = value; } } } }
次は、上記のクラスにデータをマッピングするためのSQL定義ファイルです。
<?xml version="1.0" encoding="utf-8" ?> <sqlMap namespace="CategoryAndMemos" xmlns="http://ibatis.apache.org/mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <alias> <typeAlias type="Gsf.Samples.IBatisNet.Models.Memo" alias="Memo"/> <typeAlias type="Gsf.Samples.IBatisNet.Models.CategoryAndMemos" alias="CategoryAndMemos"/> </alias> <resultMaps> <resultMap id="FindByCategoryIdResultMap" class="CategoryAndMemos" groupBy="CategoryId"> <result property="CategoryId" column="CategoryId"/> <result property="CategoryName" column="CategoryName"/> <result property="Memos" resultMapping="CategoryAndMemos.FindByCategoryIdResultMap-Memo"/> </resultMap> <resultMap id="FindByCategoryIdResultMap-Memo" class="Memo"> <result property="MemoId" column="MemoId"/> <result property="MemoTitle" column="MemoTitle"/> <result property="MemoData" column="MemoData"/> <result property="MemoAuthor.AuthorId" column="AuthorId"/> <result property="MemoAuthor.AuthorName" column="AuthorName"/> </resultMap> </resultMaps> <statements> <select id="FindByCategoryId" parameterClass="int" resultMap="FindByCategoryIdResultMap"> <![CDATA[ select c.CategoryId as CategoryId ,c.CategoryName as CategoryName ,m.MemoId as MemoId ,m.Title as MemoTitle ,m.MemoData as MemoData ,a.AuthorId as AuthorId ,a.Name as AuthorName from Categories c, Memos m, Authors a where c.CategoryId = #value# and c.CategoryId = m.CategoryId and m.AuthorId = a.AuthorId order by m.MemoId ]]> </select> </statements> </sqlMap>
重要な点は、以下の部分です。
>|
<resultMap id="FindByCategoryIdResultMap" class="CategoryAndMemos" groupBy="CategoryId">
< |
groupByという属性がついていますが、これを指定することにより、SQLを実行した結果を指定したプロパティ値に
基づいてグループ化してくれます。
つまり、SQLをそのまま実行すると以下のようになっているデータを
1 | 'C#' | 1 | 'C#-001' | 'MemoData-001' | 10 | 'gsf_zero1' |
1 | 'C#' | 2 | 'C#-002' | 'MemoData-002' | 11 | 'gsf_zero2' |
以下のようにしてくれます。
1 | 'C#' | 1 | 'C#-001' | 'MemoData-001' | 10 | 'gsf_zero1' |
2 | 'C#-002' | 'MemoData-002' | 11 | 'gsf_zero2' |
次に重要なのが、以下の部分です。
<result property="Memos" resultMapping="CategoryAndMemos.FindByCategoryIdResultMap-Memo"/>
この部分は、Memosというプロパティに対して、resultMappingで指定した結果マッピングを行なった結果をセットしなさいと
いう風になります。ibatisは、該当するプロパティの方がList<Memo>であり、結果が複数あることも認識しますので
これで、該当のリストオブジェクトにグループ化された分のデータがセットされます。
尚、データをマッピングする際に、先程のCategoryAndMemosクラスの方では、Memosプロパティのリストオブジェクトをnewして
いませんでしたが、ibatis側がリフレクションを使用して型を判別し、インスタンス化もしれくれますので、事前に
オブジェクトを作成しておく必要はありません。また、Memoクラスの方のAuthorプロパティも同じく、ibatisがインスタンス化を
面倒みてくれますので、問題ありません。
最後に、実行サンプルです。
using System; using System.Collections.Generic; using NUnit.Framework; using Gsf.Samples.IBatisNet.Models; using IBatisNet.DataMapper; using IBatisNet.Common; namespace Gsf.Samples.IBatisNet { [TestFixture] public class IBatisNetSample006 { [Test] public void JOINの動作を確認してみる(){ // // データ取得. // CategoryAndMemos categoryAndMemos = Mapper.Instance().QueryForObject<CategoryAndMemos>("CategoryAndMemos.FindByCategoryId", 1); // // データがちゃんと取得できているかどうかを確認 // Assert.IsNotNull(categoryAndMemos); Assert.AreEqual(1, categoryAndMemos.CategoryId); Assert.AreEqual("C#", categoryAndMemos.CategoryName); Assert.AreEqual(2, categoryAndMemos.Memos.Count); categoryAndMemos.Memos.ForEach(delegate(Memo target){ Assert.IsNotNull(target); Assert.IsNotNull(target.MemoAuthor); }); PrintCategoryAndMemos(categoryAndMemos); } void PrintCategoryAndMemos(CategoryAndMemos categoryAndMemos){ Console.WriteLine("CategoryId:{0}", categoryAndMemos.CategoryId); Console.WriteLine("CategoryName:{0}", categoryAndMemos.CategoryName); foreach(Memo m in categoryAndMemos.Memos){ Console.WriteLine("\tMemoId:{0}", m.MemoId); Console.WriteLine("\tMemoTitle:{0}", m.MemoTitle); Console.WriteLine("\tMemoData:{0}", m.MemoData); Author author = m.MemoAuthor; Console.WriteLine("\t\tAuthorId:{0}", author.AuthorId); Console.WriteLine("\t\tAuthorName:{0}", author.AuthorName); } } } class DummyEntryPoint006{ static void Main(){ // // noop; // } } }
上記のサンプルを実行すると、以下のように表示されます。
------ Test started: Assembly: Sample006.exe ------ CategoryId:1 CategoryName:C# MemoId:1 MemoTitle:C#-001 MemoData:MemoData-001 AuthorId:10 AuthorName:gsf_zero1 MemoId:2 MemoTitle:C#-002 MemoData:MemoData-002 AuthorId:11 AuthorName:gsf_zero2
一回のSQL呼び出しで、紐付いたデータが取得でき、マッピングも正常に行なわれている事が確認できます。