2015年10月16日 星期五

依PK的CRUD、自動提交、動態模型的CRUD (Hibernate3.x 二)

※依PK的CRUD

SQL腳本
CREATE TABLE DEPT 
   ( DEPTNO NUMBER(10,0) NOT NULL, 
 DNAME VARCHAR2(255 BYTE), 
 LOC VARCHAR2(255 BYTE), 
 PRIMARY KEY ("DEPTNO")
   );
    
Insert into DEPT (DEPTNO,DNAME,LOC) values (70,'DDD','zzz');
Insert into DEPT (DEPTNO,DNAME,LOC) values (100,'eee','jjj');
Insert into DEPT (DEPTNO,DNAME,LOC) values (10,'ACCOUNTING','NEW YORK');
Insert into DEPT (DEPTNO,DNAME,LOC) values (20,'RESEARCH','DALLAS');
Insert into DEPT (DEPTNO,DNAME,LOC) values (30,'SALES','CHICAGO');
Insert into DEPT (DEPTNO,DNAME,LOC) values (40,'OPERATIONS','BOSTON');
Insert into DEPT (DEPTNO,DNAME,LOC) values (90,'bruce','ZH_TW');
Insert into DEPT (DEPTNO,DNAME,LOC) values (80,'bruce','ZH_TW');

pom.xml
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>3.6.10.Final</version>
</dependency>
    
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.19.0-GA</version>
</dependency>

javassist不加的話,會在執行時,出「java.lang.ClassNotFoundException: javassist.util.proxy.MethodFilter」

hibernate.cfg.xml
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
        <property name="hibernate.connection.url">jdbc:oracle:thin:@127.0.0.1:1521:orcl</property>
        <property name="hibernate.connection.username">account</property>
        <property name="hibernate.connection.password">password</property>
        <property name="hibernate.dialect">org.hibernate.dialect.OracleDialect</property>
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>

        <mapping resource="org/hibernate/vo/Dept.hbm.xml" />
    </session-factory>
</hibernate-configuration>

Dept.hbm.xml
<hibernate-mapping>
    <class name="org.hibernate.vo.Dept" table="DEPT">
        <id name="deptno" type="int">
            <column name="DEPTNO" />
            <generator class="assigned" />
        </id>
        <property name="dname" type="java.lang.String" access="field">
            <column name="DNAME" />
        </property>
        <property name="loc" type="java.lang.String" column name="LOC" />
    </class>
</hibernate-mapping>
※hibernate-mapping有個屬性package,打上package名稱,而class name打class名稱也是可以
※property也可只寫一行,最後一行的property就是這樣


Dept.java
private int deptno;
private String dname;
private String loc;
//setter/getter

TestHibernate.java
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
    
Dept dept = new Dept();
dept.setDeptno(80);
dept.setDname("bruce");
dept.setLoc("ZH_TW");
    
Transaction tx = session.beginTransaction();
try {
    session.save(dept);
    tx.commit();
    System.out.println("成功!");
} catch (Exception e) {
    tx.rollback();
    System.err.println("失敗!");
    e.printStackTrace();
} finally {
    session.close();
    factory.close();
}

專案圖:

TestHibernate.java的session.save(dept);是新增的意思,重要的有六個
※delete:刪除
※save:新增
※update:修改
※saveOrUpdate:新增或修改(資料庫沒有-->新增,有-->修改)
較專業的講法是:如果物件是Transient,就呼叫save(); 如果物件是Detached,則呼叫update()
可參考官方網站良葛格
只是官網只有三種狀態,良葛格的有四種,而且還有圖,不知從哪來的?

※load:查詢
※get:查詢

※get與load的差別

Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
    
System.out.println("0");
//Dept dept = (Dept) session.get(Dept.class, 80);
Dept dept = (Dept) session.load(Dept.class, 80);
System.out.println(dept);

※如果最後一行註解掉,get在控制台還是會有sql語法,而load什麼都沒有,
   因為load()為lazy-initialization,只有真正要用到時,才會真的去做事,
   所以load()效能比get()還要高
※如果沒查到:
   get()是null
   load()會拋org.hibernate.ObjectNotFoundException: No row with the given identifier exists:

※注意事項

Dept dept = (Dept) session.get(Dept.class, 80);
dept.setDname("apple");
Transaction tx = session.beginTransaction();
try {
    //session.save(dept);此行不加也行
    tx.commit();
    System.out.println("成功!");
} catch (Exception e) {
    tx.rollback();
    System.err.println("失敗!");
    e.printStackTrace();
} finally {
    session.close();
    factory.close();
}

