2015年11月7日 星期六

樂觀鎖定 (Hibernate3.x 十三)

樂觀鎖定正如其名,每次拿資料時,都認為別人不會修改,它是通過版本檢查的機制或者時間戳來保證資料的正確性,如A和B同時讀取資料庫,版本都一樣,但B先修改了資料,版本加1,而後來A也修改了資料,這時資料庫會發現目前版本大於A的版本,所以就不給更新
使用樂觀鎖定分成xml和annotation兩種設定

※XML設定:

記得要將hibernate.cfg.xml設成mapping resource

通過版本實現樂觀鎖定:

準備資料:

DROP TABLE emp;
CREATE TABLE emp (
    empno        NUMBER(4) CONSTRAINT PK_EMP PRIMARY KEY,
    ename        VARCHAR2(10),
    JOB        VARCHAR2(9),
    mgr        NUMBER(4) default 1 not null,
    hiredate    DATE,
    sal        NUMBER(7,2),
    comm        NUMBER(7,2),
    deptno        NUMBER(2) CONSTRAINT FK_DEPTNO REFERENCES DEPT 
);
INSERT INTO emp VALUES (7369,'SMITH','CLERK',7902,to_date('17-12-1980','dd-mm-yyyy'),800,NULL,20);
INSERT INTO emp VALUES (7499,'ALLEN','SALESMAN',7698,to_date('20-2-1981','dd-mm-yyyy'),1600,300,30);
INSERT INTO emp VALUES (7521,'WARD','SALESMAN',7698,to_date('22-2-1981','dd-mm-yyyy'),1250,500,30);
INSERT INTO emp VALUES (7566,'JONES','MANAGER',7839,to_date('2-4-1981','dd-mm-yyyy'),2975,NULL,20);
INSERT INTO emp VALUES (7654,'MARTIN','SALESMAN',7698,to_date('28-9-1981','dd-mm-yyyy'),1250,1400,30);
INSERT INTO emp VALUES (7698,'BLAKE','MANAGER',7839,to_date('1-5-1981','dd-mm-yyyy'),2850,NULL,30);
INSERT INTO emp VALUES (7782,'CLARK','MANAGER',7839,to_date('9-6-1981','dd-mm-yyyy'),2450,NULL,10);
INSERT INTO emp VALUES (7788,'SCOTT','ANALYST',7566,to_date('19-04-1987','dd-mm-yyyy')-85,3000,NULL,20);
INSERT INTO emp VALUES (7839,'KING','PRESIDENT',NULL,to_date('17-11-1981','dd-mm-yyyy'),5000,NULL,10);
INSERT INTO emp VALUES (7844,'TURNER','SALESMAN',7698,to_date('8-9-1981','dd-mm-yyyy'),1500,0,30);
INSERT INTO emp VALUES (7876,'ADAMS','CLERK',7788,to_date('23-05-1987','dd-mm-yyyy')-51,1100,NULL,20);
INSERT INTO emp VALUES (7900,'JAMES','CLERK',7698,to_date('3-12-1981','dd-mm-yyyy'),950,NULL,30);
INSERT INTO emp VALUES (7902,'FORD','ANALYST',7566,to_date('3-12-1981','dd-mm-yyyy'),3000,NULL,20);
INSERT INTO emp VALUES (7934,'MILLER','CLERK',7782,to_date('23-1-1982','dd-mm-yyyy'),1300,NULL,10);
commit;

※這些測試資料和之前一樣,只是將mgr修改一下,一定不能給null,不然待會測試就算資料庫有值,第二次的樂觀鎖定會拋出NullPointerException

emp.hbm.xml

<class name="vo.Emp" table="Emp" optimistic-lock="version">
    <id name="empno" type="int">
        <column name="EMPNO" />
        <generator class="assigned" />
    </id>
    <version name="mgr" type="java.lang.Integer">
        <column name="MGR" not-null="true"/>
    </version>
    <property name="ename" type="java.lang.String">
        <column name="ENAME" />
    </property>
    <property name="job" type="java.lang.String">
        <column name="JOB" />
    </property>
    <property name="hiredate" type="java.util.Date">
        <column name="HIREDATE" />
    </property>
    <property name="sal" type="java.lang.Double">
        <column name="SAL" />
    </property>
    <property name="comm" type="java.lang.Double">
        <column name="COMM" />
    </property>
    <property name="deptno" type="java.lang.Integer">
        <column name="DEPTNO" />
    </property>
</class>

※只是將要實現的版本控制欄位改成version而已

※version一定要放在id下,不然會出「org.hibernate.InvalidMappingException: Unable to read XML」的錯

※class的optimistic-lock="version",我沒設也可以,不過最好是設定一下

※optimistic-lock有四種值:
.all:檢查所有屬性實現樂觀鎖定
.dirty:檢查發生變動過得屬性實現樂觀鎖定
.none:無鎖定
.version:使用版本機制實現樂觀鎖定


TestHibernate.java

SessionFactory sf = HibernateUtil2.getSessionFactory();
Session s1 = sf.openSession();
Session s2 = sf.openSession();
    
