2009년 10월 10일 토요일

Step2:メタ情報をExcelに移動する

http://gihyo.jp/dev/serial/01/code/000503

Step2:メタ情報をExcelに移動する

Step2ではメタプログラミングの前準備を行います(リスト2)。ここでは「電文データの読み取り処理」を,Excelから取得した情報をもとに組み立ててみます。両端空白文字の除去やデータ型の変換など「データの変換処理」はStep2では組み込まず,のちほどStep3で取り上げます。

まずでExcel形式の設定ファイルを読み込みます。設定ファイルconfig.xlsの中身は表2のとおりです。DataTableクラスはExcelなどの表形式のデータ構造を保持するSeasar2のクラスで,XlsReaderでファイルから生成します。

このコードのポイントとなるのがです。Step1では4つのデータ項目の数だけ処理を逐一書いていましたが,ここでは「Excelに定義されたデータ項目の数だけ」データを読み取っています。先頭のfor文でExcelの行数分(=定義されたデータ項目数分)処理を繰り返します。ではExcelに定義された「データ項目名」「(データ項目の読み取る)バイト数」を取得し,取得した情報をもとにで電文を読み取ります。最後にで「データ項目名」をキーとして,Mapにデータを追加しています。

さて,Step1と比べて劇的に変わった点があります。それはExcelの設定ファイルを変更するだけで「さまざまな電文フォーマットに対応できる ようになった」点です。たとえば,データ項目として「年齢」が追加されたとしてもコードの書き換えは発生しません。「年齢」の定義をExcelに1行追加 するだけで,プログラムの変更なしに挙動を追加できます。今回の電文は「ユーザ情報」を扱ったものですが,「オーダー情報」「商品マスタ」などの電文解析 が発生しても,それらに対応したExcelの設定ファイルを用意すればよく,やはりプログラムの変更は発生しません。

このようにプログラムを書かなくても,メタ情報を用意するだけでプログラムが書かれたかのようになるのがメタプログラミングです。

表2 Excel設定ファイル

Noデータ名称長さ
1送信日8
2ユーザ名10
3メールアドレス20
4ポイント5

リスト2 Step2:メタ情報をExcelに移動したコード

public static void main(String[] args) throws Exception {
    byte[] bytes = FileUtils.readFileToByteArray(
        new File("data.txt"));
    DataTable config = new XlsReader(                ┓
        new File("config.xls")).read().getTable(0);  ┛①
    MessageParser parser = 
        new MessageParser(bytes, config);
    parser.parse();
}

private static class MessageParser {
...
    private DataTable config;

    public MessageParser(byte[] bytes, DataTable config) {
        this.bytes = new ByteArray(bytes);
        this.config = config;
    }

    public void parse() throws Exception {
        while (index < bytes.getLength() - 1) {
            Map<String, Object> record =
                new HashMap<String, Object>();
            for (int i = 0; i < config.getRowSize(); i++) {      ┓
                DataRow row = config.getRow(i);
                String name =                           ┓       |
                    (String) row.getValue("データ名称");  ┛③      |
                int length =                              ┓     |
                    ((BigDecimal) row.getValue("バイト数"))  |    |
                    .intValue();                          ┛④   |
                String value = getString(length);    ―⑤         |
                record.put(name, value);      ―⑥                |
            }                                                    ┛②

            System.out.println(record);
        }
    }
...
}

考察:メタ情報とは何?

メタ情報はメタデータとも言われ,メタなデータ,すなわち「データに関するデータ」のことです。これは「普通のプログラミング」と「メタプログラミ ング」の関係に似ています。今回の例で言うと「電文そのもの」が普通のデータであり,「電文の仕様を表すデータ(Excelに移動した情報)」がメタデー タになります。データベースの定義情報やHTMLタグのmetaタグなどはメタデータの一種です。

2009년 10월 9일 금요일

サービスクラス

サービスクラス

TransactionAttributeType.SUPPORTSの利用方法

http://ml.seasar.org/archives/seasar-user/2009-May/017471.html

XlsReader Mock

