2015年11月12日 星期四

取得org.JSON(JAVA)的jar

※產生JAR檔

在JSON的官網並沒有提供,但可以自己做
首先進入後的中間偏下有個連結


點進去後,進入如下畫面,左邊有原始碼和API可參考,點上面的連結



將全部的原始檔(左邊)全部下載下來,集中在一個資料夾

這時會發現原始碼裡,每個package都是org.json
所以要建立兩層資料夾org/json,然後將剛剛下載的原始碼放進去
開啟CMD然後切換到org/json的上層好了
>javac org/json/*.java   這一行會將全部的java編譯成class
>jar -cvf json.jar org/json/*.class 這一行會將全部的class打包成json.jar,可自行命名
成功後,json.jar會出現在org/json的上層目錄,反正就是你cmd的目前的目錄


後來發現原來是有提供下載點的,就在下載原始檔的最下面,點進去就有好幾個版本,如下:

或者也可以用maven下載


※在Eclipse看原始檔

反編譯的軟體很多,我比較常用的是Java Decompiler,如下畫面


我下載的是JD-GUI,還有其他的我沒試過


下載完後,打開剛剛的json.jar,點左邊的class,右邊可看到原始碼


但現在是要在Eclipse看,使我們用Ctrl,然後點JSON的物件可以看原始碼,所以先存檔


zip不用解壓,直接在Eclipse用即可


如果日後想修改或想回復預設值,可如下設定


2015年11月8日 星期日

繼承映射一(table per concrete class) (Hibernate3.x 十六)

繼承映射分成三種形式
※table per class hierarchy(每個階層類別一個表)
※table per subclass(每個子類別一個表)
※table per concrete class(每個具體類別一個表)
尤於翻譯的有和沒有一樣,所以提供官方連結,這裡先從下往上講,個人感覺比較好懂

※table per concrete class(union-subclass)


※準備資料

CREATE TABLE STUDENT(
    ID NUMBER(5),
    NAME VARCHAR(10),
    PRICE NUMBER(5),
    PRODUCT_DATE DATE,
    SCORE NUMBER(3),
    SCHOOL VARCHAR(20),
    CONSTRAINT PK_STUDENT PRIMARY KEY(ID)
);
    
CREATE TABLE MAKER(
    ID NUMBER(5),
    NAME VARCHAR(10),
    PRICE NUMBER(5),
    PRODUCT_DATE DATE,
    SALARY NUMBER(6),
    COMPANY VARCHAR(20),
    CONSTRAINT PK_MAKER PRIMARY KEY(ID)
);

※ID、NAME、PRICE、PRODUCT_DATE,這四個欄位兩張表都有,所以可以提出來模擬成java的繼承關係,取一個名字,譬如叫Chess,然後將兩張對應的表繼承它


※XML設定

Chess.java

public abstract class Chess {
    private Integer id;
    private String name;
    private Integer price;
    private Date productDate;
    //setter/getter...
}

※Chess是個抽象類別,讓Student和Maker繼承

Student.java

public class Student extends Chess {
    private Integer score;
    private String school;
    //setter/getter...
}

Maker.java
public class Maker extends Chess {
    private Integer salary;
    private String company;
    //setter/getter...
}

Chess.hbm.xml
<class name="vo.Chess" abstract="true">
    <id name="id" type="java.lang.Integer">
        <column name="ID" />
        <generator class="assigned" />
    </id>
    
    <property name="name" type="java.lang.String">
        <column name="NAME" />
    </property>
    
    <property name="price" type="java.lang.Integer">
        <column name="PRICE" />
    </property>
    
    <property name="productDate" type="java.util.Date">
        <column name="PRODUCT_DATE" />
    </property>
    
    <union-subclass name="vo.Student" table="STUDENT">
        <property name="score" type="java.lang.Integer">
            <column name="SCORE" />
        </property>
        <property name="school" type="java.lang.String">
            <column name="SCHOOL" />
        </property>
    </union-subclass>
    
    <union-subclass name="vo.Maker" table="MAKER">
        <property name="salary" type="java.lang.Integer">
            <column name="SALARY" />
        </property>
        <property name="company" type="java.lang.String">
            <column name="COMPANY" />
        </property>
    </union-subclass>
</class>

※Chess可以加個abstract=true,更加確定是個抽象類別,其他兩個類別用union-subclass

測試類:

SessionFactory sf = HibernateUtil2.getSessionFactory();
Session s1 = sf.openSession();
Session s2 = sf.openSession();
Transaction tx = s1.beginTransaction();
Transaction tx2 = s2.beginTransaction();
try {
    System.out.println("-----測試學生-----");
    Student stu = new Student();
    stu.setId(1);
    stu.setName("象棋");
    stu.setPrice(50);
    stu.setProductDate(new Date(100, 0, 1));
    stu.setScore(78);
    stu.setSchool("家裡蹲大學");
    s1.save(stu);
    
    Student stuRtn = (Student) s1.load(Student.class, 1);
    System.out.println("學校名:" + stuRtn.getSchool());
    
    System.out.println("-----測試員工-----");
    Maker mak = new Maker();
    mak.setId(1);
    mak.setName("五子棋");
    mak.setPrice(30);
    mak.setProductDate(new Date(102, 11, 12));
    mak.setSalary(43000);
    mak.setCompany("少林寺管委會");
    s2.save(mak);
    
    Maker makRtn = (Maker) s2.load(Maker.class, 1);
    System.out.println("公司名:" + makRtn.getCompany());
    
    tx.commit();
    tx2.commit();
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
    tx.rollback();
} finally {
    if (s1.isOpen()) {
        s1.close();
    }
    if (s2.isOpen()) {
        s2.close();
    }
}

※hibernate.cfg.xml只要增加一行<mapping resource="vo/Chess.hbm.xml" />即可

※如果使用同一個session,而且id相同,就會出現「org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session:」的例外
就算有二級快取,也是一樣,所以和快取沒關係
.原因是因為同一個session不能有相同的名稱
.使用Session的clean、refresh、merge都不行
.但如果是Student和Maker分開,也就是Student做完就commit,然後clean再做Maker即可,我現在是假設二筆資料一起commit、一起rollback的話就不行
.Maker不使用save,改用merge會過,但在load時,會出現Student不能強轉成Maker的錯,很正常,因為繼承同一個類別,Session裡面存的是第一次的Student
.這是使用table per concrete class的缺點


※Annotation設定

Chess.java

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Entity
public abstract class Chess {
    private Integer id;
    private String name;
    private Integer price;
    private Date productDate;
    
    @Id
    public Integer getId() {
        return id;
    }
    
    public void setId(Integer id) {
        this.id = id;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public Integer getPrice() {
        return price;
    }
    
    public void setPrice(Integer price) {
        this.price = price;
    }
    
    @Column(name = "PRODUCT_DATE")
    public Date getProductDate() {
        return productDate;
    }
    
    public void setProductDate(Date productDate) {
        this.productDate = productDate;
    }
}

※id是一定要設的,而Column只有productDate的名稱和資料庫不同,設這個就好

※三個類別都必需設定@Inheritance

Student.java

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Entity
public class Student extends Chess {
    private Integer score;
    private String school;
    //setter/getter...
}

※由於Table和Column一樣可以不打,所以這是最精簡設定

Maker.java

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Entity
public class Maker extends Chess {
    private Integer salary;
    private String company;
    //setter/getter...
}


hibernate.cfg.xml

<mapping class="vo.Chess" />
<mapping class="vo.Student" />
<mapping class="vo.Maker" />

※三個類別都必需增加才可以

※測試類別一樣

2015年11月7日 星期六

複合主鍵 (Hibernate3.x 十五)

※準備資料

CREATE TABLE SCHOOL_USER(
    ID NUMBER(10) NOT NULL,
    UNAME VARCHAR(10) NOT NULL,
    SEX VARCHAR(1),
    CONSTRAINT PK_ID_UNAME PRIMARY KEY(ID, UNAME)
);
    
COMMENT ON TABLE SCHOOL_USER IS '使用者';
COMMENT ON COLUMN SCHOOL_USER.ID IS '使用者id';
COMMENT ON COLUMN SCHOOL_USER.NAME IS '使用者名稱';
COMMENT ON COLUMN SCHOOL_USER.SEX IS '性別';

※使用ID和NAME為複合主鍵

※XML設定

UserPK.java

public class UserPK implements Serializable {
    private Integer uid;
    private String uname;
    //setter/getter...
}

※一定要implements Serializable,不然執行時會出「org.hibernate.MappingException: composite-id class must implement Serializable: vo.UserPK」的錯

User.java

private UserPK xxx;
private String sex;
//setter/getter...

User.hbm.xml

<class name="vo.User" table="SCHOOL_USER">
    <composite-id name="xxx" class="vo.UserPK">
    
        <key-property name="uid" type="java.lang.Integer">
            <column name="ID" />
        </key-property>
    
        <key-property name="uname" type="java.lang.String">
            <column name="NAME" />
        </key-property>
    
        <generator class="assigned" />
    </composite-id>
    
    <property name="sex" type="java.lang.String">
        <column name="SEX" />
    </property>
</class>

※我資料庫的表本來想取名為USER,欄位叫UID,但這兩個剛好是Oracle的關鍵字,雖然聽說可用「"」的方式,但為了保險起見,還是算了,所以我在hbm.xml做轉換的動作

※generator必需在composite-id的最下面

測試類

Session s = HibernateUtil2.getSession();
Transaction tx = s.beginTransaction();
try {
    UserPK pk = new UserPK();
    User u = new User();
    pk.setUid(1);
    pk.setUname("one");
    u.setXxx(pk);
    u.setSex("M");
    s.save(u);
    tx.commit();
    
    UserPK pk2 = new UserPK();
    pk2.setUid(1);
    pk2.setUname("one");
    User u2 = (User) s.load(User.class, pk2);
    System.out.println(u2.getXxx().getUid() + "||" + u2.getXxx().getUname());
} catch (Exception e) {
    tx.rollback();
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    if (s.isOpen()) {
        s.close();
    }
}


※Annotation設定

記得要在hibernate.cfg.xml增加mapping class

UserPK.java

@Embeddable
public class UserPK implements Serializable {
    //...
}

※PK類只要在類別設定即可

User.java

@Entity
@Table(name = "SCHOOL_USER")
public class User {
    private UserPK xxx;
    private String sex;
    
    @EmbeddedId
    @AttributeOverrides({
        @AttributeOverride(name = "uid", column = @Column(name = "ID", nullable = false, length = 10)),
    
        @AttributeOverride(name = "uname", column = @Column(name = "NAME", nullable = false, length = 10))
    })
    public UserPK getXxx() {
        return xxx;
    }
    
    public void setXxx(UserPK xxx) {
        this.xxx = xxx;
    }
    
    public String getSex() {
        return sex;
    }
    
    public void setSex(String sex) {
        this.sex = sex;
    }
}

※注意是@EmbeddedId哦!

※在@AttributeOverride設定對應關係,當然和之前一樣,Column和類別名稱一樣可不打,只留@EmbeddedId即可

Component映射(細粒度、粗粒度) (Hibernate3.x 十四)

之前的映射,都是簡單的映射(Basic O/R Mapping),也就是一張表映射成一個persistent class
還可以用細粒度(fine-grained),也就是一張表映射成多個persistent class,被細化出來的類別,可以用Component把這幾個類別組合起來
相對於細粒度,也有人說一張表映射成一個persistent class是粗粒度
官網的連結在這


※準備資料

drop table chess;
CREATE TABLE CHESS ( 
    CHESS_NO NUMBER(10,0), 
    NAME VARCHAR2(10 BYTE), 
    PRICE NUMBER(5,0), 
    PRODUCT_DATE DATE, 
    SALARY NUMBER(7,0), 
    COMPANY VARCHAR2(20 BYTE), 
    CONSTRAINT "CHESS_PK" PRIMARY KEY ("CHESS_NO")
);
    
COMMENT ON TABLE CHESS  IS '棋表';
COMMENT ON COLUMN CHESS.CHESS_NO IS '編號';
COMMENT ON COLUMN CHESS.NAME IS '名稱';
COMMENT ON COLUMN CHESS.PRICE IS '價錢';
COMMENT ON COLUMN CHESS.PRODUCT_DATE IS '製造日期';
COMMENT ON COLUMN CHESS.SALARY IS '薪水';
COMMENT ON COLUMN CHESS.COMPANY IS '天殺的外派公司名稱';
    
Insert into CHESS (CHESS_NO,NAME,PRICE,PRODUCT_DATE,SALARY,COMPANY) values (35,'孔明棋',20,to_date('21-7月 -15','DD-MON-RR'),50000,'小棋玩具社');
INSERT INTO CHESS (CHESS_NO,NAME,PRICE,PRODUCT_DATE,SALARY,COMPANY) VALUES (7,'象棋',50,to_date('18-7月 -15','DD-MON-RR'),45000,'大毛會社');
Insert into CHESS (CHESS_NO,NAME,PRICE,PRODUCT_DATE,SALARY,COMPANY) values (8,'西洋棋',120,to_date('18-7月 -15','DD-MON-RR'),30000,'豬超人童玩');
Insert into CHESS (CHESS_NO,NAME,PRICE,PRODUCT_DATE,SALARY,COMPANY) values (9,'跳棋',30,to_date('18-7月 -15','DD-MON-RR'),15000,'小兔跳棋會社');
INSERT INTO CHESS (CHESS_NO,NAME,PRICE,PRODUCT_DATE,SALARY,COMPANY) VALUES (10,'圍棋',40,to_date('18-7月 -15','DD-MON-RR'),22000,'大牛圍棋社');
INSERT INTO CHESS (CHESS_NO,NAME,PRICE,PRODUCT_DATE,SALARY,COMPANY) VALUES (12,'軍棋',99,to_date('18-7月 -15','DD-MON-RR'),17000,'軍啊公司');
Insert into CHESS (CHESS_NO,NAME,PRICE,PRODUCT_DATE,SALARY,COMPANY) values (14,'五子棋',35,to_date('18-7月 -15','DD-MON-RR'),24000,'五子棋協會');

※細粒度就是再進一步的劃分,譬如編號、名稱、價錢、製造日期是屬於產品的基本資訊,還有薪水、外派公司屬於其他資訊,這樣就可以分成三類,也就是資料的更細一步的劃分


※XML設定

Chess.java
private Integer chessNo;
private ChessBasic xxx;
private ChessOther ooo;
//setter/getter...

ChessBasic.java
private String name;
private Integer price;
private Date productDate;
//setter/getter...

ChessOther.java
private Integer salary;
private String company;
//setter/getter...

Chess.hbm.xml
<class name="vo.Chess" table="CHESS">
    <id name="chessNo" type="java.lang.Integer">
        <column name="CHESS_NO" />
        <generator class="assigned" />
    </id>
    
    <component name="xxx" class="vo.ChessBasic">
        <property name="name" type="java.lang.String">
            <column name="NAME" />
        </property>
        <property name="price" type="java.lang.Integer">
            <column name="PRICE" />
        </property>
        <property name="productDate" type="java.util.Date">
            <column name="PRODUCT_DATE" />
        </property>
    </component>
    
    <component name="ooo" class="vo.ChessOther">
        <property name="salary" type="java.lang.Integer">
            <column name="SALARY" />
        </property>
        <property name="company" type="java.lang.String">
            <column name="COMPANY" />
        </property>
    </component>
</class>

※增加component把屬性包起來而已,name名稱要對應好
Test.java
try {
    List<Chess> list = HibernateUtil2.getSession().createQuery("from Chess").list();
    for (Chess ch : list) {
        System.out.println(ch.getChessNo());
        System.out.println(ch.getXxx().getName());
        System.out.println(ch.getOoo().getCompany());
    }
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    if (s.isOpen()) {
        s.close();
    }
}

※記得hibernate.cfg.xml的mapping resource要加


※Annotation設定

Chess.java
@Entity
@Table(name = "CHESS", schema = "c##scott")
public class Chess {
    private Integer chessNo;
    private ChessBasic xxx;
    private ChessOther ooo;
    
    @Id
    @Column(name = "CHESS_NO", unique = true, nullable = false)
    public Integer getChessNo() {
        return chessNo;
    }
    
    public void setChessNo(Integer chessNo) {
        this.chessNo = chessNo;
    }
    
    //其他setter/getter都不用annotation
}

ChessBasic.java
@Embeddable
@AttributeOverrides({
    @AttributeOverride(name = "name", column = @Column(name = "NAME")),
    @AttributeOverride(name = "price", column = @Column(name = "PRICE")),
    @AttributeOverride(name = "productDate", column = @Column(name = "PRODUCT_DATE"))
})
public class ChessBasic {
    private String name;
    private Integer price;
    private Date productDate;
    //setter/getter...且完全不用annotation
}

ChessOther.java
@Embeddable
@AttributeOverrides({
    @AttributeOverride(name = "salary", column = @Column(name = "SALARY")),
    @AttributeOverride(name = "company", column = @Column(name = "COMPANY"))
})
public class ChessOther {
    private Integer salary;
    private String company;
    //setter/getter...且完全不用annotation
}

※測試類和XML設定一樣

※如果不設@Embeddable,會報「org.hibernate.MappingException: Could not determine type for: vo.ChessBasic, at table: CHESS, for columns: [org.hibernate.mapping.Column(xxx)]」的錯

※如果資料庫和Class name一樣,可以不設@AttributeOverrieds,也就是只有@Embeddable; 這一點和@Column一樣

※@AttributeOverride因為沒有xml,所以在這設定對應關係,Chess.java也不用特別設定什麼,自然就能從getter的回傳類型找到

※如果資料庫和Class name一樣,annotation設定還蠻方便的

樂觀鎖定 (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 {
    //...
}

※測試類還是一樣

2015年11月6日 星期五

悲觀鎖定 (Hibernate3.x 十二)

悲觀鎖定認為在每次拿資料時,一定會有人修改,所以它的機制是當有人在操作資料庫時,必需等待對方操作完成,才能換下一個,也就是同一個時間只能有一個人操作,優點是絕對安全,缺點是只適合小系統或很少人使用的情況
例如去ATM領錢,此ATM是全台灣的系統,那就變成在台灣想領錢的人要等很久,如果是全世界的系統,那就等的更久,但如果只是10幾個人在用的系統,那就還ok

悲觀鎖定總共有以下11種模式:
NONE:預設值,無鎖定模式

READ:查詢時自動進行鎖定處理

UPGRADE:已廢棄-->使用PESSIMISTIC_WRITE代替

UPGRADE_NOWAIT:和UPGRADE相同,但有人鎖定就不等待返回,等同Oracle的 for update nowait

WRITE:在update、insert時自動進行鎖定處理

FORCE:已廢棄-->使用PESSIMISTIC_FORCE_INCREMENT代替

OPTIMISTIC:樂觀的假設Transaction不會出現爭用的實體

OPTIMISTIC_FORCE_INCREMENT:Transaction 立刻增加實體版本

PESSIMISTIC_READ:Implemented as PESSIMISTIC_WRITE

PESSIMISTIC_WRITE:transaction 將立即獲得一個資料庫鎖定,等同Oracle 的 for update

PESSIMISTIC_FORCE_INCREMENT:類似UPGRADE,但使用了版本記錄會強制增加版本的值

※版本在樂觀鎖定會說明
Hibernate API的說明可參考,但還不是很清楚,可看原始碼的註解,如下:

/**
 * No lock required. If an object is requested with this lock
 * mode, a <tt>READ</tt> lock will be obtained if it is
 * necessary to actually read the state from the database,
 * rather than pull it from a cache.<br>
 * <br>
 * This is the "default" lock mode.
 */
