Hibernate入門記 マッピング定義その4 property要素

順番だと次は「5.1.6. discriminator」なのですが,これは「8. Inheritance Mapping」で,その後の「5.1.7. version (optional)」および「5.1.8. timestamp (optional)」は「10. Transactions And Concurrency」で,それぞれ解説されているようなので無視しちゃいます.心より恥じる.
次行きましょう,次!(エビちゃん風)
ということで,今回は「5.1.9. property」へ進みます.毎回おなじみの

  • <property>要素

です.永続クラスのプロパティとテーブル(カラム)のマッピングを指定するものですね.
こやつの属性をザクッと.

name
唯一の必須属性で,プロパティの名前を指定します.先頭文字は小文字にしろとのことです."URI" みたいなプロパティはどうするんだろう? JavaBeans的にはプロパティ名も "URI" だったはずですが...
column
テーブルのカラム名を指定します.省略すると name 属性の値が使われます.
type
プロパティの型を指定します.オプションです.
update/insert
UPDATE/INSERTのSQL文中にこのプロパティを含めるかどうかを指定します.導出項目などの非永続的なプロパティの場合に false を指定する,と.省略すると true です.
formula
導出項目の場合にその値を求めるSQLの式を指定します.
access
Hibernateが永続クラスのプロパティにアクセスする方法を指定します.デフォルトは "property" で,setter/getter を通してプロパティにアクセスします.
"field" を指定すると,フィールドに直接アクセスします.永続クラスに setter を用意したくない場合に使えますね.
net.sf.hibernate.property.PropertyAccessorimplements したクラスを指定することもできます.どんな場合に必要になるのかイメージしにくいですが.

といった感じです.
そうか,<id> 要素にも出てきた access 属性はそういう意味だったのか... ここまで見に来ればよかった.無念だ.
<id> 要素でよくわからなかったもう一つの属性,type 属性についても,ちょっとだけまとまった説明がありました.なのですが,どうやら「5.2. Hibernate Types」でさらに詳しく解説されているようなので,ここでは無視しておきます.心より恥じる.
あれ? これでもう <property> 要素は終わりですか? あっさりしすぎかなぁ.まいっか.


ということで実験コーナーです.今回は導出項目に決まっています.導出項目といえば単価×数量というのがお約束です.あう,石を投げないでぇ〜.
石を投げられたので,年齢にしましょう.ということでモデルさんのテーブルを作成します.当然ながら,ドメインモデルとかMVCとかのモデルではなく,雑誌モデルのモデルです.

CREATE TABLE MODEL (
    ID INTEGER IDENTITY,
    FIRSTNAME VARCHAR,
    LASTNAME VARCHAR,
    BIRTHDAY DATE
)

え? 石を投げられたからじゃなくてモデルが好きなだけだろって? 違いますよぉ,そんな不謹慎な理由でサンプル作ったりしませんよぉ.あうあう.
次行きましょう,次!(エビちゃん風)
次は 永続クラスです.今回はせっかくなので,フィールドの可視性をパッケージにして getter/setter を作らないことにしました.全然意味はないのですが,とにかく試したかったのです.

package study;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import net.sf.hibernate.CallbackException;
import net.sf.hibernate.Lifecycle;
import net.sf.hibernate.Session;

public class Model implements Lifecycle {
    private static final SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");

    int id = -1;
    String firstName;
    String lastName;
    Date birthday;
    int age;

    public String toString() {
        synchronized (formatter) {
            return firstName + " " + lastName + ", " + formatter.format(birthday) + "(" + age + ")";
        }
    }

    public void onLoad(Session s, Serializable id) {
        System.out.println("onLoad() : " + toString());
    }
    public boolean onSave(Session s) throws CallbackException {
        System.out.println("onSave() : " + toString());
        return false;
    }
    public boolean onUpdate(Session s) throws CallbackException {
        System.out.println("onUpdate() : " + toString());
        return false;
    }
    public boolean onDelete(Session s) throws CallbackException {
        System.out.println("onDelete() : " + toString());
        return false;
    }
}

getter/setter がない分よけいなコードが少なくて日記的にはよいかも.
ちなみにこういう場合の Date って,java.util のでいいのでしょうか? それとも java.sql のを使うべきでしょうか? 制約がなければ java.util の方で済ませたいところです.ということでまずは java.util にしておきました.
次にマッピングファイル.

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"
>
<hibernate-mapping auto-import="false" package="study">
    <class name="Model">
        <id name="id" access="field" unsaved-value="-1">
            <generator class="identity"/>
        </id>

        <property name="firstName" access="field"/>
        <property name="lastName" access="field"/>
        <property name="birthday" access="field"/>
        <property name="age" access="field" update="false" insert="false" 
            formula="year(curdate()) - year(birthday) - casewhen(month(curdate()) * 100 + dayofmonth(curdate()) &lt; month(birthday) * 100 + dayofmonth(birthday), 1, 0)"
        />
    </class>
