2015年11月21日 星期六

集合映射 (Hibernate3.x 二十一)

集合當然就是java的Map、Set、List的東西啦!官方文件在這裡


※XML設定

※Set



假設新的學期開始,每個人都有很多不同的課本,所以在資料庫增加兩個table,如下:

※前置作業

DROP TABLE PERSON PURGE;
DROP TABLE BOOK PURGE;
    
CREATE TABLE PERSON(
    PID VARCHAR(10) ,
    PERSON_NAME VARCHAR(50) ,
    CONSTRAINT PERSON_PK PRIMARY KEY(PID)
);
    
CREATE TABLE BOOK(
    BID VARCHAR(50) ,
    BOOK_NAME VARCHAR(200) ,
    CONSTRAINT BOOK_FK FOREIGN KEY(BID) REFERENCES PERSON(PID) ON DELETE CASCADE
);

Person.java

public class Person {
    private Integer pid;
    private String personName;
    private Set<Object> xxx = new HashSet<Object>(0);
    //setter/getter...
}

※不需要Book.java,集合映射會去找


Person.hbm.xml

<class name="vo.Person" table="PERSON">
    <id name="pid" type="java.lang.Integer">
        <column name="PID" />
        <generator class="assigned" />
    </id>
    
    <property name="personName" type="java.lang.String">
        <column name="PERSON_NAME" />
    </property>
    
    <set name="xxx" table="BOOK"> <!--lazy="false"> -->
        <key not-null="true">
            <column name="BID" />
        </key>
        <element type="java.lang.String" column="BOOK_NAME"/>
    </set>
</class>

測試類:

Session s = HibernateUtil2.getSession();
Transaction tx = s.beginTransaction();
try {
    Person p = new Person();
    p.setPid(1);
    p.setPersonName("john");
    p.getXxx().add("國文");
    p.getXxx().add("英文");
    p.getXxx().add("英文");
    p.getXxx().add(null);
    s.saveOrUpdate(p);
    tx.commit();
    
    Person person = (Person) s.get(Person.class, 1);
    //s.close();
    System.out.println(person.getPersonName());
    System.out.println(person.getXxx());
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
    tx.rollback();
} finally {
    if (s.isOpen()) {
        s.close();
    }
}


在控制台會顯示:
※第一次會執行select person,然後insert person,再連續執行三次的insert book
第二次發現資料庫有值時,inser person會變成delete book的動作,其他都一樣

※設定null是可以的,就算hbm.xml設定not-null="true"也沒用,其實我一直都在用3.6.10在測,屬性常常都沒什麼用,譬如前面的<property>或者它底下的<column>,我設定length,長度故意設的比資料庫還短,或者兩個都設,但還是新增修改成功

※上面的s.close()註解打開,一樣可以很順利執行完,應該是一級快取的關係
但如果只查詢且提找關閉,也就是把commit()上面的都註解掉且close打開,而且只查xxx,就會出「org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: vo.Person.xxx, no session or session was closed」的錯誤,這時就要把hbm.xml的lazy="false"註解打開即可解決,此時控制台會先執行select person後,再執行select book


再看一個例子:
Session s = HibernateUtil2.getSession();
Transaction tx = s.beginTransaction();
try {
    Person person = (Person) s.get(Person.class, 1);
    s.close(); // Detached
    
    Set<Object> p = new HashSet<Object>();
    p.addAll(person.getXxx());
    p.add("數學");
    person.setXxx(p);
    HibernateUtil2.getSession().update(person); // Persistent
    HibernateUtil2.getSession().beginTransaction().commit();
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
    HibernateUtil2.getSession().beginTransaction().rollback();
} finally {
    if (s.isOpen()) {
        s.close();
    }
}

※尤於一查到就close,所以狀態已變成Detached,包括tx都依賴s,都不能用了,所以後面都是從HibernateUtil2開頭

※控制台會先select person-->select book-->update person-->delete book-->insert book三次
可以看出會整個bid為1的全部刪除再新增

※delete時,因為準備資料那裡有加on delete cascade,所以會幫book刪除
如果沒加就出錯,反正以資料庫為準,hibernate不幫忙,就算在set加cascade="all",還是沒用,這點很奇怪



※List

※前置作業

DROP TABLE PERSON PURGE;
DROP TABLE BOOK PURGE;
    
CREATE TABLE PERSON(
    PID VARCHAR(10) ,
    PERSON_NAME VARCHAR(50) ,
    CONSTRAINT PERSON_PK PRIMARY KEY(PID)
);
    
CREATE TABLE BOOK(
    BID VARCHAR(50) ,
    BOOK_NAME VARCHAR(200) ,
    IND NUMBER(3),
    CONSTRAINT BOOK_FK FOREIGN KEY(BID) REFERENCES PERSON(PID) ON DELETE CASCADE
);

※尤於index為關鍵字,只好取ind


Person.hbm.xml

<class name="vo.Person" table="PERSON">
    <id name="pid" type="java.lang.Integer">
        <column name="PID" />
        <generator class="assigned" />
    </id>
    
    <property name="personName" type="java.lang.String">
        <column name="PERSON_NAME" />
    </property>
    
    <list name="xxx" table="BOOK" lazy="false">
        <key>
            <column name="BID" />
        </key>
        <index column="IND" />
        <!-- <list-index column="IND" /> -->
        <element type="java.lang.String" column="BOOK_NAME"/>
    </list>
</class>

※list底下一定要有index,不然會報錯

※list-index和index我試的結果都可以

※測試類都和Set一樣,此時可以重覆,ind欄位預設會從0遞增,可以用base屬性改變



※Array

和List很像,只是hbm.xml的list改成array,而vo將List改成String[],其他都和上面的List一樣,也是index和list-index都可以

屬性有些不太一樣,像lazy,在Array就沒有這個屬性


※Bag

和List很像,但沒有index,所以前置作業和Set一樣,而hbm.xml將List改成bag,index或list-index不要,其他都和上面的List一樣,還是有lazy屬性


※Map

※前置作業

DROP TABLE PERSON PURGE;
DROP TABLE BOOK PURGE;
    
CREATE TABLE PERSON(
    PID VARCHAR(10) ,
    PERSON_NAME VARCHAR(50) ,
    CONSTRAINT PERSON_PK PRIMARY KEY(PID)
);
    
CREATE TABLE BOOK(
    BID VARCHAR(10) ,
    BOOK_NAME VARCHAR(50) ,
    BOOK_PRICE NUMBER(4),
    CONSTRAINT BOOK_FK FOREIGN KEY(BID) REFERENCES PERSON(PID) ON DELETE CASCADE
);

※增加BOOK_PRICE,將書名和書的價錢挷在一起變成Map

Person.hbm.xml

<class name="vo.Person" table="PERSON">
    <id name="pid" type="java.lang.Integer">
        <column name="PID" />
        <generator class="assigned" />
    </id>
    
    <property name="personName" type="java.lang.String">
        <column name="PERSON_NAME" />
    </property>
    
    <map name="xxx" table="BOOK" lazy="false">
        <key>
            <column name="BID" />
        </key>
        <map-key type="java.lang.String" column="BOOK_NAME" />
        <!-- <index column="BOOK_NAME" type="java.lang.String" /> -->
        <element type="java.lang.Integer" column="BOOK_PRICE"/>
    </map>
</class>

※map-key也可以用List的index,就是註解那一行,而list-index就沒這種屬性了


Person.java

public class Person {
    private Integer pid;
    private String personName;
    private Map<String, Integer> xxx = new HashMap<String, Integer>(0);
    //setter/getter...
}

測試類:

Session s = HibernateUtil2.getSession();
Transaction tx = s.beginTransaction();
try {
     Person p = new Person();
     p.setPid(1);
     p.setPersonName("john");
     p.getXxx().put("國文", 120);
     p.getXxx().put("英文", 80);
     p.getXxx().put("數學", 60);
     s.saveOrUpdate(p);
     tx.commit();
    
     Person person = (Person) s.get(Person.class, 1);
     s.close();
     System.out.println(person.getXxx());
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
    HibernateUtil2.getSession().beginTransaction().rollback();
} finally {
    if (s.isOpen()) {
        s.close();
    }
}






※Annotation設定

Set和Bag

@Entity
@Table(name = "PERSON")
public class Person {
    @Id
    private Integer pid;
    
    @Column(name = "PERSON_NAME")
    private String personName;
    
    //    @CollectionOfElements(targetElement=String.class,  fetch = FetchType.EAGER) 已廢棄,用@ElementCollection
    @ElementCollection(targetClass = String.class, fetch = FetchType.EAGER)
    // @CollectionTable(name = "BOOK", joinColumns = { @JoinColumn(name = "BID")}) 和@JoinTable取其一,都可用
    @JoinTable(name = "BOOK", joinColumns = { @JoinColumn(name = "BID") })
    @Column(name = "BOOK_NAME")
    private Set<Object> xxx = new HashSet<Object>();
    //setter/getter...
}

※FetchType有LAZY和EAGER,EAGER就是急切,不懶的意思

※FetchType.LAZY 就是XML設定的fetch="select";
FetchType.EAGER就是XML設定的 fetch ="join"



List

//和Set一樣, 但多兩行,ooo要對應好,
//不能和下面Array的Annotation混用,但不會報錯,只是IND永遠是Null,控制台的insert語句並不會包括IND
@CollectionId(columns = @Column(name="IND", nullable=false), type=@Type(type="long"), generator = "ooo")
@GenericGenerator(name="ooo",strategy="increment")
private List<Object> xxx = new ArrayList<Object>();

※從1開始累加,我找不到像Array的base選項可以設定開始值,可能是以資料庫設定為主吧

※strategy共有13種,官網在這的5.1.2.2.1. Various additional generators



Array

//和Set一樣,不能和List的Annotation混用,會報錯
@IndexColumn(name = "IND", base = 1)
private String[] xxx;

※base=1表示從1開始累加,XML也有



Map

@Entity
@Table(name = "PERSON")
public class Person {
    @Id
    private Integer pid;
    
    @Column(name = "PERSON_NAME")
    private String personName;
    
    @ElementCollection(targetClass=Integer.class, fetch = FetchType.EAGER)
    //    @org.hibernate.annotations.MapKey(columns = { @Column(name = "BOOK_NAME", nullable = false) }) 已廢棄,用@MapKeyColumn
    @MapKeyColumn(name = "BOOK_NAME")
    @CollectionTable(name = "BOOK", joinColumns = { @JoinColumn(name = "BID") })
    @Column(name = "BOOK_PRICE")
    private Map<String, Integer> xxx = new HashMap<String, Integer>(0);
    //setter/getter...
}

※用MapKey時,注意import哪一個package,所以我將全名打出來,不過反正已廢棄了

沒有留言:

張貼留言