2015年11月28日 星期六

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

一對一關聯就是A表的一筆紀錄對應B表的一筆紀錄,分成單向和雙向,
而單雙向又分PK關聯和FK關聯,一樣也是分成XML和Annotation設定
官網連結

※XML設定

※一對一雙向PK關聯

準備工作

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), 
    CONSTRAINT CAR_ATTR_PK PRIMARY KEY (CAID)
);

※車子表對應車的屬性表


hibernate.cfg.xml

要增加xml連到hbm.xml的設定
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<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">username</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>
        <!-- <property name="hibernate.connection.autocommit">true</property> -->
        <!-- <property name="hibernate.hbm2ddl.auto">update</property> -->
    
        <property name="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
        <property name="hibernate.current_session_context_class">thread</property>
    
        <!-- <property name="hibernate.cache.use_second_level_cache">true</property> -->
        <!-- <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property> -->
        <!-- <property name="hibernate.QueryCacheEnabled">true</property> -->
    
        <!-- <property name="hibernate.cache.use_query_cache">true</property> -->
    
        <!-- <mapping resource="vo/Car.hbm.xml" /> -->
        <!-- <mapping resource="vo/CarAttr.hbm.xml" /> -->
    
        <mapping class="vo.Car" />
        <mapping class="vo.CarAttr" />
        <!-- <class-cache usage="read-only" class="vo.Emp"/> -->
    </session-factory>
</hibernate-configuration>


Car.hbm.xml

<class name="vo.Car" table="CAR">
    <id name="cid" type="java.lang.Integer">
        <column name="CID" />
        <generator class="assigned" />
    </id>
    
    <property name="name" type="java.lang.String">
        <column name="NAME" />
    </property>
    
    <one-to-one name="carAttr" class="vo.CarAttr" cascade="all" />
</class>

※generator設定成assigned表示用寫程式的方式給值,如測試類的c.setCid(1);
我是Oracle的,還可以設成sequence,如下:
<generator class="sequence">
    <param name="sequence">XXX</param>
</generator>

※而資料庫就要有類似「CREATE SEQUENCE XXX;的語法」,Hibernate就會自動給值
當然還有其他方式,什麼hilo等等的

※cascade表示兩張表的互動關係,共有十種方式(其中delete-orphan用在一對多),看官網
.none(預設):無互動
.save-update:新增和修改才互動
.delete:刪除才互動
.all:等同save-update+delete

CarAttr.hbm.xml

<class name="vo.CarAttr" table="CAR_ATTR">
    <id name="caid" type="java.lang.Integer">
        <column name="CAID" />
        <generator class="foreign">
            <param name="property">car</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>
    
    <one-to-one name="car" class="vo.Car" constrained="true" />
</class>

※generator為foreign表示與Car用一樣的主鍵

※one-to-one 的constrained為true表示這張表的FK要對應另外一張表的PK,會影響save()和delete()的順序,constrained的說明看5.1.7.2. Sharing the primary key with the associated entity的4和8

※generator的設定可和Car的互換
但本來是為Car塞PK,CarAttr當然就不用,但如果塞了會報錯
互換後,就要為CarAttr的PK塞值,Car可不用,但如果塞了會報錯

Car.java

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

※Car必需有CarAttr; 而CarAttr也必需有Car,所以這樣才叫雙向

CarAttr.java

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

新增測試

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

※ca.setCar(c)一定要有,不然會出「id.IdentifierGenerationException: attempted to assign id from null one-to-one property [vo.CarAttr.car]」的錯,看來是這行在抓Car的id

※id策略是assigned,save(ca)時,只會新增car_attr; id策略是sequence時,就沒問題,但如果再自行塞id,這時又只會新增car_attr,這點很奇怪


修改測試

Car c = new Car();
c.setCid(45);
c.setName("寶石傑45");
    
CarAttr ca = new CarAttr();
ca.setColor("藍45");
ca.setCc(520);
ca.setCaid(c.getCid());
    
// 互塞
ca.setCar(c);
c.setCarAttr(ca);
    
s.update(c);
// s.update(ca);// 只會修改car_attr
tx.commit();

※ca還是要塞id,不然會出兩種錯,分別是「exception.ConstraintViolationException: Could not execute JDBC batch update」和「java.sql.BatchUpdateException: ORA-00001: 違反必須為唯一的限制條件 (xxx.CAR_ATTR_PK)」,這個問題我搞了很久,感覺很奇怪,Hibernate居然沒幫忙

※如果用s.update(ca),只會修改car_attr


查詢Car測試

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

※使用load當然是不行
※控制台會顯示:
Hibernate:
    select
        car0_.CID as CID0_1_,
        car0_.NAME as NAME0_1_,
        carattr1_.CAID as CAID1_0_,
        carattr1_.COLOR as COLOR1_0_,
        carattr1_.CC as CC1_0_
    from
        CAR car0_,
        CAR_ATTR carattr1_
    where
        car0_.CID=carattr1_.CAID(+)
        and car0_.CID=?<br />
是join的寫法,官網說預設是select,但我看控制台就是上面的結果,是join,可在Car.hbm.xml的one-to-one增加fetch="select",控制台會變成
Hibernate:
    select
        car0_.CID as CID0_0_,
        car0_.NAME as NAME0_0_
    from
        CAR car0_
    where
        car0_.CID=?

Hibernate:
    select
        carattr0_.CAID as CAID1_0_,
        carattr0_.COLOR as COLOR1_0_,
        carattr0_.CC as CC1_0_
    from
        CAR_ATTR carattr0_
    where
        carattr0_.CAID=?



查詢Car_Attr測試

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

※只能查自己的,一執行到Car就會報「SessionException: Session is closed」,但只要在CarAttr.hbm.xml的one-to-one加上lazy="false"就可以了,查詢的結果一樣是join,可是將fetch="select"沒有用,永遠都是join

※lazy有false、proxy(預設)、no-proxy,並沒有true



刪除測試

Car c = new Car();
c.setCid(49);
    
CarAttr ca = new CarAttr();
ca.setCaid(c.getCid());
    
c.setCarAttr(ca);
    
s.delete(c);
tx.commit();

※ca也必須塞値,不然只會刪Car而已

※如果用s.delete(ca),只會刪car_attr,除非加上ca.setCar(c);







-----------------------------------------------------------------------------------------
以上是修改官方的寫法,還有一種寫法是將car和carAttr全部都用<generator class="assigned" />

Car.hbm.xml

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

CarAttr.hbm.xml

<class name="vo.CarAttr" table="CAR_ATTR">
    <id name="caid" type="java.lang.Integer">
        <column name="CAID" />
        <generator class="assigned" />
    </id>
    
    <property name="color" type="java.lang.String">
        <column name="COLOR" />
    </property>
    
    <property name="cc" type="java.lang.Integer">
        <column name="CC" />
    </property>
    
    <one-to-one name="car" class="vo.Car" constrained="true" cascade="all" lazy="false" fetch="join"/>
</class>

新增測試

Car c = new Car();
c.setCid(8);
c.setName("寶石傑");
    
CarAttr ca = new CarAttr();
ca.setColor("藍");
ca.setCc(520);
ca.setCaid(c.getCid());
    
// 互塞
ca.setCar(c);// 還是要有
c.setCarAttr(ca);
    
s.save(c);
// s.save(ca);// 這行也行
tx.commit();

修改測試

Car c = new Car();
c.setCid(8);
c.setName("寶石傑88");
    
CarAttr ca = new CarAttr();
ca.setColor("藍88");
ca.setCc(520);
ca.setCaid(c.getCid());
    
// 互塞
ca.setCar(c);// 還是要有
c.setCarAttr(ca);
    
s.update(c);
// s.update(ca);// 這行也行
tx.commit();


查詢和刪除的測試和上面都一樣

個人感覺做起來比官方的還要順暢

※Annotation設定

hibernate.cfg.xml變成這兩行

<mapping class="vo.Car" />
<mapping class="vo.CarAttr" />

Car.java

@Entity
@Table
public class Car {
    @Id
    private Integer cid;
    
    private String name;
    
    @OneToOne(mappedBy = "car", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private CarAttr carAttr;
    
    //setter/getter...
}

※FetchType有兩種值,EAGER(預設)和LAZY,EAGER就是XML設定的fetch="join"; LAZY就是XML設定的fetch="select"

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

CarAttr.java

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
    
@Entity
@Table(name = "CAR_ATTR")
public class CarAttr {
    @Id
    @GenericGenerator(name = "xxx", strategy = "foreign", parameters = { @Parameter(name = "property", value = "car") })
    @GeneratedValue(generator = "xxx")
    private Integer caid;

    private String color;

    private Integer cc;

    @OneToOne(fetch = FetchType.LAZY)
    @PrimaryKeyJoinColumn
    private Car car;
    
    //setter/getter...
}


增刪改查的測試只有一個地方不同,那就是查詢CarAttr時,FetchType.LAZY和FetchType.EAGER(也就是select和join)都有用了,這點居然不同步


※sequence設定是這樣

Car.java

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "XXX")
@SequenceGenerator(name = "XXX", sequenceName = "CAR_SEQ")

※XXX要對應好,sequenceName對應資料庫的sequence名稱


沒有留言:

張貼留言