Top / Hibernate / TIPS集

基本操作

HQLでクエリを発行する

SessionFactory sessionfactory = config.buildSessionFactory();
session = sessionfactory.openSession();
List list = session.find("from UserId as userid where userid.userId like 'Masatomi%'");
↑ 結果(UserIdクラス)の格納されたListが返ってくる

Hibernate3.0から上のfindはdeprecatedになりました。3.0からは

Query query = session.createQuery
   ("from UserId as userid where userid.userId like 'Masatomi%'");
List messages = query.list();

としてください。

HQLの文法などはいろんなサイトで説明されているので割愛。上の意味だけ。

from UserId[1] as userid[2] where userid[2].userId[3] like 'Masatomi%'
↑ 意味は、[1]のクラスを検索します。条件は[3]のフィールドが'Masatomi%'であるようなレコード
↑ [2]はエイリアスなんでなんでもよいでしょう

Hibernateの一時オブジェクト、永続化オブジェクト、分離オブジェクト

一時オブジェクト

通常のJavaオブジェクト。newしたヤツ。セッションで管理されていない。対応するロウがテーブルにない。

永続化オブジェクト

セッションで管理されている。対応するロウがテーブルにある。

分離オブジェクト

セッションで管理されていない。対応するロウがテーブルにある。セッションを閉じたらこうなる。

各状態への遷移方法

一時オブジェクト ---save---> 永続化オブジェクト <-------update---------- 分離オブジェクト
一時オブジェクト <--delete-- 永続化オブジェクト --evict,session.close--> 分離オブジェクト
一時オブジェクト --saveOrUpdate--> 永続化オブジェクト
分離オブジェクト --saveOrUpdate--> 永続化オブジェクト

デバッグ張って、ためしてみた

当たり前の事をまとめてます。。。

分離オブジェクトをsaveするとinsert文が走った

final SessionFactory sessionFactory = getSessionFactory();

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
User user = new User();
user.setName("kino");
session.save(user);
System.out.println(user);
tx.commit();  <-この処理後、insertが走る
session.flush();
session.close();

System.out.println(user);

session = sessionFactory.openSession();
tx = session.beginTransaction();
session.save(user); さっきの分離オブジェクトをsaveしてる
// session.update(user);
// user.setName("hogehoge");
tx.commit();  <-この処理後、insertが走る(updateではない)
session.flush();
session.close();

分離オブジェクトをsaveすると、insertが走った

分離オブジェクトをupdateするとupdate文が走った(永続化オブジェクトにする、という意味でupdate文が走ってる)

final SessionFactory sessionFactory = getSessionFactory();

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
User user = new User();
user.setName("kino");
session.save(user);
System.out.println(user);
tx.commit();  <-この処理後、insertが走る
session.flush();
session.close();

System.out.println(user);

session = sessionFactory.openSession();
tx = session.beginTransaction();
//session.save(user);
session.update(user); さっきの分離オブジェクトをupdateしてる
user.setName("hogehoge");
tx.commit();  <-この処理後、updateが走る。setNameしてるけど、
                上のupdateと併せて一回だけupdateが走る
                (setNameしなくても一回はupdateが走った)
session.flush();
session.close();

分離オブジェクトをupdateすると、update文が走った。ただこれは分離オブジェクトをふたたび永続化オブジェクトにしただけ。SQLのupdate文とはちょっと意味が違う。ちなみにupdate文を発行しないで永続化オブジェクトにするには

session = sessionFactory.openSession();
tx = session.beginTransaction();
// session.save(user);
// session.update(user);
session.lock(user, LockMode.NONE); <-これ
// user.setName("hogehoge");
tx.commit();
session.flush();
session.close();

session.lockを使用します。上の例だとupdate文は走らないけど永続化されてるみたい。試しにuser.setName("hogehoge");をアンコメントすると、tx.commitでupdate文が発行された。lockメソッド自体で新しいセッションに結びつけられたことによって、setNameとかするだけでupdate文が走っていますね。

分離オブジェクトをupdateしないで変更してもupdate文は走らない

ちなみに

session = sessionFactory.openSession();
tx = session.beginTransaction();
// session.save(user);
// session.update(user);
//        session.lock(user, LockMode.NONE);
user.setName("hogehoge");
tx.commit();
session.flush();
session.close();

この場合userは分離オブジェクトなので、これではupdate文は走らない

その他

すでに永続化オブジェクトがセッション中にロードされている状態で、更にUpdateしようとすると例外。

org.hibernate.NonUniqueObjectException?: a different object with the same identifier value was already associated with the session という例外が発生しますね。

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

User user = new User();
user.setName("hogehoge");
session.save(user);
System.out.println(user);
int num = user.getId();