public static final LockMode NONE = new LockMode(0, "NONE");
    
/**
 * A shared lock. Objects in this lock mode were read from
 * the database in the current transaction, rather than being
 * pulled from a cache.
 */
public static final LockMode READ = new LockMode(5, "READ");
    
/**
 * An upgrade lock. Objects loaded in this lock mode are
 * materialized using an SQL <tt>select ... for update</tt>.
 * @deprecated instead use PESSIMISTIC_WRITE
 */
public static final LockMode UPGRADE = new LockMode(10, "UPGRADE");
    
/**
 * Attempt to obtain an upgrade lock, using an Oracle-style
 * <tt>select for update nowait</tt>. The semantics of
 * this lock mode, once obtained, are the same as
 * <tt>UPGRADE</tt>.
 */
public static final LockMode UPGRADE_NOWAIT = new LockMode(10, "UPGRADE_NOWAIT");
    
/**
 * A <tt>WRITE</tt> lock is obtained when an object is updated
 * or inserted.   This lock mode is for internal use only and is
 * not a valid mode for <tt>load()</tt> or <tt>lock()</tt> (both
 * of which throw exceptions if WRITE is specified).
 */
public static final LockMode WRITE = new LockMode(10, "WRITE");
    
/**
 * Similiar to {@link #UPGRADE} except that, for versioned entities,
 * it results in a forced version increment.
 * @deprecated instead use PESSIMISTIC_FORCE_INCREMENT
 */
