先看最终的目录结构:
(注:去掉AccountClient.java,客户端测试在另一web项目中进行!)
下面给出EJB3工程中各个.java文件:
Account.java:
package examples;
import javax.ejb.*;
import java.rmi.RemoteException;
/**
* This is the remote interface for AccountBean.
*
* This interface is what clients operate on when they interact with
* beans. The container will implement this interface; the
* implemented object is called the EJB object, which delegates
* invocations to the actual bean.
*/
public interface Account extends EJBObject {
/**
* Deposits amt into account.
*/
public void deposit(double amt) throws AccountException, RemoteException;
/**
* Withdraws amt from bank account.
* @throw AccountException thrown in amt < available balance
*/
public void withdraw(double amt) throws AccountException, RemoteException;
// Getter/setter methods on Entity Bean fields
public double getBalance() throws RemoteException;
public String getOwnerName() throws RemoteException;
public void setOwnerName(String name) throws RemoteException;
public String getAccountID() throws RemoteException;
public void setAccountID(String id) throws RemoteException;
}
AccountBean.java:
package examples;
import java.sql.*;
import javax.naming.*;
import javax.ejb.*;
import java.util.*;
/**
* Demonstration Bean-Managed Persistent Entity Bean.
* This Entity Bean represents a Bank Account.
*/
public class AccountBean implements EntityBean {
protected EntityContext ctx;
//
// Bean-managed state fields
//
private String accountID; // PK
private String ownerName;
private double balance;
public AccountBean() {
System.out.println("New Bank Account Entity Bean Java Object created by EJB Container.");
}
//
// Business Logic Methods
//
/**
* Deposits amt into account.
*/
public void deposit(double amt) throws AccountException {
System.out.println("deposit(" + amt + ") called.");
balance += amt;
}
/**
* Withdraws amt from bank account.
* @throw AccountException thrown in amt < available balance
*/
public void withdraw(double amt) throws AccountException {
System.out.println("withdraw(" + amt + ") called.");
if (amt > balance) {
throw new AccountException("Your balance is " + balance + "! You cannot withdraw " + amt + "!");
}
balance -= amt;
}
// Getter/setter methods on Entity Bean fields
public double getBalance() {
System.out.println("getBalance() called.");
return balance;
}
public void setOwnerName(String name) {
System.out.println("setOwnerName() called.");
ownerName = name;
}
public String getOwnerName() {
System.out.println("getOwnerName() called.");
return ownerName;
}
public String getAccountID() {
System.out.println("getAccountID() called.");
return accountID;
}
public void setAccountID(String id) {
System.out.println("setAccountID() called.");
this.accountID = id;
}
/**
* This home business method is independent of any
* particular account instance. It returns the total
* of all the bank accounts in the bank.
*/
public double ejbHomeGetTotalBankValue() throws AccountException {
PreparedStatement pstmt = null;
Connection conn = null;
try {
System.out.println("ejbHomeGetTotalBankValue()");
/*
* Acquire DB connection
*/
conn = getConnection();
/*
* Get the total of all accounts
*/
pstmt = conn.prepareStatement("select sum(balance) as total from accounts");
ResultSet rs = pstmt.executeQuery();
/*
* Return the sum
*/
if (rs.next()) {
return rs.getDouble("total");
}
}
catch (Exception e) {
e.printStackTrace();
throw new AccountException(e);
}
finally {
/*
* Release DB Connection for other beans
*/
try { if (pstmt != null) pstmt.close(); }
catch (Exception e) {}
try { if (conn != null) conn.close(); }
catch (Exception e) {}
}
throw new AccountException("Error!");
}
//
// EJB-required methods
//
/**
* Called by Container. Implementation can acquire
* needed resources.
*/
public void ejbActivate() {
System.out.println("ejbActivate() called.");
}
/**
* Removes entity bean data from the database.
* Corresponds to when client calls home.remove().
*/
public void ejbRemove() throws RemoveException {
System.out.println("ejbRemove() called.");
/*
* Remember that an entity bean class can be used to
* represent different data instances. So how does
* this method know which instance in the database
* to delete?
*
* The answer is to query the container by calling
* the entity context object. By retrieving the
* primary key from the entity context, we know
* which data instance, keyed by the PK, that we
* should delete from the DB.
*/
AccountPK pk = (AccountPK) ctx.getPrimaryKey();
String id = pk.accountID;
PreparedStatement pstmt = null;
Connection conn = null;
try {
/*
* 1) Acquire a new JDBC Connection
*/
conn = getConnection();
/*
* 2) Remove account from the DB
*/
pstmt = conn.prepareStatement("delete from accounts where id = ?");
pstmt.setString(1, id);
/*
* 3) Throw a system-level exception if something
* bad happened.
*/
if (pstmt.executeUpdate() == 0) {
throw new RemoveException("Account " + pk + " failed to be removed from the database");
}
}
catch (Exception ex) {
throw new EJBException(ex.toString());
}
finally {
/*
* 4) Release the DB Connection
*/
try { if (pstmt != null) pstmt.close(); }
catch (Exception e) {}
try { if (conn != null) conn.close(); }
catch (Exception e) {}
}
}
/**
* Called by Container. Releases held resources for
* passivation.
*/
public void ejbPassivate() {
System.out.println("ejbPassivate () called.");
}
/**
* Called by the container. Updates the in-memory entity
* bean object to reflect the current value stored in
* the database.
*/
public void ejbLoad() {
System.out.println("ejbLoad() called.");
/*
* Again, query the Entity Context to get the current
* Primary Key, so we know which instance to load.
*/
AccountPK pk = (AccountPK) ctx.getPrimaryKey();
String id = pk.accountID;
PreparedStatement pstmt = null;
Connection conn = null;
try {
/*
* 1) Acquire a new DB Connection
*/
conn = getConnection();
/*
* 2) Get account from the DB, querying
* by account ID
*/
pstmt = conn.prepareStatement("select ownerName, balance from accounts where id = ?");
pstmt.setString(1, id);
ResultSet rs = pstmt.executeQuery();
rs.next();
ownerName = rs.getString("ownerName");
balance = rs.getDouble("balance");
}
catch (Exception ex) {
throw new EJBException("Account " + pk + " failed to load from database", ex);
}
finally {
/*
* 3) Release the DB Connection
*/
try { if (pstmt != null) pstmt.close(); }
catch (Exception e) {}
try { if (conn != null) conn.close(); }
catch (Exception e) {}
}
}
/**
* Called from the Container. Updates the database
* to reflect the current values of this in-memory
* entity bean instance.
*/
public void ejbStore() {
System.out.println("ejbStore() called.");
PreparedStatement pstmt = null;
Connection conn = null;
try {
/*
* 1) Acquire a new DB Connection
*/
conn = getConnection();
/*
* 2) Store account in DB
*/
pstmt = conn.prepareStatement("update accounts set ownerName = ?, balance = ? where id = ?");
pstmt.setString(1, ownerName);
pstmt.setDouble(2, balance);
pstmt.setString(3, accountID);
pstmt.executeUpdate();
}
catch (Exception ex) {
throw new EJBException("Account " + accountID + " failed to save to database", ex);
}
finally {
/*
* 3) Release the DB Connection
*/
try { if (pstmt != null) pstmt.close(); }
catch (Exception e) {}
try { if (conn != null) conn.close(); }
catch (Exception e) {}
}
}
/**
* Called by the container. Associates this bean
* instance with a particular context. We can query
* the bean properties which customize the bean here.
*/
public void setEntityContext(EntityContext ctx) {
System.out.println("setEntityContext called");
this.ctx = ctx;
}
/**
* Called by Container. Disassociates this bean
* instance with a particular context environment.
*/
public void unsetEntityContext() {
System.out.println("unsetEntityContext called");
this.ctx = null;
}
/**
* Called after ejbCreate(). Now, the Bean can retrieve
* its EJBObject from its context, and pass it as
* a 'this' argument.
*/
public void ejbPostCreate(String accountID, String ownerName) {
}
/**
* This is the initialization method that corresponds to the
* create() method in the Home Interface.
*
* When the client calls the Home Object's create() method,
* the Home Object then calls this ejbCreate() method.
*
* @return The primary key for this account
*/
public AccountPK ejbCreate(String accountID, String ownerName) throws CreateException {
PreparedStatement pstmt = null;
Connection conn = null;
try {
System.out.println("ejbCreate() called.");
this.accountID = accountID;
this.ownerName = ownerName;
this.balance = 0;
/*
* Acquire DB connection
*/
conn = getConnection();
/*
* Insert the account into the database
*/
pstmt = conn.prepareStatement("insert into accounts (id, ownerName, balance) values (?, ?, ?)");
pstmt.setString(1, accountID);
pstmt.setString(2, ownerName);
pstmt.setDouble(3, balance);
pstmt.executeUpdate();
/*
* Generate the Primary Key and return it
*/
return new AccountPK(accountID);
}
catch (Exception e) {
throw new CreateException(e.toString());
}
finally {
/*
* Release DB Connection for other beans
*/
try { if (pstmt != null) pstmt.close(); }
catch (Exception e) {}
try { if (conn != null) conn.close(); }
catch (Exception e) {}
}
}
/**
* Finds a Account by its primary Key
*/
public AccountPK ejbFindByPrimaryKey(AccountPK key) throws FinderException {
PreparedStatement pstmt = null;
Connection conn = null;
try {
System.out.println("ejbFindByPrimaryKey(" + key + ") called");
/*
* Acquire DB connection
*/
conn = getConnection();
/*
* Find the Entity in the DB
*/
pstmt = conn.prepareStatement("select id from accounts where id = ?");
pstmt.setString(1, key.toString());
ResultSet rs = pstmt.executeQuery();
rs.next();
/*
* No errors occurred, so return the Primary Key
*/
return key;
}
catch (Exception e) {
throw new FinderException(e.toString());
}
finally {
/*
* Release DB Connection for other beans
*/
try { if (pstmt != null) pstmt.close(); }
catch (Exception e) {}
try { if (conn != null) conn.close(); }
catch (Exception e) {}
}
}
/**
* Finds all Accounts by a name
*/
public Collection ejbFindByOwnerName(String name) throws FinderException {
PreparedStatement pstmt = null;
Connection conn = null;
Vector v = new Vector();
try {
System.out.println("ejbFindByOwnerName(" + name + ") called");
/*
* Acquire DB connection
*/
conn = getConnection();
/*
* Find the primary keys in the DB
*/
pstmt = conn.prepareStatement("select id from accounts where ownerName = ?");
pstmt.setString(1, name);
ResultSet rs = pstmt.executeQuery();
/*
* Insert every primary key found into a vector
*/
while (rs.next()) {
String id = rs.getString("id");
v.addElement(new AccountPK(id));
}
/*
* Return the vector of primary keys
*/
return v;
}
catch (Exception e) {
throw new FinderException(e.toString());
}
finally {
/*
* Release DB Connection for other beans
*/
try { if (pstmt != null) pstmt.close(); }
catch (Exception e) {}
try { if (conn != null) conn.close(); }
catch (Exception e) {}
}
}
/**
* Gets JDBC connection from the connection pool.
*
* @return The JDBC connection
*/
public Connection getConnection() throws Exception {
try {
Context ctx = new InitialContext();
javax.sql.DataSource ds = (javax.sql.DataSource) ctx.lookup("java:/MySqlDS");
return ds.getConnection();
}
catch (Exception e) {
System.err.println("Could not locate datasource! Reason:");
e.printStackTrace();
throw e;
}
}
}
AccountException:
package examples;
/**
* Exceptions thrown by Accounts
*/
public class AccountException extends Exception {
public AccountException() {
super();
}
public AccountException(Exception e) {
super(e.toString());
}
public AccountException(String s) {
super(s);
}
}
AccountHome:
package examples;
import javax.ejb.*;
import java.util.Collection;
import java.rmi.RemoteException;
/**
* This is the home interface for Account. This
* interface is implemented by the EJB container's tools - the
* implemented object is called the home object, which
* is a factory for EJB objects.
*/
public interface AccountHome extends EJBHome {
/**
* We define a single create() method is in this home interface,
* which corresponds to the ejbCreate() method in AccountBean.
* This method creates the local EJB object.
*
* Notice that the local home interface returns a local interface,
* whereas the bean returns a PK.
*
* Notice we don't throw RemoteExceptions because we are local.
*
* @param accountID The number of the account (unique)
* @param ownerName The name of the person who owns the account
* @return The newly created local object.
*/
Account create(String accountID, String ownerName) throws CreateException, RemoteException;
/**
* Finds an Account by its primary Key (Account ID)
*/
public Account findByPrimaryKey(AccountPK key) throws FinderException, RemoteException;
/**
* Finds all Accounts under an owner's name
*/
public Collection findByOwnerName(String name) throws FinderException, RemoteException;
/**
* This home business method is independent of any particular
* account. It returns the total of all accounts in the bank.
*/
public double getTotalBankValue() throws AccountException, RemoteException;
}
AccountPK.java:
package examples;
import java.io.Serializable;
/**
* Primary Key class for Account.
*/
public class AccountPK implements java.io.Serializable {
public String accountID;
public AccountPK(String id) {
this.accountID = id;
}
public AccountPK() {
}
public String toString() {
return accountID;
}
public int hashCode() {
return accountID.hashCode();
}
public boolean equals(Object account) {
return ((AccountPK)account).accountID.equals(accountID);
}
}
下面给出部署描述信息:
ejb-jar.xml:
<?xml version="1.0"?>
<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>
<ejb-jar>
<enterprise-beans>
<entity>
<ejb-name>Account</ejb-name>
<home>examples.AccountHome</home>
<remote>examples.Account</remote>
<ejb-class>examples.AccountBean</ejb-class>
<persistence-type>Bean</persistence-type>
<prim-key-class>examples.AccountPK</prim-key-class>
<reentrant>False</reentrant>
<resource-ref>
<res-ref-name>AccountHome</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</entity>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>Account</ejb-name>
<method-intf>Remote</method-intf>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
jboss.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 3.0//EN" "http://www.jboss.org/j2ee/dtd/jboss_3_0.dtd">
<jboss>
<unauthenticated-principal>nobody</unauthenticated-principal>
<enterprise-beans>
<!--
To add beans that you have deployment descriptor info for, add
a file to your XDoclet merge directory called jboss-beans.xml that contains
the <session></session>, <entity></entity> and <message-driven></message-driven>
markup for those beans.
-->
<entity>
<ejb-name>Account</ejb-name>
<jndi-name>AccountHome</jndi-name>
</entity>
</enterprise-beans>
<resource-managers>
</resource-managers>
</jboss>
persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
<persistence-unit name="bbmmppPU" transaction-type="JTA">
<jta-data-source>java:/MySqlDS</jta-data-source>
</persistence-unit>
</persistence>
新建一个web项目,加入上面的ejb工程,编写servlet程序测试:
bmp.java:
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Iterator;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import examples.Account;
import examples.AccountHome;
import examples.AccountPK;
public class bmp extends HttpServlet {
/**
* The doGet method of the servlet. <br>
*
* This method is called when a form has its tag value method equals to get.
*
* @param request the request send by the client to the server
* @param response the response send by the server to the client
* @throws ServletException if an error occurred
* @throws IOException if an error occurred
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
out.println("<HTML>");
out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>");
out.println(" <BODY>");
Account account = null;
try {
/*
* Get a reference to the Account Home Object - the
* factory for Account EJB Objects
*/
Context ctx = new InitialContext(System.getProperties());
Object obj = ctx.lookup("AccountHome");
AccountHome home = (AccountHome) javax.rmi.PortableRemoteObject.narrow(obj, AccountHome.class);
System.err.println("Total of all accounts in bank initially = " + home.getTotalBankValue());
/*
* Use the factory to create the Account EJB Object
*/
home.create("123-456-7890", "John Smith");
/*
* Find an account
*/
Iterator i = home.findByOwnerName("John Smith").iterator();
if (i.hasNext()) {
account = (Account)
javax.rmi.PortableRemoteObject.narrow(
i.next(), Account.class);
}
else {
throw new Exception("Could not find account");
}
/*
* Call the balance() method, and print it
*/
System.out.println("Initial Balance = " + account.getBalance());
/*
* Deposit $100 into the account
*/
account.deposit(1000);
/*
* Retrieve the resulting balance.
*/
System.out.println("After depositing 1000, account balance = " + account.getBalance());
System.out.println("Total of all accounts in bank now = " + home.getTotalBankValue());
/*
* Retrieve the Primary Key from the EJB Object
*/
AccountPK pk = (AccountPK) account.getPrimaryKey();
/*
* Release our old EJB Object reference. Now call
* find() again, this time querying on Account ID
* (i.e. the Primary Key).
*/
account = null;
account = home.findByPrimaryKey(pk);
/*
* Print out current balance
*/
System.out.println("Found account with ID " + pk + ". Balance = " + account.getBalance());
/*
* Try to withdraw $150
*/
System.out.println("Now trying to withdraw $150,if more than is currently available. This should generate an exception..");
account.withdraw(150);
}
catch (Exception e) {
System.out.println("Caught exception!");
e.printStackTrace();
}
finally {
/*
* Destroy the Entity permanently
*/
try {
System.out.println("Destroying account..");
if (account != null) {
account.remove();
}
}
catch (Exception e) {
e.printStackTrace();
}
}
out.println(" </BODY>");
out.println("</HTML>");
out.flush();
out.close();
}
}
MySql数据库结构:
插入一条记录后:
测试http://localhost:8080/bbmmppTest/bmp,在jboss命令行输出:
12:57:07,328 INFO [STDOUT] New Bank Account Entity Bean Java Object created by EJB Container.
12:57:07,328 INFO [STDOUT] setEntityContext called
12:57:07,328 INFO [STDOUT] ejbHomeGetTotalBankValue()
12:57:07,344 ERROR [STDERR] Total of all accounts in bank initially = 500.0
12:57:07,344 INFO [STDOUT] ejbCreate() called.
12:57:07,344 INFO [STDOUT] ejbStore() called.
12:57:07,360 INFO [STDOUT] ejbPassivate () called.
12:57:07,376 INFO [STDOUT] ejbFindByOwnerName(John Smith) called
12:57:07,376 INFO [STDOUT] ejbActivate() called.
12:57:07,376 INFO [STDOUT] ejbLoad() called.
12:57:07,376 INFO [STDOUT] getBalance() called.
12:57:07,376 INFO [STDOUT] ejbStore() called.
12:57:07,407 INFO [STDOUT] ejbPassivate () called.
12:57:07,407 INFO [STDOUT] Initial Balance = 0.0
12:57:07,407 INFO [STDOUT] ejbActivate() called.
12:57:07,407 INFO [STDOUT] ejbLoad() called.
12:57:07,423 INFO [STDOUT] deposit(1000.0) called.
12:57:07,423 INFO [STDOUT] ejbStore() called.
12:57:07,438 INFO [STDOUT] ejbPassivate () called.
12:57:07,438 INFO [STDOUT] ejbActivate() called.
12:57:07,438 INFO [STDOUT] ejbLoad() called.
12:57:07,438 INFO [STDOUT] getBalance() called.
12:57:07,438 INFO [STDOUT] ejbStore() called.
12:57:07,454 INFO [STDOUT] ejbPassivate () called.
12:57:07,454 INFO [STDOUT] After depositing 1000, account balance = 1000.0
12:57:07,454 INFO [STDOUT] ejbHomeGetTotalBankValue()
12:57:07,469 INFO [STDOUT] Total of all accounts in bank now = 1500.0
12:57:07,469 INFO [STDOUT] ejbFindByPrimaryKey(123-456-7890) called
12:57:07,469 INFO [STDOUT] ejbActivate() called.
12:57:07,469 INFO [STDOUT] ejbLoad() called.
12:57:07,485 INFO [STDOUT] getBalance() called.
12:57:07,485 INFO [STDOUT] ejbStore() called.
12:57:07,501 INFO [STDOUT] ejbPassivate () called.
12:57:07,501 INFO [STDOUT] Found account with ID 123-456-7890. Balance = 1000.0
12:57:07,501 INFO [STDOUT] Now trying to withdraw $150,if more than is currently available. This should generate an exception..
12:57:07,501 INFO [STDOUT] ejbActivate() called.
12:57:07,501 INFO [STDOUT] ejbLoad() called.
12:57:07,501 INFO [STDOUT] withdraw(150.0) called.
12:57:07,501 INFO [STDOUT] ejbStore() called.
12:57:07,547 INFO [STDOUT] ejbPassivate () called.
12:57:07,547 INFO [STDOUT] Destroying account..
12:57:07,547 INFO [STDOUT] ejbActivate() called.
12:57:07,547 INFO [STDOUT] ejbLoad() called.
12:57:07,547 INFO [STDOUT] ejbStore() called.
12:57:07,547 INFO [STDOUT] ejbRemove() called.
从以上结果可以看出运行过程:
最先实体Bean构造方法AccountBean()、环境EntityContext初始化。
然后调用主接口(home接口)的商业方法,此方法是独立于具体账户Account实例的,返回数据库中总的余额。
然后调用ejbCreate()方法插入数据,再保存关联数据ejbStore(),阻塞ejbPassivate();再调用查找方法ejbFindByOwnerName(),激活ejbActivate(),根据id提取数据ejbLoad();
再调用 获取数据方法getBalance(),保存ejbStore(),阻塞ejbPassivate()。这样主调一直是:激活->提取->调用方法->保存->阻塞;最后移除账户实例ejbRemove()。
这些实现EntityBean接口的方法都是由容器自动调用的。