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)
刪除從表只會刪從表

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名稱


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,所以我將全名打出來,不過反正已廢棄了

2015年11月15日 星期日

FK約束、查詢約束 (DDL 四)

新增

.必須先新增父表,才能新增子表
.如先新增子表-->ORA-02291: 違反完整性限制條件 (ACCOUNT.FK_FKNAME) - 找不到父項索引鍵


刪除

※delete

.必須先刪除子表,才能刪除父表
.如先刪父表-->ORA-02292: 違反完整性限制條件 (ACCOUNT.FK_FKNAME) - 發現子項記錄

※truncate(DDL)

.只能刪除子表
.如刪父表或刪完子表再刪父表都會-->ORA-02266: 已啟用的外來索引鍵參照表格中的唯一索引鍵/主索引鍵

修改

.關聯字段有對應到都不能改
.如修改父表-->ORA-02292: 違反完整性限制條件 (ACCOUNT.FK_FKNAME) - 發現子項記錄
.如修改子表-->ORA-02291: 違反完整性限制條件 (ACCOUNT.FK_FKNAME) - 找不到父項索引鍵

※修改父項時,如沒有子項對應,就可以改
※修改子項時,只能修改父項有的值


DROP TABLE(DDL)

.必須先刪除子表,才能刪除父表
.如先刪父表-->ORA-02449: 外來索引鍵參照表格中的唯一索引鍵/主索引鍵



※測試

DROP TABLE dad PURGE;
CREATE TABLE dad (
    did number(5) PRIMARY KEY,
    dname varchar2(20)
);
    
DROP TABLE son PURGE;
CREATE TABLE son (
    sid number(5) PRIMARY KEY,
    sname varchar2(20),
    did number(5),
    
    CONSTRAINT fk_did FOREIGN KEY(did) REFERENCES dad(did)
);
    
INSERT INTO dad VALUES (1, 'apple');
INSERT INTO dad VALUES (2, 'banana');
INSERT INTO son VALUES (1, 'monkey', 1);
    
DELETE FROM dad WHERE did = 1;

※執行到DELETE那一行,就會出「ORA-02292: 違反完整性限制條件 (帳號.FK_SID) - 發現子項記錄」的錯,因為發現子項記錄,所以就要先把子項記錄刪了,才能刪父項記錄



※ON DELETE CASCADE、ON DELETE SET NULL

ALTER TABLE son DROP CONSTRAINT fk_did;
ALTER TABLE son ADD CONSTRAINT fk_did FOREIGN KEY(sid) REFERENCES dad(did) ON DELETE CASCADE;

※後面增加ON DELETE CASCADE,這樣子刪除父項記錄時,就會將子項記錄刪除了

※如果後面增加的是ON DELETE SET NULL,這樣子刪除父項記錄時,就會將對應的欄位變成null



※DROP TABLE

刪除表時,如果先刪的是父項,就會出「ORA-02449: 外來索引鍵參照表格中的唯一索引鍵/主索引鍵」的錯(因為表有CONSTRAINT,所以不管有沒有記錄都會出這種錯)

如果確定都要刪,就要這樣下「DROP TABLE dad CASCADE CONSTRAINT PURGE;」,此時父表被刪除了,而子表的記錄完全沒影響,記錄沒被刪除,也不會變成null,但子表的FK約束消失了



※查詢約束

USER_CONSTRAINTS、USER_CONS_COLUMNS是查約束用的
CONSTRAINT_TYPE有英文符號,意思如下:
.PRIMARY KEY:P
.UNIQUE KEY:U
.FOREIGN KEY:R
.CHECK、NOT NULL:C
官網連結拉到最下面有一張表,寫的很清楚

2015年11月14日 星期六

客製化型態轉換(兩種JSON) (Hibernate3.x 二十)

不會用的可先參考這裡的文章

先寫個JSON和List的互轉程式

pom.xml

<dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20140107</version>
</dependency>
    
<dependency>
    <groupId>net.sf.json-lib</groupId>
    <artifactId>json-lib</artifactId>
    <version>2.4</version>
    <classifier>jdk15</classifier>
</dependency>

※java版本太高(7、8)要降版
※org.json選較低版本即可, net.sf要加一行classifier


org.json

import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONObject;
    
public class OrgJSONTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");
    
        JSONObject jObj = new JSONObject();
        jObj.put("interest", list);
        System.out.println(jObj);// {"interest":["A","B","C"]}
    
        JSONArray jArray = jObj.getJSONArray("interest");// get()也可以
        List<String> rtn = new ArrayList<>();
        for (int i = 0; i < jArray.length(); i++) {
            rtn.add(jArray.getString(i));
        }
        System.out.println(rtn);// [A, B, C]
    }
}


net.sf.json

import java.util.ArrayList;
import java.util.List;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
    
public class NetSfJSONTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");
    
        JSONObject jObj = new JSONObject();
        jObj.put("interest", JSONArray.fromObject(list));
        System.out.println(jObj);// {"interest":["A","B","C"]}
    
        JSONArray jArray = jObj.getJSONArray("interest");// get()也可以
        List<String> rtn = new ArrayList<>();
        for (int i = 0; i < jArray.size(); i++) {
            rtn.add(jArray.getString(i));
        }
        System.out.println(rtn);// [A, B, C]
    }
}





依照客製化型態轉換 (Hibernate3.x 十九)來改
主要改UserType裡面的兩個方法,assemble和disassemble

org.json

/**
 * JSON轉List
 */
@Override
public Serializable disassemble(Object value) throws HibernateException {
    JSONObject jObj = new JSONObject(value);
    JSONArray jArray = jObj.getJSONArray("interest");// get()也可以
    List<String> rtn = new ArrayList<>();
    for (int i = 0; i < jArray.length(); i++) {
        rtn.add(jArray.getString(i));
    }
    return (Serializable) rtn;
}
    
/**
 * List轉JSON
 */
@SuppressWarnings("unchecked")
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
    JSONObject jObj = new JSONObject();
    jObj.put("interest", ((List<String>) owner));
    return jObj;
}


net.sf.json

/**
 * JSON轉List
 */
@Override
public Serializable disassemble(Object value) throws HibernateException {
    JSONObject jObj = JSONObject.fromObject(value) ;
    JSONArray jArray = jObj.getJSONArray("interest");// get()也可以
    List<String> rtn = new ArrayList<>();
    for (int i = 0; i < jArray.size(); i++) {
        rtn.add(jArray.getString(i));
    }
    return (Serializable) rtn;
}
    
/**
 * List轉JSON
 */
@SuppressWarnings("unchecked")
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
    JSONObject jObj = new JSONObject();
    jObj.put("interest", JSONArray.fromObject((List<String>) owner));
    return jObj;
}

測試類和設定都和客製化型態轉換 (Hibernate3.x 十九)一樣

在Java使用org.JSON和net.sf.JSON(連結)

目前在網路上常常看到JSON of JAVA的操作,發現流行的有這兩種
.org.JSON
.net.sf.JSON
還有一種是google的,很少聽人在討論
這兩種的類別名稱很多都一模一樣,但用法不見得相同
java聽說要到JAVA9,預計2016年9月才有內建JSON,也不知是哪一種JSON

※net.sf.JSON

尤於官方網站已寫的很清楚,且英文不難,一進去就會看到型態對應和要有的jar檔
左邊Getting Started裡面有教怎麼寫,而Javadoc為API
如果是maven下載,可來這裡

※org.JSON

首先要下載,可參考我的這篇文章或者用maven下載
這裡選其中一個版本
尤於我本來是下載2015的版本,但是執行時出「java.lang.UnsupportedClassVersionError: org/json/JSONObject : Unsupported major.minor version 52.0」的錯,可能是java7或8的關係,所以只好降版了,用的是如下的版本

<dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20140107</version>
</dependency>


※JSONObject和JSONArray

// 像Map的JSONObject
JSONObject jObj = new JSONObject();
jObj.put("A", "1");
jObj.put("B", 2);
jObj.put("C", 3.3);
jObj.put("D", '4');// 不會轉為ASCII
jObj.put("E", true);
System.out.println(jObj);// {"D":52,"E":true,"A":"1","B":2,"C":3.3}
System.out.println(jObj.get("B"));// 2
    
// 像List的JSONArray
JSONArray jArray = new JSONArray();
jArray.put("F");
jArray.put(5);
jArray.put(6.6);
jArray.put('7');// ASCII為55,會轉為55
jArray.put(false);
System.out.println(jArray);// ["F",5,6.6,55,false]
System.out.println(jArray.get(3));// 55



※加入Collection

// 增加Collection
Map<String, Object> map = new HashMap<>();
map.put("AA", "1");
map.put("BB", 1);
map.put("CC", 1.1);
map.put("DD", '1');
map.put("EE", false);
    
List<Object> list = new ArrayList<>();
list.add("2");
list.add(2);
list.add(2.2);
list.add('2');
list.add(true);
    
Set<Object> set = new HashSet<>();
set.add("3");
set.add(3);
set.add(3.3);
set.add('3');
set.add(false);
    
JSONObject jObj = new JSONObject();
JSONArray jArray = new JSONArray();
    
System.out.println("JSONArray裡面放Collection");
jArray.put(map);
jArray.put(list);
jArray.put(set);
System.out.println(jArray);// [{"AA":"1","BB":1,"CC":1.1,"DD":"1","EE":false},["2",2,2.2,"2",true],["3","3",false,3,3.3]]
    
