先看最终的目录结构:
(注:去掉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接口的方法都是由容器自动调用的。