hibernateの永続化オブジェクトの操作の注意点について(メモ)
永続化オブジェクトの操作
Hibernateのorg.hibernate.criterion.Exampleを少し使ってみた。
例に使用するテーブルとエンティティは「hibernateの双方向many-to-manyについて」と同じものを使うので、そちらを参照してほしい。
なお、検証にはこの時点のhibernateの最新バージョン(3.3.1.GA)をもちいた。
単純なfind
まずは、次に示すコードを見てほしい。
int id = 1;
Session session = getSession();
Query query = session
.createQuery("FROM Users as u where u.usersId = :id");
query.setInteger("id", id);
List<Users> list = query.list();
Users user = null;
if(list != null && list.size() > 0){
user = list.get(0);
}
user.setUsername("wawawwawaawaw");
session.close();
ご覧のように、usersIdが1であるUsersインスタンスuserを取得して、usernameに"wawawwawaawaw"をセットしている。このuserはいわゆる永続化オブジェクトではあるが、この場合は"wawawwawaawaw"はDBに保存されない。
トランザクションありでfind
先ほどのコードを少し書き換えて、次のようにする。
int id = 1;
Session session = getSession();
Transaction transaction = session.beginTransaction();
Query query = session
.createQuery("FROM Users as u where u.usersId = :id");
query.setInteger("id", id);
List<Users> list = query.list();
Users user = null;
if(list != null && list.size() > 0){
user = list.get(0);
}
user.setUsername("wawawwawaawaw");
transaction.commit();
session.close();
今度は、トランザクションありなので、明示的に保存をしなくとも、永続化オブジェクトへの変更がDBに反映されるため、"wawawwawaawaw"がDBに保存される。
次のようにしても同じであった。
int id = 1;
Session session = getSession();
Query query = session
.createQuery("FROM Users as u where u.usersId = :id");
query.setInteger("id", id);
List<Users> list = query.list();
Users user = null;
if(list != null && list.size() > 0){
user = list.get(0);
}
user.setUsername("wawawwawaawaw");
Transaction transaction = session.beginTransaction();
transaction.commit();
session.close();
トランザクションありでも、DBに保存されないパタン
先ほどのコードを少し書き換えて、次のようにする。
int id = 1;
Session session = getSession();
Transaction transaction = session.beginTransaction();
Query query = session
.createQuery("FROM Users as u where u.usersId = :id");
query.setInteger("id", id);
List<Users> list = query.list();
Users user = null;
if(list != null && list.size() > 0){
user = list.get(0);
}
transaction.commit();
session.close();
//セッションクローズ後に変更
user.setUsername("wawawwawaawaw");
先ほどのとの違いは、"wawawwawaawaw"をセットしているのがsessionを閉じた後である、という点である。sessionを閉じた後であるので、userは永続化オブジェクトではなく分離オブジェクトである。であるので、このインスタンスの変更はDBに保存されない。
次の二つのようにした場合も、DBには変更が反映されなかった。
int id = 1;
Session session = getSession();
Transaction transaction = session.beginTransaction();
Query query = session
.createQuery("FROM Users as u where u.usersId = :id");
query.setInteger("id", id);
List<Users> list = query.list();
Users user = null;
if(list != null && list.size() > 0){
user = list.get(0);
}
transaction.commit();
//commitの後に永続化オブジェクトを変更
user.setUsername("wawawwawaawaw");
session.close();
int id = 1;
Session session = getSession();
Transaction transaction = session.beginTransaction();
transaction.commit();
//commitの後に永続化オブジェクトを取得して変更
Query query = session
.createQuery("FROM Users as u where u.usersId = :id");
query.setInteger("id", id);
List<Users> list = query.list();
Users user = null;
if(list != null && list.size() > 0){
user = list.get(0);
}
user.setUsername("wawawwawaawaw");
session.close();
commitのときにDBに、反映するので、その後の変更は反映しないということであろう。
永続化オブジェクトと分離オブジェクト、そしてSessionの同一性
上の例を見ていると、getSession()で取得されるSessionは、その都度、別物になるのか?ということが気になったので、調べてみる。
int id = 1;
//一つ目のSession
Session session = getSession();
Query query = session
.createQuery("FROM Users as u where u.usersId = :id");
query.setInteger("id", id);
List<Users> list = query.list();
Users user = null;
if(list != null && list.size() > 0){
user = list.get(0);
}
//このとき、userはsession管理下の永続化オブジェクト
//usernameに適当な文字列を入れる
user.setUsername("yeahhhh");
//ふたつめのSession
Session session1 = getSession();
//同じSessionか?
if(session == session1 || session.equals(session1)){
System.out.println("two session is same session");
}
//二つ目のセッションの方でTransaction
Transaction transaction = session1.beginTransaction();
transaction.commit();
//セッションを閉じる
session.close();
session1.close();
上の例のように実行したところ、DBに変更が保存されることもなく、同じSessionかどうかチェックしているif文に入らなかったので、どうやら、getSession()メソッドはコールされるたびに新しくセッションを開いて返すようだ。
次に、分離オブジェクトのアップデートを確認してみた。
int id = 1;
//一つ目のSession
Session session = getSession();
Query query = session
.createQuery("FROM Users as u where u.usersId = :id");
query.setInteger("id", id);
List<Users> list = query.list();
//一つ目のSessionクローズ
session.close();
Users user = null;
if(list != null && list.size() > 0){
user = list.get(0);
}
//このとき、userは分離オブジェクトである。
//usernameに適当な文字列を入れる
user.setUsername("yeahhhh");
//ふたつめのSession
Session session1 = getSession();
//二つ目のセッションの方でTransaction
Transaction transaction = session1.beginTransaction();
//分離オブジェクトuserをupdateする
session.update(user);
//この時点でuserは永続化オブジェクトになっているはず。
//passwordに適当な文字列をセット
user.setPassword("ppppppppppppppp");
transaction.commit();
session1.close();
このように実行したところ、DBのusernameもpasswordもUsersインスタンスuserにセットしたとおりにupdateされていた。
このことから、分離オブジェクトは他のセッションのupdateによって永続化オブジェクトにすることが出来るということがわかる。
struts + Spring + hibernate
普段よく使ってる組み合わせでは、どうなのかを、ざっと確認してみた。正確に検証したわけではないが参考までに。
まず、適当なアクションで、適当なDAOを使ってfindして取得したインスタンスを書き換えてみた。それだけなら、DBに保存されることはなかった。
つぎに、updateやsaveのためにトランザクション管理されているクラスを通してDBから取得したインスタンスを書き換えてみたが、DBに保存はされなかった。
そこで今度は、適当なアクションで、適当なDAOを使ってfindして取得したインスタンスを書き換えて、それから新たに作成した別のインスタンスをsaveしてみた。そうしたところ、新たなインスタンスの分が挿入されるのと同時に、先に変更していたインスタンスの分がアップデートされた。
今度は、適当なアクションで、新たなインスタンスを作成して、saveした後で、別にDBから取得したインスタンスを変更してみた。その場合は、変更がDBに反映されなかった。
どうやら、同じアクション内だとずっとおなじsessionを使ってDB操作するっぽい。であるので、saveやupdateを行う場所では、commitが発生する前に、永続クラスに余計なことをしないように注意。
Springに管理を任せた場合、セッション管理がどうなっているかを詳しく調べる必要がある。
by 柴田 一志