先get或load,然後再set也能成功修改,但還是要commit
如上面的程式碼,整個都在Transaction外面,還是可以

※session混合
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
    
Dept dept = (Dept) session.load(Dept.class, 80);
dept.setDname("xxx3");
session.close();
    
Session session2 = factory.openSession();
dept.setLoc("ZH_TW");// 此行對DB無效,因為session已關閉,dept是包在裡面的
// session2.update(dept);// 這行才有用
    
Transaction tx = session2.beginTransaction();
try {
    tx.commit();
    System.out.println("成功!");
} catch (Exception e) {
    tx.rollback();
    System.err.println("失敗!");
    e.printStackTrace();
} finally {
    session2.close();
    factory.close();
}

注意下面的Transaction接收的是session2
session關閉對資料庫沒作用,但java也不會出錯

※資料庫的抓取欄位策略

<hibernate-mapping default-access="field">
    <class name="org.hibernate.vo.Dept" table="DEPT">
        <id name="deptNo" type="int" access="property">
            <column name="DEPTNO" />
            <generator class="assigned" />
        </id>
        <property name="dName" type="java.lang.String" access="property">
            <column name="DNAME" />
        </property>
        <property name="loc" type="java.lang.String" access="property">
            <column name="LOC" />
        </property>
    </class>
</hibernate-mapping>

id和property有個屬性access,有三個可以設定
※property:預設值,vo一定要設setter/getter,不然會報「Could not find a setter for property xxx in class xxx」
※field:hibernate會用反射的技術獲取值
※noop:自定的策略,要implements org.hibernate.property.PropertyAccessor介面
可參考這裡的5.1.4.2

※hibernate-mapping也有default-access可以設定,上面是針對一個field,這邊的設定是針對整個java bean,但如果都設定,以上面的為主

※我設定field後,將setter/getter刪除,欄位還是private
使用get可以抓到值; 使用load,值都是null

※事務自動提交

在hibernate.cfg.xml加上
<property name="hibernate.connection.autocommit">true</property>

TestHibernate.java
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
    
Dept dept = new Dept();
dept.setDeptno(80);
dept.setDname("bruce");
dept.setLoc("zh_TW");
    
session.saveOrUpdate(dept);
session.flush();
// Transaction tx = session.beginTransaction();
try {
    session.saveOrUpdate(dept);
    // tx.commit();
    System.out.println("成功!");
} catch (Exception e) {
    // tx.rollback();
    System.err.println("失敗!");
    e.printStackTrace();
} finally {
    session.close();
    factory.close();
}

主要就是加上session.flush();

※多個執行一起commit

再創一個腳本
CREATE TABLE EMP
   (    EMPNO NUMBER(4,0), 
    ENAME VARCHAR2(10 BYTE), 
    JOB VARCHAR2(9 BYTE), 
    MGR NUMBER(4,0), 
    HIREDATE DATE, 
    SAL NUMBER(7,2), 
    COMM NUMBER(7,2), 
    DEPTNO NUMBER(2,0), 
    CONSTRAINT "PK_EMP" PRIMARY KEY (EMPNO)
   );
    
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (9999,'Bruce','MANAGER',null,to_date('18-10月-15','DD-MON-RR'),5000,null,80);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7369,'SMITH','CLERK',7902,to_date('17-12月-80','DD-MON-RR'),800,null,20);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7499,'ALLEN','SALESMAN',7698,to_date('20-2月 -81','DD-MON-RR'),1600,300,30);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7521,'WARD','SALESMAN',7698,to_date('22-2月 -81','DD-MON-RR'),1250,500,30);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7566,'JONES','MANAGER',7839,to_date('02-4月 -81','DD-MON-RR'),2975,null,20);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7654,'MARTIN','SALESMAN',7698,to_date('28-9月 -81','DD-MON-RR'),1250,1400,30);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7698,'BLAKE','MANAGER',7839,to_date('01-5月 -81','DD-MON-RR'),2850,null,30);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7782,'CLARK','MANAGER',7839,to_date('09-6月 -81','DD-MON-RR'),2450,null,10);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7788,'SCOTT','ANALYST',7566,to_date('24-1月 -87','DD-MON-RR'),3000,null,20);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7839,'KING','PRESIDENT',null,to_date('17-11月-81','DD-MON-RR'),5000,null,10);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7844,'TURNER','SALESMAN',7698,to_date('08-9月 -81','DD-MON-RR'),1500,0,30);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7876,'ADAMS','CLERK',7788,to_date('02-4月 -87','DD-MON-RR'),1100,null,20);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7900,'JAMES','CLERK',7698,to_date('03-12月-81','DD-MON-RR'),950,null,30);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7902,'FORD','ANALYST',7566,to_date('03-12月-81','DD-MON-RR'),3000,null,20);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7934,'MILLER','CLERK',7782,to_date('23-1月 -82','DD-MON-RR'),1300,null,10);