System.out.println("JSONObject裡面放Collection");
jObj.put("MapOfObj", map);
jObj.put("ListOfObj", list);
jObj.put("SetOfObj", set);
System.out.println(jObj);// {"SetOfObj":["3","3",false,3,3.3],"ListOfObj":["2",2,2.2,"2",true],"MapOfObj":{"AA":"1","BB":1,"CC":1.1,"DD":"1","EE":false}}



※JSONObject放JSONArray和JSONArray放JSONObject

JSONObject jObj = new JSONObject();
jObj.put("A", "1");
jObj.put("B", 2);
jObj.put("C", 3.3);
jObj.put("D", '4');// 不會轉為ASCII
jObj.put("E", true);
    
JSONArray jArray = new JSONArray();
jArray.put("F");
jArray.put(5);
jArray.put(6.6);
jArray.put('7');// ASCII為55,會轉為55
jArray.put(false);
    
// 以下兩組擇其一,否則會StackOverflowError(死循環)
jObj.put("ObjAll", jArray);
System.out.println(jObj);// {"D":52,"E":true,"A":"1","B":2,"C":3.3,"ObjAll":["F",5,6.6,55,false]}
System.out.println(jObj.getJSONArray("ObjAll"));// ["F",5,6.6,55,false]
    
jArray.put(jObj);
System.out.println(jArray);// ["F",5,6.6,55,false,{"D":52,"E":true,"A":"1","B":2,"C":3.3}]




※JSONObject和JSONArray的建構子

Map<String, Object> map = new HashMap<>();
map.put("AA", "1");
map.put("BB", '1');
    
List<Object> list = new ArrayList<>();
list.add("2");
list.add('2');
    
Set<Object> set = new HashSet<>();
set.add("3");
set.add('3');
    
System.out.println(new JSONObject(map));// {"AA":"1","BB":"1"}
System.out.println(new JSONObject(list));// {"empty":false}
System.out.println(new JSONObject(set));// {"empty":false}
    
// System.out.println(new JSONArray(map));
System.out.println(new JSONArray(list));// ["2","2"]
System.out.println(new JSONArray(set));// ["3","3"]
//直接塞List或Set不會轉成ASCII Code

※JSONObject類似Map,所以給list和set,不知要顯示什麼,所以會出現這個預設值

※同樣的JSONArray類似List,所以給Map,不知要顯示什麼,所以會出「org.json.JSONException: JSONArray initial value should be a string or collection or array.」的例外

客製化型態轉換 (Hibernate3.x 十九)

假設前端有個叫興趣的checkbox,那傳到後端用List來接好了,但存進資料庫想用「,」隔開的方式儲存,且取出來時又要變成List,這時就可以用以下的方法,官方有三種,這裡介紹其中一種

※準備資料

CREATE TABLE INDIVISUAL_FILE(
    ID NUMBER(5),
    NAME VARCHAR(10),
    INTEREST VARCHAR(120),
    CONSTRAINT PK_INDIVISUAL_FILE PRIMARY KEY(ID)
);

※INTEREST設大一個,因為有很多興趣


※UserType

先寫一個class,繼承UserType,總共要覆寫11個方法
public class InterestUserType implements UserType {
    
    @Override
    public int[] sqlTypes() {
        System.out.println("sqlTypes");
        return new int[] { Types.VARCHAR };
    }
    
    @Override
    public Class<?> returnedClass() {
        System.out.println("returnedClass");
        return List.class;
    }
    
    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        System.out.println("equals");
        if (x == null || y == null) {
            return false;
        }
        if (x == y) {
            return true;
        }
        return x.equals(y);
    }
    
    @Override
    public int hashCode(Object x) throws HibernateException {
        System.out.println("hashCode");
        return x.hashCode();
    }
    
    /**
     * 從資料庫取出
     */
    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
            throws HibernateException, SQLException {
        System.out.println("nullSafeGet");
        return this.disassemble(rs.getString(names[0]));
    }
    
    /**
     * 存到資料庫
     */
    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index)
            throws HibernateException, SQLException {
        System.out.println("nullSafeSet");
        if (value == null) {
            st.setNull(index, java.sql.Types.NULL);
        } else {
            st.setString(index, this.assemble(null, value).toString());
        }
    }
    
    /**
     * 複製原始資料
     */
    @SuppressWarnings("unchecked")
    @Override
    public Object deepCopy(Object value) throws HibernateException {
        System.out.println("deepCopy");
        if (value == null) {
            return null;
        }
        List<String> newData = new ArrayList<String>();
        newData.addAll((List<String>) value);
        return newData;
    }
    
    /**
     * 此實體是否可被改變
     */
    @Override
    public boolean isMutable() {
        System.out.println("isMutable");
        return true;
    }
    
    /**
     * 將「,」轉成List
     */
    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        System.out.println("disassemble");
        List<String> list = new ArrayList<String>();
        String comma[] = value.toString().split(",");
        for (int i = 0; i < comma.length; i++) {
            list.add(comma[i]);
        }
        return (Serializable) list;
    }
    
    /**
     * 將List轉成「,」的型式
     */
    @Override
    public Object assemble(Serializable cached, Object owner)
            throws HibernateException {
        System.out.println("assemble");
        StringBuffer sb = new StringBuffer();

        @SuppressWarnings("unchecked")
        Iterator<String> it = ((List<String>) owner).iterator();
        while (it.hasNext()) {
            sb.append(it.next()).append(",");
        }
        return sb;
    }
    
    @Override
    public Object replace(Object original, Object target, Object owner)
            throws HibernateException {
        System.out.println("replace");
        return original;
    }
}