public static final LockMode FORCE = new LockMode( 15, "FORCE" );
    
/**
 *  start of javax.persistence.LockModeType equivalent modes
 */
    
/**
 * Optimisticly assume that transaction will not experience contention for
 * entities.  The entity version will be verified near the transaction end.  
 */
public static final LockMode OPTIMISTIC = new LockMode( 3, "OPTIMISTIC");
    
/**
 * Optimisticly assume that transaction will not experience contention for entities.
 * The entity version will be verified and incremented near the transaction end. 
 */
public static final LockMode OPTIMISTIC_FORCE_INCREMENT = new LockMode( 4, "OPTIMISTIC_FORCE_INCREMENT");
    
/**
 * Implemented as PESSIMISTIC_WRITE.
 * TODO:  introduce separate support for PESSIMISTIC_READ
 */
public static final LockMode PESSIMISTIC_READ = new LockMode( 12, "PESSIMISTIC_READ");
    
/**
 * Transaction will obtain a database lock immediately.
 * TODO:  add PESSIMISTIC_WRITE_NOWAIT
 */
public static final LockMode PESSIMISTIC_WRITE = new LockMode( 13, "PESSIMISTIC_WRITE");
    
/**
 * Transaction will immediately increment the entity version.
 */
public static final LockMode PESSIMISTIC_FORCE_INCREMENT = new LockMode( 17, "PESSIMISTIC_FORCE_INCREMENT");

