程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> hibernate3學習筆記(二十三)|進階特性(一)

hibernate3學習筆記(二十三)|進階特性(一)

編輯:關於JAVA

1.悲觀鎖定:

在多個客戶端可能讀取同一筆數據或同時更新一筆數據的情況下,必須要有訪問控制的手段,防止同一個數據被修改而造成混亂,最簡單的手段就是對資料進行鎖定,在自己進行資料讀取或更新等動作時,鎖定其他客戶端不能對同一筆資料進行任何的動作。

悲觀鎖定(Pessimistic Locking)一如其名稱所示,悲觀的認定每次資料存取時,其它的客戶端也會存取同一筆資料,因此對該筆資料進行鎖定,直到自己操作完成後解除鎖定。

悲觀鎖定通常透過系統或資料庫本身的功能來實現,依賴系統或資料庫本身提供的鎖定機制,Hibernate即是如此,可以利用Query或Criteria的setLockMode()方法來設定要鎖定的表或列(Row)及其鎖定模式,可設定的鎖定模式有以下的幾個:

LockMode.UPGRADE:利用資料庫的for update子句進行鎖定。

LockMode.UPGRADE_NOWAIT:使用for update nowait子句進行鎖定,在Oracle資料庫中使用。

一個設定鎖定的例子如下:

Session session = sessionFactory.openSession();
Query query = session.createQuery("from User user");
query.setLockMode("user", LockMode.UPGRADE);
List users = query.list();

...

session.close();這個程式片段會使用以下的SQL進行查詢:

Hibernate: select user0_.id as id, user0_.name as name0_, user0_.age as age0_

from user user0_ for update也可以在使用Session的load()或是lock()時指定鎖定模式以進行鎖定。

另外還有三種加鎖模式Hibernate內部自動對資料進行鎖定,與資料庫無關:

LockMode.WRITE:在insert或update時進行鎖定,Hibernate會在save()方法時自動獲得鎖定。

LockMode.READ:在讀取記錄時Hibernate會自動獲得鎖定。

LockMode.NONE:沒有鎖定。

如果資料庫不支援所指定的鎖定模式,Hibernate會選擇一個合適的鎖定替換,而不是丟出一個例外。

2.樂觀鎖定:

悲觀鎖定假定任何時刻存取資料時,都可能有另一個客戶也正在存取同一筆資料,因而對資料采取了資料庫層次的鎖定狀態,在鎖定的時間內其他的客戶不能對資料 進行存取,對於單機或小系統而言,這並不成問題,然而如果是在網路上的系統,同時間會有許多連線,如果每一次讀取資料都造成鎖定,其後繼的存取就必須等 待,這將造成效能上的問題,造成後繼使用者的長時間等待。

樂觀鎖定(Optimistic locking)則樂觀的認為資料的存取很少發生同時存取的問題,因而不作資料庫層次上的鎖定,為了維護正確的資料,樂觀鎖定使用應用程式上的邏輯實現版本控制的解決。

在不實行悲觀鎖定策略的情況下,資料不一致的情況一但發生,有幾個解決的方法,一種是先更新為主,一種是後更新的為主,比較復雜的就是檢查發生變動的資料來實現,或是檢查所有屬性來實現樂觀鎖定。

Hibernate中透過版本號檢查來實現後更新為主,這也是Hibernate所推薦的方式,在資料庫中加入一個version欄位記錄,在讀取資料時 連同版本號一同讀取,並在更新資料時比對版本號與資料庫中的版本號,如果等於資料庫中的版本號則予以更新,並遞增版本號,如果小於資料庫中的版本號就丟出 例外。

實際來透過范例了解Hibernate的樂觀鎖定如何實現,首先在資料庫中新增一個表格:

CREATE TABLE user (
   id INT(11) NOT NULL auto_increment PRIMARY KEY,
   version INT,
   name VARCHAR(100) NOT NULL default '',
   age INT
);

這個user表格中的version用來記錄版本號,以供Hibernate實現樂觀鎖定,接著設計User類別,當中必須包括version屬性:

User.java
package onlyfun.caterpillar;

public class User {
   private Integer id;
   private Integer version; // 增加版本屬性
   private String name;
   private Integer age;

   public User() {
   }

   public Integer getId() {
     return id;
   }

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

   public Integer getVersion() {
     return version;
   }

   public void setVersion(Integer version) {
     this.version = version;
   }

   public String getName() {
     return name;
   }

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

   public Integer getAge() {
     return age;
   }

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

在映射文件的定義方面,則如下所示:

User.hbm.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping
   PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
   "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

   <class name="onlyfun.caterpillar.User"
      table="user"
      optimistic-lock="version">

     <id name="id" column="id" type="java.lang.Integer">
       <generator class="native"/>
     </id>

     <version name="version"
         column="version"
         type="java.lang.Integer"/>

     <property name="name" column="name" type="java.lang.String"/>

