2015年12月31日 星期四

多對多單向FK關聯(關聯表無主鍵或其他欄位) (Hibernate3.x 三十一)

※準備工作

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

※和二十九篇完全一樣

※XML設定

hibernate.cfg.xml

<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.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/Toilet.hbm.xml" />
    <mapping resource="vo/Person.hbm.xml" />
    
    <!-- <class-cache usage="read-only" class="vo.Emp"/> -->
</session-factory>



Person.java

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



Toilet.java

public class Toilet {
    private Integer tid;
    private String tname;
    // setter/getter...
}

※因為是單向,所以Toilet不用關聯Person


Person.hbm.xml

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



Toilet.hbm.xml

<class name="vo.Toilet" table="TOILET">
    <id name="tid" type="java.lang.Integer">
        <column name="TID" />
        <generator class="sequence">
            <param name="sequence">TOILET_SEQ</param>
        </generator>
    </id>
    
    <property name="tname" type="java.lang.String" column="TNAME" />
</class>



新增測試

Session s = HibernateUtil2.getSession();
Transaction tx = s.beginTransaction();
try {
    Toilet t1 = new Toilet();
    t1.setTname("便所1號");
    
    Toilet t2 = new Toilet();
    t2.setTname("便所2號");
    
    Toilet t3 = new Toilet();
    t3.setTname("便所3號");
    
    Person p1 = new Person();
    p1.setPname("掃便所英俠");
    
    Person p2 = new Person();
    p2.setPname("茅坑王");
    
    // 掃便所英俠可以上廁所1~3號
    p1.getToilets().add(t1);
    p1.getToilets().add(t2);
    p1.getToilets().add(t3);
    
    // 茅坑俠可以上廁所1、3號
    p2.getToilets().add(t1);
    p2.getToilets().add(t3);
    
    s.save(p1);
    s.save(p2);
    tx.commit();
} catch (Exception e) {
    tx.rollback();
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    if (s.isOpen()) {
        s.close();
    }
}



修改測試

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



查詢測試

Person p = (Person) s.get(Person.class, 161);// 掃便所英俠的ID
s.close();
System.out.println(p.getPname());
    
// 必需在Person.hbm.xml設定lazy="false"
for (Toilet toi : p.getToilets()) {
    System.out.println(toi.getTname());
}



刪除測試

Person pp = (Person) s.get(Person.class, 167);
s.delete(pp);
tx.commit();// 關聯表不會刪

※cascade不要設delete相關,怪怪的



※Annotation設定

Person.java

@Entity
@Table
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "XXX")
    @SequenceGenerator(name = "XXX", sequenceName = "PERSON_SEQ")
    private Integer pid;
    
    private String pname;
    
    @ManyToMany
    @LazyCollection(value = LazyCollectionOption.FALSE)
    @JoinTable(name = "PERSON_TOILET", joinColumns = { @JoinColumn(name = "PERSON_ID", updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "TOILET_ID", updatable = false) })
    private Set toilets = new HashSet<>();
    
    // setter/getter...
}



Toilet.java

@Entity
@Table
public class Toilet {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "XXX")
    @SequenceGenerator(name = "XXX", sequenceName = "TOILET_SEQ")
    private Integer tid;
    
    private String tname;
    // setter/getter...
}

※刪除的時候,我將cascade整個拿掉,才和XML一樣

基於 Java container 的設定 (Spring3.x 十四)

參考文件
基於 Java container的annotation最主要就是@Configuration和@Bean



※@Configuration和@Bean

BookAction.java

@Configuration
public class BookAction {
    @Inject
    private IBookService service;
    
    @Bean(name = "og")
    public String go() {
        return service.get();
    }
}



IBookService.java和BookServiceImpl.java

public interface IBookService {
    public String get();
}
    
@Named
public class BookServiceImpl implements IBookService {
    @Inject
    private BookDAOImpl dao;
    
    public String get() {
        return dao.getBook();
    }
}



BookDAOImpl.java

@Component
public class BookDAOImpl {
    public String getBook() {
        return "資料庫操作";
    }
}



測試類

ApplicationContext ctx3 = new AnnotationConfigApplicationContext(BookAction.class, BookServiceImpl.class, BookDAOImpl.class);
    
System.out.println(ctx3.getBeanDefinitionCount());
for (String s : ctx3.getBeanDefinitionNames()) {
    System.out.println(s);
}
System.out.println("----------------------------------------");
    
// BookAction controller = (BookAction) ctx3.getBean("bookAction");
BookAction controller = ctx3.getBean(BookAction.class);
System.out.println(controller.go());
    
((AnnotationConfigApplicationContext) ctx3).close();

※結果:
9
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
bookAction
bookServiceImpl
bookDAOImpl
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
og
----------------------------------------
資料庫操作

※如果只設定@Bean,那id就是方法名稱

※@Bean不能在回傳值是void上面,否則會出「needs to have a non-void return type!」的錯

※@Named、@Component、@Controller、@Service、@Repository、@Configuration可混用

※@Autowired、@Resource、@Inject可混用

※測試類的BookAction那兩行都可以,本來是呼叫id,也還是可以用,現在變成呼叫class

※還可以這樣使用:
AnnotationConfigApplicationContext ctx3 = new AnnotationConfigApplicationContext();
ctx3.register(BookAction.class, BookServiceImpl.class);
ctx3.register(BookDAOImpl.class);
// ctx3.scan("\\");
ctx3.refresh();

※scan()等同xml設定的<context:component-scan base-package="\" />



※@Bean的初始銷毀方法


BookAction.java

@Configuration
public class BookAction {
    @Inject
    @Named("bookServiceImpl")
    private IBookService service;
    
    @Bean(name = "og", initMethod = "ooo", destroyMethod = "xxx")
    public IBookService go() {
        return service.get();
    }
}



IBookService.java和BookServiceImpl.java

public interface IBookService {
    public IBookService get();
}
    
@Named
public class BookServiceImpl implements IBookService {
    @Inject
    private BookDAOImpl dao;
    
    public void ooo() {
        System.out.println("ooo");
    }
    
    public void xxx() {
        System.out.println("xxx");
    }
    
    public IBookService get() {
        return this;
    }
}

※@Named("bookServiceImpl")是因為會出「 org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [service.IBookService] is defined: expected single matching bean but found 2: bookServiceImpl,og」的錯,有兩個id的型態都是IBookService,所以才加這行,@Named就是這兩種用法,就是上一篇介紹的連結,有一張表寫的很清楚

※initMethod 和 destroyMethod 要寫在回傳值的class裡面才行,但我試過回傳null就不會執行了(但也不會出錯),所以一定真的有到class裡面才行,就算是new個空的也會抓到


2015年12月30日 星期三

基於 Annotation container 設定 (Spring3.x 十二)

參考文件
這篇開始講annotation,基本的annotation不了解的可看這篇

※@Autowired

※XML設定


三個VO

public class Cartoon {
    public Cartoon() {}
}
    
public class Comic {
    public Comic() {}
}
    
public class Book {
    private Cartoon cartoon;
    private Comic comic;
    // setter/getter...
    
    public Book() {}
}



applicationContext.xml

<bean id="ca" class="vo.Cartoon" />
<bean id="co" class="vo.Comic" />
<bean id="bo" class="vo.Book" p:cartoon-ref="ca" p:comic-ref="co" />



測試類

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
// 總共有幾個bean
System.out.println(ctx.getBeanDefinitionCount());
    
// 將全部的bean名稱印出來
for (String s : ctx.getBeanDefinitionNames()) {
    System.out.println(s);
}
    
// 兩種寫法都可以
Book book1 = ctx.getBean("bo", Book.class);
Book book2 = (Book) ctx.getBean("bo");
    
System.out.println(book1.getComic());
System.out.println(book2.getCartoon());
    
((ClassPathXmlApplicationContext) ctx).close();

※結果:
3
ca
co
bo
vo.Comic@14acaea5
vo.Cartoon@46d56d67



※Annotation設定

Book.java

public class Book {
    private Cartoon cartoon;
    private Comic comic;
    
    public Book() {}
    
    public Cartoon getCartoon() {
        return cartoon;
    }
    
    @Autowired
    public void setCartoon(Cartoon cartoon) {
        this.cartoon = cartoon;
    }
    
    public Comic getComic() {
        return comic;
    }
    
    @Autowired
    public void setComic(Comic comic) {
        this.comic = comic;
    }
}

※只是在setter上面加@Autowired

<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="ca" class="vo.Cartoon" />
<bean id="co" class="vo.Comic" />
<bean id="bo" class="vo.Book" />

因為要用@Autowired,所以也要將相關的class宣告一下

@Autowired等同在 xml 設定的autowire


結果:
4
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#0
ca
co
bo
vo.Comic@6d00a15d
vo.Cartoon@51efea79

---------------------------------------------------------------------------------
還有很多的Annotation都要向上面的@Autowired一樣,都要宣告一支class,如果用了5個,就要宣告5支class,太麻煩了,所以spring提供了<context:component-scan />,所以xml修改如下:
<context:component-scan base-package="\" />
<bean id="ca" class="vo.Cartoon" />
<bean id="co" class="vo.Comic" />
<bean id="bo" class="vo.Book" />

※base-package裡面是要掃描哪些包,裡面寫包名,有很多包可用「,」隔開,而我是根目錄,所以就是「\」,但在 web 這樣寫會出「@EnableAsync annotation metadata was not injected」的錯

※結果:
8
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
ca
co
bo
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
vo.Comic@1f554b06
vo.Cartoon@694e1548

※<context:annotation-config />,就是開啟上面迴圈預設的 annotation 的意思,但這類的設定都是針對 ClassPathXmlApplicationContext 才有用;對 AnnotationConfigApplicationContext 就沒用,所以這個設定是強制只能使用 XML 的設定,所有的 annotation 都沒效
而 context:component-scan 有個屬性 annotation-config,預設就是 true 了,所以可有可無

use-default-filters="false",表示不要用 base-package 裡的 @Component 等 annotation,使用子標籤的方式過濾(include、exclude)

resource-pattern="ComicBook*.class" 表示條件再加上 ComicBook開頭,.class結尾的才掃瞄,但要注意是 .class,不是 .java

annotation-config 有子標籤
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />,表示排除 @Controller 及其子類的,如 @Component 是 @Controller、@Service、@Repository、@Configuration 的父類
型態是 annotation,表示 expression 是什麼 annotation

有排除,當然也有包括(context:include-filter)

若型態是 assignable,如 <context:exclude-filter type="assignable" expression="xxx.ooo.Book" />,表示 expression 是什麼class


※annotation 設定要用 @ComponentScan(basePackages="ooo.xxx"),只會針對 annotation 有用,3.1 才有,然後使用 new AnnotationConfigApplicationContext(Book.class);,但要注意 Book.java 不在 ooo.xxx 的套件裡,但只要有宣告 @ComponentScan ,也下了如 @Component 之類的注解,一樣也會抓到
屬性 value 為 basePackages 的別名,所以也可用 value,且只要是 annotation 叫 value 的,還可以省略不寫
屬性 nameGenerator,如預設@Component 裡面不寫東西,那 bean 名稱就是小寫開頭的 class 名稱,想改就要繼承裡面的類

---------------------------------------------------------------------------------
@Autowired還可以放在屬性上面,而這時setter就可以刪除了,如下:
public class Book {
    @Autowired
    private Cartoon cartoon;
    
    @Autowired
    private Comic comic;
    
    public Book() {}
    
    public Cartoon getCartoon() {
        return cartoon;
    }
    
    public Comic getComic() {
        return comic;
    }
}

結果還是一樣

---------------------------------------------------------------------------------
如果將xml裡的ca拿掉就會出現 「org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [vo.Cartoon] found for dependency:」的錯
可以將ca的annotation變成@Autowired(required = false),表示不要報錯

※結果:
7
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
co
bo
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
vo.Comic@3c0f93f1
null
可以看到不會報錯,就變null了

※@Autowired 如果寫在 field 上,intellij 會出現警告,會建議寫在建構子比較好,因為有可能建構子有用到這個注入的變數,但注入順序是先注入建構子才會注入 field,所以這時會抓不到,所以會有警告




※@Required

表示一定要注入,但只能放在方法上

Book.java

public class Book {
    private Cartoon cartoon;
    
    @Autowired
    private Comic comic;
    
    public Book() {
    }
    
    public Cartoon getCartoon() {
        return cartoon;
    }
    
    // @Autowired
    @Required
    public void setCartoon(Cartoon cartoon) {
        this.cartoon = cartoon;
    }
    
    public Comic getComic() {
        return comic;
    }
    
    public void setComic(Comic comic) {
        this.comic = comic;
    }
}



※如上,因為Cartoon沒注入,所以會報「org.springframework.beans.factory.BeanInitializationException: Property 'cartoon' is required for bean 'bo'」的錯,這是只要把注解打開注入即可解決

※注意@Autowired可以放在屬性上的,如下:
public class Book {
    @Autowired
    private Cartoon cartoon;
    
    @Autowired
    private Comic comic;
    
    public Book() {
    }
    
    public Cartoon getCartoon() {
        return cartoon;
    }
    
    @Required
    public void setCartoon(Cartoon cartoon) {
        this.cartoon = cartoon;
    }
    
    public Comic getComic() {
        return comic;
    }
    
    public void setComic(Comic comic) {
        this.comic = comic;
    }
}

※這時還是會報上面的錯,因為和@Required合用,就一定都要放在方法上,不然它還是認為你沒注入




※@Qualifier

@Autowired是個by type,一定要剛好只有一個才有辦法注入,例如將Comic.java修改如下:
public class Comic extends Cartoon {}

※就只是多個繼承Cartoon,這時就會出「org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [vo.Cartoon] is defined: expected single matching bean but found 2: ca,co」的錯,錯誤也很明顯,它說ca和co都是Cartoon,所以無法注入,這時Book.java的Cartoon屬性可修改如下即可解決:
@Autowired
@Qualifier("ca")
private Cartoon cartoon;

※看起來也很清楚,如果型態一樣,就以名稱是ca的來注入
---------------------------------------------------------------------------------

※除了放在屬性,也可以放在方法上,和上面的@Required一樣,全部統一放在屬性或方法上,不要分開,不然還是會認為有很多型態
@Autowired
@Qualifier("ca")
public void setCartoon(Cartoon cartoon) {
    this.cartoon = cartoon;
}

※也就是將ca的bean給參數名稱cartoon

※但如果有很多參數,就只好寫在參數上了,如下:
@Autowired
public Book(Comic com, @Qualifier("ca") Cartoon car) {}




※@Resource

※我用的時候,居然沒提示,所以我用maven下載jar
<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>jsr250-api</artifactId>
    <version>1.0</version>
</dependency>

和@Autowired很像,但是它可以不用 @Qualifier
// @Autowired
// @Qualifier("ca")
@Resource(name = "ca")
private Cartoon cartoon;

※上面的需求,它只要一行就可解決了

※@Resource有兩個重要屬性name和type
1.如果只有設定name,一定要剛好一筆,否則出錯
2.如果只有設定type,一定要剛好一筆,否則出錯
3.如果name和type都有設定,都要符合才注入,否則就出錯誤訊息
反正電腦是很笨的,一定要剛剛好,否則出錯
4.如果上面的例子沒有name="ca",會找 cartoon,找不到才找 type,但如果找到了卻不是 Cartoon 就會報錯

而官方說如果name和type都沒有設定,就等同name,但我試的結果還是可以注入,但一定要有預設建構子

Book.java

public class Book {
    @Resource
    private Cartoon xxx;
    
    @Resource
    private Comic ooo;
    
    // setter/getter...
}



Cartoon.java 和 Comic.java

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



applicationContext.xml

<context:annotation-config />
<bean id="ca" class="vo.Cartoon" p:cartoonName="卡通" />
<bean id="co" class="vo.Comic" p:comicName="漫畫" />
<bean id="bo" class="vo.Book" />



測試類

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
System.out.println(ctx.getBeanDefinitionCount());
for (String s : ctx.getBeanDefinitionNames()) {
    System.out.println(s);
}
    
Book book1 = ctx.getBean("bo", Book.class);
Book book2 = (Book) ctx.getBean("bo");
    
System.out.println(book1.getOoo().getComicName());
System.out.println(book2.getXxx().getCartoonName());
    
((ClassPathXmlApplicationContext) ctx).close();

※按官方所說xxx和ooo都沒有啊!但就是印的出卡通和漫畫,所以個人覺得都不寫應該是 by type 才對

※也可以在Comic.java的屬性或方法上加@Value("海賊王"),也可以塞值,這時xml就不用設了,但如果兩個都設,我試的結果是xml贏了

※最近發現其實 @Resource 裡面有寫,雖然 name 是寫空,但註解有寫是抓 name 的名稱



※@PostConstruct和@PreDestroy

這兩個annotation等同於第九篇的init-method 和 destroy-method,只是現在用的是annotation而已

public class Book {
    @Resource(name = "ca")
    private Cartoon cartoon;
    
    @Autowired
    private Comic comic;

    public Book() {
    }
    
    @PostConstruct
    public void init() {
        System.out.println("init");
    }
    
    @PreDestroy
    public void destroy() {
        System.out.println("destroy");
    }
}


※可以使用很多個@PostConstruct和@PreDestroy

2015年12月29日 星期二

@Component 和 JSR330 的 Annotation (Spring3.x 十三)

參考文件

上一篇的xml還是要宣告,現在可以將它完全刪了
@Component("bo") // Book.java
@Component("ca") // Cartoon.java
@Component("co") // Comic.java

※在三支vo的class上面增加如上的annotation
這時xml只要這兩行就可以了
<context:annotation-config />
<context:component-scan base-package="\" />



轉換個場景,一般在開發時都會有controller-->Service-->ServiceImpl-->DAO可以這樣定義

※XML設定

BookAction.java

public class BookAction {
    private IBookService service;
    
    public void setService(BookServiceImpl service) {
        this.service = service;
    }
    
    public void go() {
        service.get();
    }
}



IBookService.java

public interface IBookService {
    public void get();
}



BookServiceImpl.java

public class BookServiceImpl implements IBookService {
    private BookDAOImpl dao;
    
    public void setDao(BookDAOImpl dao) {
        this.dao = dao;
    }
    
    public void get() {
        dao.getBook();
    }
}



BookDAOImpl.java

public class BookDAOImpl {
    public void getBook() {
        System.out.println("資料庫操作");
    }
}



applicationContext.xml

<bean id="bookAction" class="controller.BookAction" p:service-ref="bookServiceImpl" />
<bean id="bookServiceImpl" class="serviceImpl.BookServiceImpl" p:dao-ref="bookDAOImpl" />
<bean id="bookDAOImpl" class="dao.BookDAOImpl" />



測試類

ApplicationContext ctx2 = new ClassPathXmlApplicationContext("applicationContext.xml");
    
BookAction c = (BookAction) ctx2.getBean("bookAction");
c.go();
    
((ClassPathXmlApplicationContext) ctx2).close();

結果:
資料庫操作




※Annotation設定

BookAction.java

package controller;
    
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import service.IBookService;
import serviceImpl.BookServiceImpl;
    
@Controller
public class BookAction {
    // @Resource
    @Autowired
    private IBookService service;
    
    public void setService(BookServiceImpl service) {
        this.service = service;
    }
    
    public void go() {
        service.get();
    }
}



BookServiceImpl.java

package serviceImpl;
    
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import dao.BookDAOImpl;
import service.IBookService;
    
@Service
public class BookServiceImpl implements IBookService {
    // @Autowired
    @Resource
    private BookDAOImpl dao;
    
    public void setDao(BookDAOImpl dao) {
        this.dao = dao;
    }
    
    public void get() {
        dao.getBook();
    }
}



BookDAOImpl.java

@Repository
public class BookDAOImpl {
    public void getBook() {
        System.out.println("資料庫操作");
    }
}



applicationContext.xml

