2015年12月20日 星期日

多對一單向FK關聯 (Hibernate3.x 二十八)

※準備工作

DROP TABLE STUDENT PURGE;
DROP TABLE CLAZZ PURGE;
    
CREATE TABLE CLAZZ(
    CID NUMBER(5),
    CNAME VARCHAR(15),
    CONSTRAINT CLAZZ_PK PRIMARY KEY(CID)
);
    
CREATE TABLE STUDENT(
    SID NUMBER(5),
    SNAME VARCHAR(15),
    CLAZZ_ID NUMBER(5),
    CONSTRAINT STUDENT_PK PRIMARY KEY(SID),
    CONSTRAINT STUDENT_FK FOREIGN KEY(CLAZZ_ID) REFERENCES CLAZZ(CID) ON DELETE CASCADE
);
    
CREATE SEQUENCE CLAZZ_SEQ;
CREATE SEQUENCE STUDENT_SEQ;

※和一對多雙向FK關聯一模一樣



※XML設定


Clazz.hbm.xml

<class name="vo.Clazz" table="CLAZZ">
    <id name="cid" type="java.lang.Integer">
        <column name="CID" />
        <generator class="sequence">
            <param name="sequence">CLAZZ_SEQ</param>
        </generator>
    </id>
    
    <property name="cname" type="java.lang.String">
        <column name="CNAME" />
    </property>
</class>

※因為是多對一單向,這裡是一,所以不用特別設定

Student.hbm.xml

<class name="vo.Student" table="STUDENT">
    <id name="sid" type="java.lang.Integer">
        <column name="SID" />
        <generator class="sequence">
            <param name="sequence">STUDENT_SEQ</param>
        </generator>
    </id>
    
    <property name="sname" type="string">
        <column name="SNAME" />
    </property>
    
    <many-to-one name="clazz" column="CLAZZ_ID" not-null="true" lazy="false" cascade="all"/>
</class>

※因為是多對一單向,這裡是多,所以要設定

Clazz.java

public class Clazz {
    private Integer cid;
    private String cname;
    // setter/getter...
}

※因為是多對一單向,這裡是一,所以不用特別設定

Student.java

public class Student {
    private Integer sid;
    private String sname;
    private Clazz clazz;
    // setter/getter...
}

※因為是多對一單向,這裡是多,所以要設定

新增測試

Clazz clazz = new Clazz();
clazz.setCname("一年壬班");
    
for (int i = 1; i <= 2; i++) {
    Student stu = new Student();
    stu.setSname("小" + i + "班");
    stu.setClazz(clazz);
    s.save(stu);
}
tx.commit();

修改測試

Clazz clazz = new Clazz();
clazz.setCid(47);
clazz.setCname("三年丙班");
    
String sql = " SELECT * FROM STUDENT WHERE CLAZZ_ID = :id ";
SQLQuery query = s.createSQLQuery(sql)
        .addScalar("SID", StandardBasicTypes.INTEGER)
        .addScalar("SNAME", StandardBasicTypes.STRING);
query.setParameter("id", clazz.getCid());
    
@SuppressWarnings("unchecked")
List<Object> list = query.list();
for (int i = 0; i < list.size(); i++) {
    Object[] o = (Object[]) list.get(i);
    
    Student stu = new Student();
    stu.setSid((Integer) o[0]);
    stu.setSname("中" + i + "班");
    stu.setClazz(clazz);
    s.update(stu);
}
tx.commit();

查詢多方(Student)測試

Student stu = (Student) s.get(Student.class, 90);
s.close();
System.out.println(stu.getSname());
System.out.println(stu.getClazz().getCname());

※還是要設定lazy="false",否則Clazz(一方)會查不到

※預設fetch是select


刪除多方(Student)測試

Student stu = (Student) s.get(Student.class, 90);
s.delete(stu);
tx.commit();

※只要刪除多方,就會幫我們刪除一方了



※Annotation設定

Clazz.java

@Entity
@Table
public class Clazz {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "XXX")
    @SequenceGenerator(name = "XXX", sequenceName = "CLAZZ_SEQ")
    private Integer cid;
    
    @Column
    private String cname;
    
    // setter/getter...
}


Student.java

@Entity
@Table
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "OOO")
    @SequenceGenerator(name = "OOO", sequenceName = "STUDENT_SEQ")
    private Integer sid;
    
    @Column
    private String sname;
    
    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "CLAZZ_ID")
    @LazyToOne(value = LazyToOneOption.FALSE)
    private Clazz clazz;
    
    // setter/getter...
}

※測試的結果和XML設定相比較,只有查詢略有不同
XML預設是select;  Annotation預設是FetchType.EAGER(join)

一對多單向FK關聯 (Hibernate3.x 二十七)

