2015年11月30日 星期一

一對一雙向FK關聯 (Hibernate3.x 二十三)

※準備工作

DROP TABLE CAR PURGE;
DROP TABLE CAR_ATTR PURGE;
    
CREATE TABLE CAR (    
    CID NUMBER(5,0),
    NAME VARCHAR2(20),
    CONSTRAINT CAR_PK PRIMARY KEY(CID)
);
    
CREATE TABLE CAR_ATTR (
    CAID NUMBER(5,0),
    COLOR VARCHAR2(20),
    CC NUMBER(3,0),
    CAR_CID NUMBER(5,0),
    CONSTRAINT CAR_ATTR_PK PRIMARY KEY(CAID),
    CONSTRAINT CAR_ATTR_FK FOREIGN KEY(CAR_CID) REFERENCES CAR(CID) ON DELETE CASCADE
);

※和一對一雙向PK關聯多了一個CAR_CID的FK


※XML設定

Car.java

public class Car {
    private Integer cid;
    private String name;
    private CarAttr carAttr;
    // setter/getter...
}

CarAttr.java

public class CarAttr {
    private Integer caid;
    private String color;
    private Integer cc;
    private Car car;
    // setter/getter...
}

※和一對一雙向PK關聯一模一樣,也不用多CAR_CID的欄位


Car.hbm.xml

<class name="vo.Car" table="CAR">
    <id name="cid" type="java.lang.Integer">
        <column name="CID" />
        <!-- <generator class="assigned" /> -->
        <generator class="sequence">
            <param name="sequence">CAR_SEQ</param>
        </generator>
    </id>
    
    <property name="name" type="java.lang.String">
        <column name="NAME" />
    </property>
    
    <one-to-one name="carAttr" class="vo.CarAttr" cascade="all" fetch="select" lazy="false" property-ref="car" />
</class>

※one-to-one可說是主表,另一方就是從表

※property-ref裡面的字要對應CarAttr.hbm.xml裡的many-to-one的name

CarAttr.hbm.xml

<class name="vo.CarAttr" table="CAR_ATTR">
    <id name="caid" type="java.lang.Integer">
        <column name="CAID" />
        <!-- <generator class="assigned" /> -->
        <generator class="sequence">
            <param name="sequence">CAR_ATTR_SEQ</param>
        </generator>
    </id>
    
    <property name="color" type="java.lang.String">
        <column name="COLOR" />
    </property>
    
    <property name="cc" type="java.lang.Integer">
        <column name="CC" />
    </property>
    
    <many-to-one name="car" class="vo.Car" column="CAR_CID"
        unique="true" not-null="true" lazy="false" />
</class>

※many-to-one可說是從表,另一方就是主表,但要加上unique=true才是一對一

新增測試

Session s = HibernateUtil2.getSession();
Transaction tx = s.beginTransaction();
try {
    Car c = new Car();
    // c.setCid(1);
    c.setName("寶石傑1");
    
    CarAttr ca = new CarAttr();
    // ca.setCaid(1);
    ca.setColor("藍1");
    ca.setCc(530);
    
    ca.setCar(c);
    c.setCarAttr(ca);
    
    s.save(c);
    // s.saveOrUpdate(ca);//會報錯
    
    tx.commit();
} catch (Exception e) {
    tx.rollback();
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    if (s.isOpen()) {
        s.close();
    }
}

※如果新增ca,會報「PropertyValueException: not-null property references a null or transient value: vo.CarAttr.car」的錯

修改測試

Session s = HibernateUtil2.getSession();
Transaction tx = s.beginTransaction();
try {
    Car c = new Car();
    c.setCid(28);
    c.setName("寶石傑2");
    
    CarAttr ca = new CarAttr();
    // ca.setCaid(40);
    ca.setColor("藍2");
    ca.setCc(530);
    
    ca.setCar(c);
    c.setCarAttr(ca);
    
    s.update(c);
    // s.update(ca);//會報錯
    
    tx.commit();
} catch (Exception e) {
    tx.rollback();
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    if (s.isOpen()) {
        s.close();
    }
}