user = new User();
user.setId(num);
user.setName("name");
session.update(user); <-ここで例外
System.out.println(user);

tx.commit();
session.flush();
session.close();

同じ識別子の値で別のオブジェクトがすでに関連付いている、、と。といってもsaveしたuserをevictして分離オブジェクトにすればOKかな?と思ったけど別の例外(org.hibernate.AssertionFailure?: possible nonthreadsafe access to session)がtx.commit時に発生しちゃいます。そもそも一時オブジェクトをupdateすること自体間違ってるのかな?

あ、flushしてからevictしなくちゃいけないんですね。

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

User user = new User();
user.setName("hogehoge");
session.save(user);
session.flush();
session.evict(user);
int num = user.getId();

user = new User();
user.setId(num);
user.setName("name");
session.update(user); <-例外は発生しなくなる
System.out.println(user);

tx.commit();
session.flush();
session.close();

これでOKでした。

saveしたオブジェクトをすぐevictするとエラー

上のようにsaveしたあとflushしてからevictしないと、

org.hibernate.AssertionFailure - an assertion failure occured
(this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)
org.hibernate.AssertionFailure: possible nonthreadsafe access to session

が発生する。

selectしてきたオブジェクトにはCGLIBで拡張されている

newしてsaveしたオブジェクトは普通のクラス。session.loadなどしてselectしたクラスはCGLIBで拡張されたクラスになっている。

複数の設定ファイルを使用したい

hibernate.cfg.xmlはどうもひとつのデータベース設定しか書けないみたい(たぶん)。 でも場合によっては一つのデータベースだけではなく、複数のデータベースに接続することがあります。そのときに、複数の設定ファイルを切り替える方法がようやく分かりました。ちなみにTorqueで試行錯誤した経緯はTorque/複数のDBで同時に利用するをご参照。

Configuration config = new Configuration();
//    config = config.configure(); // <-通常の設定の読み込み
config = config.configure(new File(
      getServlet().getServletContext().getRealPath(
        "/WEB-INF/lib/hibernate.cfg.hoge.xml")));

このようにすることで、ランタイムに設定ファイルを指定することが出来るようです。。

結合(関連)を考える

fig1.gif
  • Member -> WorkGroup?を表現する

JavaBeans?(ここではMember)にプロパティを追加してマッピングファイル(Member.hbm.xml)に

<many-to-one name ="workgroup" column="GROUPNO"
 class="nu.mine.kino.binding.ait.hibernate.Workgroup" cascade="all" outer-join="auto"
 update="false" insert="false" />

を追加すればよいようだ。追加しない場合はMember#getWorkgroup()の返り値がnullになるみたい。

Member(N) -> WorkGroup?(1) なので、many-to-oneとなる

  • WorkGroup? -> Memberを表現する

JavaBeans?(ここではWorkGroup?)にプロパティを追加してマッピングファイル(WorkGroup?.hbm.xml)に

<set name="memberList" >
 <key ><column name="groupno" /></key>
 <one-to-many class="nu.mine.kino.binding.ait.hibernate.Member" />
</set>

を追加すればよいようだ。追加しない場合はWorkgroup#getMemberList?()の返り値がnullになるみたい。

WorkGroup?(1) -> Member(N) なので、one-to-manyとなる

クラス設計で考えるとWorkGroup? -> Member はあってもその逆はあまりないかなぁ。ようするに

aWorkGroup.getMembers()

はあっても

aMember.getWorkGroup()

はないことが多いように思うけど。。。

http://www.atmarkit.co.jp/fjava/rensai3/ormap05/ormap05_1.html

WEBアプリの時、設定ファイルなどはどこに置くべきか。

hibernate.cfg.xml
classesの直下などに置いて、各リソースの設定ファイルは
<mapping resource="nu/mine/kino/MtAuthor.hbm.xml" />
などと指定する
各オブジェクトの設定ファイル
Javaファイルと同じ場所に置く。

middlegenで作ったhbmファイルについて

たとえば

<id name="id" type="java.lang.Integer" column="id" >
  <meta attribute="field-description">
     @hibernate.id
     generator-class="Increment"
     type="java.lang.Integer"
     column="id"
  </meta>
  <generator class="Increment" />
</id>

  <meta attribute="field-description">
     @hibernate.id
     generator-class="Increment"
     type="java.lang.Integer"
     column="id"
  </meta>

の部分は、コメントに使われるだけ、だと思う。

MySQLのauto_incrementについて

`name_id` int(10) unsigned NOT NULL auto_increment,

と定義されるカラムがあるテーブルについて、middlegenでJavaBeans?とhbm.xmlファイルを作成したら以下のようになりました。