#org.seasar.extension.dataset.impl.XlsReader

    /**
     * {@link XlsReader}を作成します。
     *
     * @param in
     *            入力ストリーム
     * @param trimString
     *            文字列をトリムするかどうか
     */
    public XlsReader(InputStream in, boolean trimString) {
        this.trimString = trimString;
        try {
            workbook = new HSSFWorkbook(in);
        } catch (IOException ex) {
            throw new IORuntimeException(ex);
        }
        dataFormat = workbook.createDataFormat();
        dataSet = new DataSetImpl();
        for (int i = 0; i < workbook.getNumberOfSheets(); ++i) {
            createTable(workbook.getSheetName(i), workbook.getSheetAt(i));
        }
    }

    public DataSet read() {
        return dataSet;
    }

# DataTable
    /**
     * @see org.seasar.extension.dataset.DataTable#getRowSize()
     */
    public int getRowSize() {
        return rows.size();
    }

    /**
     * @see org.seasar.extension.dataset.DataTable#getRow(int)
     */
    public DataRow getRow(int index) {
        return (DataRow) rows.get(index);
    }

2009년 10월 8일 목요일

Using reflection to copy a DataRow to a class

Using reflection to copy a DataRow to a class

Very often when working with DB tables we serialize a row in to class.  This entity class simply holds the data and hosts a rack of get and set methods. The data layer serializes this class, the business logic layer manipulated it, and the UI layer ultimately displays it.  Here is a simple example:

 

    class Class1

    {

        private string m_sProp1;

        private int m_nProp2;

 

        public string Prop1

        {

            get

            {

                return m_sProp1;

            }

            set

            {

                m_sProp1 = value;

            }

        }

 

        public int Prop2

        {

            get

            {

                return m_nProp2;

            }

            set

            {

                m_nProp2 = value;

            }

        }

    }

 

Very often somewhere in the UI and/or logic layers you have a large function laboriously copying each value in or out of the entity class.  In my case, I was serializing the entity to a DataSet to be displayed in a Repeater control.  So I thought to myself, “Self, wouldn’t it be cool to use reflection to automatically perform the copy”.  While I didn’t know much about reflection a couple hours ago, it’s remarkably easy to use and, as you probably know, very powerful.  Let’s get started…

 

I need a copy function to copy a DataRow to my entity class and vice versa.  We’ll address the first case. 

 

        public static object SetValuesToObject(Type tClass, DataRow dr)

        {

            try

            {

                Object oClass        = System.Activator.CreateInstance(tClass);

                MemberInfo[] methods = tClass.GetMethods();

                ArrayList aMethods   = new ArrayList();

                object[] aoParam     = new object[1];

 

                //Get simple SET methods

                foreach (MethodInfo method in methods)

                {

                    if (method.DeclaringType == tClass && method.Name.StartsWith("set_"))

                        aMethods.Add(method);

                }

 

                //Invoke each set method with mapped value

                for (int i = 0; i < aMethods.Count; i++)

                {

                    try

                    {

                        MethodInfo mInvoke = (MethodInfo)aMethods[i];

                        //Remove "set_" from method name

                        string sColumn = mInvoke.Name.Remove(0, 4);

                        //If row contains value for method...

                        if (dr.Table.Columns.Contains(sColumn))

                        {

                            //Get the parameter (always one for a set property)

                            ParameterInfo[] api = mInvoke.GetParameters();

                            ParameterInfo   pi  = api[0]; 

 

                            //Convert value to parameter type

                            aoParam[0] = Convert.ChangeType(dr[sColumn], pi.ParameterType);

                           

                            //Invoke the method

                            mInvoke.Invoke(oClass, aoParam);

                        }

                    }

                    catch

                    {

                        System.Diagnostics.Debug.Assert(false, "SetValuesToObject failed to set a value to an object");

                    }

                }

                return oClass;

            }

            catch

            {

                System.Diagnostics.Debug.Assert(false, "SetValuesToObject failed to create an object");

                return null;

            }

        } 