※如果修改ca,會報「TransientObjectException: The given object has a null identifier: vo.CarAttr」的錯

※修改時,一定要塞Car的PK值,這很正常,不然資料庫怎麼會知道你要改哪一筆?
我本來想說塞Car的PK就會幫我關聯到FK了,但看起來是沒有(我有試過兩邊都加cascade="all",但還是沒用),所以CarAttr不塞會變成新增(因為PK沒有當然是新增)



查詢主表測試

Car car = (Car) s.get(Car.class, 31);
s.close();
System.out.println(car.getName());
System.out.println(car.getCarAttr().getColor());

※如果使用load,怎麼設都是不行的,因為session提早關了

※因為是查主表,所以設定要看Car.hbm.xml
fetch預設是select,上一篇的一對一雙向PK關聯,控制台顯示的是join,但這次真的是select了,
修改成join也真的變join了

※lazy我不打和false、proxy(預設)、no-proxy,我查詢都沒有問題

查詢從表測試

CarAttr carAttr = (CarAttr) s.get(CarAttr.class, 46);
s.close();
System.out.println(carAttr.getColor());
System.out.println(carAttr.getCar().getName());

※使用load當然還是不行

※因為是查從表,所以設定要看CarAttr.hbm.xml,fetch不管怎麼設都是select,控制台都不會join了

※session提早關閉只能查到自己的表(CarAttr),查Car的會報session已經關閉的訊息,此時加上lazy="false",就可以查到了,lazy的其他兩個選項也都會報session已經關閉的訊息



刪除測試

Car c = new Car();
c.setCid(31);
s.delete(c);  
tx.commit();
    
CarAttr ca = new CarAttr();
ca.setCaid(47);
s.delete(ca);
tx.commit();

※刪除主表OK,但資料庫要加ON DELETE CASCADE,我把cascade="all"拿掉還是可以

※刪除從表會出「PropertyValueException: not-null property references a null or transient value: vo.CarAttr.car」的錯

另一種刪除

Car c = new Car();
c.setCid(60);
    
CarAttr ca = new CarAttr();
ca.setCaid(63);// 實務上須自行用Car.cid去資料庫撈CarAttr.caid出來
    
ca.setCar(c);// 一定要有
c.setCarAttr(ca);
    
s.delete(c);
tx.commit();

※這種寫法感覺多此一舉了,但如果上面的刪除,在資料庫沒有ON DELETE CASCADE,那也就加減用了



※Annotation設定

Car.java

@Entity
@Table
public class Car {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "OOO")
    @SequenceGenerator(name = "OOO", sequenceName = "CAR_SEQ")
    private Integer cid;
    
    private String name;
    
    @OneToOne(mappedBy = "car", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private CarAttr carAttr;
    //setter/getter...
}

※mappedBy裡面的字要對應CarAttr.java裡的屬性名稱


CarAttr.java

@Entity
@Table(name = "CAR_ATTR")
public class CarAttr {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "XXX")
    @SequenceGenerator(name = "XXX", sequenceName = "CAR_ATTR_SEQ")
    private Integer caid;
    
    private String color;
    
    private Integer cc;
    
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "CAR_CID")
    @LazyToOne(value = LazyToOneOption.FALSE)
    private Car car;
    //setter/getter...
}


※和XML設定不一樣,是one-to-one,不是many-to-one加上unique="true"哦




測試只列出和XML設定不一樣的地方

新增測試

s.save(ca);會報「org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: vo.Car」的錯


修改測試

s.update(ca),只會修改Car_Attr

查主表

預設為join

查從表

會查兩次
預設為join,所以控制台會出現兩個(+),如果改為select,會出現一個(+),和兩次的select car_attr,很奇怪

刪除測試

刪主表在控制台只會出現刪主表,但實際上連從表也刪了(ON DELETE CASCADE)
刪除從表只會刪從表

沒有留言:

張貼留言