</hibernate-mapping>

年齢を求めるところがひどく見苦しいですね... すっきりした求め方がわからなかったので苦し紛れです.心より恥じる.
次行きましょう,次!(エビちゃん風)
最近ご無沙汰の hibernate.cfg.xml で今までの Customer.hbm.cfg に替えて Model.hbm.xmlマッピングファイルとして指定します.

<?xml version="1.0"?>
<!DOCTYPE hibernate-configuration
    PUBLIC "-//Hibernate/Hibernate Configuration DTD 2.0//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd"
>
<hibernate-configuration>
    <session-factory>
        <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
        <property name="connection.url">jdbc:hsqldb:hsql://localhost:9001</property>
        <property name="connection.username">sa</property>
        <property name="connection.password"></property>
        <property name="dialect">net.sf.hibernate.dialect.HSQLDialect</property>
        <property name="show_sql">true</property>

        <mapping resource="study/Model.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

最後に実行用のクラス.相変わらず後処理とかしてなくて心より恥じるです.

package study;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.cfg.Configuration;

public class Main {
    public static void main(String[] args) {
        try {
            Configuration config = new Configuration();
            SessionFactory factory = config.configure().buildSessionFactory();

            Session session = factory.openSession();

            Model yuri = new Model();
            yuri.firstName = "Yuri";
            yuri.lastName = "Ebihara";
            yuri.birthday = new GregorianCalendar(1979, 10 - 1, 3).getTime();
            session.save(yuri);

            Model yu = new Model();
            yu.firstName = "Yu";
            yu.lastName = "Yamada";
            yu.birthday = new GregorianCalendar(1984, 7 - 1, 5).getTime();
            session.save(yu);

            Model moe = new Model();
            moe.firstName = "Moe";
            moe.lastName = "Oshikiri";
            moe.birthday = new GregorianCalendar(1979, 12 - 1, 29).getTime();
            session.save(moe);

            session.flush();

            session = factory.openSession();
            List models = session.find("from study.Model");
            Iterator it = models.iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }
        }
        catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

工夫なさ過ぎでINSETしてSELECTするだけなんですが,いいんですっ!
これを実行!!!!

 onSave() : Yuri Ebihara, 1979/10/03(0)
 Hibernate: insert into MODEL (firstName, lastName, birthday, id) values (?, ?, ?, null)
 Hibernate: CALL IDENTITY()
 onSave() : Yu Yamada, 1984/07/05(0)
 Hibernate: insert into MODEL (firstName, lastName, birthday, id) values (?, ?, ?, null)
 Hibernate: CALL IDENTITY()
 onSave() : Moe Oshikiri, 1979/12/29(0)
 Hibernate: insert into MODEL (firstName, lastName, birthday, id) values (?, ?, ?, null)
 Hibernate: CALL IDENTITY()
 Hibernate: select model0_.id as id, model0_.firstName as firstName, model0_.lastName as lastName, model0_.birthday as birthday, year(curdate()) - year(model0_.birthday) - casewhen(month(curdate()) * 100 + dayofmonth(curdate()) < month(model0_.birthday) * 100 + dayofmonth(model0_.birthday), 1, 0) as f0_ from MODEL model0_
 onLoad() : Yuri Ebihara, 1979/10/03(24)
 onLoad() : Yu Yamada, 1984/07/05(19)
 onLoad() : Moe Oshikiri, 1979/12/29(24)
 Yuri Ebihara, 1979/10/03(24)
 Yu Yamada, 1984/07/05(19)
 Moe Oshikiri, 1979/12/29(24)

おっしゃー!
なんか久しぶりに「ぐはぁっ」を使わずにすんだような気が.まぁ,毎回のように使っている <property> 要素なのですから,いちいち吐血なんてしてたら出血多量で死んじゃいますよ.
ともあれ,java.util.Date で大丈夫みたいですね.
これで getter/setter のないクラスでも大丈夫♪ 導出項目があっても大丈夫♪


ついでに getURI() みたいな場合のプロパティ名がどうなるか試してみました.結論はやっぱり "URI" が正解っぽい."uri" だと PropertyNotFoundException なる例外が吹っ飛んできました.念のため "uRI" も試しましたがこっちはもっとひどい例外の連鎖が.どうして違うわけ? まいっか.
先頭小文字とか気にしないで,普通にJavaBeansのお約束に従えばいいみたいです.