Emp.java
private int empno;
private String ename;
private String job;
private Integer mgr;
private Date hiredate;
private Double sal;
private Double comm;
private Integer deptno;
//setter/getter

Emp.hbm.xml
<class name="org.hibernate.vo.Emp" table="Emp">
    <id name="empno" type="int">
        <column name="EMPNO" />
        <generator class="assigned" />
    </id>
    <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="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>

hibernate.cfg.xml要多增加一行
<mapping resource="org/hibernate/vo/Emp.hbm.xml" />

TestHibernate.java
public static void main(String[] args) {
    Configuration cfg = new Configuration().configure();
    SessionFactory factory = cfg.buildSessionFactory();
    Session session = factory.openSession();
    
    try {
        session.saveOrUpdate(saveOrUpdateDept());
        session.save(saveEmp());
        session.flush();
        System.out.println("成功!");
    } catch (Exception e) {
        System.err.println("失敗!");
        e.printStackTrace();
    } finally {
        session.close();
        factory.close();
    }
}
    
public static Dept saveOrUpdateDept(){
    Dept dept = new Dept();
    dept.setDeptno(80);
    dept.setDname("bruce");
    dept.setLoc("zh_TW");
    return dept;
}
    
public static Emp saveEmp(){
    Emp emp = new Emp();
    emp.setEmpno(9999);
    emp.setEname("Bruce");
    emp.setJob("MANAGER");
    emp.setHiredate(new Date());
    emp.setSal(5000D);
    emp.setDeptno(80);
    return emp;
}

※如果是PK可以設定int,不是的話就設Integer,因為int不可以是null,如果怕錯就全部設Wrapper類別
※先用get方法測試Emp行不行,如果如上行設成int,查詢時若資料庫對應的欄位有null值,會出「Null value was assigned to a property of primitive type setter of xxx」的錯
※save是新增,所以執行第二次之後,一定會報錯
※讀取到session.flush();因為是同一個session,會一起commit
※如果要一個一個commit,就在下方多增加一行session.flush(),如下:
session.saveOrUpdate(saveOrUpdateDept());
session.flush();
session.save(saveEmp());
session.flush();



※動態模型

就是沒有class和資料庫的表對應,直接取一個entity名稱就搞定了


Dept.hbm.xml

<class entity-name="entityDept" table="DEPT">
    <id name="deptno" type="int">
        <column name="DEPTNO" />
        <generator class="sequence">
            <param name="sequence">DEPT_SEQ</param>
        </generator>
    </id>
    <property name="dname" type="java.lang.String" access="field">
        <column name="DNAME" />
    </property>
    <property name="loc" type="java.lang.String" column="LOC" />
</class>

※這裡的PK用自動的,如果資料庫沒有就下CREATE SEQUENCE DEPT_SEQ吧!

※property的name為必填

新增測試

Session s = HibernateUtil2.getSession();
Transaction tx = s.beginTransaction();
try {
    Map<String, Object> dept = new HashMap<>();
    dept.put("dname", "業務部");
    dept.put("loc", "芝加哥"); 

    s.save("entityDept", dept);
    tx.commit();
} catch (Exception e) {
        tx.rollback();
        System.err.println("例外錯誤!");
        e.printStackTrace();
    } finally {
        if (s.isOpen()) {
            s.close();
        }
    }
}

※map的key是xml的property裡面的name屬性


修改測試

// Map<String, Object> dept = new HashMap<>();
// dept.put("deptno", 41);
// dept.put("dname", "業務部");
// dept.put("loc", "舊金山"); 
// s.update("entityDept", dept);

Map<String, Object> dept = (Map<String, Object>) s.get("entityDept", 41);
dept.put("loc", "舊金山");
s.update("entityDept", dept);
tx.commit();

※註解的部分也可以,但不寫出來就會給null,這張表只有三個欄位,全寫出來也還好,但如果有很多欄位,譬如20個,那實在是太累了,所以可以用get或load取出來,然後再針對想修改的欄位做修改即可


刪除測試

s.delete(s.load("entityDept", 41));
tx.commit();

沒有留言:

張貼留言