Home > Hibernate

Hibernate Archive

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
  • Comments (Close): -
  • TrackBack (Close): -

HibernateProxyオブジェクトのinstanceof

mattyです。
Hibernateで遅延ロードしたプロキシオブジェクトをinstanceofするときのメモ。

マッピング時にlazy属性をtrueに指定するとプロキシオブジェクトが設定されるため 本来のクラスでinstanceofしてもfalseになってしまいます。

そのようなプロキシオブジェクトではなく本来のオブジェクトを呼び出したいときに便利なのが
org.hibernate.proxy.HibernateProxyHelpeクラスの getClassWithoutInitializingProxyメソッドです。
このメソッドでは下記のように本来のオブジェクトプロキシオブジェクトから取得。

public static Class getClassWithoutInitializingProxy(Object object) {
	if (object instanceof HibernateProxy) {
		HibernateProxy proxy = (HibernateProxy) object;
		LazyInitializer li = proxy.getHibernateLazyInitializer()
		return li.getPersistentClass();
	}
	else {
		return object.getClass();
	}
}

このメソッドにオブジェクトを渡せば本来のクラスを取得してinstanceof出来ます。

ScrollableResultsで取得した結果に重複が発生、、したときのメモ

こんにちは。mattyです。

O/Rマッピングを利用して大量レコードの検索を行うと OutOfMemory が発生する場合があります。
Hibernateを利用していてこの問題にブチ当たりました。

いくつか解決方法があるようですが、今回 ScrollableResults を利用して回避することにしました。
ところが、、ScrollableResultsで取得した結果に同じIDのEntityがいくつか重複していました。
かなりjoinしているのですが、重複して返って来てほしくはなかったのです。。

distinctはCLOB型があるので指定出来ませんでしたので
試しにユニーク項目でOrder byしてみたところ、、重複が解消されました!
うん、まとまってないとダメですよね、処理的に。

Hibernateで変更されたカラムのみをUpdate

take©です。
Session#saveOrUpdate()は、1つのカラムのみの変更でも全てのカラムに対してUpdateするSQLを発行します。 今回は、HibernateのSession#merge()を使って変更されたカラムのみUpdateする方法をメモします。

エンティティを用意

sample/Person.java

package sample;

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Version;

@Entity
@org.hibernate.annotations.Entity(dynamicInsert=true,dynamicUpdate=true)
public class Person {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;
	
	private String name;
	
	private int age;
	
	@Version
	private Date timestamp;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public Date getTimestamp() {
		return timestamp;
	}

	public void setTimestamp(Date timestamp) {
		this.timestamp = timestamp;
	}
}

@org.hibernate.annotations.Entity(dynamicInsert=true,dynamicUpdate=true)
dynamicInsert=trueは、Insert時にnullカラムを除いたSQLを発行してくれます。
dynamicUpdate=trueは、Update時に変更されたカラムのみを更新するSQLを発行してくれます。
※org.hibernate.annotations.Entityはjavax.persistence.Entityの代わりではなく、両方必要なので注意!

@Version
dynamicUpdateを指定した場合、楽観的ロックが利用できます。 今回は日付をversionカラムとして利用します。 永続化対象をSession#merge()する前に、Session#lock()をすることで、Hibernateがすでに更新されているかどうかを日付を比較して確認してくれます。
dynamicUpdateを使用する場合、versionカラムを利用することがパフォーマンス上推奨されます。

Hibernateの設定ファイルを用意

sample/hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
	"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
	"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
	<session-factory>
		<property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>
		<property name="connection.driver_class">org.postgresql.Driver</property>
		<property name="connection.username">[username]</property>
		<property name="connection.password">[password]</property>
		<property name="connection.url">jdbc:postgresql://[hostname]:[post]/[dbname]</property>

		<property name="current_session_context_class">thread</property>
		<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>

		<property name="show_sql">true</property>
		<property name="hibernate.format_sql">true</property>
		<property name="hibernate.use_sql_comments">true</property>

		<property name="hbm2ddl.auto">update</property>

		<mapping package="sample"/>
		<mapping class="sample.Person"/>
	</session-factory>
</hibernate-configuration>

今回は、PostgreSQLを使ってみます。

実行クラスを用意

Main.java

import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;

import sample.Person;

public class Main {
	public static void main(String[] args)
	throws Exception {
		SessionFactory factory = new AnnotationConfiguration()
			.configure("/sample/hibernate.cfg.xml")
			.buildSessionFactory();
		
		Session session = null;
		session = factory.openSession();
		session.beginTransaction();
		Person person = (Person) session.load(Person.class, 1);
		session.getTransaction().commit();
		session.close(); // personをSessionから切り離します
		
		session = factory.openSession();
		session.beginTransaction();
		session.lock(person, LockMode.UPGRADE);
		person.setName("太郎");
		session.merge(person);
		session.getTransaction().commit();
		factory.close();
	}
}

Webアプリでの利用を踏まえて、永続化対象がSessionから切り離された状態を再現しています。
personテーブルにidが「1」のデータが既に存在することが前提です。
ここで使用するのはもちろんSession#merge()です。事前にSession#lock()をしておきます。

実行!

用意したMain.javaを実行したときに発行されたSQL

Hibernate: 
    /* load sample.Person */ select
        person0_.id as id0_0_,
        person0_.age as age0_0_,
        person0_.name as name0_0_,
        person0_.timestamp as timestamp0_0_ 
    from
        Person person0_ 
    where
        person0_.id=?
Hibernate: 
    /* UPGRADE lock sample.Person */ select
        id 
    from
        Person 
    where
        id =? 
        and timestamp =? for update
            
Hibernate: 
    /* update
        sample.Person */ update
            Person 
        set
            name=?,
            timestamp=? 
        where
            id=? 
            and timestamp=?

1回目のSQL:ID=1のPersonを取得するSELECT文
2回目のSQL:永続化対象のtimestampが最新かどうかを確認することを兼ねた、SELECT FOR UPDATE文
3回目のSQL:nameとtimestampのUPDATE文

ageカラムは更新対象のカラムから外されていることが確認できました。


まとめ

Session#merge() & dynamicUpdateは、ORMのパフォーマンス戦略に欠かせない方法です。 JavaEE準拠にこだわらなければ、積極的に使っていっても良さそうです。

hqlでキーワード検索メモ

こんにちは、引き続きmattyです。
あるキーワードを持つデータをデータベースから抽出する際、検索対象となるカラムが多くなるとwhere句が煩雑になります。 そこで連結を用いて記述してみました。
以下の記述はhql部分の抜粋で、説明の為カラムの数は4つにしています。


例) 顧客管理でキーワード検索(対象カラムは以下)

  • name:顧客名
  • prefecture:都道府県
  • city:市区町村以下
  • building:ビル/マンション名(任意:null許可)

連結使用前

		StringBuilder hql = new StringBuilder();
		hql.append("from User user ");
		hql.append("where user.name like :keyword ");
		hql.append("or user.prefecture like :keyword ");
		hql.append("or user.city like :keyword ");
		hql.append("or user.building like :keyword ");

連結使用後

		StringBuilder hql = new StringBuilder();
		hql.append("from User user ");
		hql.append("where (user.name||user.prefecture||user.city||coalesce(user.building,'')) like :keyword ");

値がnullの項目と連結すると、他の項目に値があっても結果がnullになってしまうので coalesce関数を用いて対処しています。上記でcoalesce関数はuser.buildingがnullだったら空文字を返しています。
SQL-92標準の構文なので、ほとんどのデータベースで対応しています。

Home > Hibernate

Recent Comments
Recent Trackback
Search
Meta
Links
Feeds

Page Top

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。