可以使用鎖定模式的有以下幾個地方(有可能還有其他地方):
Session.lock-->已廢棄
Session.refresh-->已廢棄
Session.get/load-->已廢棄
Session.buildLockRequest.lock(LockOptions)
Query.setLockMode(LockMode)
Criteria.setLockMode(LockMode)
以下是使用的情況:
Session s = HibernateUtil2.getSession();
    
try {
    Emp e1 = (Emp) s.get(Emp.class, 7369, LockMode.UPGRADE_NOWAIT);
    System.out.println("e1:" + e1.getEmpno());
    
    Emp e2 = (Emp) s.get(Emp.class, 7369);
    LockOptions l = new LockOptions().setLockMode(LockMode.UPGRADE_NOWAIT);
    s.buildLockRequest(l).lock(e2);
    
    Emp e3 = (Emp) s.get(Emp.class, 7369);
    s.buildLockRequest(LockOptions.UPGRADE).lock(e3);
    
    Query q = s.createQuery("from Emp e where empno = 7369");
    q.setLockMode("e", LockMode.UPGRADE_NOWAIT);
    Emp e4 = (Emp) q.uniqueResult();
    System.out.println("e4:" + e4.getEname());
    
    Criteria c = s.createCriteria(Emp.class);
    c.setLockMode(LockMode.PESSIMISTIC_WRITE);
    c.add(Restrictions.eq("empno", 7369));
    Emp e5 = (Emp) c.uniqueResult();
    System.out.println("e5:" + e5.getEname());
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    if (s.isOpen()) {
        s.close();
    }
}