<id
    name="nameId"
    type="java.lang.Object"
    column="name_id"
>
    <meta attribute="field-description">
       @hibernate.id
        generator-class="assigned"
        type="java.lang.Object"
        column="name_id"

    </meta>
    <generator class="assigned" />
</id>

実際はIntegerとマッピングして欲しいのですが、うまくいかないですね。とりあえず自分でjava.lang.Integerに変更しました。あと

<generator class="assigned" />

<generator class="increment" />

に変更しました。もちろんJavaソースも作り直しました。

複合キーを使ったテーブルのマッピング。

主キーが複数、たとえば

create table Customer3 (customer_id varchar(20) not null, 
item_id varchar(20) not null, 
value varchar(255), 
upd_date timestamp,
primary key (customer_id, item_id));

というようにcustomer_id,item_idでユニークになるテーブルのマッピングについてです。

XDocletなどを使ってhbm.xmlを作る場合がほとんどだと思いますので、まずはPOJOから。結論を言うと、上の複合キーを一つのオブジェクトとするために一つPOJOを作ります。

public class Customer3Id implements Serializable {
  private String customerId;
  private String itemId;
  public Customer3Id() {
  }


  /**
   * @hibernate.property column = "customer_id" not-null = "true" length ="20"
   */
  public String getCustomerId() {
    return customerId;
  }

  /**
   * @hibernate.property column = "item_id" not-null = "true" length = "20"
   */
  public String getItemId() {
    return itemId;
  }

  public String toString(){...}
  public boolean equals(Object obj) {...}
  public int hashCode() {...}
}

どうも、

  • toString,equals,hashCodeは必ずOverrideしなくてはいけない
  • implements Serializableであること みたいです。

で、実際のレコードの方(?)のPOJOはさっきの複合オブジェクトを保持しておきます。

/**
 * @hibernate.class table = customer3 
 */
public class Customer3 {
  private Customer3Id compositeId;

  ....

  /**
   * @return compositeId を戻します。
   * @hibernate.id generator-class="assigned"
   */
  public Customer3Id getCompositeId() {
    return compositeId;
  }
}

XDocletでhbmを生成したところ、

<?xml version="1.0" encoding="UTF-8"?>
<hibernate-mapping>
  <class
    name="nu.mine.kino.plugin.hsqldb.hibernate.Customer3" >
    <composite-id  name="compositeId"
      class="nu.mine.kino.plugin.hsqldb.hibernate.Customer3Id">
           <key-property
            name="customerId"
            type="java.lang.String"
            column="customer_id"
            length="20"
        />
           <key-property
            name="itemId"
            type="java.lang.String"
            column="item_id"
            length="20"
        />
    </composite-id>
    <property
      name="value"
    ....................
  </class>
</hibernate-mapping>

なんてのができあがりました。ポイントは複合キーはそのキーのオブジェクトを作る必要がある、ってことですかね。これ、結構めんどくさいね。

middlegenおぼえがき

http://boss.bekk.no/boss/middlegen/

samples/build.xmlを修正し流用します。まずは

<!ENTITY database SYSTEM "file:./config/database/hsqldb.xml" >

を実際に使用するデータベース用のファイルに変更します。ほかには

name: javaクラスのパッケージ名に対応
database.name :これも変えるのかな?

などを変更します。また上で指定した

file:./config/database/XXXXX.xml

の内容を、個々人の環境に応じて書き換えます。たとえばこんな感じ:

<property name="database.url"  value="jdbc:sybase:Tds:[IP]:[PORT]/[DB]"/>
<property name="database.userid"    value="[ID]"/>
<property name="database.password"  value="[PASS]"/>
<property name="database.schema"    value="dbo"/> <-おもにこれ?
<property name="database.catalog"   value="XXXXXX"/> use XXXXXXとかするやつ

で、ant hbm2javaでOK!*1

ちなみに、自動生成されたxmlファイルを手で修正してJavaを生成したい場合は、hbm2javaタスクのdependをなくしてhbm2java単体で稼動するようにすること。そうしないとmiddlegenタスクとかが動いてxmlファイルを上書きしてしまう。


この記事は

選択肢 投票
おもしろかった 61  
そうでもない 8  
  • HQLの文法ってなかなかおぼえられん。 "from Messages as m order by m.messageText asc" といわれても。。。 -- きの? 2006-06-18 21:33:13 (日)
Top / Hibernate / TIPS集

現在のアクセス:64548


*1 ほかにもant middlegenでさまざまなファイルが生成される。

添付ファイル: filefig1.gif 1348件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2016-01-12 (火) 19:48:54 (503d)