<context:annotation-config />
<context:component-scan base-package="\" />



測試類

ApplicationContext ctx1 = new AnnotationConfigApplicationContext("applicationContext.xml");
    
System.out.println(ctx1.getBeanDefinitionCount());
for (String s : ctx1.getBeanDefinitionNames()) {
    System.out.println(s);
}
    
((AnnotationConfigApplicationContext) ctx1).close();
System.out.println("----------------------------------------");
ApplicationContext ctx2 = new ClassPathXmlApplicationContext("applicationContext.xml");
    
System.out.println(ctx2.getBeanDefinitionCount());
for (String s : ctx2.getBeanDefinitionNames()) {
    System.out.println(s);
}
    
IBookService service1 = ctx2.getBean("bookServiceImpl", IBookService.class);
BookServiceImpl service2 = (BookServiceImpl) ctx2.getBean("bookServiceImpl");
service1.get();
service2.get();
    
BookAction c = (BookAction) ctx2.getBean("bookAction");
c.go();
    
((ClassPathXmlApplicationContext) ctx2).close();
System.out.println("----------------------------------------");
    
ApplicationContext ctx3 = new AnnotationConfigApplicationContext(BookAction.class, BookServiceImpl.class,
        BookDAOImpl.class);
    
System.out.println(ctx3.getBeanDefinitionCount());
for (String s : ctx3.getBeanDefinitionNames()) {
    System.out.println(s);
}
    
IBookService service3 = ctx3.getBean("bookServiceImpl", BookServiceImpl.class);
BookServiceImpl service4 = (BookServiceImpl) ctx3.getBean("bookServiceImpl");
service3.get();
service4.get();
    
BookAction controller = (BookAction) ctx3.getBean("bookAction");
controller.go();
    
((AnnotationConfigApplicationContext) ctx3).close();

※結果:
5
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
-------------------------------------------------------------------------
8
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
bookAction
bookDAOImpl
bookServiceImpl
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
資料庫操作
資料庫操作
資料庫操作
-------------------------------------------------------------------------
8
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
bookAction
bookServiceImpl
bookDAOImpl
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
資料庫操作
資料庫操作
資料庫操作

※service.java什麼註解都不用,和xml設定的一樣,註了也沒什麼效果

※@Controller、@Service、@Repository這三個annotation裡面都包括@Component,所以也可以用@Component代替,經測試過這四個annotation可以隨便使用,不過最好是按照它的名稱來使用會比較直覺
@Controller:就放在controller上
@Service:就放在service上
@Repository:就放在DAO上
而預設的id名稱就是class名稱,然後第一個字改小寫,所以上面的controller也等於@Controller("bookAction"),測試類在拿來用即可

※上面還有@Resource和@Autowired,有這兩個其中之一,setter可以不寫,會自動塞值
如果沒有這兩個annotation,service和dao會是null,所以在「.」就會NullPointException,不想用annotation的話只能new了-->private BookDAOImpl dao = new BookDAOImpl();

※getBeanDefinitionCount方法可以取得所有的bean數目,預設有5筆; getBeanDefinitionNames方法可以取得所有的bean名稱

※測試類一共有三種取bean的方式,ctx1是不行的,因為bean數目是預設的5筆

※這次的專案內容路徑



※@Scope

和之前的xml設定一樣,可以控制要不要同一個實體

BookAction.java

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Controller
public class BookAction {}



applicationContext.xml

ApplicationContext ctx2 = new ClassPathXmlApplicationContext("applicationContext.xml");
    
BookAction s1 = (BookAction) ctx2.getBean("bookAction");
System.out.println(s1);
    
BookAction s2 = (BookAction) ctx2.getBean("bookAction");
System.out.println(s2);
    
((ClassPathXmlApplicationContext) ctx2).close();




※JSR330 的 Annotation

參考資料
Spring                      javax.inject.*
@Autowired                  @Inject
@Component                  @Named
@Scope("singleton")         @Singleton
@Qualifier                  @Named
@Value                      無
@Required                   無
@Lazy                       無

※連結往上一點,就會看到有maven的jar可以複製,我覺得這張表已經很清楚了,我沒有全試,不過run起來還蠻順的,要注意@Named可以用在兩個地方



※自動裝配 Map、List


package xxx;
    
public interface IspeakService {
    void speak();
}
    
    
    
package xxx;
    
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
    
@ComponentScan("xxx")
@Component
public class Bird implements IspeakService {
    public void speak() {
        System.out.println("吱吱吱");
    }
}
    
    
    
package xxx;
    
import org.springframework.stereotype.Component;
    
@Component
public class Lion implements IspeakService {
    public void speak() {
        System.out.println("吼吼吼");
    }
}

※一個介面多個實作


package xxx;
    
import xxx.Bird;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
    
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Bird.class)
public class TestSomething {
    @Resource
    public Map<String, IspeakService> mapService;
    
    @Resource
    public List<IspeakService> listService;
    
    @Test
    public void testXxx() {
        System.out.println(mapService);
        listService.forEach(x -> System.out.println(x.getClass()));
    }
}

※直接使用 @Autowired 或 @Resource 就可以直接抓到了

※Map 的 key 值是 @Component 的小寫開頭類名

※可以利用這個特性做多型

2015年12月27日 星期日

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

上一篇的關聯表沒有主鍵,只有兩個欄位分別對應到兩張表,這裡多一個主鍵,當然也可以多一些其他的欄位,如新增的日期等…


※準備工作

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

※在關聯表加個主鍵



※XML設定

Person.java

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



Toilet.java

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



PersonToilet.java

public class PersonToilet {
    private Integer ptId;
    private Person centerPerson;
    private Toilet centerToilet;
    // setter/getter...
}



Person.hbm.xml

<class name="vo.Person" table="PERSON">
    <id name="pid" type="java.lang.Integer">
        <column name="PID" />
        <generator class="sequence">
            <param name="sequence">PERSON_SEQ</param>
        </generator>
    </id>
    
    <property name="pname" type="java.lang.String" column="PNAME" />
    
    <set name="personToilets" inverse="true" cascade="all" lazy="false">
        <key column="PERSON_ID" />
        <one-to-many class="vo.PersonToilet" />
    </set>
</class>



Toilet.hbm.xml

<class name="vo.Toilet" table="TOILET">
    <id name="tid" type="java.lang.Integer">
        <column name="TID" />
        <generator class="sequence">
            <param name="sequence">TOILET_SEQ</param>
        </generator>
    </id>
    
    <property name="tname" type="java.lang.String" column="TNAME" />
    
    <set name="personToilets" inverse="true" cascade="all" lazy="false">
        <key column="TOILET_ID" />
        <one-to-many class="vo.PersonToilet" />
    </set>
</class>



PersonToilet.hbm.xml