官方不推薦使用,看原文,希望我們使用join table,也就是多對多的方法


※準備工作

DROP TABLE STUDENT PURGE;
DROP TABLE CLAZZ PURGE;
    
CREATE TABLE CLAZZ(
    CID NUMBER(5),
    CNAME VARCHAR(15),
    CONSTRAINT CLAZZ_PK PRIMARY KEY(CID)
);
    
CREATE TABLE STUDENT(
    SID NUMBER(5),
    SNAME VARCHAR(15),
    CLAZZ_ID NUMBER(5),
    CONSTRAINT STUDENT_PK PRIMARY KEY(SID),
    CONSTRAINT STUDENT_FK FOREIGN KEY(CLAZZ_ID) REFERENCES CLAZZ(CID) ON DELETE CASCADE
);
    
CREATE SEQUENCE CLAZZ_SEQ;
CREATE SEQUENCE STUDENT_SEQ;

※和一對多雙向FK關聯一模一樣



※XML設定


Clazz.hbm.xml

<class name="vo.Clazz" table="CLAZZ">
    <id name="cid" type="java.lang.Integer">
        <column name="CID" />
        <generator class="sequence">
            <param name="sequence">CLAZZ_SEQ</param>
        </generator>
    </id>
    
    <property name="cname" type="java.lang.String">
        <column name="CNAME" />
    </property>
    
    <set name="setStudent" inverse="false" cascade="all" lazy="false" fetch="join">
        <key column="CLAZZ_ID" not-null="true" />
        <one-to-many class="vo.Student" />
    </set>
</class>

※inverse一定要false(因為Student不知道Clazz存在),要自己維護,所以會產生多餘的SQL,可能就是因為這樣,所以官方不推薦使用


Student.hbm.xml

<class name="vo.Student" table="STUDENT">
    <id name="sid" type="java.lang.Integer">
        <column name="SID" />
        <generator class="sequence">
            <param name="sequence">STUDENT_SEQ</param>
        </generator>
    </id>
    
    <property name="sname" type="string">
        <column name="SNAME" />
    </property>
</class>

Clazz.java

public class Clazz {
    private Integer cid;
    private String cname;
    private Set<Student> setStudent = new HashSet<>();
    // setter/getter...
}

Student.java

public class Student {
    private Integer sid;
    private String sname;
    // setter/getter...
}

新增測試

Clazz clazz = new Clazz();
clazz.setCname("1年1班");

for (int i = 1; i <= 2; i++) {
    Student stu = new Student();
    stu.setSname("小" + i + "號");
    clazz.getSetStudent().add(stu);
}
    
s.save(clazz);
tx.commit();

※如果inverse="true",關聯的字段(CLAZZ_ID)是null,不會報錯,因為多方Student不知道一方Clazz的存在,我的<key> 有設not null,但也沒報錯誤訊息; 而<set>沒有not null 選項

修改測試

Clazz clazz = new Clazz();
clazz.setCid(51);
clazz.setCname("1年2班");
    
String sql = " SELECT * FROM STUDENT WHERE CLAZZ_ID = :id ";
SQLQuery query = s.createSQLQuery(sql)
        .addScalar("SID", StandardBasicTypes.INTEGER)
        .addScalar("SNAME", StandardBasicTypes.STRING);
query.setParameter("id", clazz.getCid());
    
@SuppressWarnings("unchecked")
List<Object> list = query.list();
for (int i = 0; i < list.size(); i++) {
    Object[] o = (Object[]) list.get(i);
    
    Student stu = new Student();
    stu.setSid((Integer) o[0]);
    stu.setSname("小" + i);
    clazz.getSetStudent().add(stu);
}
    
s.update(clazz);
tx.commit();

查詢一方(Clazz)測試

Clazz clazz = (Clazz) s.get(Clazz.class, 51);
s.close();
System.out.println(clazz.getCname());
    
for (Student stu : clazz.getSetStudent()) {
    System.out.println(stu.getSname());
}

※預設是select,lazy="false"才能查到Student的表

刪除一方(Clazz)測試

Clazz clazz = (Clazz) s.get(Clazz.class, 51);
s.delete(clazz);
tx.commit();



※Annotation設定

Clazz.java

@Entity
@Table
public class Clazz {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "XXX")
    @SequenceGenerator(name = "XXX", sequenceName = "CLAZZ_SEQ")
    private Integer cid;
    
    @Column
    private String cname;
    
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "CLAZZ_ID")
    @LazyCollection(value = LazyCollectionOption.FALSE)
    private Set setStudent = new HashSet<>();
    
    // setter/getter...
}

Student.java

@Entity
@Table
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "OOO")
    @SequenceGenerator(name = "OOO", sequenceName = "STUDENT_SEQ")
    private Integer sid;
    
    @Column
    private String sname;
    
    // setter/getter...
}

※測試的結果和XML設定一樣,包括查詢的預設值也是select(因為之前的X對X,Annotation的設定預設幾乎都是join)

2015年12月19日 星期六

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

※一對多只有FK關聯,因為PK不能重覆
※而因為是雙向,所以一對多也是多對一,看你站在哪個角度想而已


※準備工作

DROP TABLE STUDENT PURGE;
DROP TABLE CLAZZ PURGE;
    
CREATE TABLE CLAZZ(
    CID NUMBER(5),
    CNAME VARCHAR(15),
    CONSTRAINT CLAZZ_PK PRIMARY KEY(CID)
);
    
CREATE TABLE STUDENT(
    SID NUMBER(5),
    SNAME VARCHAR(15),
    CLAZZ_ID NUMBER(5),
    CONSTRAINT STUDENT_PK PRIMARY KEY(SID),
    CONSTRAINT STUDENT_FK FOREIGN KEY(CLAZZ_ID) REFERENCES CLAZZ(CID) ON DELETE CASCADE
);
    
CREATE SEQUENCE CLAZZ_SEQ;
CREATE SEQUENCE STUDENT_SEQ;

※一個班級有很多學生,在多那邊多一個FK欄位與一那邊的PK相關聯

※XML設定

hibernate.cfg.xml

<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.use_sql_comments">true</property> -->
    
        <property name="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
        <property name="hibernate.current_session_context_class">thread</property>
    
        <mapping resource="vo/Clazz.hbm.xml" />
        <mapping resource="vo/Student.hbm.xml" />
    </session-factory>
</hibernate-configuration>

clazz.hbm.xml

<hibernate-mapping>
    <class name="vo.Clazz" table="CLAZZ">
        <id name="cid" type="java.lang.Integer">
            <column name="CID" />
            <generator class="sequence">
                <param name="sequence">CLAZZ_SEQ</param>
            </generator>
        </id>
    
        <property name="cname" type="java.lang.String">
            <column name="CNAME" />
        </property>
    
        <set name="setStudent" inverse="true" cascade="all" lazy="false" fetch="join">
            <key column="CLAZZ_ID" />
            <one-to-many class="vo.Student" />
        </set>
    </class>
</hibernate-mapping>

student.hbm.xml

<class name="vo.Student" table="STUDENT">
    <id name="sid" type="java.lang.Integer">
        <column name="SID" />
        <generator class="sequence">
            <param name="sequence">STUDENT_SEQ</param>
        </generator>
    </id>
    
    <property name="sname" type="string">
        <column name="SNAME" />
    </property>
    
    <many-to-one name="clazz" column="CLAZZ_ID" not-null="true" lazy="false" fetch="join" />
</class>

Clazz.java

public class Clazz {
    private Integer cid;
    private String cname;
    private Set<Student> setStudent = new HashSet<>();
    // setter/getter...
}

Student.java

public class Student {
    private Integer sid;
    private String sname;
    private Clazz clazz;
    // setter/getter...
}

新增測試

Session s = HibernateUtil2.getSession();
Transaction tx = s.beginTransaction();
try {
    Clazz clazz = new Clazz();
    clazz.setCname("三年丁班");
    
    for (int i = 1; i <= 5; i++) {
        Student stu = new Student();
        stu.setSname("Mary" + i + "號");
        stu.setClazz(clazz);
        clazz.getSetStudent().add(stu);
    }
    
    s.save(clazz);
    tx.commit();
} catch (Exception e) {
    tx.rollback();
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    if (s.isOpen()) {
        s.close();
    }
}

※只有新增Clazz也可以,這就是網路上所謂的快速註冊與詳細註冊

※如果要新增Student,要設定cascade

修改測試

Clazz clazz = new Clazz();
clazz.setCid(16);
clazz.setCname("九年戊班");
    
String sql = " SELECT * FROM STUDENT WHERE CLAZZ_ID = :id ";
SQLQuery query = s.createSQLQuery(sql)
        .addScalar("SID", StandardBasicTypes.INTEGER)
        .addScalar("SNAME", StandardBasicTypes.STRING);
query.setParameter("id", clazz.getCid());
    
@SuppressWarnings("unchecked")
List<Object> list = query.list();
for (int i = 0; i < list.size(); i++) {
    Object[] o = (Object[]) list.get(i);
    
    Student stu = new Student();
    stu.setSid((Integer) o[0]);
    stu.setSname("第" + i + "號");
    stu.setClazz(clazz);
    clazz.getSetStudent().add(stu);
}
    
s.update(clazz);
tx.commit();

※也可以只修改Clazz

查詢Clazz