The way this works is I’m going to pass in a Type and a DataRow.  First we look through at all the methods in the entity class and pull out all the set methods.  Then look at the DataRow to see if we have a column which corresponds to the method.  Looking at the example entity class above, I would be hoping to have a column called Prop1 and Prop2*.  We the look to see what type of set method is expecting: string, int, DateTime, ect.  We use this to convert the value from the DataRow and set this in to an object array of length 1.  Finally, invoke the set method passing in the object array.  The end result, a newly instantiated entity class pre-populated with the values from the DataRow.

 

* Although it’s rare to abide by a naming convention in reality, this is new code, so we will keep naming identical from DB to entity to DataTable.  It would be trivial to introduce a mapping of column to property.

2009년 10월 6일 화요일

HirDB vs Oracle sql

これまでのHiRDB対応でOracleのSQLと異なっているために、
つまづいた点を下記に纏めました。
少しずつノウハウの蓄積が必要な感じです。

1.シーケンス値の設定
[Oracle]
 シーケンス名.NEXTVAL
 forcast_id_seq.NEXTVAL
[HiRDB]
 NEXT VALUE FOR シーケンス名
 NEXT VALUE FOR forcast_id_seq

※HiRDBでは、単純なSELECT文ではシーケンス値の取得はできない。
 INSERT INTO SELECTやUPDATEの中でのみ利用可能。

2.SELECT文でのNULL取得
[Oracle]
 SELECT NULL AS TEST1 FROM TEST
[HiRDB]
 SELECT CAST(NULL AS VARCHAR) AS TEST1 FROM TEST

※HiRDBでは、OracleのようにはNULLを取得できない。
 型を指定してCASTが必要。

3.SQL ExecuterにおけるSQL実行
[HiRDB]
 改行コードが取り除かれて次行のSQLと連結されるため、先頭行に
 FROMを記述した場合に、SQLの構文エラーとなる。

 SELECT *
 FROM TEST;
 を実行とすると、SELECT *FROM TESTとなり、構文エラー。

4.総件数の取得について
 総ヒット件数返却機能というのがあるそうです。
 http://www.hitachi.co.jp/Prod/comp/soft1/manual/pc/d635640/W3560167.HTM
通常,総ヒット件数とヒットした行の値を求める場合,
 二つのSQL文を実行しますが, 総ヒット件数返却機能を
 使用すると,総ヒット件数を求めるSQLと,ヒットした行の
 値を求めるSQLとを,一つのSQLに統合できます。
 これによって,二つのSQL文を実行するための検索時間が,
 一つのSQL文を実行する検索時間とほぼ等しくなります。

 必ずORDER BY等はつくので、一覧取得のSQLにCOUNT(*) OVER()を
 加えて、1回で取得した方がよさそうです。

 ※ちなみに、Oracleも同じSQLが実行できます
4.17.2 使用例

在庫表(ZAIKO)から単価(TANKA)が5000円以上の商品の総数と,その商品名(SNAME)を求め,数量(ZSURYO)でソートする場合の例を次に示します。

  • 総ヒット件数返却機能を使用しない場合
    SELECT COUNT(*) FROM ZAIKO WHERE TANKA>=5000
    SELECT SNAME    FROM ZAIKO WHERE TANKA>=5000 ORDER BY ZSURYO
    [説明]
    総ヒット件数返却機能を使用しない場合は,二つのSQL文が必要となります。
  • 総ヒット件数返却機能を使用する場合
    SELECT COUNT(*) OVER(), SNAME 
                    FROM ZAIKO WHERE TANKA>=5000 ORDER BY ZSURYO
    [説明]
    二つのSQL文の下線部が同じなので,総ヒット件数返却機能を使用することで一つのSQL文に統合し,初回取り出し時に総ヒット件数を取得できます。

2009년 10월 2일 금요일

seasar interceptor (MockInterceptor etc)

AOP(Aspect Oriented Programming)を簡単にいうと元のソースコードに手を加えることなく、透明なサービスを提供する技術といえるのではないかと思います。透明というのは、使っている側からその存在が見えないという意味です。キーとなる概念には次のようなものがあります。

Advice(MethodInterceptor)

プログラム中に挿入されるコードを表します。Interceptorと呼ばれることもあります。

Joinpoint(MethodInvocation)