<class name="vo.PersonToilet" table="PERSON_TOILET">
    <id name="ptId" type="java.lang.Integer">
        <column name="PT_ID" />
        <generator class="sequence">
            <param name="sequence">PERSON_TOILET_SEQ</param>
        </generator>
    </id>
    
    <many-to-one name="centerPerson" column="PERSON_ID" lazy="false" not-null="true" cascade="all" class="vo.Person" />
    
    <many-to-one name="centerToilet" column="TOILET_ID" lazy="false" not-null="true" cascade="all" class="vo.Toilet" />
</class>



新增測試

Session s = HibernateUtil2.getSession();
Transaction tx = s.beginTransaction();
try {
    Toilet t1 = new Toilet();
    t1.setTname("便所1號");
    
    Toilet t2 = new Toilet();
    t2.setTname("便所2號");
    
    Toilet t3 = new Toilet();
    t3.setTname("便所3號");
    
    Person p1 = new Person();
    p1.setPname("掃便所英俠");
    
    Person p2 = new Person();
    p2.setPname("茅坑王");
    
    // 因為掃便所英俠可以上3間廁所,而茅坑王可以上2間廁所,所以要創5個實體
    PersonToilet pt1 = new PersonToilet();
    PersonToilet pt2 = new PersonToilet();
    PersonToilet pt3 = new PersonToilet();
    PersonToilet pt4 = new PersonToilet();
    PersonToilet pt5 = new PersonToilet();
    
    // -----設定互相關聯-----
    // 掃便所英俠(p1)可以上便所1號(t1)
    // 此時的中間表就代替了Toilet的位置,然後自己在去關聯Toilet
    p1.getPersonToilets().add(pt1);
    pt1.setCenterPerson(p1);
    pt1.setCenterToilet(t1);// 就是這時去關聯Toilet
    t1.getPersonToilets().add(pt1);
    
    // 掃便所英俠(p1)可以上廁所2號(t2)
    p1.getPersonToilets().add(pt2);
    pt2.setCenterPerson(p1);
    pt2.setCenterToilet(t2);
    t2.getPersonToilets().add(pt2);
    
    // 掃便所英俠(p1)可以上廁所3號(t3)
    p1.getPersonToilets().add(pt3);
    pt3.setCenterPerson(p1);
    pt3.setCenterToilet(t3);
    t3.getPersonToilets().add(pt3);
    
    // 茅坑王(p2)可以上廁所1號(t1)
    p2.getPersonToilets().add(pt4);
    pt4.setCenterPerson(p2);
    pt4.setCenterToilet(t1);
    t1.getPersonToilets().add(pt4);
    
    // 茅坑王(p2)可以上廁所3號(t3)
    p2.getPersonToilets().add(pt5);
    pt5.setCenterPerson(p2);
    pt5.setCenterToilet(t3);
    t3.getPersonToilets().add(pt5);
    
    s.save(pt1);
    s.save(pt2);
    s.save(pt3);
    s.save(pt4);
    s.save(pt5);
    tx.commit();
} catch (Exception e) {
    tx.rollback();
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    if (s.isOpen()) {
        s.close();
    }
}



修改Person測試

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



修改Toilet測試

Toilet toilet = (Toilet) s.get(Toilet.class, 184);// 便所1號的ID
toilet.setTname("廁所1號");
    
for (PersonToilet pt : toilet.getPersonToilets()) {
    Person p = pt.getCenterPerson();
    p.setPname("臭臭王");
}
    
s.update(toilet);
tx.commit();



查詢測試

// 查詢掃便所英俠能用哪些廁所
Person p = (Person) s.get(Person.class, 114);// 掃便所英俠的ID
s.close();
System.out.println(p.getPname());
    
// 必需在Person.hbm.xml和PersonToilet.hbm.xml的centerToilet設定lazy="false"
for (PersonToilet pt : p.getPersonToilets()) {
    System.out.println(pt.getCenterToilet().getTname());
}
    
// 查詢便所1號能被哪些人使用
Toilet t = (Toilet) s.get(Toilet.class, 184);// 便所1號的ID
s.close();
System.out.println(t.getTname());
    
// 必需在Toilet.hbm.xml和PersonToilet.hbm.xml的centerPerson設定lazy="false"
for (PersonToilet pt : t.getPersonToilets()) {
    System.out.println(pt.getCenterPerson().getPname());
}



刪除測試

Person person = (Person) s.get(Person.class, 114);
s.delete(person);
tx.commit();
    
Toilet toilet = (Toilet) s.get(Toilet.class, 185);
s.delete(toilet);
tx.commit();

※刪除很奇怪,我把三張表的4個cascade拿掉或設none才會成功,中間表也會刪



※結論:

關聯表有沒有主鍵最大的差異就是要不要將關聯表建立出來,但如果像現在這個例子只有新增主鍵的話,可以不用建,但因為oracle的主鍵生成方式和其他資料庫不一樣,所以只好建出來,其他資料庫有像increment的語法可用


※Annotation設定

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



Toilet.java
@Entity
@Table
public class Toilet {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "XXX")
    @SequenceGenerator(name = "XXX", sequenceName = "TOILET_SEQ")
    private Integer tid;
    
    private String tname;
    
    @OneToMany(mappedBy = "centerToilet", cascade = CascadeType.ALL)
    @LazyCollection(value = LazyCollectionOption.FALSE)
    private Set<PersonToilet> personToilets = new HashSet<>();
    
    // setter/getter...
}



PersonToilet.java
@Entity
@Table(name = "PERSON_TOILET")
public class PersonToilet {
    @Id
    @Column(name = "PT_ID")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "XXX")
    @SequenceGenerator(name = "XXX", sequenceName = "PERSON_TOILET_SEQ")
    private Integer ptId;
    
    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "PERSON_ID", unique = true)
    private Person centerPerson;
    
    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "TOILET_ID", unique = true)
    private Toilet centerToilet;
    
    // setter/getter...
}

※查詢時,中間表不需要@LazyToOne,只要在兩張表加@LazyCollection就可以查得到了

※刪除時,在資料庫有查到資料會將三張表全刪了,所以我將4個cascade拿掉才能刪,中間表也會刪,而CascadeType.REMOVE不等於整個拿掉,會出錯

2015年12月26日 星期六

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

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

※準備工作

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



※XML設定

Person.java

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

Toilet.java

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



Person.hbm.xml

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



Toilet.hbm.xml

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

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


新增測試

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

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

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

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



修改Person測試

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


修改Toilet測試

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



查詢Person測試

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



查詢Toilet測試

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



刪除Person測試

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

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

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


刪除Toilet測試

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

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

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






※Annotation設定


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



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

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

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






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

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

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

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



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



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

2015年12月24日 星期四

util: (Spring3.x 十)