Clazz clazz = (Clazz) s.get(Clazz.class, 6);
s.close();
System.out.println(clazz.getCname());
    
for (Student stu : clazz.getSetStudent()) {
    System.out.println(stu.getSname());
}

※查詢到多的那一方會出Session is closed的錯(查不到資料也會),所以要在Clazz設定lazy="false"

※fetch預設為select

查詢Student

Student stu = (Student) s.get(Student.class, 13);
s.close();
System.out.println(stu.getSname());
System.out.println(stu.getClazz().getCname());

※查詢到一的那一方會出Session is closed的錯(查不到資料也會),所以要在Student設定lazy="false"

※fetch預設為select

※fetch="select"或不設定的結果:
Hibernate:
    select
        student0_.SID as SID1_0_,
        student0_.SNAME as SNAME1_0_,
        student0_.cid as cid1_0_
    from
        STUDENT student0_
    where
        student0_.SID=?

Hibernate:
    select
        clazz0_.CID as CID0_0_,
        clazz0_.CNAME as CNAME0_0_
    from
        CLAZZ clazz0_
    where
        clazz0_.CID=?

Hibernate:
    select
        setstudent0_.cid as cid0_1_,
        setstudent0_.SID as SID1_,
        setstudent0_.SID as SID1_0_,
        setstudent0_.SNAME as SNAME1_0_,
        setstudent0_.cid as cid1_0_
    from
        STUDENT setstudent0_
    where
        setstudent0_.cid=?
Maza1號
六年甲班

※fetch="join"的結果:
Hibernate:
    select
        student0_.SID as SID1_1_,
        student0_.SNAME as SNAME1_1_,
        student0_.cid as cid1_1_,
        clazz1_.CID as CID0_0_,
        clazz1_.CNAME as CNAME0_0_
    from
        STUDENT student0_,
        CLAZZ clazz1_
    where
        student0_.cid=clazz1_.CID
        and student0_.SID=?

Hibernate:
    select
        setstudent0_.cid as cid0_1_,
        setstudent0_.SID as SID1_,
        setstudent0_.SID as SID1_0_,
        setstudent0_.SNAME as SNAME1_0_,
        setstudent0_.cid as cid1_0_
    from
        STUDENT setstudent0_
    where
        setstudent0_.cid=?
Maza1號
六年甲班

※因為是對Student查詢,所以會有查Student的語句,然後執行到要關聯時,又會發出語句,這時就看是select或join了


刪除測試

Clazz clazz = (Clazz) s.get(Clazz.class, 6);
s.delete(clazz);
tx.commit();

※刪除Clazz就會幫我們把多的一方全刪了,當然資料庫要on delete cascade

※刪除Student(就是刪除多的一方),當然只會刪Student的一筆紀錄而已

※如果資料庫沒有可以刪除的資料就會出「java.lang.IllegalArgumentException: attempt to create delete event with null entity」的錯


※Annotation設定

hibernate.cfg.xml

<mapping class="vo.Clazz" />
<mapping class="vo.Student" />

Clazz.java

package vo;
    
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
    
@Entity
@Table
public class Clazz {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "XXX")
    @SequenceGenerator(name = "XXX", sequenceName = "CLAZZ_SEQ")
    private Integer cid;
    
    @Column
    private String cname;
    
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "clazz" )
    @LazyCollection(value = LazyCollectionOption.FALSE)
    private Set<Student> setStudent = new HashSet<>();
    
    // setter/getter...
}

※mappedBy裡的字串要對應Student裡的屬性名稱

※lazy="false",在Annotation有兩種一種就是上面針對集合用的,一種就是下面Student.java的,針對單一使用,如果設定錯誤,也不會拋Exception,只是沒作用而已

Student.java

package vo;
    
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.hibernate.annotations.LazyToOne;
import org.hibernate.annotations.LazyToOneOption;
    
@Entity
@Table
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "OOO")
    @SequenceGenerator(name = "OOO", sequenceName = "STUDENT_SEQ")
    private Integer sid;

    @Column
    private String sname;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "CLAZZ_ID")
    @LazyToOne(value = LazyToOneOption.FALSE)
    private Clazz clazz;
    
    // setter/getter...
}

※測試的結果和XML設定有一點小小的不同,就是在查詢多方(Student),而且又設定fetch=join或@ManyToOne(fetch = FetchType.EAGER)時
用Annotation設定是外連接,就是有(+); 而XML設定是內連接,沒有(+)

※其實搜尋的欄位順序和欄位的數目也不太一樣,可以自行試試



※Inverse

※在一方(Clazz)時,inverse預設為false; 在多方(Student)無此設定
※查詢不會影響
※inverse就是逆、反的意思,在一方設false,當然就是自己(一方)維護關係;
設成true就是Student(多方)維護關係



