JBoss EJB3(Entity Beans)备忘记

EJB3 不只是标准的 J2EE 编程接口, 它还提供新的企业应用程式开发方案.
EJB3 使用 POJOs (Plain Old Java Object), Java annotation 及 依赖注入设计模式.
使代码分离及便于测试.

在 multi tier 的企业应用系统中, 典型地分为两种 objects.
Application logic components 及 Persistent data objects.
前者主要是处理 business processes. 如计算结帐价格.
后者则是处理生命周期较长的 business data. 如将结帐价格存入资料库.

Entity Beans 为 ORM (Object Relational Mapping) 的实作.
是一种模组化并对应关系式资料库. 它是一个完全 POJO 为基楚的储存框架,

当开发资料库相关的应用程式时, 开发员典型地需要管理两种不同的 model 结构.

第一种 model 结构为 java objects ,
java objects model 资料存于内存中, 整个应用程式建构于这些资料 objects,
Java 编程语言及其 APIs 就是设计成有效率地控制这些 objects.
开发员则利用这些 APIs 来开发应用程式.

第二种则为关联式 model 结构.
资料存于关联式资料库里, 有利于分布式环境及有效率地提高大量资料的搜寻.

处理这些 java objects 及 关联式资料表 需要不同的语法及 APIs,
因此开发员需要同时模组化相同的资料两次, 及处理两种资料所在的系统及中间的转换,
这是非常没有效率的事情.

Entity beans 则是设计成处理这两种资料的桥梁, 并且它是简单的 java objects,
可以在其上利用标记简单地定义如何存入资料库,
而这些 objects 及 relational 资料映射关系自动地交由 EJB3 容器处理,
开发员则不用关心资料库的结构, 管理及定义访问的APIs.
而 EntityManager 提供的访问接口及使用 EJB QL 来处理这些 objects instance.

Entity beans 及其 callback methods 的生命周期:
可以在 entity beans 定义下面的标记, 容器会自动管理及唤起这些被标记的 methods.

@PrePersist: 此标记为当 entity 存入在资料库 前 被唤起.
@PostPersist: 此标记为当 entity 存入在资料库 后 被唤起.
@PreRemove: 此标记为当 entity 在资料库里删除 前 被唤起.
@PostRemove: 此标记为当 entity 在资料库里删除 后 被唤起.
@PreUpdate: 此标记为当 entity 在资料库里更新 前 被唤起.
@PostUpdate: 此标记为当 entity 在资料库里更新 后 被唤起.
@PostLoad: 此标记为当 entity 从资料库取得 后 被唤起.

@Remove: 这标记并不是 callback methods, 当应用程式呼叫此 method 时,
                    bean instance 会从 EntityManager managed context 里移除.
                   这时侯 bean 变成分离状况及不能使用.

亦可选择建立新 class 并标记这些 callback methods,
然后在 entity bean 里使用 @EntityListener 标记这新 class 为 callback listener.
如下例所示:
@EntityListener(CostomizeEntityListener.class)

为使这备忘记更简化, 故这里并不实作这些唤回函数.
本编主要介绍如何实作简单的 Entity Beans.
要熟识 Entity Beans, 需要了解的知识实在不少,
本编结尾提供连结, 可免费下载 Mastering EJB 3.0 PDF 版本.

开始备忘记:
这次备忘记以 Shopping Cart 作为例子, 是一种 One-To-Many 的映射关系实作.

------------                        -----------
|                  |  1            *     |              |
|  Order      | <---------> |   Item   |
|                  |                       |              |
------------                        -----------

[1] Eclipse 启动 JBoss Server
[2] Eclipse 建立 Shopping Cart Project
[3] 建立 Shopping cart Entity Beans
[4] 建立 D:/eclipse_wtp/workspace/ShoppingCartEJB3/src/META-INF/persistence.xml
[5] 建立 Shopping cart Stateless Session Beans
[6] 建立 Shopping cart Stateful Session Beans
[7] 建立客户端测试程式
[8] 使用 ANT 建立 EJB-JAR 并执行 ShoppingCartClient 程式
[9] 使用 HSQL Database Manager 浏览资料库

[1] Eclipse 启动 JBoss Server:
Eclipse: Windows -> Show View -> Other
  -->> JBoss-IDE -> Server Configuration 就会显示 JBoss Server Configuration console
  然后 right client 它按 start , JBoss 就会启动, 如下图所示:


[2] Eclipse 建立 Shopping Cart Project:
Eclipse: File -> New -> Other -> EJB 3.0 -> EJB 3.0 Project
Project Name: ShoppingCartEJB3 -> Next
选择上一编已建立的 JBoss 4.0.x: jboss_configuration [default](not running)
打开后右键点选 JBoss 4.0.x -> new
然后按 Finish. ShoppingCartEJB3 project 就建立了

[3] 建立 Shopping cart Entity Beans:
/*----------------------- Order.java ---------------------*/
package ejb3.joeyta.domain;
import java.util.ArrayList;
import java.util.Collection;
import javax.persistence.CascadeType;
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.Table;

@SuppressWarnings("serial")
@Entity  // 表示此为 Entity Beans
@Table(name = "PURCHASE_ORDER")   // 表示将建立的 Table 名为 PURCHASE_ORDER
public class Order implements java.io.Serializable
{
   private int id;
   private double total;
   private Collection<Item> items;

   @Id @GeneratedValue(strategy=GenerationType.AUTO) 
           // AUTO 定义由 persistence provider 决定怎样产生 ID
   public int getId()
   {
      return id;
   }

   public void setId(int id)
   {
      this.id = id;
   }

   public double getTotal()
   {
      return total;
   }

   public void setTotal(double total)
   {
      this.total = total;
   }

   public void addPurchase(String product, int quantity, double price)
   {
      if (items == null) items = new ArrayList<Item>();
      Item item = new Item();
      item.setOrder(this);
      item.setProduct(product);
      item.setQuantity(quantity);
      item.setPrice(quantity * price);
      items.add(item);
      total += quantity * price;
   }

   @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy="order")
   public Collection<Item> getItems()
   {
      return items;
   }

   public void setItems(Collection<Item> items)
   {
      this.items = items;
   }
}
/*----------------------- Order.java ---------------------*/

/*----------------------- Item.java ---------------------*/
package ejb3.joeyta.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

@SuppressWarnings("serial")
@Entity  // 表示此为 Entity Beans
public class Item implements java.io.Serializable
{
   private int id;
   private double price;
   private int quantity;
   private String product;
   private Order order;


   @Id @GeneratedValue(strategy=GenerationType.AUTO) 
          // AUTO 定义由 persistence provider 决定怎样产生 ID
   public int getId()
   {
      return id;
   }

   public void setId(int id)
   {
      this.id = id;
   }

   public double getPrice()
   {
      return price;
   }

   public void setPrice(double price)
   {
      this.price = price;
   }

   public int getQuantity()
   {
      return quantity;
   }

   public void setQuantity(int quantity)
   {
      this.quantity = quantity;
   }

   public String getProduct()
   {
      return product;
   }

   public void setProduct(String product)
   {
      this.product = product;
   }

   @ManyToOne    // 定义 Many-To-One 关系
   @JoinColumn(name = "order_id")   // 定义使用 foreign key, 即 order_id 对应到 Order 里的 id
   public Order getOrder()
   {
      return order;
   }

   public void setOrder(Order order)
   {
      this.order = order;
   }
}
/*----------------------- Item.java ---------------------*/

以上 Order 与 Item 为 One-To-Many 的关系.
使用 EJB3 Annotation 比 设定 Hibernate mapping file 还要轻松.
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy="order")
类似 Hibernate,
OneToMany 表示为 One-To-Many mapping.
cascade 定义 Order 的每个 DML 是否关联至 Item.
cascade = CascadeType.ALL 表示所有DML均关联, 例如 Order delete 时, 所有相关的 Item 亦会 delete.
fetch 可定义 lazy initialization
fetch = FetchType.EAGER 表示不使用 lazy initialization,
例如当 load Order 时, 所有相关 Item 也一起 load 出来. 如使用 LAZY 刚 Item 使用时才 load 出来.
mappedBy 定义 Order 与 Item 相向关系, Persistence manager 根据此值了解相向对应关系.
mappedBy="order" 表示 Item 里的 order property 与此对应, 即 Hibernate 里的 inverse. 防止 duplication DML.

这里如果没明确设定 @Table , Persistence manager 就会根据 Class 名来建立 Table.
这里如果没明确设定 @Column , Persistence manager 就会根据 Class 里的 Property 名来建立 column.

这里如果不使用 EJB3 Annotation,
Persistence provider 预设会搜寻 META-INF 下的 orm.xml
亦可以在 META-INF/persistence.xml 里设定 mapping file orm.xml. 然后在 orm.xml 里设定 OR Mapping.
jboss.xml 则可设定 JNDI XML Binding.

[4] 建立 D:/eclipse_wtp/workspace/ShoppingCartEJB3/src/META-INF/persistence.xml:
<!--------------------- persistence.xml -------------------->
<?xml version="1.0" encoding="UTF-8"?>
<persistence>
 <persistence-unit name="joeyta">
  <jta-data-source>java:/DefaultDS</jta-data-source>
  <properties>
   <property name="hibernate.hbm2ddl.auto" value="create-drop" />
  </properties>
 </persistence-unit>
</persistence>
<!--------------------- persistence.xml -------------------->
这个档案定义资料库的相关部署.
joeyta 为 unit name, EntityManager 需要指定相应的 unit name.
java:/DefaultDS 表示使用 D:/jboss/server/default/deploy 下 xxxxx-ds.xml 结尾资料库设定档对应的 jndi name.
这实作里使用 JBoss 预设的 HSQLDB, 即 D:/jboss/server/default/deploy/hsqldb-ds.xml
<property name="hibernate.hbm2ddl.auto" value="create-drop" /> 定义:
当project deploy 时就会自动产生 database schema. 当project undeploy 时就会自动清除 database schema.


[5] 建立 Shopping cart Stateless Session Beans:
/*----------------------- ShoppingCartDAO.java ---------------------*/
package ejb3.joeyta.sessions;
import java.util.List;
public interface ShoppingCartDAO {
 public List findByProduct(String product);
 public List loadAllOrders();
}
/*----------------------- ShoppingCartDAO.java ---------------------*/

/*----------------------- ShoppingCartDAOBean.java ---------------------*/
package ejb3.joeyta.sessions;
import java.util.List;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Stateless  // 定义 Stateless Session Beans
@Remote(ShoppingCartDAO.class)   // 定义 ShoppingCartDAO.class 为 Remote interface
public class ShoppingCartDAOBean {
 @PersistenceContext(unitName = "joeyta")
 private EntityManager manager;
 // 这里定义将 EntityManager inject 进来. unitName="joeyta" 为上面定义的 persistence.xml 
 public List findByProduct(String product) {
  return manager.createQuery("from Item o where o.product = :product")
    .setParameter("product", product).getResultList();
 }

 public List loadAllOrders(){
  return manager.createQuery("from Order").getResultList(); 
 }
}
/*----------------------- ShoppingCartDAOBean.java ---------------------*/


[6] 建立 Shopping cart Stateful Session Beans:
/*----------------------- ShoppingCart.java ---------------------*/
package ejb3.joeyta.sessions;
import ejb3.joeyta.domain.Order;
public interface ShoppingCart {
 public void buy(String product, int quantity, double price);
 public Order getOrder();
 public void checkout();
}
/*----------------------- ShoppingCart.java ---------------------*/

/*----------------------- ShoppingCartBean.java ---------------------*/
package ejb3.joeyta.sessions;

import javax.ejb.Remote;
import javax.ejb.Remove;
import javax.ejb.Stateful;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import ejb3.joeyta.domain.Item;
import ejb3.joeyta.domain.Order;

@SuppressWarnings("serial")
@Stateful  // 定义此为 Stateful Session Beans
@Remote(ShoppingCart.class)  // 定义 ShoppingCart.class 为 Remote interface
public class ShoppingCartBean implements ShoppingCart, java.io.Serializable {
 @PersistenceContext(unitName = "joeyta")
 private EntityManager manager;
 // 这里定义将 EntityManager inject 进来. unitName="joeyta" 为上面定义的 persistence.xml

 private Order order;

 public void buy(String product, int quantity, double price) {
  if(order == null) order = new Order();
  order.addPurchase(product, quantity, price);
 }
 
 public Order getOrder() {
  return order;
 }

 @Remove   // @Remove 表示当呼叫 checkout(), 就会清除这个 Instance
 public void checkout() {
  manager.persist(order);
  System.out.println("checkout"); 
  System.out.println("*******************************"); 
  System.out.println("Print total order list on server:");
  for(Item item : order.getItems()){
   System.out.println("Item:" + item.getProduct() + "  Price:" + item.getPrice());   
  } 
  System.out.println("Order id:" + order.getId() + "  Total Price: " + order.getTotal()); 
 }
}
/*----------------------- ShoppingCartBean.java ---------------------*/