対象となるクラスとAdviceを結合するポイントを表します。AdviceはJoinpointから引数やメソッドの情報を取得することができます。

Pointcut

どこにJoinpointを設定するのかを定義します。

Aspect

AdviceとPointcutを関連付けます。

S2AOPは、AOP Allianceに準拠しています。それでは、Seasarに最初からついているトレースのためのInterceptorを見てみましょう。

TraceInterceptor

package org.seasar.framework.aop.interceptors;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class TraceInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        StringBuffer buf = new StringBuffer(100);
        buf.append(invocation.getThis().getClass().getName());
        buf.append("#");
        buf.append(invocation.getMethod().getName());
        buf.append("(");
        Object[] args = invocation.getArguments();
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; ++i) {
                buf.append(args[i]);
                buf.append(", ");
            }
            buf.setLength(buf.length() - 2);
        }
        buf.append(")");
        System.out.println("BEGIN " + buf);
        try {
            Object ret = invocation.proceed();
            buf.append(" : ");
            buf.append(ret);
            return ret;
        } catch (Throwable t) {
            buf.append(" Throwable:");
            buf.append(t);
            throw t;
        } finally {
            System.out.println("END " + buf);
        }
    }
}

Interceptorを実装するには、MethodInterceptorをimplementsしてinvokeメソッドを実装します。MethodInvocationのgetThis()、getMethod()、getArguments()で対象となるオブジェクト、メソッド、引数を取得できます。proceed()を呼び出すと実際のメソッドが呼び出され実行結果を取得することができます。

それでは、DateクラスにTraceInterceptorを適用します。対象となるメソッドはgetTime()とします。

Pointcut pointcut = new PointcutImpl(new String[]{"getTime"});
Aspect aspect = new AspectImpl(new TraceInterceptor(), pointcut);
AopProxy aopProxy = new AopProxy(Date.class, new Aspect[]{aspect});
Date proxy = (Date) aopProxy.create();
proxy.getTime();

PointcutImplのコンストラクタの引数で対象となるメソッド名を指定(複数可)します。AutoNumberImplのようにインターフェースを実装しているなら、new PointcutImpl(AutoNumberImpl.class)のようにクラスを指定することで、そのクラスが実装しているインターフェースのメソッドをすべて自動的に適用させることもできます。メソッド名には正規表現(JDK1.4のreqex)も使えます。

AopProxyのコンストラクタで、対象となるクラスとAspectの配列を指定します。その後、create()でAspectが適用されたProxyオブジェクトを取得できます。

ThrowsInterceptor

例外処理をCrosscutting concernとして扱えるように、S2ではorg.seasar.framework.aop.interceptors.ThrowsInterceptorが用意されています。使い方は簡単で、ThrowsInterceptorを継承し、Object handleThrowable(Throwable, MethodInvocation)を実装するだけです。ThrowableにはThrowableのサブクラスを指定することができます。例えば、handleThrowable(IOException, MethodInvocation)のようにメソッド定義すると、発生した例外がIOExceptionもしくはIOExceptionのサブクラスの場合に、 呼び出されることになります。handleThrowable()はいくつでも定義することができます。

MockInterceptor

Mockを使ったテストを簡単に行えるように、S2ではorg.seasar.framework.aop.interceptors.MockInterceptorが用意されています。例えば、次のようなインターフェースがあったとします。

public interface Hello {
    public String greeting();
    public String echo(String str);
}

このインターフェースのモックを作成します。モックの仕様として、greetingメソッドが呼び出されたときは、"Hello"を返し、echoメソッドが呼び出されたときは"Hoge"を返すことにします。そのようなモックは次のようにして作成します。

MockInterceptor mi = new MockInterceptor();
mi.setReturnValue("greeting", "Hello");
mi.setReturnValue("echo", "Hoge");
Hello hello = mi.createMock(Hello.class);

MockInterceptor#setReturnValue()の最初の引数にメソッド名、2番目の引数に1番目の引数で指定したメソッドが呼び出されたときに返す引数を指定します。setReturnValue("Hello")のようにメソッド名を省略することもできます。その場合、常に"Hello"がかえってきます。1つのメソッドしか呼び出さないことが分かっている場合に使います。上記のコードをコンポーネント定義で書くと次のようになります。