※設定為true時 控制台輸出語句如下: 

新增:

Hibernate:
    select
        CLAZZ_SEQ.nextval
    from
        dual

Hibernate:
    select
        STUDENT_SEQ.nextval
    from
        dual

Hibernate:
    select
        STUDENT_SEQ.nextval
    from
        dual

Hibernate:
    insert
    into
        CLAZZ
        (CNAME, CID)
    values
        (?, ?)

Hibernate:
    insert
    into
        STUDENT
        (SNAME, CLAZZ_ID, SID)
    values
        (?, ?, ?)

Hibernate:
    insert
    into
        STUDENT
        (SNAME, CLAZZ_ID, SID)
    values
        (?, ?, ?)


修改:

Hibernate:
    SELECT
        *
    FROM
        STUDENT
    WHERE
        CLAZZ_ID = ?

Hibernate:
    update
        CLAZZ
    set
        CNAME=?
    where
        CID=?

Hibernate:
    update
        STUDENT
    set
        SNAME=?,
        CLAZZ_ID=?
    where
        SID=?

Hibernate:
    update
        STUDENT
    set
        SNAME=?,
        CLAZZ_ID=?
    where
        SID=?


刪除:

Hibernate:
    select
        clazz0_.CID as CID0_0_,
        clazz0_.CNAME as CNAME0_0_
    from
        CLAZZ clazz0_
    where
        clazz0_.CID=?

Hibernate:
    select
        setstudent0_.CLAZZ_ID as CLAZZ3_0_1_,
        setstudent0_.SID as SID1_,
        setstudent0_.SID as SID1_0_,
        setstudent0_.SNAME as SNAME1_0_,
        setstudent0_.CLAZZ_ID as CLAZZ3_1_0_
    from
        STUDENT setstudent0_
    where
        setstudent0_.CLAZZ_ID=?

Hibernate:
    delete
    from
        STUDENT
    where
        SID=?

Hibernate:
    delete
    from
        STUDENT
    where
        SID=?

Hibernate:
    delete
    from
        CLAZZ
    where
        CID=?




※設定為false或不設定時 控制台輸出語句如下:

新增:

結果為設定true的語句加上下面兩條SQL
Hibernate:
    update
        STUDENT
    set
        CLAZZ_ID=?
    where
        SID=?

Hibernate:
    update
        STUDENT
    set
        CLAZZ_ID=?
    where
        SID=?



修改:

結果為設定true的語句加上下面三條SQL
Hibernate:
    update
        STUDENT
    set
        CLAZZ_ID=null
    where
        CLAZZ_ID=?

Hibernate:
    update
        STUDENT
    set
        CLAZZ_ID=?
    where
        SID=?

Hibernate:
    update
        STUDENT
    set
        CLAZZ_ID=?
    where
        SID=?


刪除:

結果為設定true的語句加上下面一條SQL
Hibernate:
    update
        STUDENT
    set
        CLAZZ_ID=null
    where
        CLAZZ_ID=?

※可以發現設定為false或不設多出來的語句都是針對多方的Student做處理,且都是update
不過多出來的語句似乎沒什麼用,所以在一對多大部分都是設成true,讓多方去維護關係,這樣才不會出現多的語句,影響效能

2015年12月18日 星期五

ref 的 local、bean、parent 和 ref 與 idref 的區別(Spring3.x 六)

※ref 的 local、bean、parent

參考文件
官方的大概意思是說
※local:抓自己xml上的id
※bean:抓全部的xml上的id或name
※parent:抓爸爸的xml的id或name


Book.java

public class Book {
    private Comic comic;
    // setter/getter...
    }
}

Comic.java

public class Comic {
    private String comicName;
    // setter/getter...
}


 papa.xml

<bean id="cName" class="book.vo.Comic">
    <property name="comicName" value="滾球王" />
</bean>

son.xml

<bean id="cName" class="book.vo.Comic">
    <property name="comicName" value="七龍珠" />
</bean>
    
<bean id="local" class="book.vo.Book">
    <property name="comic">
        <ref local="cName" />
    </property>
</bean>
    
<bean id="bean" class="book.vo.Book">
    <property name="comic">
        <ref bean="cName" />
    </property>
</bean>
    
<bean id="parent" class="book.vo.Book">
    <property name="comic">
        <ref parent="cName" />
    </property>
</bean>


測試類:

BeanFactory papa = new XmlBeanFactory(new ClassPathResource("papa.xml"));
BeanFactory son = new XmlBeanFactory(new ClassPathResource("son.xml"), papa);
    
Book local = (Book) son.getBean("local");
Book bean = (Book) son.getBean("bean");
Book parent = (Book) son.getBean("parent");
    