※XML設定

IndivisualFile.java

public class IndivisualFile {
    private Integer id;
    private String name;
    private List<String> interest;
    //setter/getter...
}

IndivisualFile.hbm.xml

<class name="vo.IndivisualFile" table="INDIVISUAL_FILE">
    <id name="id" type="java.lang.Integer">
        <column name="ID" />
        <generator class="assigned" />
    </id>
    
    <property name="name" type="java.lang.String">
        <column name="NAME" />
    </property>
    
    <property name="interest" type="vo.InterestUserType">
        <column name="INTEREST" />
    </property>
</class>

※注意interest的type要連到前面寫的InterestUserType

測試類

Session s = HibernateUtil2.getSession();
Transaction tx = s.beginTransaction();
try {
    IndivisualFile idv = new IndivisualFile();
    idv.setId(1);
    idv.setName("王小明");
    idv.setInterest(new ArrayList<String>());
    idv.getInterest().add("下棋");
    idv.getInterest().add("爬山");
    idv.getInterest().add("聽音樂");
    s.save(idv);
    tx.commit();
    
    IndivisualFile rtn = (IndivisualFile) s.get(IndivisualFile.class, 1);
    System.out.println("id:" + rtn.getId());
    System.out.println("name:" + rtn.getName());
    System.out.println("size:" + rtn.getInterest().size());
    for (String str : rtn.getInterest()) {
        System.out.println(str);
    }
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
    tx.rollback();
} finally {
    if (s.isOpen()) {
        s.close();
    }
}

控制台的執行結果:
sqlTypes
sqlTypes
sqlTypes
isMutable
sqlTypes
sqlTypes
sqlTypes
sqlTypes
deepCopy
equals
Hibernate:
    insert
    into
        INDIVISUAL_FILE
        (NAME, INTEREST, ID)
    values
        (?, ?, ?)
nullSafeSet
assemble
id:1
name:王小明
size:3
下棋
爬山
聽音樂

※而資料庫的INTEREST欄位是:下棋,爬山,聽音樂,
最後有個逗點,但無所謂,取出時大小還是3



※Annotation設定

IndivisualFile.java

@Entity
@Table(name = "INDIVISUAL_FILE")
public class IndivisualFile {
    private Integer id;
    private String name;
    private List<String> interest;
    
    @Id
    public Integer getId() {
        return id;
    }
    
    public void setId(Integer id) {
        this.id = id;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    @Type(type = "vo.InterestUserType")
    public List<String> getInterest() {
        return interest;
    }
    
    public void setInterest(List<String> interest) {
        this.interest = interest;
    }
}


2015年11月13日 星期五

繼承映射三(table per class hierarchy) (Hibernate3.x 十八)

※table per class hierarchy

※準備資料

DROP TABLE CHESS;
CREATE TABLE CHESS (
    ID NUMBER(5),
    NAME VARCHAR(10),
    PRICE NUMBER(5),
    PRODUCT_DATE DATE,
    SCORE NUMBER(3),
    SCHOOL VARCHAR(20),
    SALARY NUMBER(6),
    COMPANY VARCHAR(20),
    JUDGE VARCHAR(1),
    CONSTRAINT PK_CHESS PRIMARY KEY(ID)
);

※將所有的欄位全部放在一張表,然後多新增一個判斷的欄位,以上例就是JUDGE
如果是S,那SALARY和COMPANY就不使用;
如果是M,那SCORE和SCHOOL就不使用


※XML設定

Chess.java

public abstract class Chess {
    private Integer id;
    private String name;
    private Integer price;
    private Date productDate;
    //setter/getter...
}

Maker.java

public class Maker extends Chess {
    private Integer salary;
    private String company;
    //setter/getter...
}

Student.java

public class Student extends Chess {
    private Integer score;
    private String school;
    //setter/getter...
}

Chess.hbm.xml

<class name="vo.Chess" abstract="true">
    <id name="id" type="java.lang.Integer">
        <column name="ID" />
        <generator class="assigned" />
    </id>
    