針對這篇內部 bean 和 Collection (Spring3.x 七),官方還是一種util的寫法,必需在namespace頁籤勾util才能用,和之前的p:和c:同一個頁籤
<bean id="book" class="book.vo.Book">
    <property name="comicNames">
        <util:set>
            <value>七龍珠</value>
            <value>滾球王</value>
            <value>通靈王</value>
            <value>滾球王</value>
        </util:set>
    </property>
    
    <property name="prices">
        <util:list>
            <value type="java.lang.Integer">65</value>
            <value type="java.lang.Integer">70</value>
            <value type="int">75</value>
            <value type="int">70</value>
        </util:list>
    </property>
    
    <property name="comicNameAndPrice">
        <util:map>
            <entry key="七龍珠" value="65" />
            <entry key="滾球王" value="70" />
            <entry>
                <key>
                    <value>通靈王</value>
                </key>
                <value>75</value>
            </entry>
        </util:map>
    </property>
    
    <property name="prop">
        <util:properties>
            <prop key="aaa">111</prop>
            <prop key="bbb">222</prop>
            <prop key="ccc">333</prop>
        </util:properties>
    </property>
</bean>

※測試的結果還是一樣

※util:properties還可以連結外面的檔案,如下:

xxx.ooo

apple=\u860B\u679C
banana=\u9999\u8549

applicationContext.xml

<property name="prop">
    <util:properties location="classpath:properties/xxx.ooo" />
</property>

※properties是我的package名稱,\u開頭的可用native2ascii.exe,在安裝java目錄的bin下,或者在Eclipse增加Spring tools做完,直接打字也可以幫我們轉換

※結果:
{banana=香蕉, apple=蘋果}


※其他屬性

list-class

以List為例,<util:list id="emails" list-class="java.util.LinkedList">list-class就是多型,以java的寫法就是List emails = new LinkedList();
而Map的屬性叫 map-class;Set的叫set-class

整個util不只能寫在bean裡,也可和bean同層,取個id後,然後用@Resource(name="id")就可以將值放進去

value-type

value-type屬性類似泛型

塞物件


<util:list id="xxx" value-type="java.lang.String">
    <value>ooo</value>
    <ref bean="cb" />
    <ref bean="bo" />
    <ref bean="ch" />
</util:list>
    
<bean id="bo" class="vo.Book" p:emails-ref="xxx" />
<bean id="cb" class="vo.ComicBook" />
<bean id="ch" class="vo.Chess" />



※util:constant

參考文件
看到constant就知道是和static有關

Book.java

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

XXX.java

public class XXX {
    public static final String OOO = "XOXOX";
}

applicationContext.xml

<bean id="book" class="book.vo.Book">
    <!-- 方法一 -->
    <property name="bookStaFin">
        <bean id="book.vo.XXX.OOO" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
    </property>
    
    <!-- 方法二 -->
    <!-- <property name="bookStaFin"> -->
    <!--     <util:constant static-field="book.vo.XXX.OOO" /> -->
    <!-- </property> -->
    
    <!-- 方法三 -->
    <!-- <property name="bookStaFin" ref="ooo" /> -->
</bean>
    
<!-- 方法三 -->
<!-- <util:constant static-field="book.vo.XXX.OOO" id="ooo" /> -->

測試類
ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    
Book book = (Book) appContext.getBean("book");
System.out.println(book.getBookStaFin());
    
((ClassPathXmlApplicationContext) appContext).close();

※方法一是還沒有util的時候用的,class一定要是FieldRetrievingFactoryBean

※方法二和方法三(要打開兩個)就是一個寫裡面一個寫外面而已

※book.vo是package名稱,XXX是Class名稱,OOO是filed名稱




※util:property-path

參考文件
一個bean已經有實體了,所以可以取得裡面的方法,而java就直接「.」就可以了,在XML就是現在要介紹的方法,總之就是可以讓我們用id或name一直點下去取值

Book.java

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

Comic.java

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

applicationContext.xml

<bean id="book" class="book.vo.Book">
    <property name="bookName" value="資治通鑑"/>
    <property name="comic">
        <bean class="book.vo.Comic" p:comicName="七龍珠" />
    </property>
</bean>
    
<!-- 方法一 -->
<bean id="book.bookName" class="org.springframework.beans.factory.config.PropertyPathFactoryBean" />
<bean id="book.comic" class="org.springframework.beans.factory.config.PropertyPathFactoryBean" />
    
<!-- 方法二 -->
<util:property-path id="xxx" path="book.bookName"/>
<util:property-path id="ooo" path="book.comic"/>

測試類

ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    
// 方法一
System.out.println(appContext.getBean("book.bookName"));
Comic comic1 = (Comic) appContext.getBean("book.comic");
System.out.println(comic1.getComicName());
    
// 方法二
System.out.println(appContext.getBean("xxx"));
Comic comic2 = (Comic) appContext.getBean("ooo");
System.out.println(comic2.getComicName());
    
((ClassPathXmlApplicationContext) appContext).close();

※結果:
資治通鑑
七龍珠
資治通鑑
七龍珠

※方法一是還沒有util的時候用的,class一定要是PropertyPathFactoryBean

※方法一的缺點是id不能自訂,但方法二的id就可以自訂了

autowire (Spring3.x 十一)

參考文件


Book.java

public class Book {
    private Comic comic;
    
    public Book() {}
    
    public Book(Comic comic) {
        this.comic = comic;
    }
    
    // setter/getter...
}

Comic.java

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

applicationContext.xml

<!-- <bean name="ooo" class="book.vo.Comic" p:comicName="七龍珠" /> -->
<!-- <bean name="book1" class="book.vo.Book" autowire="byType" /> -->
    
<bean name="comic" class="book.vo.Comic" p:comicName="滾球王" />
<bean id="book2" class="book.vo.Book" autowire="byName" />
    
<bean name="xxx" class="book.vo.Comic" p:comicName="通靈王" />
<bean id="book3" class="book.vo.Book" autowire="constructor" />

測試類

ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    
Book book = (Book) appContext.getBean("book2");
if (book.getComic() != null) {
    System.out.println(book.getComic().getComicName());
}
    
((ClassPathXmlApplicationContext) appContext).close();

※我試的結果byType和另外其中一種混用,就會出「org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [book.vo.Comic] is defined: expected single matching bean but found 2: ooo,comic」的錯
因為byType是根據型態,只要是class裡的field有的型態都會去抓,沒抓到不會報錯,只是null而已,但抓到2個以上就會報錯,因為它不知道要抓哪個

※byName是根據名稱,只要是class裡的名稱和xml的name、id、alias一樣就會去抓,沒抓到不會報錯,只是null而已
不用擔心name和id名稱都一樣的問題,run起來console就會說不能重覆了

※constructor是根據建構子的型態,一定要剛好是一個,沒有或超過都會報錯
我試的結果不管byName寫在constructor上面或下面,都會被byName覆蓋,

工廠方法、lazy、初始、結束、繼承、depends-on、scope (Spring3.x 九)

※工廠方法、lazy、初始、結束

Book.java

public class Book {
    public static String getBookName() {
        String s = "七龍珠";
        System.out.println(s);
        return s;
    }
    
    public void ooo() {
        System.out.println("初始");
    }
    
    public void xxx() {
        System.out.println("結束");
    }
}

applicationContext.xml

<bean id="book1" class="book.vo.Book" init-method="ooo" destroy-method="xxx" />
<bean id="book2" class="book.vo.Book" factory-method="getBookName" lazy-init="true" />
    
<bean id="ca" class="java.util.Calendar" factory-method="getInstance" />
<bean id="result" factory-bean="ca" factory-method="getTime" />

測試類

ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    
Date date = (Date) appContext.getBean("result");
System.out.println(date);
    
((ClassPathXmlApplicationContext) appContext).close();

結果:
初始
Thu Dec 24 15:32:06 CST 2015
結束

※如果將book2的lazy拿掉,就會印出七龍珠了,或者呼叫book2才會去抓

※book2、ca、result可以用如下的想法比較容易了解
// book2
Book.getBookName();
    
// ca
Date date = Calendar.getInstance().getTime();
    
//result
date.getDate();


※getBookName()必需提供回傳值,否則會出「needs to have a non-void return type!」的錯

※scope 使用 prototype 時,在getBean 時才會去抓值;而 scope 使用預設的 singleton 在 new ClassPathXMLApplicationContext 時就會去抓,如果想和 prototype 一樣,可以用lazy="true"

※ init-method 裡的方法至少要 private void xxx(),不能有參數,如果沒有此方法會報錯
destroy-method 一定要將 ApplicationContext close 才會呼叫,但要將ApplicationContext 強轉成有 close 方法的子類,而且也必須是 singleton,annotation 用的是 @Bean

※如果有很多 class 都想用 init-method 或 destroy-method,每一個都要設很麻煩,所以 Spring 有提供設定,在 beans 裡用 default-init-method、default-destroy-init-method,在設定完 bean class 時才會呼叫,而且沒有寫方法,也不會報錯


※FactoryBean 介面

public class Book {
    private String bookName;
    private int bookMoney;
    
    public Book() {}
    
    public Book(int bookMoney, String bookName) {
        this.bookMoney = bookMoney;
        this.bookName = bookName;
    }
    
    //setter/getter...
}
------------------------------
public class BookFactoryBean implements FactoryBean<Book> {
    @Override
    public Book getObject() throws Exception {
        return new Book(90, "寶島少年");
    }
    
    @Override
    public Class<Book> getObjectType() {
        return Book.class;
    }
    
    @Override
    public boolean isSingleton() {
        return true;
    }
}




<bean id="b" class="xxx.ooo.Book" p:bookName="七龍珠" p:bookMoney="65" />
<bean id="bfb" class="xxx.ooo.BookFactoryBean" />

※getBean("b") 時是之前的做法,使用 getBean("bfb") 就能看見效果了


※繼承

<bean id="book1" class="book.vo.Book" p:name="資治通鑑" p:money="2000" p:color="紅" p:size="A4" />
<bean id="comic1" class="book.vo.Comic" p:name="遊戲王" p:money="65" p:color="紅" p:size="A4" />
    
<bean id="book2" class="book.vo.Book" p:name="資治通鑑" p:money="2000" p:color="紅" p:size="A4" abstract="true" />
<bean id="comic2" class="book.vo.Comic" p:name="遊戲王" p:money="65" parent="book2" />

※book1和comic1有兩個屬性一樣,這時可以透過繼承的方法,變成book2和comic2,這時只要在測試類,呼叫comic2的color或size即可取得父類的值,當然重點是abstract和parent




※depends-on


<bean name="dao" class="xxx.Dao" depends-on="database" />
<bean id="database" class="ooo.xxx.Database" />

※dao在連資料庫時,必需取得database,設定depends-on可以保證database先實例化好再實例化dao




※scope


<bean id="book1" class="book.vo.Book" scope="singleton" />
<bean id="book2" class="book.vo.Book" scope="prototype" />

ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    
System.out.println(appContext.getBean("book1"));
System.out.println(appContext.getBean("book1"));
System.out.println(appContext.getBean("book2"));
System.out.println(appContext.getBean("book2"));
    
((ClassPathXmlApplicationContext) appContext).close();

※結果
book.vo.Book@6df97b55
book.vo.Book@6df97b55
book.vo.Book@3cbbc1e0
book.vo.Book@35fb3008

※scope="singleton"為預設

※可以看出結果一、二行一模一樣,因為它是singleton

※只有用getBean才會有single的效果哦,如果用new或者反射的invoke就不會一模一樣了

※還有三種request、session、global session,都是用在web的

2015年12月23日 星期三

Compound property names 和 空與null (Spring3.x 八)

※Compound property names 

不知道怎麼翻譯會比較傳神,反正就是多層賦值的意思,看範例


三個VO

public class Book {
    private Comic ooo;
    // setter/getter...
}
    
public class Comic {
    private Cartoon xxx;
    // setter/getter...
}
    
public class Cartoon {
    private String oooxxx;
    // setter/getter...
}

applicationContext.xml

<bean id="book1" class="book.vo.Book">
    <property name="ooo">
        <bean class="book.vo.Comic">
            <property name="xxx">
                <bean class="book.vo.Cartoon">
                    <property name="oooxxx" value="海綿寶寶" />
                </bean>
            </property>
        </bean>
    </property>
</bean>
    
<bean id="co" class="book.vo.Comic" />
<bean id="ca" class="book.vo.Cartoon" />
<bean id="book2" class="book.vo.Book">
    <property name="ooo" ref="co" />
    <property name="ooo.xxx" ref="ca" />
    <property name="ooo.xxx.oooxxx" value="喜洋洋與灰太郎" />
</bean>
    
<bean id="book3" class="book.vo.Book">
    <property name="ooo">
        <bean class="book.vo.Comic" />
    </property>
    
    <property name="ooo.xxx">
        <bean class="book.vo.Cartoon" />
    </property>
    
    <property name="ooo.xxx.oooxxx" value="灰太郎追喜洋寶寶" />
</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.getOoo().getXxx().getOooxxx());
System.out.println(book2.getOoo().getXxx().getOooxxx());
System.out.println(book3.getOoo().getXxx().getOooxxx());
    
((ClassPathXmlApplicationContext) appContext).close();

※結果:
海綿寶寶
喜洋洋與灰太郎
灰太郎追喜洋寶寶

※book1為之前介紹的內部bean; book2和book3就是多層賦值了

※多層賦值要保證每一層都不是null,所以才會多宣告co和ca,如果不想宣告就用book3的方法




※空與null


Book.java

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

applicationContext.xml

<bean id="book1" class="book.vo.Book" />
    
<bean id="book2" class="book.vo.Book">
    <property name="bookName">
        <null />
    </property>
</bean>
    
<bean id="book3" class="book.vo.Book">
    <property name="bookName" value="" />
</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.getBookName());
System.out.println(book2.getBookName());
System.out.println(book3.getBookName().length());
    
((ClassPathXmlApplicationContext) appContext).close();

※結果:
null
null
0

※宣告property一定要給value或ref,不然會報錯,但其實還可以用null標籤

內部 bean 和 Collection (Spring3.x 七)

※內部 bean

參考文件

Comic.java

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

Book.java

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

applicationContext.xml

<bean id="co" class="book.vo.Comic" p:comicName="七龍珠"/>
<bean id="book1" class="book.vo.Book">
    <property name="comic" ref="co" />
</bean>
    
<bean id="book2" class="book.vo.Book">
    <property name="comic">
        <bean class="book.vo.Comic">
            <property name="comicName" value="通靈王" />
        </bean>
    </property>
</bean>

測試類

ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    
Book book1 = (Book) appContext.getBean("book1");
Book book2 = (Book) appContext.getBean("book2");
    
System.out.println(book1.getComic().getComicName());
System.out.println(book2.getComic().getComicName());
    
((ClassPathXmlApplicationContext) appContext).close();

※結果:
七龍珠
通靈王

※book1為一般的參考寫法; book2就是內部的bean寫法



※Collection

參考文件


※放基本型別

Book.java

public class Book {
    private Set<String> comicNames;
    private List<Integer> prices;
    private Map<String, Integer> comicNameAndPrice;
    private Properties prop;
    // setter/getter...
}

※可以不用new實體,不過聽說舊版本要new實體

applicationContext.xml

<bean id="book" class="book.vo.Book">
    <property name="comicNames">
        <set>
            <value>七龍珠</value>
            <value>滾球王</value>
            <value>通靈王</value>
            <value>滾球王</value>
        </set>
    </property>
    
    <property name="prices">
        <list>
            <value type="java.lang.Integer">65</value>
            <value type="java.lang.Integer">70</value>
            <value type="int">75</value>
            <value type="int">70</value>
        </list>
    </property>
    
    <property name="comicNameAndPrice">
        <map>
            <entry key="七龍珠" value="65" />
            <entry key="滾球王" value="70" />
            <entry>
                <key><value>通靈王</value></key>
                <value>75</value>
            </entry>
        </map>
    </property>
    
    <property name="prop">
        <props>
            <prop key="aaa">111</prop>
            <prop key="bbb">222</prop>
            <prop key="ccc">333</prop>
        </props>
    </property>
</bean>

測試類

ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    
Book book = (Book) appContext.getBean("book");
System.out.println(book.getComicNames());
System.out.println(book.getPrices());
System.out.println(book.getComicNameAndPrice());
System.out.println(book.getProp());
    
((ClassPathXmlApplicationContext) appContext).close();

※結果:
[七龍珠, 滾球王, 通靈王]
[65, 70, 75, 70]
{七龍珠=65, 滾球王=70, 通靈王=75}
{bbb=222, aaa=111, ccc=333}

※可以看出Set真的會把相同的值去掉,而map有兩種寫法

※map可以和Properties互換,就是xml是<prop>,卻用map接; 相反也是一樣,只要注意Properties只能兩邊都是String,而Map用<String, String>即可



※放物件

Comic.java

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

Book.java

public class Book {
    private Set<Comic> comicNames;
    private List<Comic> prices;
    private Map<Comic, Integer> comicNameAndPrice;
    // setter/getter...
}

applicationContext.xml

<bean id="xxx1" class="book.vo.Comic" p:comicName="七龍珠" />
<bean id="xxx2" class="book.vo.Comic" p:comicName="通靈王" />
<bean id="xxx3" class="book.vo.Comic" p:comicName="滾球王" />
    
<bean id="book" class="book.vo.Book">
    <property name="comicNames">
        <set>
            <ref local="xxx1" />
            <ref local="xxx2" />
            <ref local="xxx3" />
            <ref local="xxx2" />
        </set>
    </property>
    
    <property name="prices">
        <list>
            <ref local="xxx1" />
            <ref local="xxx2" />
            <ref local="xxx3" />
            <ref local="xxx2" />
        </list>
    </property>
    
    <property name="comicNameAndPrice">
        <map>
            <entry key-ref="xxx1" value="65" />
            <entry key-ref="xxx2" value="70" />
            <entry key-ref="xxx3" value="75" />
        </map>
    </property>
</bean>

測試類

ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    
Book book = (Book) appContext.getBean("book");
    
Iterator<Comic> itSet = book.getComicNames().iterator();
while (itSet.hasNext()) {
    System.out.println(itSet.next().getComicName());
}
System.out.println();
    
Iterator<Comic> itList = book.getPrices().iterator();
while (itList.hasNext()) {
    System.out.println(itList.next().getComicName());
}
System.out.println();
    
Map<Comic, Integer> map = book.getComicNameAndPrice();
Iterator<Comic> itMap = map.keySet().iterator();
while (itMap.hasNext()) {
    System.out.println(itMap.next().getComicName());
}
System.out.println(map.values());
    
((ClassPathXmlApplicationContext) appContext).close();

※結果:
七龍珠
通靈王
滾球王

七龍珠
通靈王
滾球王
通靈王

七龍珠
通靈王
滾球王
[65, 70, 75]

※Properties就沒有關聯到物件的ref屬性了



※Collection的merge

參考文件

Book.java

public class Book {
    private List<Integer> prices;
    // setter/getter...
}

applicationContext.xml

<bean id="bookPapa" class="book.vo.Book">
    <property name="prices">
        <list>
            <value type="java.lang.Integer">65</value>
            <value type="java.lang.Integer">70</value>
        </list>
    </property>
</bean>
    
<bean id="bookSon" class="book.vo.Book" parent="bookPapa">
    <property name="prices">
        <list merge="true">
            <value type="int">75</value>
            <value type="int">80</value>
        </list>
    </property>
</bean>

測試類

ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    
Book bookPapa = (Book) appContext.getBean("bookPapa");
Book bookSon = (Book) appContext.getBean("bookSon");
    
System.out.println(bookPapa.getPrices());
System.out.println(bookSon.getPrices());
    
((ClassPathXmlApplicationContext) appContext).close();

※結果:
[65, 70]
[65, 70, 75, 80]

※bookSon增加屬性parent,還有list有個屬性merge

※set、list、map、prop都有merge