System.out.println(local.getComic().getComicName());
System.out.println(bean.getComic().getComicName());
System.out.println(parent.getComic().getComicName());

結果:
七龍珠
七龍珠
滾球王

※如果將son.xml上的id="cName",改成name="cName",結果還是一樣,想不到local還是可以抓到name的

※如果將son.xml上的cName整個刪除,local和bean的結果都抓到滾球王,想不到local還是可以抓到其他的xml

※如果將papa.xml的cName刪除,parent會發生例外,這點和官方說的一樣

※看來都是local和官方的說法不一樣,感覺上local和bean沒區別,也許是版本問題吧!不過聽說spring 4已將local移除了



剛剛的測試類,裡面的BeanFactory已經deprecation了,所以還可以用如下的方法
ApplicationContext papa = new ClassPathXmlApplicationContext("papa.xml");
ApplicationContext son = new ClassPathXmlApplicationContext(new String[] { "son.xml" }, papa);
    
Book local = (Book) son.getBean("local");
Book bean = (Book) son.getBean("bean");
Book parent = (Book) son.getBean("parent");
    
System.out.println(local.getComic().getComicName());
System.out.println(bean.getComic().getComicName());
System.out.println(parent.getComic().getComicName());
    
((ClassPathXmlApplicationContext) son).close();

※測試的結果也和上面一樣


※用import的方式

<import resource="papa.xml" />
    
<bean id="cName" class="book.vo.Comic">
    <property name="comicName" value="七龍珠" />
</bean>
    
<bean id="local" class="book.vo.Book">
    <property name="comic">
        <ref local="cName" />
    </property>
</bean>
    
<bean id="bean" class="book.vo.Book">
    <property name="comic">
        <ref bean="cName" />
    </property>
</bean>

※這和官方說的父子沒什麼關係,所以parent不能使用

※papa.xml的內容和上面的一樣

※import的id可以重覆

※如果自己的xml有就抓,沒有就會去import找,再沒有就出錯誤訊息,和上面的一樣




※ref 與 idref 的區別

ref:上面就有了,就是個參考
idref:參考文件下面一點有個The idref element,有說是個String,不是參考

applicationContext.xml

<import resource="papa.xml" />
    
<bean id="co" class="book.vo.Comic" />
    
<bean id="book" class="book.vo.Book">
    <property name="bookName">
        <!-- <idref local="co" /> -->
        <value>co</value>
    </property>
</bean>

測試類

ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    
Book book = (Book) appContext.getBean("book");
System.out.println(book.getBookName());
    
((ClassPathXmlApplicationContext) appContext).close();

※也就是說idref和value一樣,就差在idref裡面的字,必需在import裡或者自己的xml裡的id(name我試過也OK)有才行,有就是多個驗證的功能,用的地方應該很少,官方有說它帶來的價值是在AOP的攔截器

※idref有local和bean,沒有parent屬性,local和bean和上面的測試一樣,我測不出兩者的差別,一樣都是自己有就抓,沒有就往import裡找,找不到就出錯

※idref和value只能選擇其一

2015年12月17日 星期四

Setter-based 注入 與 實體參照 (Spring3.x 五)

參考文件

※Setter-based注入

使用 Setter-base 時,一定要有預設建構子,否則會 runtime-exception

Book.java

public class Book {
    private String bookName;
    private int bookMoney;
    private Comic comic;
    //setter/getter...
}

Comic.java

public class Comic {
    private String comicName;
    private int comicMoney;
    private Book book;
    
    public String getComicName() {
        return comicName;
    }
    
    public void setComicName(String comicName) {
        System.out.println("comicName=" + comicName);
        this.comicName = comicName;
    }
    
    public int getComicMoney() {
        return comicMoney;
    }
    
    public void setComicMoney(int comicMoney) {
        System.out.println("comicMoney=" + comicMoney);
        this.comicMoney = comicMoney;
    }
    
    public Book getBook() {
        return book;
    }
    
    public void setBook(Book book) {
        System.out.println("book=" + book.getBookName());
        this.book = book;
    }
}

applicationContext.xml

<bean id="book" class="book.vo.Book" p:comic-ref="comic">
    <property name="bookName">
        <value>三國志</value>
    </property>
    
    <property name="bookMoney" value="620" />
</bean>
    
<bean id="comic" class="book.vo.Comic" p:comicName="七龍珠" p:comicMoney="85" p:book-ref="book" />

測試類

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
Comic comic = (Comic) ctx.getBean("comic");
System.out.println(comic.getBook().getBookName());
System.out.println(comic.getBook().getBookMoney());
    
((ClassPathXmlApplicationContext) ctx).close();

※結果:
book=null
comicMoney=85
comicName=七龍珠
三國志
620

※comic的「p:」,必須在Hello World (Spring3.x 一)的標頭有Namespaces的圖,勾p的checkbox,就會在xml上方出現一些設定,這時才可以用這種寫法

※最後兩行是在測試類才run的到的程式碼,但第一次book=null,所以在啟動時是無法取得對方的內容的


※屬性第一個字與第二個字的問題

Book.java

public class Book {
    private String aBc;
    
    public String getaBc() {
        return aBc;
    }
    
    public void setaBc(String aBc) {
        this.aBc = aBc;
    }
}

applicationContext.xml

<bean id="book" class="book.vo.Book" p:aBc="abc" />

※在網路上有人說前二個字必需全大寫或全小寫,像我這個例子就沒有,有人說啟動時spring會報錯,但我試的結果並沒有,setter/getter我都用eclipse自動產生,xml也有提示可用,我試著把第一個字改成大寫,都還是可以,可能是版本的差異吧!



※實體參照

有五個特殊字在設定XML時有可能會錯誤,可以用以下的方法取代(建構子注子和Setter-based 注入都可以這樣用)
<:&lt;
>:&gt;
&:&amp;
":&quot;
':&apos;

Book.java
public class Book {
    private String spcialString;
    // setter/getter...
}

applicationContext.xml
<bean id="book1" class="book.vo.Book">
    <property name="spcialString">
        <value>&lt;</value>
    </property>
</bean>
    
<bean id="book2" class="book.vo.Book">
    <property name="spcialString">
        <value><![CDATA[<]]></value>
    </property>
</bean>
    
<bean id="book3" class="book.vo.Book">
    <property name="spcialString">
        <value>
            <![CDATA[
                <
            ]]>
        </value>
    </property>
</bean>

測試類
ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    
Book book1 = (Book) appContext.getBean("book1");
Book book2 = (Book) appContext.getBean("book2");
Book book3 = (Book) appContext.getBean("book3");
    
System.out.println(book1.getSpcialString());
System.out.println(book2.getSpcialString());
System.out.println(book3.getSpcialString());
    
((ClassPathXmlApplicationContext) appContext).close();

※結果:
<
<


<



※book3因為在xml設定有換行,所以也會換行,可以用trim()或者用book1或book2的方式就好了,不然真的很難看

2015年12月16日 星期三

建構子注入 (Spring3.x 四)

參考文件

Comic.java

package book.vo;
    
public class Comic {
    public Comic() {
        System.out.println("無參建構子" + "\n");
    }
    
    public Comic(String xxx) {
        System.out.println("一個參數建構子");
        System.out.println("xxx=" + xxx + "\n");
    }
    
    public Comic(String xxx, int ooo) {
        System.out.println("先字串後數字");
        System.out.println("xxx=" + xxx);
        System.out.println("ooo=" + ooo + "\n");
    }
    
    public Comic(int ooo, String xxx) {
        System.out.println("先數字後字串");
        System.out.println("ooo=" + ooo);
        System.out.println("xxx=" + xxx + "\n");
    }
}

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:c="http://www.springframework.org/schema/c"
    xmlns:context="http://www.springframework.org/schema/context" 
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/util 
        http://www.springframework.org/schema/util/spring-util-3.2.xsd
    ">
    
    <bean class="book.vo.Comic" id="comic1" />
    
    <bean class="book.vo.Comic" id="comic2">
        <constructor-arg index="0">
            <value>字串2</value>
        </constructor-arg>
    </bean>
    
    <bean class="book.vo.Comic" id="comic3">
        <constructor-arg type="String" value="字串3" />
        <constructor-arg type="int">
            <value>50</value>
        </constructor-arg>
    </bean>
    
    <bean class="book.vo.Comic" id="comic4">
        <constructor-arg type="int" value="100" />
        <constructor-arg type="String">
            <value>字串4</value>
        </constructor-arg>
    </bean>
    
    <bean class="book.vo.Comic" id="comic5">
        <constructor-arg type="int" index="1" value="150" />
        <constructor-arg type="String" index="0" value="字串5" />
    </bean>
    
    <bean class="book.vo.Comic" id="comic6" c:ooo="33" c:xxx="字串6" />
</beans>

測試類

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
for(int i=1; i<=6; i++){
    ctx.getBean("comic" + i);
}
((ClassPathXmlApplicationContext) ctx).close();

※結果:
無參建構子

一個參數建構子
xxx=字串2

先數字後字串
ooo=50
xxx=字串3

先數字後字串
ooo=100
xxx=字串4

先字串後數字
xxx=字串5
ooo=150

先數字後字串
ooo=33
xxx=字串6

※注意上面的value寫法很多種,都可以混用

