いろいろ備忘録日記

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

iBatis.NET奮闘記-003 (基本的な操作(Insert)) (Mapper, ISqlMapper, Insert, IDalSession, BeginTransaction, selectKey)


今回は、Insertを行なってみます。基本的に、InsertもUpdateもDeleteも同じです。
SQL定義ファイルに定義を行い、後はInsertの場合はInsertメソッド、Updateの場合はUpdateメソッドを呼ぶのみです。


但し、Insertの場合のみ他の変更系クエリの場合と一点違うのは、IDENTITYカラムやオラクルシーケンスなどを用いて
IDカラムの値をセットする場合、クエリ発行はIBatisが行なってしまうので何のIDが割り振られたか分かりません。


それを取得できるようにするための機能がIBatisにはあります。
詳しくは、SQL定義ファイルを見ていただけるとわかりますが、selectKey要素をinsert要素内に定義することで
クエリ発行後に採番された値を取得することが出来るようになります。


今回は、Authorsテーブルにデータを登録してみます。
データベースの定義内容については、第1回目のダイアグラム図を参照願います。


まず、データモデルとなるAuthorクラスを定義します。

[Author.cs]

using System;

namespace Gsf.Samples.IBatisNet.Models {

    [Serializable]
    public class Author {

        int      _authorId = -1;
        string   _name;
        int?     _age;
        DateTime _created;
        DateTime _updated;

        public int AuthorId{
            get{
                return _authorId;
            }
            protected set{
                _authorId = value;
            }
        }

        public string Name{
            get{
                return _name;
            }
            set{
                _name = value;
            }
        }

        public int? Age{
            get{
                return _age;
            }
            set{
                _age = value;
            }
        }

        public DateTime Created{
            get{
                return _created;
            }
            protected set{
                _created = value;
            }
        }

        public DateTime Updated{
            get{
                return _updated;
            }
            protected set{
                _updated = value;
            }
        }
    }
}

Ageの値は、データベース上, 型がintでnullを許容するようになっているのでint?と宣言している部分に
注目してください。特にこうしないといけないものではありませんが、intのままだとnull値に対応できないので
このようにすると便利です。


次は、SQL定義ファイルです。


[Author.ibatis.xml]

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

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

    <statements>
        <select id="FindByAuthorId" parameterClass="int" resultClass="Author">
            <![CDATA[
                select
                     AuthorId
                    ,Name
                    ,Age
                    ,Created
                    ,Updated
                from
                    Authors
                where
                    AuthorId = #value#
            ]]>
        </select>

        <insert id="InsertAuthor" parameterClass="Author">
            <![CDATA[
                insert into Authors
                    (Name, Age)
                    values
                    (#Name#, #Age#)
            ]]>

            <!--
                自動採番されたデータをメソッド実行後の戻り値として受け取りたい場合は
                以下のようにてselectKey要素を定義する。
                
                propertyには、パラメータとして与えているオブジェクトのどのプロパティの値かを指定。
                resultClassには、自動採番された結果の型を指定。通常は、intとなる。
                typeには、insert文の発行前(pre)にselectKey要素のSQLを実行するか、insert発行後(post)に実行するかを指定する。
                これは、各DBにより異なり、Oracleの場合はpre, SQL-Server, MySQLの場合はpostを指定する。
            -->
            <selectKey property="AuthorId" resultClass="int" type="post">
                select @@IDENTITY as value
            </selectKey>
        </insert>
    </statements>
</sqlMap>


この後、SqlMap.configファイルにSQL定義ファイルの設定を行ないます。
どのように設定するのかは、前回を参照願います。


では、実際に実行してみます。
コード内にも出てきますが、IBatis.NETでトランザクションを行なうには以下のようにします。
TransactionScopeを使う場合とよく似ていますね。

using(IBatisNet.Common.IDalSession session = Mapper.Instance().BeginTransaction()){
    //
    // 処理を行なう.
    //


    //
    // データを確定.
    //
    session.Complete();

} // ブロックを抜ける際に、Completeメソッドが呼ばれていない場合は、自動でロールバックされる。

当然ながら、IBatisトランザクション処理はSystem.Transactionsの機能を使っていないので
MSDTCサービスは起動する必要はありません。

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 IBatisNetSample003 {

        [Test]
        public void データの登録を行なってみる(){
            //
            // 登録用データを作成.
            //
            Author newAuthor = new Author();
            newAuthor.Name   = "gsf.zero1";
            newAuthor.Age    = 30;

            //
            // データの登録
            //
            // IBatis.NETにてトランザクション処理を行なうには、Mapper.Instance().BeginTransaction()メソッドをコールする。
            // BeginTransactionメソッドは、IDalSessionオブジェクトを返す。このオブジェクトは, TransactionScopeと同じような
            // 使い方が出来るので、通常usingブロック内でBeginTransactionし、結果をCompleteメソッドで確定する。
            // TransactionScopeと同じく、usingブロックを抜ける際にCompleteメソッドが呼ばれていない場合は自動的にロールバックされる。
            //
            using(IDalSession session = Mapper.Instance().BeginTransaction()){
                //
                // insert.
                //
                // SQL定義ファイルの方で、selectKeyの設定を行なっているため、戻り値として発行されたAuthorIdの値が返却される。
                // また、パラメータとして指定したnewAuthorオブジェクトのAuthorIdの方にも値が設定されている。
                //
                int newAuthorId = (int) Mapper.Instance().Insert("Authors.InsertAuthor", newAuthor);

                //
                // データがちゃんと登録できているかどうかを確認.
                //
                Author insertedAuthor = Mapper.Instance().QueryForObject("Authors.FindByAuthorId", newAuthorId);

                Assert.AreEqual(newAuthorId,        insertedAuthor.AuthorId);
                Assert.AreEqual(newAuthor.AuthorId, insertedAuthor.AuthorId);
                Assert.AreEqual(newAuthor.Name,     insertedAuthor.Name);
                Assert.AreEqual(newAuthor.Age,      insertedAuthor.Age);
                //
                // 日付のカラムにもちゃんとデータが登録されているかどうかを確認.
                //
                Assert.IsNotNull(insertedAuthor.Created);
                Assert.IsNotNull(insertedAuthor.Updated);

                //
                // データをわざとロールバックさせる。
                //
                //session.Complete();
            }
        }
    }

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


次は、Updateをやってみます。