2011年3月10日 星期四

[初探JavaEE6] 使用Eclipse與GlassFish開發JEE6應用程式 (Part I -- JPA & EJB)

JavaEE 6標準已經完成一陣子了,現在JavaEE 7也開始起跑,只是這幾年來似乎JavaEE 6標準的提升似乎並沒有吸引了太多台灣開發人員的注目,畢竟之前J2EE 1.3、J2EE 1.4至JavaEE 5改版對於多數人的開發來說,差異性沒有太大 (EJB導入的比例太小也是其中原因之一),大部分的開發人員都傾向擁抱比較light-weight的開發framework與平台,導致Spring + Hibernate現在仍舊是主流技術。

為什麼我會突然開始關心起JavaEE 6呢?

實在是因為這次JavaEE 6所包裝的一些技術與標準讓我覺得 -- 也許JavaEE 6會是讓開發人員開始考慮回歸JavaEE 6標準主流的一個轉機。幾個吸引我的主題包括:模組化設計(Web Profile)的設計、CDI的引進、大量使用annotation開發、廢除EntityBean使用JPA以及JAX-RS Restful Service的標準。

我的想法是,試著開發出一個簡單的應用場景,看看使用JavaEE 6對開發人員是否有一些吸引力。我將場景設定為:使用EJB開發一個Business Object,其中應用JPA將資料庫中兩個table的一對多資料抓出;同時,為此EJB提供對外兩種介面:HttpServlet網頁的顯示與Restful Service應用程式查詢介面。

開發環境準備


這邊我所使用的開發工具與伺服器是Eclipse + GlassFish,如果你想要省事的話,可以到Oracle Enterprise Pack for Eclipse的網站下載最新版本的開發工具,其中已經將Eclipse與GlassFish Tools plug-in安裝預設安裝好。然後在這邊下載GlassFish 3.1版。
註:這篇不是Eclipse教學,我不會說明怎麼安裝Eclipse、設定Server...等,網路上已經有夠多的文件,有需要的人可以拜一下孤狗大神
另外,我選用Oracle資料庫做為資料儲存的地方,你可以用下列DDL產生兩個所開發需要的table:

CREATE TABLE MASTER (
MASTER_ID NUMBER(22 , 0) NOT NULL,
COL1 VARCHAR2(30),
COL2 VARCHAR2(30));
ALTER TABLE MASTER ADD CONSTRAINT PK_MASTER PRIMARY KEY (MASTER_ID);



CREATE TABLE DETAIL (
DETAIL_ID NUMBER(22 , 0) NOT NULL,
MASTER_ID NUMBER(22 , 0),
COL1 VARCHAR2(30));
ALTER TABLE DETAIL ADD CONSTRAINT PK_DETAIL PRIMARY KEY (DETAIL_ID);
ALTER TABLE DETAIL ADD CONSTRAINT FK_MASTER FOREIGN KEY (MASTER_ID) REFERENCES MASTER (MASTER_ID);




Part I -- JPA開發

產生一個Dynamic Web Project -- BlogJEE6。

我們從底層部分開始做起,首先開啟Project Properties,在Project Facet中勾選JPA。


在Project上右鍵 --> JPA Tools --> Generate Entities from Tables。會有個對話框讓你指定DB connection、Schema以及Tables。下圖我指定了TimDB的Oracle DB連線,其中我們會用到的就是環境準備時使用DDL所產生的MASTERDETAIL兩個table。


下一步,由於在兩個table之間有foreign key的設定,Eclipse自動幫我們抓出了中間的關係:


下一步,指定Entity的一些特性,特別需要注意的是,根據使用情境,我希望抓取Master時一次將Detail的資料也一併取出,因此Associations fetch指定為Eager;反之,可以指定為Lazy。


下一步,分別為你的兩個Entity個別設定,這裡因為沒有指定使用Oracle DB的SEQUENCE做為PK的值,我的Key generator使用的是 "none"。


完成之後,你會發現有兩個class自動被產生 -- MasterDetail,偷看一下內容吧:


@Entity
public class Master implements Serializable {
  private static final long serialVersionUID = 1L;


  @Id
  @Column(name="MASTER_ID")
  private long masterId;


  private String col1;


  private String col2;


  //bi-directional many-to-one association to Detail
  @OneToMany(mappedBy="master", fetch=FetchType.EAGER)
  private List<Detail> details;


  public Master() {
  }
...



@Entity
public class Detail implements Serializable {
  private static final long serialVersionUID = 1L;


  @Id
  @Column(name="DETAIL_ID")
  private long detailId;


  private String col1;


  //bi-directional many-to-one association to Master
  @ManyToOne
  @JoinColumn(name="MASTER_ID")
  private Master master;


  public Detail() {
  }
...


有看到許多的annotation嗎? JPA就是使用這些annotation來指定OR-mapping的方式,不需要額外的XML configuration檔案設計對應。首先,JPA的Entity class定義上,需要先指定@Entity代表此class需做OR-mapping,在此class中的property只要不是定義為transient的,都會被對應至table的欄位之中,其對應方式用@Column (name="<COLUMN_NAME>")。眼尖的你如果發現上面產生出來的code中,有些properties沒有被定義@Column,通常代表這些property的名稱就是和table中欄位名稱一致。

@Id被用來定義為Primary Key欄位,在我們的例子中,Primary Key是我們自己產生,所以並沒有指定產生的方式,若是有需要用DB內建的SEQUENCE或是自動欄位產生該值的話,可以使用@GeneratedValue(strategy=GenerationType.AUTO)(其實GenerationTypeSEQUENCETABLEIDENTITY可選,你需要根據你所使用的資料庫與Table做相對應的設計)

在一對多與多對一的情境則使用@OneToMany@ManyToOne的方式來指定。當你不是額外使用一個table紀錄master與detail table之間的關係時,@OneToMany@ManyToOne的設定就需要互相呼應,先看在@ManyToOne這邊,需要@JoinColumn(name="MASTER_ID")指定在Table foreign key所設定的欄位名稱為MASTER_ID;而另外在@OneToMany的設定上,需要對應mappedBy="master",用來指定在Detail class中指向Master class的property名稱。


接下來,只剩下一個資訊要指定了,我們需要告訴GlassFish這個DB的位置在哪裡,一般來說大家都會利用container本身的data source與connection pool來管理資料庫連線,因此我們只需要指定這個OR-Mapping是使用到哪一個data source即可。


打開Project內的JPA Content --> persistence.xml (沒錯,少數我們還需要關心的XML檔案,其餘大部分都給annotation取代了),在connection tab下指定JTA data source的名稱。



記得你需要在GlassFish中設定好對應的data source與connection pool:




到此為止,你已經完成了database OR-Mapping的部分了,接下來,需要個business interface 讓前端的Servlet或Restful WS呼叫執行資料的產生或查詢,這裡我們將開發一個Stateless Session Bean做為此Business Object。


在Project中新建一個名稱為InfoBean的EJB 3的Session Bean:




自動產生的程式碼如下:


package blogjee6;

import javax.ejb.LocalBean;
import javax.ejb.Stateless;


/**
 * Session Bean implementation class InfoBean
 */
@Stateless
@LocalBean
public class MasterFacade{

  /**
   * Default constructor. 
   */
  public InfoBean() {
    // TODO Auto-generated constructor stub
  }

}




從EJB3開始,我只要用@Stateless去指定該POJO為一EJB,夠簡單吧?


在EJB中使用JPA做簡單的查詢,我們只需要加入下列的code在class之中:



  @PersistenceContext EntityManager em;


  public Master queryMaster(long id) {
    return em.find(Master.class, id);
  }



特別注意的是,這裡使用了Dependency Injection的方式,用@PersistenceContext取得EntityManager物件,你可以試著使用em.persist()em.merge()em.remove()執行新增刪除與修改的動作。


到這裡你已經完成了傳統在business object、data access object所該做的事情了,比比看以前的EntityBean與SessionBean,是不是少了很多東西? EntityBean、value object、home interface... 等等討厭的東西看來都不見囉~


下一篇我們再來看看前端的HttpServlet與Restful Service該怎麼做吧!



沒有留言: