1. 程式人生 > >一個完整的EJB Web應用(JPA示例)

一個完整的EJB Web應用(JPA示例)

Java EE5到底有什麼系列 – Java Persistence API 1.0( EJB3 Entity Bean)
作者:黃海波(Charles Huang)

Java EE5作為新一代Java企業開發平臺的規範,從開始設計就引來了整個java開發社群的注目,引起無數的辯論和帶來了眾多的期盼。Java EE5作為J2EE平臺誕生幾近6年後的第4代規範重點關注的是目前java開發的幾個熱點:開發效率,執行效率和企業應用整合。目標也是讓J2EE開發簡單,簡單再簡單。那我們就看看J2EE5規範到底有什麼,是否真的能給開發者/企業帶來真正的實惠?

Java EE5規範是一個所謂的雨傘規範(Umbrella),在其下是一系列的子規範,主要包括:

EJB 3.0 (JSR 220)
Java Persistence API 1.0 (JSR 220)
JSP 2.1 (JSR 245)
JSF 1.2 (JSR 252)
JAX-WS 2.0 (JSR 224)
StAX 1.0 (JSR 173)
JAXB 2.0 (JSR 222)
Web Services Annotations 1.0 (JSR 181)
Common Annotations 1.0 (JSR 250)
SAAJ 1.3 maintenance
JTA 1.1 maintenance
JavaMail 1.4 & JAF 1.1 maintenance
JSTL 1.2 maintenance
Java EE Mgmt maintenance
JACC maintenance
Servlet maintenance
Java EE Deployment maintenance
WSEE maintenance


Java Persistence API 1.0( EJB3 Entity Bean) 在Java EE5中, Entity Bean做為EJB規範中負責持久化的元件將逐漸成為一個歷史名詞了,作為J2EE 4規範中最為人所垢病的Entity Bean在Java EE5中被推到重來,取而代之的是java開發的通用持久化規範Java Persistence API 1.0, 其實就是完全重新定義了的Entity Bean規範(目前在很多場合中,由於歷史原因我們仍然使用ejb3持久化來稱呼這個規範)。JPA作為java中負責關係資料持久化的元件已經完全獨立出來成為一個單獨的規範,而不再屬於Enterprise Java Bean的範疇(EJB更多的是指Stateless/Stateful session bean和Message Driven Bean)。

Java Persistence AP(JPA)可以說是java持久化技術的一個集大成者,它吸取了Hiberante,JDO,TopLink等優秀技術和框架,將這幾年發展成熟起來的基於POJO模型的O/R Mapping技術標準化,成為在J2EE和J2SE環境中通用的java持久化API。值得注意的是Java Persistence API並不是J2EE環境專用,而是在java中的通用API。意味著我們可以在任何需要訪問關係資料庫的地方使用JPA,甚至包括swing開發的桌面應用。JPA也不要求一定在J2EE容器中才能執行,而是任何有JVM的環境都可以運用。 這就使得我們可以很容易的把JPA作為一個持久化元件自由的和各種容器/框架(EJB3容器, Spring等等)組合。

JPA如何簡化原來EJB2中Entity Bean的開發,看一個簡單對比:
EJB2.0 EJB3.0(JPA)
Business Interface
public inerface HelloWold extends EJBLocalObject{
    Public String getResult();
}
						
無需定義介面
對映配置檔案 編寫EJB3 Deployment descriptor 可選
EJB實現
public class HelloWorldEntityBean 
         implements HelloWold, EntityBean{
    private int id;
    private String result;
    private EntityContext txt;
   
    public HelloWorldEntityBean(){}
    public void setEntityContext( EntityContext text ){ 
        txt = text;
    }
   
    public String getResult(){ 
        Return result; 
    }
        
    public int getId(){
        return id; 
    }
    
    public void setResult( String result ){ 
        this.result = result;
    }
    
    public String cretaeByName( String name ) throws EJBException{
        .....
    }
   
 }						
						
@Entity
@Table(name=”hellotable”)
public class HelloWoldEntity{   
    @Id
    private int id; p
    private String result; 
    
    public HelloWoldEntity(){} 
    
    public String getResult(){ 
        return result;
    }
    
    public int getId(){
        return id;
    }  
    
    public void setResult( String result ){ 
        this.result = result;
    }
}						
						

在JPA 中,ejb3的Entity Bean就是一個簡單的java bean,即POJO( Plain Old Java Object)。不象EJB2中的EntityBean需要跟容器有密切的關聯(EJB2中必須有EntityContext),EJB3 中的entityBean和容器無關,事實上在JPA中,EntityBean也不再稱為EntityBean,而是Entity,和Session Bean/Message Driven Bean的仍然存在的EJB區別開來。

為了簡化O/R Mapping的配置,JPA大量採用JDK1.5的最重要的新特性annotaion直接在java程式碼中進行配置的標註。 採用annotation標註O/R Mapping配置可以大幅度減少以往使用xml配置O/R Mapping工作量,提高效率和可維護性。

下面是一個最簡單的一對一關聯關係採用annotation和xml的配置比較。
Java Persistence API(EJB3 Persistence) Hiberante
配置檔案 可選 需要
One-To-One配置 可選
<one-to-one        
    name="address"        
    class="com.foo.Address"        
    cascade="All"        
    lazy="false"/>
Java程式碼
public class Order{     
	@OneToOne(cascade=CascadeType.ALL,fetch=FetchType.LAZYL)    
	Address address;
    ......	
}
						
public class Order{    
    Address address;
    ......   
}
採用annotation的優勢在於:
  • 減少了配置檔案的數量,特別是在實體(Entity)比較多的系統中,維護大量的O/R Mapping xml配置檔案是不少的工作量。
  • 減少了配置需要標註的內容。由於annotation由java compiler來編譯解析,很多需要在xml配置中顯式宣告的內容不再需要(比如變數名稱,型別,集合中的物件型別等)。
  • Annotation的編譯期檢查可以避免xml檔案中容易出現的配置語法錯誤,在IDE中及時發現和糾正。
  • 無需在xml配置檔案和java程式碼中切換,較少思維的跳躍,提高了開發效率。
  • annotation被編譯到java bytecode中,省略了xml的解析過程,極大的提升應用的啟動速度和記憶體佔用(特別是Entity多的情況)。
JPA在啟動上做了很多程度的簡化,使我們能夠很容易地在容器內(container)和J2SE環境中使用JPA。JPA擁有一個最基本的工廠類EntityManagerFactory。通過呼叫這個工廠類的createEntityManager()方法獲得EntityManager。所有對實體(Entity)的操作包括持久化,查詢,刪除等等操作都都定義EntityManager上。
public interface EntityManager {
    public void persist(Object entity);
    public  T merge(T entity);
    public void remove(Object entity);
    public  T find(Class entityClass, Object primaryKey);
    public  T getReference(Class entityClass, Object primaryKey);
    public void flush();
    public void setFlushMode(FlushModeType flushMode);
    public FlushModeType getFlushMode();
    public void lock(Object entity, LockModeType lockMode);
    public void refresh(Object entity);
    public void clear();
    public boolean contains(Object entity);
    public Query createQuery(String ejbqlString);
    public Query createNamedQuery(String name);
    public Query createNativeQuery(String sqlString);
    public Query createNativeQuery(String sqlString, Class result-
    Class);
    public Query createNativeQuery(String sqlString, String result-
    SetMapping);
    public void close();
    public boolean isOpen();
    public EntityTransaction getTransaction();
}
				
				

那又如何獲得EntityManagerFactory呢?不管是在J2EE或者J2SE中,都需要通過一個persistence.xml配置檔案對EntityMangaerFactory進行配置。下面是一個最簡單的persistence.xml的範例。
<entity-manager>
    <name>myEntityManager>/name>
    <provider>com.redsoft.ejb3.PersistenceProviderImpl>/provider>
    <class>com.redsoft.samples.HelloEntityBean>/class>
    <properties>
        <property name="ConnectionDriverName" value="com.mysql.jdbc.Driver"/>
        <property name="ConnectionURL" value="jdbc:mysql://localhost/EJB3Test"/>
        <property name="ConnectionUserName" value="ejb3"/>
        <property name="ConnectionPassword" value="ejb3"/>
   >/properties>
</entity-manager>

}
				
				

name – 定了當前這個EntityMangaerFactory的名字,我們可以在一個persistence.xml中定義多個EntityManagerFactory。

Provider – 定了提供EntityManagerFactory的具體實現類。這個實現類由不同的持久化產品開發商提供。例子中採用的是國產紅工場的ejb3持久化實現的 EntityManagerFactory實現類。如如果我們需要更換成其他廠商的產品,就需要更換具體的實現類。

class – 列出所有需要被JPA管理的實體類。為了保證在J2SE/J2EE中的通用性和可移植性,JPA要求這裡必須列出所有被JPA管理的實體類。

properties – 由持久化廠商自行定義的屬性。

如果使用JTA事務,也可以使用 myDataSource定義。

在J2EE容器環境中和J2SE環境中,都是通過讀取這個配置檔案來初始化EntityMangaerFactory。在J2EE容器環境下,ejb3容器負責讀取persistence.xml並初始化EntityManagerFactory,並將EntityManagerFactory幫定到JDNI中,這樣我們就可以通過訪問JNDI獲得EntityManagerFactory, 進而獲得EntityManager。由於EJB3容器支援IOC模式,我們也可以通過IOC將EntityMangerFactory直接注射給需要的使用JPA持久化的java類。通過IOC注射的方式獲得EntityManagerFactory或者EntityManager是更方便,合理和推薦的方式。

而在J2SE環境中,我們可以通過標準的javax.persistence.Persistence類來獲得EntityManagerFactory。Javax.persistence.Persistence會在當前classpath或者jar包的META-INF/下搜尋並讀persistence.xml後初始化EntityManagerFactory。

下面是一個簡單的示例如何在J2SE環境中獲得EntityManagerFactory並獲得EntityManager,運用EntityManager持久化HelloWorldEntityBean.
public class HelloWorld {

    public static void main( final String[] args ){

        /*
         * Obtain an EJB3 entity manager
         */
        final EntityManagerFactory emf = Persistence.createEntityManagerFactory();
        final EntityManager entityManager = emf.createEntityManager();

        // Construct a HelloEntityBean
        final HelloEntityBean hello = new HelloEntityBean( 1, "foo" );
        EntityTransaction trans = entityManager.getTransaction();
        trans.begin();
        entityManager.persist( hello );
        trans.commit();
        System.out.println( "Successfully persist hello: " + hello );

        // Look up the HelloEntityBean by primarky key
        final HelloEntityBean anotherHello = entityManager.find( HelloEntityBean.class, new Integer( hello.getId() ) );
        System.out.println( "Found hello: " + anotherHello );

        // close the EntityManager
        entityManager.close();
        emf.close();
    }
}
				
事實上不管是在J2SE還是J2EE中我們都可以這樣通過javax.persistence.Persistence來初始化EntityManagerFactory。

在上面HelloWorld的例子中我們需要顯式呼叫javax.persistence.Persistence.createEntityManagerFactory, 並且顯式地開始事務和關閉事務。在今天大量使用IOC託管容器的時代,這樣的編碼已經顯得落後。

作為J2EE一個部分的JPA自然可以利用EJB3的IOC容器託管事務和注射資源,同樣的也可以使用開源IOC容器spring來託管事務和注射資源。紅工場也提供了一個開源的spring DAO擴充套件 http://sourceforge.net/projects/ejb3daosupport是來支援JPA和Spring的結合。

下面是一個如何在Spring中託管事務和在DAO中注入EntityManager的配置範例:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "file://spring-beans.dtd">

<beans>
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
		<property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
		<property name="url"><value>jdbc:mysql://localhost/EJB3Test</value></property>
		<property name="username"><value>ejb3</value></property>
		<property name="password"><value>ejb3</value></property>
	</bean>
	<bean id="entityManagerFactory"
		class="org.springframework.orm.ejb3.LocalEntityManagerFactoryBean">
		<property name="persistenceInfo"><ref local="persistenceInfo"/></property>
		
	</bean>
	<bean id="persistenceInfo" class="com.redsoft.ejb3.PersistenceInfoImpl">
		<property name="nonJtaDataSource"><ref local="dataSource"/></property>
		<property name="entityManagerName"><value>myEntityManager</value></property>
		<property name="persistenceProviderClassName">
			<value>
				com.redsoft.ejb3.PersistenceProviderImpl
			</value>
		</property>
		<property name="entityClassNames">
		<list>
			<value>com.redsoft.ejb3.spring.Child</value>
			<value>com.redsoft.ejb3.spring.Father</value>
		</list>
		</property>
		<property name="properties">
			<props>
			<prop key="javax.jdo.PersistenceManagerFactoryClass">
				com.redsoft.jdo.PersistenceManagerFactoryImpl
			</prop>
			</props>
		</property>
	</bean>
	<bean id="transactionManager" class="org.springframework.orm.ejb3.EJB3TransactionManager"
		singleton="true">
		<property name="entityManagerFactory">
			<ref local="entityManagerFactory" />
		</property>
	</bean>
	
	<bean id="dao"
		class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
		singleton="true">
		<property name="transactionManager">
			<ref local="transactionManager" />
		</property>
		<property name="target">
			<bean class="com.redsoft.ejb3.spring.DAOImpl">
				<property name="entityManagerFactory">
					<ref local="entityManagerFactory" />
				</property>
			</bean>
		</property>
		<property name="transactionAttributes">
			<props>
				<prop key="save*">PROPAGATION_REQUIRED</prop>
				<prop key="remove*">PROPAGATION_REQUIRED</prop>
				<prop key="del*">PROPAGATION_REQUIRED</prop>
				<prop key="update*">PROPAGATION_REQUIRED</prop>
				<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
				<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
				<prop key="query*">PROPAGATION_REQUIRED,readOnly</prop>
			</props>
		</property>
	</bean>
</beans>