    <discriminator column="JUDGE" type="string" />
    
    <property name="name" type="java.lang.String">
        <column name="NAME" />
    </property>
    
    <property name="price" type="java.lang.Integer">
        <column name="PRICE" />
    </property>
    
    <property name="productDate" type="java.util.Date">
        <column name="PRODUCT_DATE" />
    </property>
    
    <subclass name="vo.Student" discriminator-value="S">
        <property name="score" type="java.lang.Integer">
            <column name="SCORE" />
        </property>
        <property name="school" type="java.lang.String">
            <column name="SCHOOL" />
        </property>
    </subclass>
    
    <subclass name="vo.Maker" discriminator-value="M">
        <property name="salary" type="java.lang.Integer">
            <column name="SALARY" />
        </property>
        <property name="company" type="java.lang.String">
            <column name="COMPANY" />
        </property>
    </subclass>
</class>

※注意discriminator和subclass的discriminiator-value,順序也要注意

測試類

Session s = HibernateUtil2.getSession();
Transaction tx = s.beginTransaction();
try {
    System.out.println("-----測試學生-----");
    Student stu = new Student();
    stu.setId(1);
    stu.setName("象棋");
    stu.setPrice(50);
    stu.setProductDate(new Date(100, 0, 1));
    stu.setScore(78);
    stu.setSchool("家裡蹲大學");
    s.save(stu);
    
    System.out.println("-----測試員工-----");
    Maker mak = new Maker();
    mak.setId(2);
    mak.setName("五子棋");
    mak.setPrice(30);
    mak.setProductDate(new Date(102, 11, 12));
    mak.setSalary(43000);
    mak.setCompany("少林寺管委會");
    s.save(mak);
    tx.commit();
    
    Student stuRtn = (Student) s.get(Student.class, 1);
    System.out.println("學校名:" + stuRtn.getSchool());
    
    Maker makRtn = (Maker) s.get(Maker.class, 2);
    System.out.println("公司名:" + makRtn.getCompany());
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
    tx.rollback();
    tx.rollback();
} finally {
    if (s.isOpen()) {
        s.close();
    }
}

※控制台的訊息,會自動將JUDGE塞值



※Annotation設定

hibernate.cfg.xml

<mapping class="vo.Chess" />
<mapping class="vo.Student" />
<mapping class="vo.Maker" />

Chess.java

@DiscriminatorColumn(name="JUDGE", discriminatorType = DiscriminatorType.STRING)
@Entity
@Table(name = "CHESS")
public abstract class Chess {
    private Integer id;
    private String name;
    private Integer price;
    private Date productDate;
    
    @Id
    public Integer getId() {
        return id;
    }
    