[7] 建立客户端测试程式:
/*----------------------- ShoppingCartClient.java ---------------------*/
package ejb3.joeyta.clients;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import javax.naming.InitialContext;
import ejb3.joeyta.domain.Item;
import ejb3.joeyta.domain.Order;
import ejb3.joeyta.sessions.ShoppingCart;
import ejb3.joeyta.sessions.ShoppingCartDAO;

public class ShoppingCartClient {
  public static InitialContext getInitialContext() throws javax.naming.NamingException {
     // 这里亦可在 CLASSPATH 下建立 jndi.properties
  Properties p = new Properties();
  p.put(InitialContext.INITIAL_CONTEXT_FACTORY,
    "org.jnp.interfaces.NamingContextFactory");
  p.put(InitialContext.URL_PKG_PREFIXES, " org.jboss.naming:org.jnp.interfaces");
  p.put(InitialContext.PROVIDER_URL, "jnp://localhost:1099");
  return new javax.naming.InitialContext(p);
 }
 
 public static void main(String[] args) throws Exception {
  InitialContext ctx = getInitialContext();
  ShoppingCart cart = (ShoppingCart) ctx.lookup("ShoppingCartBean/remote");

  System.out.println("Buying Cake");
  cart.buy("Cake", 1, 3.9);    // 买cake

  System.out.println("Buying Bread");
  cart.buy("Bread", 1, 2.9);   // 买bread

  System.out.println("Checkout");
  cart.checkout();  // 结帐

  ShoppingCartDAO cartDAO = (ShoppingCartDAO) ctx.lookup("ShoppingCartDAOBean/remote"); 

  System.out.println("*******************************"); 
  System.out.println("Print total order list on client:");  // 列印所有 order list
  List orders = cartDAO.loadAllOrders();
  for (Iterator iter = orders.iterator(); iter.hasNext();) {
   Order order = (Order) iter.next();
   for(Item item : order.getItems()){
    System.out.println("Item:" + item.getProduct() + "  Price:" + item.getPrice());   
   }
   System.out.println("Order id:" + order.getId() + "  Total Price: " + order.getTotal());
  }

  System.out.println("*******************************"); 
  System.out.println("Print cake order list on client:"); // 列印所有 cake order list
  List orderList = cartDAO.findByProduct("Cake");
  for (Iterator iter = orderList.iterator(); iter.hasNext();) {
   Item element = (Item) iter.next();
   System.out.println(element.getProduct() + "   "
     + element.getQuantity() + "   " + element.getPrice());
  }

 }
}
/*----------------------- ShoppingCartClient.java ---------------------*/
这里当 选购项目时使用 Shopping Cart 的 Statefull Session Beans.
列印己选购项目使用 DAO (Data Access Model) 的 Stateless Session Beans.

项目结构如下所示:

 [8] 使用 ANT 建立 EJB-JAR 并执行 ShoppingCartClient 程式:
在 ShoppingCartEJB3 project 下建立 build.xml [ ANT build File ]
内容如下:
<!------------------------- build.xml ------------------------------->
<?xml version="1.0"?>
<project name="JBoss" default="run.client" basedir=".">
 <property environment="env" />
 <property name="src.dir" value="${basedir}/src" />
 <property name="resources" value="${basedir}/META-INF" />
 <property name="jboss.home" value="${env.JBOSS_HOME}" />
 <property name="classes.dir" value="bin" />

 <path id="classpath">
  <fileset dir="${jboss.home}/client">
   <include name="**/*.jar" />
  </fileset>
  <pathelement location="${classes.dir}" />
  <pathelement location="${basedir}/client-config" />
 </path>

 <target name="clean">
  <delete file="${basedir}/ShoppingCart.jar" />
  <delete file="${jboss.home}/server/default/deploy/ShoppingCart.jar" />
 </target>

 <target name="ejbjar" depends="clean">
  <jar jarfile="ShoppingCart.jar">
   <fileset dir="${classes.dir}">
    <include name="ejb3/joeyta/domain/*.class" />
    <include name="ejb3/joeyta/sessions/*.class" />
    <include name="META-INF/*.xml" />
   </fileset>
  </jar>
  <copy file="ShoppingCart.jar" todir="${jboss.home}/server/default/deploy" />
 </target>

 <target name="run.client">
  <java classname="ejb3.joeyta.clients.ShoppingCartClient" fork="yes" dir=".">
   <classpath refid="classpath" />
  </java>
 </target>

</project>
<!------------------------- build.xml ------------------------------->

build.xml 项目:
classpath 描述所需的 libraries 及 classes 的位置.
clean 清除项目所产生的 ShoppingCart.jar 及 JBoss deploy 里的 ShoppingCart.jar
ejbjar 产生 ShoppingCart.jar
run.ShoppingCartClient 执行 Client 测试程式 ShoppingCartClient

点选 build -> Run As -> 3. Ant Build ->> ejbjar
JBoss 自动 deploy 后就会产生相应的 Database Schema.
Eclipse Console 输出为
Buildfile: D:/eclipse_wtp/workspace/ShoppingCartEJB3/build.xml
clean:
   [delete] Deleting: D:/eclipse_wtp/workspace/ShoppingCartEJB3/ShoppingCart.jar
   [delete] Deleting: D:/jboss/server/default/deploy/ShoppingCart.jar
ejbjar:
      [jar] Building jar: D:/eclipse_wtp/workspace/ShoppingCartEJB3/ShoppingCart.jar
     [copy] Copying 1 file to D:/jboss/server/default/deploy
BUILD SUCCESSFUL
Total time: 4 seconds

JBoss Console  输出为
15:48:14,835 INFO  [SchemaExport] Running hbm2ddl schema export
15:48:14,835 INFO  [SchemaExport] exporting generated schema to database
15:48:14,850 INFO  [SchemaExport] schema export complete
15:48:14,850 INFO  [NamingHelper] JNDI InitialContext properties:{java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory, java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces}
15:48:14,866 INFO  [JmxKernelAbstraction] installing MBean: jboss.j2ee:jar=ShoppingCart.jar,name=ShoppingCartBean,service=EJB3 with dependencies:
15:48:14,866 INFO  [JmxKernelAbstraction]  persistence.units:jar=ShoppingCart.jar,unitName=joeyta
15:48:14,975 INFO  [EJBContainer] STARTED EJB: ejb3.joeyta.sessions.ShoppingCartBean ejbName: ShoppingCartBean
15:48:14,991 INFO  [JmxKernelAbstraction] installing MBean: jboss.j2ee:jar=ShoppingCart.jar,name=ShoppingCartDAOBean,service=EJB3 with dependencies:
15:48:14,991 INFO  [JmxKernelAbstraction]  persistence.units:jar=ShoppingCart.jar,unitName=joeyta
15:48:15,022 INFO  [EJBContainer] STARTED EJB: ejb3.joeyta.sessions.ShoppingCartDAOBean ejbName: ShoppingCartDAOBean
15:48:15,053 INFO  [EJB3Deployer] Deployed: file:/D:/jboss/server/default/deploy/ShoppingCart.jar

点选 build -> Run As -> 3. Ant Build ->> run.client
Eclipse Console 输出为
Buildfile: D:/eclipse_wtp/workspace/ShoppingCartEJB3/build.xml
run.client:
     [java] log4j:WARN No appenders could be found for logger (org.jboss.remoting.Client).
     [java] log4j:WARN Please initialize the log4j system properly.
     [java] Buying Cake
     [java] Buying Bread
     [java] Checkout
     [java] *******************************
     [java] Print total order list on client:
     [java] Item:Cake  Price:3.9
     [java] Item:Bread  Price:2.9
     [java] Order id:1  Total Price: 6.8
     [java] *******************************
     [java] Print cake order list on client:
     [java] Cake   1   3.9
BUILD SUCCESSFUL
Total time: 8 seconds
如下图所示:

JBoss Console  输出为
15:49:58,379 INFO  [STDOUT] checkout
15:49:58,379 INFO  [STDOUT] *******************************
15:49:58,379 INFO  [STDOUT] Print total order list on server:
15:49:58,379 INFO  [STDOUT] Item:Cake  Price:3.9
15:49:58,379 INFO  [STDOUT] Item:Bread  Price:2.9
15:49:58,379 INFO  [STDOUT] Order id:1  Total Price: 6.8
如下图所示:

进入 HSQL Database Manager 后, 执行下面 DML.
select * from purchase_order x,item y where x.id = y.order_id;
就会出现如下图所示:
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章