今回は、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呼び出しで、紐付いたデータが取得でき、マッピングも正常に行なわれている事が確認できます。