<component class="Hello">
    <aspect>
	    <component class="org.seasar.framework.aop.interceptors.MockInterceptor">
		    <initMethod name="setReturnValue">
			    <arg>"greeting"</arg>
				<arg>"Hello"</arg>
			</initMethod>
			<initMethod name="setReturnValue">
			    <arg>"echo"</arg>
				<arg>"Hoge"</arg>
			</initMethod>
		</component>
	</aspect>
</component>

また、常に"Hello"を返す場合は次のように定義します。

<component class="Hello">
    <aspect>
	    <component class="org.seasar.framework.aop.interceptors.MockInterceptor">
		    <property name="returnValue">"Hello"</property>
		</component>
	</aspect>
</component>

MockInterceptor#isInvoked(String methodName) : booleanでメソッドが呼び出されたかどうかをチェックできます。 MockInterceptor#getArgs(String methodName) : Object[]で呼び出されたメソッドの引数もチェックできます。これらのチェックを行うには次のようにします。

<component name="helloMockInterceptor" class="org.seasar.framework.aop.interceptors.MockInterceptor">
    <property name="returnValue">"Hello"</property>
</component>
<component class="Hello">
    <aspect>helloMockInterceptor</aspect>
</component>
Hello hello = container.getComponent(Hello.class);
MethodInterceptor mi = container.getComponent("helloMockInterceptor");
hello.echo("Hello");
assertEquals(true, mi.isInvoked("echo"));
assertEquals("Hello", mi.getArgs("echo")[0]);

SyncInterceptor

メソッド呼び出しをAspectを使って同期化できるように、S2ではorg.seasar.framework.aop.interceptors.SyncInterceptorが用意されています。ソースを変更することなく、メソッド呼び出しを同期化できます

2009년 10월 1일 목요일

java-DB(HiRDB or Oracle) 型対応

http://www.hitachi.co.jp/Prod/comp/soft1/manual/common/hirdb.htm


21 アプリ-DB型対応表


項番

Java

COBOLHiRDB接続)

HiRDB

Oracle

1

Integer

L1 基本項目名
PICTURE S9(9)
COMPUTATIONAL.

INTEGER

NUMBER

2

Short

L1 基本項目名
PICTURE S9(4)
COMPUTATIONAL.

SMALLINT

NUMBER

3

BigDecimal

L1 基本項目名
PICTURE S9(p-s)[V9(s)]
COMPUTATIONAL-3.

ここで、1<=p<=18
0<=s<=p
p=s
の場合,SV9(s)
s=0
の場合,[V9(s)]を省略

DECIMAL

NUMBER

4

Double

L1 基本項目名
COMPUTATIONAL-2.

FLOAT

NUMBER

5

String

L1 基本項目名
PICTURE X(n).

ここで、1<=n<=30000

MCHAR

CHAR

6

String

L2 集団項目名.
L3
基本項目名1
PICTURE S9(4)
COMPUTATIONAL.
L3
基本項目名2
PICTURE X(n).

ここで、1<=n<=32000

MVARCHAR

VARCHAR2

7

Date

L1 基本項目名
PICTURE X(10).

DATE

使用しない

8

Time

L1 基本項目名 PICTURE X(8).

TIME

使用しない

9

Timestamp

L1 基本項目名 PICTURE X(22).

TIMESTAMP

TIMESTAMP

10

byte[]

L2 集団項目名
[USAGE [IS]]
SQL TYPE IS
BLOB(n{K|M|G}).

単位省略時:
1<=n<=2147483647
単位がK
1<=n<=2097152
単位がM
1<=n<=2048
単位がG
1<=n<=2

BLOB

BINARY

BLOB

11

String

同上

BLOB

BINARY

BLOB

12

String

使用しない

使用しない

CLOB

13

String

HIRDBMVARCHAR型に従う

FREEWORD

使用しない

14

byte[]

HIRDBBLOB型に従う

SGMLTEXT

使用しない

15

String

同上

SGMLTEXT

使用しない