※字串3和字串4都會執行相同的建構子,感覺是後者蓋前者,為了要正確的呼叫正確的建構子,所以多一個字串5的設定,增加index

※字串6的設定方法,必須在Hello World (Spring3.x 一)的標頭有Namespaces的圖,勾c的checkbox,就會在xml上方出現一些設定,這時才可以用這種寫法,但看起來和字串4一樣,沒有辦法決定正確的建構子,而且我也找不到類似index的屬性,如果有找到就推薦使用這個方法,一行就可以以搞定,但我目前是沒找到

※我測試類只有第一行也能跑出一樣的結果



Book.java

public class Book {
    public Book(Comic comic) {
        System.out.println("Book Constructor!");
    }
}

applicationContext.xml

<bean class="book.vo.Book" id="book1" c:comic-ref="comic1" />
    
<bean class="book.vo.Book" id="book2">
    <constructor-arg type="book.vo.Comic" ref="comic1" />
</bean>
    
<bean class="book.vo.Comic" id="comic1" />

※結果:
無參建構子

Book Constructor!
Book Constructor!

※book1和book2的寫法都可以


※建構子注入不能使用循環

兩個VO

public class Book {
    public Book() {}
    
    public Book(Comic comic) {
        System.out.println("Book Constructor!");
    }
}
    
    
public class Comic {
    public Comic() {}
    
    public Comic(Book book) {
        System.out.println("Comic Constructor!");
    }
}

applicationContext.xml

<bean class="book.vo.Book" id="xxx" c:comic-ref="ooo" />
<bean class="book.vo.Comic" id="ooo" c:book-ref="xxx" />

※會出現「nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'xxx': Requested bean is currently in creation: Is there an unresolvable circular reference?」的錯,所以要改成其他的注入方式

XML的 id、name、alias、import、繼承、placeholder (Spring3.x 三)

applicationContext.xml

<import resource="xxx.xml" />
    
<bean class="book.vo.Book" id="book,book3" name="a, b,c">
    <property name="bookName" value="Spring in Action" />
    
    <property name="bookMoney">
        <value>500</value>
    </property>
</bean>
    
<bean class="book.vo.Comic" id="comic" p:comicMoney="65" p:comicName="七龍珠" name="c"/>
    
<alias name="b" alias="xxx" />
<alias name="xxx" alias="ooo" />

測試類:

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Book book = (Book) ctx.getBean("book,book3");
System.out.println(book.getInfo());
((ClassPathXmlApplicationContext) ctx).close();

※name可以一次取多個名稱,用「,」隔開,中間可空隔 (網路上很多人說不行空,不過我試的結果就是這樣,可能版本有差吧!)
使用時只要ctx.getBean("b")就可以了

※id無法一次取多個名稱,如上的程式,使用時要ctx.getBean("book,book3")

※<bean>的 name 和 id 重覆都會出現「org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Bean name 'a' is already used in this <beans> element Offending resource: class path resource [applicationContext.xml]」的錯! (網路上很多人說name可以重覆,後者會蓋前者,但我試就是不行,可能版本有差吧!)

※alias的name要對應bean的name或id或其他的alias的alias
可以對應name的其中一個,但不能全部,如上面的b就是a, b,c的其中一個,使用時只要ctx.getBean("xxx或ooo");就可以了

※最上面的 import 就是把其他的 spring 設定檔抓進來

※annotation 也有個 @Import,但他是將沒有標 @Component 類似這樣的,也可以將其給 spring 管理,id 名為套件名+類別名;@ImportResource 才是這裡說的



※繼承

※parent

<bean id="b" class="Book" p:bookName="七龍珠" p:bookMoney="65" />
<bean id="cb" class="ComicBook" parent="b" />

※java 裡的 ComicBook 不用真的去繼承 Book,這裡指的是 xml 的繼承

※cb 繼承 b 後,class、bookName、bookMoney都繼承了,但 cb 還是可以覆寫

※有些屬性還是沒有繼承,如 abstract、autowire

※abstract

<bean id="b" class="Book" p:bookName="七龍珠" p:bookMoney="65" abstract="true" />
<bean id="cb" parent="b" />

※寫後 abstract 後,就不能用 getBean 去取得了,通常是把它當做模版使用


※placeholder

可以連接一般的文字檔,然後用「${}」去抓

<context:property-placeholder location="classpath:text/xxx.txt" />
<bean id="b" class="Book" p:bookName="${a}" p:bookMoney="65" />
<bean id="cb" class="ComicBook" p:bookName="${b}" p:bookMoney="50" />

※注意classpath:後面的路徑是在 src下,如果是 maven 或 gradle 是在 src\main\java下,此例是在這下面又增加一個 text 的資料夾,用「..」(上一層)是沒用的