※e1的get或load是廢棄方法
※注意控制台的輸出有for update和for update nowait,我是用oracle的,其他資料庫有可能會不太一樣

2015年11月1日 星期日

在Java利用泛型,呼叫同樣的名稱,回傳不同的類型

使用泛型類別:
public class Test {
    public static void main(String[] args) {
        System.out.println(new DEF<>().bbb(2));
        System.out.println(new DEF<>().bbb("2"));
        System.out.println(new DEF<>().bbb('2'));
    }
}
    
class DEF<T> {
    public T bbb(T c) {
        return new ABC<T>().aaa(c);
    }
}
    
class ABC<T> {
    @SuppressWarnings("unchecked")
    public T aaa(T a) {
        if (a instanceof String) {
            return (T) ((String) a).concat("1");
        } else if (a instanceof Integer) {
            return (T) Integer.valueOf((((Integer) a) + 1));
        }
        return a;
    }
}



也可以使用泛型方法:
public class Test {
    public static void main(String[] args) {
        System.out.println(new DEF().bbb(2));
        System.out.println(new DEF().bbb("2"));
        System.out.println(new DEF().bbb('2'));
    }
}
    
class DEF {
    public <T> T bbb(T c) {
        return new ABC<T>().aaa(c);
    }
}
    
class ABC<T> {
    @SuppressWarnings("unchecked")
    public T aaa(T a) {
        if (a instanceof String) {
            return (T) ((String) a).concat("1");
        } else if (a instanceof Integer) {
            return (T) Integer.valueOf((((Integer) a) + 1));
        }
        return a;
    }
}