     <property name="age" column="age" type="java.lang.Integer"/>

   </class>

</hibernate-mapping>注意<version>標簽必須出現在<id>標簽之後,接著您可以試著在資料庫中新增資料,例如:

User user = new User();
user.setName("caterpillar");
user.setAge(new Integer(30));
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
session.save(user);
tx.commit();
session.close();

您可以檢視資料庫中的資料,每一次對同一筆資料進行更新,version欄位的內容都會自動更新,接著來作個實驗,直接以范例說明:

// 有使用1者開啟了一個session1
Session session1 = sessionFactory.openSession();
// 在這之後,馬上有另一個使用者2開啟了session2
Session session2 = sessionFactory.openSession();

Integer id = new Integer(1);

// 使用者1查詢資料
User userV1 = (User) session1.load(User.class, id);
// 使用者2查詢同一筆資料
User userV2 = (User) session2.load(User.class, id);

// 此時兩個版本號是相同的
System.out.println(" v1 v2 "
         + userV1.getVersion().intValue() + " "
         + userV2.getVersion().intValue());

Transaction tx1 = session1.beginTransaction();
Transaction tx2 = session2.beginTransaction();

// 使用者1更新資料
userV1.setAge(new Integer(31));
tx1.commit();

// 此時由於資料更新,資料庫中的版本號遞增了
// 兩筆資料版本號不一樣了
System.out.println(" v1 v2 "
         + userV1.getVersion().intValue() + " "
         + userV2.getVersion().intValue());

// userV2 的 age 資料還是舊的
// 資料更新
userV2.setName("justin");
// 因版本號比資料庫中的舊
// 送出更新資料會失敗,丟出StableObjectStateException例外
tx2.commit();

session1.close();
session2.close();

運行以下的程式片段,會出現以下的結果:

Hibernate:
select user0_.id as id0_, user0_.version as version0_0_, user0_.name as name0_0_, user0_.age as age0_0_
from user user0_ where user0_.id=?

Hibernate:
select user0_.id as id0_, user0_.version as version0_0_, user0_.name as name0_0_, user0_.age as age0_0_
from user user0_ where user0_.id=?
v1 v2 0 0

Hibernate:
update user set version=?, name=?, age=? where id=? and version=?
v1 v2 1 0

Hibernate:
update user set version=?, name=?, age=? where id=? and version=?
16:11:43,187 ERROR AbstractFlushingEventListener:277 - Could not synchronize database state with session
org.hibernate.StaleObjectStateException:
  Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [onlyfun.caterpillar.User#1]

  at org.hibernate.persister.entity.BasicEntityPersister.check(BasicEntityPersister.java:1441)

由於新的版本號是1,而userV2的版本號還是0,因此更新失敗丟出StableObjectStateException,您可以捕捉這個例外作善後處理,例如在處理中重新讀取資料庫中的資料,同時將目前的資料與資料庫中的資料秀出來,讓使用者有機會比對不一致的資料,以決定要變更的部份,或者您可以 設計程式自動讀取新的資料,並比對真正要更新的資料,這一切可以在背景執行,而不用讓您的使用者知道。

要注意的是,由於樂觀鎖定是使用系統中的程式來控制,而不是使用資料庫中的鎖定機制,因而如果有人特意自行更新版本訊息來越過檢查,則鎖定機制就會無效, 例如在上例中自行更改userV2的version屬性,使之與資料庫中的版本號相同的話就不會有錯誤,像這樣版本號被更改,或是由於資料是由外部系統而來,因而版本資訊不受控制時,鎖定機制將會有問題,設計時必須注意。

3.Lifecycle 介面、Validatable 介面:

可以在實體物件定義時實作Lifecycle介面,這個介面定義如下:

Lifecycle.java
package org.hibernate.classic;

import java.io.Serializable;
import org.hibernate.CallbackException;
import org.hibernate.Session;

public interface Lifecycle {
   public static final boolean VETO = true;
   public static final boolean NO_VETO = false;

   public boolean onSave(Session s) throws CallbackException;
   public boolean onUpdate(Session s) throws CallbackException;
   public boolean onDelete(Session s) throws CallbackException;
   public void onLoad(Session s, Serializable id);
}

當物件實作Lifecycle介面時,會在save()、update()、delete()、load()等方法執行之前呼叫對應的onSave()、 onUpdate()、onDelete()與onLoad(),其中onSave()、onUpdate()、onDelete()與onLoad() 若傳回true或丟出CallbackException,則對應的操作中止。

可以在實體物件定義時實作Validatable介面,其定義如下:

Validatable.java
package org.hibernate.classic;

public interface Validatable {
   public void validate() throws ValidationFailure;
}

如果定義時實作了Validatable介面,當物件被持久化之前會呼叫validate()方法,如果丟出ValidationFailure,則驗證失敗,物件的資料不會儲存至資料庫中。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved