2015年12月26日 星期六

多對多雙向FK關聯(關聯表無主鍵或其他欄位) (Hibernate3.x 二十九)

雙向關聯就是中間多一張表,其實就是兩個多對一

※準備工作

DROP TABLE PERSON_TOILET PURGE;
DROP TABLE TOILET PURGE;
DROP TABLE PERSON PURGE;
    
CREATE TABLE PERSON(
    PID NUMBER(5),
    PNAME VARCHAR(20),
    CONSTRAINT PERSON_PK PRIMARY KEY(PID)
);
    
CREATE TABLE TOILET(
    TID NUMBER(5),
    TNAME VARCHAR(20),
    CONSTRAINT TOILET_PK PRIMARY KEY(TID)
);
    
CREATE TABLE PERSON_TOILET(
    PERSON_ID NUMBER(5),
    TOILET_ID NUMBER(5),
    CONSTRAINT FK_PID FOREIGN KEY(PERSON_ID) REFERENCES PERSON(PID) ON DELETE CASCADE,
    CONSTRAINT FK_TID FOREIGN KEY(TOILET_ID) REFERENCES TOILET(TID) ON DELETE CASCADE
);
    
CREATE SEQUENCE TOILET_SEQ;
CREATE SEQUENCE PERSON_SEQ;



※XML設定

Person.java

public class Person {
    private Integer pid;
    private String pname;
    private Set<Toilet> toilets = new HashSet<>();
    // setter/getter...
}

Toilet.java

public class Toilet {
    private Integer tid;
    private String tname;
    private Set<Person> persons = new HashSet<>();
    // setter/getter...
}



Person.hbm.xml

<class name="vo.Person" table="PERSON">
    <id name="pid" type="java.lang.Integer">
        <column name="PID" />
        <generator class="sequence">
            <param name="sequence">PERSON_SEQ</param>
        </generator>
    </id>
    
    <property name="pname" type="java.lang.String" column="PNAME" />
    
    <set name="toilets" table="PERSON_TOILET" cascade="save-update">
        <key column="PERSON_ID" />
        <many-to-many column="TOILET_ID" class="vo.Toilet" />
    </set>
</class>



Toilet.hbm.xml

<class name="vo.Toilet" table="TOILET">
    <id name="tid" type="java.lang.Integer">
        <column name="TID" />
        <generator class="sequence">
            <param name="sequence">TOILET_SEQ</param>
        </generator>
    </id>
    
    <property name="tname" type="java.lang.String" column="TNAME" />
    
    <set name="persons" table="PERSON_TOILET" cascade="save-update" inverse="true">
        <key column="TOILET_ID" />
        <many-to-many column="PERSON_ID" class="vo.Person" />
    </set>
</class>

※兩個xml設定的cascade都設成save-update,因為多對多很少刪除的時候,另一張也刪除的


新增測試

Toilet t1 = new Toilet();
t1.setTname("便所1號");
    
Toilet t2 = new Toilet();
t2.setTname("便所2號");
    
Toilet t3 = new Toilet();
t3.setTname("便所3號");
    
Person p1 = new Person();
p1.setPname("掃便所英俠");
    
Person p2 = new Person();
p2.setPname("茅坑王");
    
// -----設定互相關聯-----
    
// 掃便所英俠可以上廁所1~3號
p1.getToilets().add(t1);
p1.getToilets().add(t2);
p1.getToilets().add(t3);
t1.getPersons().add(p1);
t2.getPersons().add(p1);
t3.getPersons().add(p1);
    
// 茅坑俠可以上廁所1、3號
p2.getToilets().add(t1);
p2.getToilets().add(t3);
t1.getPersons().add(p2);
t3.getPersons().add(p2);
    
s.save(p1);
s.save(p2);
// s.save(t1);
// s.save(t2);
// s.save(t3);
tx.commit();

※只新增其中之一也是可以的

※不管是save(person)或save(toilet)都可以新增成功,三張表都有值因為有互相關聯,因為有設cascade="all"的關係

※如果全不設cascade,也就是cascade="none"且在person設定inverse="true"
如果不打開,只新增p1、p2,那就只會新增person表,其他兩張表不會新增
而新增t1、t2、t3會報「org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: vo.Toilet」的錯
如果是在toilet設定inverse="true",情況就相反,解決方法是將那三行註解打開



修改Person測試

Person person = (Person) s.get(Person.class, 33);// 掃便所英俠的ID
person.setPname("吸茅坑大王");
    
for (Toilet toilet : person.getToilets()) {
    toilet.setTname("廁" + toilet.getTname().substring(1));
}
    
s.update(person);
tx.commit();


修改Toilet測試