    public void setId(Integer id) {
        this.id = id;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public Integer getPrice() {
        return price;
    }
    
    public void setPrice(Integer price) {
        this.price = price;
    }
    
    @Column(name = "PRODUCT_DATE")
    public Date getProductDate() {
        return productDate;
    }
    
    public void setProductDate(Date productDate) {
        this.productDate = productDate;
    }
}

Maker.java

@DiscriminatorValue("M")
@Entity
public class Maker extends Chess {
    //...
}

Student.java

@DiscriminatorValue("S")
@Entity
public class Student extends Chess {
    //...
}


繼承映射二(table per subclass) (Hibernate3.x 十七)

※table per subclass(joined-subclass)

※準備資料

DROP TABLE STUDENT;
DROP TABLE MAKER;
DROP TABLE CHESS;
    
CREATE TABLE CHESS (
    ID NUMBER(5),
    NAME VARCHAR(10),
    PRICE NUMBER(5),
    PRODUCT_DATE DATE,
    CONSTRAINT PK_CHESS PRIMARY KEY(ID)
);
    
CREATE TABLE STUDENT(
    ID NUMBER(5),
    SCORE NUMBER(3),
    SCHOOL VARCHAR(20),
    CONSTRAINT PK_STUDENT PRIMARY KEY(ID),
    CONSTRAINT FK_STUDENT_ID FOREIGN KEY(ID) REFERENCES CHESS(ID) ON DELETE CASCADE
);
    
CREATE TABLE MAKER(
    ID NUMBER(5),
    SALARY NUMBER(6),
    COMPANY VARCHAR(20),
    CONSTRAINT PK_MAKER PRIMARY KEY(ID),
    CONSTRAINT FK_MAKER_ID FOREIGN KEY(ID) REFERENCES CHESS(ID) ON DELETE CASCADE
);

※和table per concrete class相比較,差在多一張CHESS表

※XML設定

Chess.java

public abstract class Chess {
    private Integer id;
    private String name;
    private Integer price;
    private Date productDate;
    //setter/getter...
}

※Chess.java、Student.java、Maker.java和table per concrete class (Hibernate3.x 十六)一樣

Student.java

public class Student extends Chess {
    private Integer score;
    private String school;
    //setter/getter...
}

Maker.java

public class Maker extends Chess {
    private Integer salary;
    private String company;
    //setter/getter...
}

Chess.hbm.xml

<id name="id" type="java.lang.Integer">
    <column name="ID" />
    <generator class="assigned" />
</id>
    
<property name="name" type="java.lang.String">
    <column name="NAME" />
</property>
    
<property name="price" type="java.lang.Integer">
    <column name="PRICE" />
</property>
    
<property name="productDate" type="java.util.Date">
    <column name="PRODUCT_DATE" />
</property>
    
<joined-subclass name="vo.Student" table="STUDENT">
    <key column="id" />
    <property name="score" type="java.lang.Integer">
        <column name="SCORE" />
    </property>
    <property name="school" type="java.lang.String">
        <column name="SCHOOL" />
    </property>
</joined-subclass>
    
<joined-subclass name="vo.Maker" table="MAKER">
    <key column="id" />
    <property name="salary" type="java.lang.Integer">
        <column name="SALARY" />
    </property>
    <property name="company" type="java.lang.String">
        <column name="COMPANY" />
    </property>
</joined-subclass>

※和table per concrete class的union-subclass很像,將union-subclass改成joined-class,然後將對應的key(也就是FK)寫出來即可,一定要放在第一行

測試類別

SessionFactory sf = HibernateUtil2.getSessionFactory();
Session s1 = sf.openSession();
Session s2 = sf.openSession();
Transaction tx1 = s1.beginTransaction();
Transaction tx2 = s2.beginTransaction();
try {
    System.out.println("-----測試學生-----");
    Student stu = new Student();
    stu.setId(1);
    stu.setName("象棋");
    stu.setPrice(50);
    stu.setProductDate(new Date(100, 0, 1));
    stu.setScore(78);
    stu.setSchool("家裡蹲大學");
    s1.save(stu);
    tx1.commit();
    
    Student stuRtn = (Student) s1.get(Student.class, 1);
    System.out.println("學校名:" + stuRtn.getSchool());
    
    System.out.println("-----測試員工-----");
    Maker mak = new Maker();
    mak.setId(2);
    mak.setName("五子棋");
    mak.setPrice(30);
    mak.setProductDate(new Date(102, 11, 12));
    mak.setSalary(43000);
    mak.setCompany("少林寺管委會");
    s2.save(mak);
    tx2.commit();
    
    Maker makRtn = (Maker) s2.load(Maker.class, 2);
    System.out.println("公司名:" + makRtn.getCompany());
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
    tx1.rollback();
    tx2.rollback();
} finally {
    if (s1.isOpen()) {
        s1.close();
    }
    if (s2.isOpen()) {
        s2.close();
    }
}

※hibernate.cfg.xml只要多一行<mapping resource="vo/Chess.hbm.xml" />

※catch裡的rollback如果寫在第一行,在commit之後有錯,會到catch,執行完rollback後會直接到finally,編譯居然沒問題,也就是catch後面的程式碼沒執行
如果load/get一個沒有記錄的資料會null或exception,然後null在「.」也是exception,這時因為rollback在第一行,所以錯在哪一行和訊息沒有印出來,只會出現「org.hibernate.TransactionException: Transaction not successfully started」,而且資料還新增成功,因為已經先commit,後面才出錯

修改成兩個表一次commit

Session s = HibernateUtil2.getSession();
Transaction tx = s.beginTransaction();
try {
    System.out.println("-----測試學生-----");
    Student stu = new Student();
    stu.setId(1);
    stu.setName("象棋");
    stu.setPrice(50);
    stu.setProductDate(new Date(100, 0, 1));
    stu.setScore(78);
    stu.setSchool("家裡蹲大學");
    s.save(stu);
    
    Student stuRtn = (Student) s.get(Student.class, 1);
    System.out.println("學校名:" + stuRtn.getSchool());
    
    System.out.println("-----測試員工-----");
    Maker mak = new Maker();
    mak.setId(2);
    mak.setName("五子棋");
    mak.setPrice(30);
    mak.setProductDate(new Date(102, 11, 12));
    mak.setSalary(43000);
    mak.setCompany("少林寺管委會");
    s.save(mak);
    
    Maker makRtn = (Maker) s.load(Maker.class, 2);
    System.out.println("公司名:" + makRtn.getCompany());
    
    tx.commit();
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
    tx.rollback();
    tx.rollback();
} finally {
    if (s.isOpen()) {
        s.close();
    }
}

※雖然解決了一次commit的問題,但尤於key是FK,所以Student和Maker的key不能重復,這是沒辦法的

※注意控制台出的select訊息是select xxx from Student student0_, CHESS student0_1_ ,所以是join,會有效能慢的問題


※Annotation設定

hibernate.cfg.xml

<mapping class="vo.Chess" />
<mapping class="vo.Student" />
<mapping class="vo.Maker" />

Chess.java

@Inheritance(strategy = InheritanceType.JOINED)
@Entity
@Table(name = "CHESS")
public abstract class Chess {
    private Integer id;
    private String name;
    private Integer price;
    private Date productDate;
    
    @Id
    public Integer getId() {
        return id;
    }
    
    public void setId(Integer id) {
        this.id = id;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public Integer getPrice() {
        return price;
    }
    
    public void setPrice(Integer price) {
        this.price = price;
    }
    
    @Column(name = "PRODUCT_DATE")
    public Date getProductDate() {
        return productDate;
    }
    
    public void setProductDate(Date productDate) {
        this.productDate = productDate;
    }
}

※@Id一定要打以外,其他@Column如和資料庫同名可不打,而productDate因不同,所以要打

Student.java

@Inheritance(strategy = InheritanceType.JOINED)
@Entity
public class Student extends Chess {
    //...
}

※因為Column和資料庫全同名,所以只要設定class上頭的annotation

Maker.java

@Inheritance(strategy = InheritanceType.JOINED)
@Entity
public class Maker extends Chess {
    //...
}

※因為Column和資料庫全同名,所以只要設定class上頭的annotation

※測試類一樣

2015年11月12日 星期四

取得org.JSON(JAVA)的jar

※產生JAR檔

在JSON的官網並沒有提供,但可以自己做
首先進入後的中間偏下有個連結


點進去後,進入如下畫面,左邊有原始碼和API可參考,點上面的連結



將全部的原始檔(左邊)全部下載下來,集中在一個資料夾

這時會發現原始碼裡,每個package都是org.json
所以要建立兩層資料夾org/json,然後將剛剛下載的原始碼放進去
開啟CMD然後切換到org/json的上層好了
>javac org/json/*.java   這一行會將全部的java編譯成class
>jar -cvf json.jar org/json/*.class 這一行會將全部的class打包成json.jar,可自行命名
成功後,json.jar會出現在org/json的上層目錄,反正就是你cmd的目前的目錄


後來發現原來是有提供下載點的,就在下載原始檔的最下面,點進去就有好幾個版本,如下:

或者也可以用maven下載


※在Eclipse看原始檔

反編譯的軟體很多,我比較常用的是Java Decompiler,如下畫面


我下載的是JD-GUI,還有其他的我沒試過


下載完後,打開剛剛的json.jar,點左邊的class,右邊可看到原始碼


但現在是要在Eclipse看,使我們用Ctrl,然後點JSON的物件可以看原始碼,所以先存檔


zip不用解壓,直接在Eclipse用即可


如果日後想修改或想回復預設值,可如下設定


2015年11月8日 星期日

繼承映射一(table per concrete class) (Hibernate3.x 十六)

繼承映射分成三種形式
※table per class hierarchy(每個階層類別一個表)
※table per subclass(每個子類別一個表)
※table per concrete class(每個具體類別一個表)
尤於翻譯的有和沒有一樣,所以提供官方連結,這裡先從下往上講,個人感覺比較好懂

※table per concrete class(union-subclass)


※準備資料

CREATE TABLE STUDENT(
    ID NUMBER(5),
    NAME VARCHAR(10),
    PRICE NUMBER(5),
    PRODUCT_DATE DATE,
    SCORE NUMBER(3),
    SCHOOL VARCHAR(20),
    CONSTRAINT PK_STUDENT PRIMARY KEY(ID)
);
    
CREATE TABLE MAKER(
    ID NUMBER(5),
    NAME VARCHAR(10),
    PRICE NUMBER(5),
    PRODUCT_DATE DATE,
    SALARY NUMBER(6),
    COMPANY VARCHAR(20),
    CONSTRAINT PK_MAKER PRIMARY KEY(ID)
);

※ID、NAME、PRICE、PRODUCT_DATE,這四個欄位兩張表都有,所以可以提出來模擬成java的繼承關係,取一個名字,譬如叫Chess,然後將兩張對應的表繼承它


※XML設定

Chess.java

public abstract class Chess {
    private Integer id;
    private String name;
    private Integer price;
    private Date productDate;
    //setter/getter...
}

※Chess是個抽象類別,讓Student和Maker繼承

Student.java

public class Student extends Chess {
    private Integer score;
    private String school;
    //setter/getter...
}

Maker.java
public class Maker extends Chess {
    private Integer salary;
    private String company;
    //setter/getter...
}

Chess.hbm.xml
<class name="vo.Chess" abstract="true">
    <id name="id" type="java.lang.Integer">
        <column name="ID" />
        <generator class="assigned" />
    </id>
    
    <property name="name" type="java.lang.String">
        <column name="NAME" />
    </property>
    
    <property name="price" type="java.lang.Integer">
        <column name="PRICE" />
    </property>
    
    <property name="productDate" type="java.util.Date">
        <column name="PRODUCT_DATE" />
    </property>
    
    <union-subclass name="vo.Student" table="STUDENT">
        <property name="score" type="java.lang.Integer">
            <column name="SCORE" />
        </property>
        <property name="school" type="java.lang.String">
            <column name="SCHOOL" />
        </property>
    </union-subclass>
    
    <union-subclass name="vo.Maker" table="MAKER">
        <property name="salary" type="java.lang.Integer">
            <column name="SALARY" />
        </property>
        <property name="company" type="java.lang.String">
            <column name="COMPANY" />
        </property>
    </union-subclass>
</class>

※Chess可以加個abstract=true,更加確定是個抽象類別,其他兩個類別用union-subclass

測試類:

SessionFactory sf = HibernateUtil2.getSessionFactory();
Session s1 = sf.openSession();
Session s2 = sf.openSession();
Transaction tx = s1.beginTransaction();
Transaction tx2 = s2.beginTransaction();
try {
    System.out.println("-----測試學生-----");
    Student stu = new Student();
    stu.setId(1);
    stu.setName("象棋");
    stu.setPrice(50);
    stu.setProductDate(new Date(100, 0, 1));
    stu.setScore(78);
    stu.setSchool("家裡蹲大學");
    s1.save(stu);
    
    Student stuRtn = (Student) s1.load(Student.class, 1);
    System.out.println("學校名:" + stuRtn.getSchool());
    
    System.out.println("-----測試員工-----");
    Maker mak = new Maker();
    mak.setId(1);
    mak.setName("五子棋");
    mak.setPrice(30);
    mak.setProductDate(new Date(102, 11, 12));
    mak.setSalary(43000);
    mak.setCompany("少林寺管委會");
    s2.save(mak);
    
    Maker makRtn = (Maker) s2.load(Maker.class, 1);
    System.out.println("公司名:" + makRtn.getCompany());
    
    tx.commit();
    tx2.commit();
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
    tx.rollback();
} finally {
    if (s1.isOpen()) {
        s1.close();
    }
    if (s2.isOpen()) {
        s2.close();
    }
}

※hibernate.cfg.xml只要增加一行<mapping resource="vo/Chess.hbm.xml" />即可

※如果使用同一個session,而且id相同,就會出現「org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session:」的例外
就算有二級快取,也是一樣,所以和快取沒關係
.原因是因為同一個session不能有相同的名稱
.使用Session的clean、refresh、merge都不行
.但如果是Student和Maker分開,也就是Student做完就commit,然後clean再做Maker即可,我現在是假設二筆資料一起commit、一起rollback的話就不行
.Maker不使用save,改用merge會過,但在load時,會出現Student不能強轉成Maker的錯,很正常,因為繼承同一個類別,Session裡面存的是第一次的Student
.這是使用table per concrete class的缺點


※Annotation設定

Chess.java

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Entity
public abstract class Chess {
    private Integer id;
    private String name;
    private Integer price;
    private Date productDate;
    
    @Id
    public Integer getId() {
        return id;
    }
    
    public void setId(Integer id) {
        this.id = id;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public Integer getPrice() {
        return price;
    }
    
    public void setPrice(Integer price) {
        this.price = price;
    }
    
    @Column(name = "PRODUCT_DATE")
    public Date getProductDate() {
        return productDate;
    }
    
    public void setProductDate(Date productDate) {
        this.productDate = productDate;
    }
}

※id是一定要設的,而Column只有productDate的名稱和資料庫不同,設這個就好

※三個類別都必需設定@Inheritance

Student.java

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Entity
public class Student extends Chess {
    private Integer score;
    private String school;
    //setter/getter...
}

※由於Table和Column一樣可以不打,所以這是最精簡設定

Maker.java

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Entity
public class Maker extends Chess {
    private Integer salary;
    private String company;
    //setter/getter...
}


hibernate.cfg.xml

<mapping class="vo.Chess" />
<mapping class="vo.Student" />
<mapping class="vo.Maker" />

※三個類別都必需增加才可以

※測試類別一樣