try {
    Criteria c1 = s1.createCriteria(Emp.class).add(Restrictions.eq("empno", 7369));
    Criteria c2 = s2.createCriteria(Emp.class).add(Restrictions.eq("empno", 7369));
    
    Emp e1 = (Emp) c1.uniqueResult();
    Emp e2 = (Emp) c2.uniqueResult();
    
    e1.setEname("SMITH");
    s1.beginTransaction().commit();
    
    e2.setEname("OOO");
    s2.beginTransaction().commit();
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    if (s1.isOpen()) {
        s1.close();
    }
    if (s2.isOpen()) {
        s2.close();
    }
}

※在執行最後一行commit時,因為發現版本不符,所以會出「org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): 」的錯

※注意控制台的SQL語法where最後有「and MGR=?」,就知道版本控制設定成功了

※第一個commit會先看需不需要修改,如果和資料庫一樣,就不修改;
而第二個commit我設不一樣,所以這時如果第一個commit有修改,就會報版本第一個※的錯; 如果沒修改就修改

※通過時間戳實現樂觀鎖定:

Emp.hbm.xml

<class name="vo.Emp" table="Emp" optimistic-lock="version">
    <id name="empno" type="int">
        <column name="EMPNO" />
        <generator class="assigned" />
    </id>
    <timestamp name="hiredate" column="HIREDATE" />
    <property name="ename" type="java.lang.String">
        <column name="ENAME" />
    </property>
    <property name="job" type="java.lang.String">
        <column name="JOB" />
    </property>
    <property name="mgr" type="java.lang.Integer">
        <column name="MGR" />
    </property>
    <property name="sal" type="java.lang.Double">
        <column name="SAL" />
    </property>
    <property name="comm" type="java.lang.Double">
        <column name="COMM" />
    </property>
    <property name="deptno" type="java.lang.Integer">
        <column name="DEPTNO" />
    </property>
</class>

※控制台一樣在where後會出現and HIREDATE=?,就知道時間戳設定成功了

※timestamp一樣寫在id後,optimistic-lock還是一樣,不寫我試的結果也是可以

※注意timestamp的子層沒有column屬性,用content assist就知道了,不然執行時,還是會出Unable to read XML的錯

※測試代碼還是一樣,執行到第二次commit也是一樣的錯

※和版本控制一樣,不要有null,所以在create table時,要設定not null,只是timestamp在資料庫有值時,程式很順利進行; 但版本控制不管有沒有值都會出錯



※Annotation設定

記得要將hibernate.cfg.xml設成mapping class

Emp.java

@Version
@Column(name = "MGR")
public Integer getMgr() {
    return mgr;
}
或
@Version
@Column(name = "HIREDATE")
public Date getHiredate() {
    return hiredate;
}

※使用annotation很簡單,就在就版本控制的getter方法上加@Version即可

※測試類還是和上面一樣

※如果要設optimistic-lock,可以看最下面的「樂觀鎖定的其他相關設定」


※其他相關設定

※Hibernate有dynamic-update和dynamic-insert可以設定,如下:
<class name="vo.Emp" table="Emp"  dynamic-insert="true" dynamic-update="true">
    ...
</class>

或者是用annotation設定
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
    
@org.hibernate.annotations.Entity(
    dynamicUpdate = true,
    dynamicInsert = true
)
@Entity
@Table(name = "EMP", schema = "c##scott")
public class Emp {
    //...
}

※注意Entity就有兩種

※這個設定表示效能有所提升,如果沒設定時,新增和修改如下:
Emp emp = new Emp();
emp.setEmpno(9997);
emp.setDeptno(30);
emp.setSal(1000.5);
emp.setMgr(7369);
s1.saveOrUpdate(emp);
s1.beginTransaction().commit();

※此時控制台會出insert或者update的SQL語法
insert into Emp (ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO, EMPNO)  values (?, ?, ?, ?, ?, ?, ?, ?)

update Emp  set ENAME=?, JOB=?, MGR=?, HIREDATE=?, SAL=?, COMM=?, DEPTNO=? where EMPNO=?

但是如果有加dynamic,控制台的SQL語法只有針對用到的才會新增或刪除
insert into Emp (MGR, SAL, DEPTNO, EMPNO) values (?, ?, ?, ?)

update Emp set ENAME=? where EMPNO=?


※樂觀鎖定的其他設定

在XML設定的hbm.xml的optimistic-lock=還有兩個設定all和dirty,這兩個設定都要加上dynamic-update="true",不然會出「org.hibernate.MappingException: optimistic-lock=all|dirty requires dynamic-update="true":」的錯

設定範例如下:
<class name="vo.Emp" table="Emp" optimistic-lock="all" dynamic-update="true">
    ...
</class>

或者
@org.hibernate.annotations.Entity(
    dynamicUpdate = true,
    dynamicInsert = true,
    optimisticLock = OptimisticLockType.DIRTY
)
@Entity
@Table(name = "EMP", schema = "c##scott")
public class Emp {
    //...
}

※測試類還是一樣

沒有留言:

張貼留言