Toilet toilet = (Toilet) s.get(Toilet.class, 63);// 便所1號的ID
toilet.setTname("廁所1號");
    
for (Person p : t.getPersons()) {
    p.setPname("ooooo");
}
    
s.update(toilet);
tx.commit();



查詢Person測試

// 查詢掃便所英俠能上哪些廁所
Person p1 = (Person) s.get(Person.class, 33);// 掃便所英俠的ID
s.close();
System.out.println(p1.getPname());
    
// 必需在Person.hbm.xml設定lazy="false"
for (Toilet toi : p1.getToilets()) {
    System.out.println(toi.getTname());
}



查詢Toilet測試

// 查詢便所1號能被哪些人使用
Toilet t1 = (Toilet) s.get(Toilet.class, 63);// 便所1號的ID
s.close();
System.out.println(t1.getTname());
    
// 必需在Toilet.hbm.xml設定lazy="false"
for (Person per : t1.getPersons()) {
    System.out.println(per.getPname());
}



刪除Person測試

Person person = (Person) s.get(Person.class, 33);
s.delete(person);
tx.commit();

※記得要將cascade改成delete相關,才能一次刪兩張表,不然會出「deleted object would be re-saved by cascade (remove deleted object from associations): [vo.Toilet#61]」的錯

※如果只想刪Person和中間表,可以多加一行person.setToilets(null);將關聯刪除


刪除Toilet測試

Toilet t = (Toilet) s.get(Toilet.class, 64);
s.delete(t);
tx.commit();

※刪除Toilet要小心,如果資料庫有值,會將表清空

※如果只想刪Toilet和中間表,可以多加一行t.setPersons(null);將關聯刪除






※Annotation設定


Person.java
@Entity
@Table
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "OOO")
    @SequenceGenerator(name = "OOO", sequenceName = "PERSON_SEQ")
    private Integer pid;
    
    private String pname;
    
    @ManyToMany(cascade = CascadeType.ALL, mappedBy = "persons")
    @LazyCollection(value = LazyCollectionOption.FALSE)
    private Set<Toilet> toilets = new HashSet<>();
    
    // setter/getter...
}



Toilet.java
@Entity
@Table
public class Toilet {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "XXX")
    @SequenceGenerator(name = "XXX", sequenceName = "TOILET_SEQ")
    private Integer tid;
    
    private String tname;
    
    @ManyToMany(cascade = CascadeType.ALL)
    @LazyCollection(value = LazyCollectionOption.FALSE)
    @JoinTable(name = "PERSON_TOILET", joinColumns = { @JoinColumn(name = "TOILET_ID", updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "PERSON_ID", updatable = false) })
    private Set<Person> persons = new HashSet<>();
    
    // setter/getter...
}

※有問題的地方是刪除,只要資料庫真的有值,就會將全部的表(三張表)清空

※我將cascade改成 cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH }就可以成功了,但中間表也都會刪除






Annotation的cascade我知道的就有三種,我也不知道差在哪?等我有空在研究了,先把原始碼貼出來方便觀看

@ManyToMany(cascade = javax.persistence.CascadeType.ALL)
@Cascade(value = { org.hibernate.annotations.CascadeType.ALL })
@OnDelete(action = org.hibernate.annotations.OnDeleteAction.CASCADE)

※有兩個enum都叫CascadeType,但package是不同的哦

package javax.persistence;
    
public enum CascadeType {
    /** Cascade all operations */
    ALL,
    
    /** Cascade persist operation */
    PERSIST,
    
    /** Cascade merge operation */
    MERGE,
    
    /** Cascade remove operation */
    REMOVE,
    
    /** Cascade refresh operation */
    REFRESH,
    
    /**
     * Cascade detach operation
     *
     * @since Java Persistence 2.0
     *
     */
    DETACH
}



package org.hibernate.annotations;
    
/**
 * Cascade types (can override default EJB3 cascades
 */
public enum CascadeType {
    ALL,
    PERSIST,
    MERGE,
    REMOVE,
    REFRESH,
    DELETE,
    SAVE_UPDATE,
    REPLICATE,
    
    /** @deprecated use @OneToOne(orphanRemoval=true) or @OneToMany(orphanRemoval=true) */
    @Deprecated
    DELETE_ORPHAN,
    
    LOCK,
    
    /** @deprecated use javax.persistence.CascadeType.DETACH */
    @Deprecated
    EVICT,
    
    DETACH
}



package org.hibernate.annotations;
    
public enum OnDeleteAction {
    /**
     * the default
     */
    NO_ACTION,
    
    /**
     * use cascade delete capabilities of the DD
     */
    CASCADE
}

沒有留言:

張貼留言