12、构建业务逻辑层:会话 Bean 设计与实现

构建业务逻辑层:会话 Bean 设计与实现

1. 引言

在掌握了 Java 持久化 API(Java Persistence API)之后,接下来将深入探讨如何在企业 Bean 中运用该 API,为示例应用程序构建业务逻辑层。具体而言,将创建会话 Bean 来实现示例应用的业务逻辑,并对新创建的会话 Bean 进行快速测试。

2. 规划业务逻辑层

在构建会话 Bean 之前,需要进行规划,通常要回答以下问题:
- 应该构建哪些 Bean?
- 每个 Bean 将执行哪些操作?
- 构建的 Bean 是否需要在方法调用之间保持会话状态?

在本示例中,将创建两个 Bean:OrderBean 和 CartBean。下面分别介绍这两个 Bean:
| Bean 名称 | 类型 | 功能 | 方法 | 会话状态需求 |
| — | — | — | — | — |
| OrderBean | 无状态会话 Bean | 处理订单 | placeOrder、getOrdersList | 不需要 |
| CartBean | 有状态会话 Bean | 管理购物车 | initialize、addItem、removeItem、getItems、emptyCart | 需要 |

2.1 OrderBean 无状态会话 Bean

OrderBean 用于处理订单,至少包含两个业务方法:placeOrder 和 getOrdersList。由于该 Bean 在方法调用之间不需要保持会话状态,因此将其创建为无状态会话 Bean。当用户下单时,调用 placeOrder 方法,会在底层的 orders 表中创建新记录,并可能在 details 表中创建相关记录。以下是 OrderBean 的源代码:

package ejbjpa.ejb;
import java.io.Serializable;
import javax.ejb.EJBException;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
import ejbjpa.entities.*;
@Stateless
public class OrderBean implements OrderSample {
    @PersistenceContext
    private EntityManager em;
    public void placeOrder(Integer cust_id,
                           Integer empno) {
        try {
            Customer cust = (Customer) em.find(Customer.class, cust_id);
            Employee emp = (Employee) em.find(Employee.class, empno);
            Order order = new Order();
            order.setCustomer(cust);
            order.setEmployee(emp);
            em.persist(order);
        } catch (Exception e) {
            throw new EJBException(e.getMessage());
        }
    }
    public List<Order> getOrdersList() {
        List<Order> orders = null;
        try {
            orders = (List<Order>)em.createQuery("SELECT o FROM Order o")
                   .getResultList();
        } catch (Exception e) {
            throw new EJBException(e.getMessage());
        }
        return orders;
    }
}

当调用 placeOrder 方法时,会触发一系列复杂的数据库操作,具体流程如下:
1. 根据传入的 cust_id 和 empno 参数获取 Customer 和 Employee 实体的实例。
2. 创建 Order 实体的实例,并设置其字段值。
3. 调用 EntityManager 的 persist 方法,尝试在 orders 表中插入新记录。
4. 数据库服务器在插入记录之前触发 neworder BEFORE INSERT 触发器,自动填充订单记录的 shipping_date 和 delivery_estimate 字段。
5. 数据库服务器触发 afterinsertorder AFTER INSERT 触发器,将与下单客户相关的记录从 shoppingCarts 表移动到 details 表。
6. 数据库服务器触发 details 表上的 newdetail 触发器,调用 updateBooks 存储过程,尝试更新 books 表中的指定记录。
7. 数据库服务器在更新 books 表中的记录之前触发 newquantity BEFORE UPDATE 触发器,如果更新记录的 quantity 字段的新值小于 0,则会引发错误。

2.2 CartBean 有状态会话 Bean

CartBean 用于管理购物车,需要保持会话状态,因为每个实例将与特定客户的购物车相关联,并在整个客户会话期间保持其状态。以下是 CartBean 的源代码:

package ejbjpa.ejb;
import java.util.List;
import javax.ejb.Remove;
import javax.ejb.Stateful;
import javax.ejb.EJBException;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import ejbjpa.entities.*;
@Stateful
public class CartBean implements Cart {
    @PersistenceContext(type=PersistenceContextType.EXTENDED)
    EntityManager em;
    Integer custId;
    List<ShoppingCart> items;
    public void initialize(Integer cust_id) {
        if (cust_id == null) {
            throw new EJBException("Null cust_id provided.");
        } else {
            custId = cust_id;
        }
    }
    public void addItem(String item_id, Integer quantity, Double price) {
        ShoppingCart cart = (ShoppingCart) em.find(ShoppingCart.class, 
                new ShoppingCartKey(custId, item_id));
        if(cart != null)
        {
            em.remove(cart);
            em.flush();
        }
        cart = new ShoppingCart();
        cart.setCart_id(custId);
        cart.setBook_id(item_id);
        cart.setUnits(quantity);
        cart.setUnit_price(price);
        em.persist(cart);
    }
    public void removeItem(String item_id) {
        ShoppingCart cart = (ShoppingCart) em.find(ShoppingCart.class, 
                new ShoppingCartKey(custId, item_id));
        if(cart == null){
            throw new EJBException("This item is not in cart.");
        } else {
            em.remove(cart);
        }
    }
    public List<ShoppingCart> getItems() {
        items = (List<ShoppingCart>)em.createQuery("SELECT s FROM " +
                "ShoppingCart s WHERE s.cart_id =:cust_id")
               .setParameter("cust_id", custId)
               .getResultList();
        em.clear();
        return items;
    }
    @Remove
    public Integer emptyCart() {
        Integer num =0;
        num = em.createQuery("DELETE FROM ShoppingCart s WHERE s.cart_id =:cust_id")
               .setParameter("cust_id", custId)
               .executeUpdate();
        return num;
    }
    @Remove
    public void clearCartInstance() {
    }
}

在 CartBean 的代码中,使用了 @Remove 注解,用于标记在方法执行完成后应移除 Bean 实例的方法。有两个移除方法:emptyCart 用于清空客户的购物车,返回删除的行数;clearCartInstance 用于销毁 Bean 实例。

3. 编译、打包和部署会话 Bean

创建好会话 Bean 后,需要将其编译、打包并部署到应用服务器。具体操作步骤如下:
1. 编译 :在终端中进入示例项目目录(sampleapp),执行以下命令编译 Bean 和 JPA 实体:

# javac -d target src/ejbjpa/entities/*.java src/ejbjpa/ejb/*.java

如果编译成功,类文件将出现在 sampleapp/target/ejbjpa/entities 和 sampleapp/target/ejbjpa/ejb 目录中。
2. 创建 META - INF 目录并添加配置文件 :创建 sampleapp/target/META - INF 目录,并将 persistence.xml 文件放入其中。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="sampleapp-pu" transaction-type="JTA">
        <jta-data-source>jdbc/mysqlpool</jta-data-source>
    </persistence-unit>
</persistence>
  1. 打包 :在 target 目录下执行以下命令创建部署归档文件:
cd target
jar cvf appejb.jar .
  1. 部署 :将归档文件部署到应用服务器:
asadmin deploy appejb.jar

如果一切正常,将收到以下消息:

Command deploy executed successfully

4. 测试新创建的会话 Bean

在进入示例的表示层之前,建议对新创建和部署的会话 Bean 进行测试。对于 OrderBean 的简单测试,可以参考之前相关内容。下面将介绍如何使用 Servlet 测试 CartBean 有状态会话 Bean。

4.1 使用 Servlet 测试会话 Bean

为了测试 CartBean 有状态会话 Bean,可以创建两个 Servlet,依次启动它们,使第二个 Servlet 使用第一个 Servlet 调用期间创建和使用的 CartBean 实例。

4.1.1 第一个 Servlet(TestSampleServlet)

第一个 Servlet 主要完成以下任务:
1. 获取 CartBean 实例,并将其与 HttpSession 对象关联,绑定到会话参数。
2. 向购物车实例中添加几个商品。
3. 遍历购物车,显示添加的商品。
4. 仅在发生异常时,从会话中移除购物车实例。

以下是 TestSampleServlet 的源代码:

package ejbjpa.servlets;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.ejb.EJB;
import javax.naming.InitialContext;
import java.util.List;
import java.util.Iterator;
import ejbjpa.entities.*;
import ejbjpa.ejb.*;
public class TestSampleServlet extends HttpServlet {
    @EJB(name="ejb/CartBean", beanInterface=Cart.class)
    public void doGet(
            HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        response.setBufferSize(8192);
        PrintWriter out = response.getWriter();
        HttpSession session = request.getSession();
        Cart cart = (Cart)session.getAttribute("cartatt");
        try{
            if (cart == null) {
                cart = (Cart) (new 
                        InitialContext()).lookup("java:comp/env/ejb/CartBean");
                session.setAttribute("cartatt", cart);
                cart.initialize(2);
                out.println("Cart initialized" +"<br/>");
            }
            cart.addItem("1430209631", 1, 29.69);
            cart.addItem("1590595300", 2, 32.99);
            List<ShoppingCart> items = cart.getItems();
            Iterator i = items.iterator();
            ShoppingCart shoppingCart;
            while (i.hasNext()) {
                shoppingCart = (ShoppingCart) i.next();
                out.println("Cart id: "+ shoppingCart.getCart_id() +"<br/>");
                out.println("Book id: "+ shoppingCart.getBook_id() +"<br/>");
                out.println("Quantity: "+ shoppingCart.getUnits() +"<br/>");
                out.println("Unit price: "+ shoppingCart.getUnit_price() +"<br/>");
                out.println("----------"+ "<br/>");
            }
        }
        catch (Exception e){
            e.printStackTrace();
            session.removeAttribute("cartatt");
        }
    }
}
4.1.2 第二个 Servlet(TestSampleServletCont)

第二个 Servlet 主要完成以下任务:
1. 获取 CartBean 实例,并将其与 HttpSession 对象关联,绑定到会话参数。
2. 移除第一个 Servlet 执行期间添加的一个购物车商品。
3. 遍历购物车,显示当前购物车中的商品。
4. 清空购物车并销毁其实例。
5. 从会话中移除购物车实例。

以下是 TestSampleServletCont 的源代码:

package ejbjpa.servlets;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.ejb.EJB;
import javax.naming.InitialContext;
import java.util.List;
import java.util.Iterator;
import javax.ejb.EJBException;
import ejbjpa.entities.*;
import ejbjpa.ejb.*;
public class TestSampleServletCont extends HttpServlet {
    @EJB(name="ejb/CartBean", beanInterface=Cart.class)
    public void doGet(
            HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        response.setBufferSize(8192);
        PrintWriter out = response.getWriter();
        HttpSession session = request.getSession();
        Cart cart = (Cart)session.getAttribute("cartatt");
        try{
            if (cart == null) {
                cart = (Cart) (new InitialContext()).lookup("java:comp/env/ejb/CartBean");
                session.setAttribute("cartatt", cart);
                cart.initialize(2);
                out.println("Cart initialized" +"<br/>");
            }
            out.println("Remove the first item from the cart "+ "<br/>");
            cart.removeItem("1430209631");
            cart.removeItem("1590595300");
            cart.addItem("1590595300", 2, 32.99);
            List<ShoppingCart> items = cart.getItems();
            ShoppingCart shoppingCart;
            Iterator i = items.iterator();
            while (i.hasNext()) {
                shoppingCart = (ShoppingCart) i.next();
                out.println("Cart id: "+ shoppingCart.getCart_id() +"<br/>");
                out.println("Book id: "+ shoppingCart.getBook_id() +"<br/>");
                out.println("Quantity: "+ shoppingCart.getUnits() +"<br/>");
                out.println("Unit price: "+ shoppingCart.getUnit_price() +"<br/>");
                out.println("----------"+ "<br/>");
            }
            Integer num = cart.emptyCart();
            out.println(num + " item(s) removed " + "<br/>");
        }
        catch (Exception e){
            e.printStackTrace();
        }
        finally {
            session.removeAttribute("cartatt");
        }
    }
}
4.1.3 部署描述符

使用这些 Servlet 时,部署描述符(web.xml)应包含 ejb - ref 元素,示例如下:

<web-app
    ...
    <ejb-ref>
        <ejb-ref-name>ejb/CartBean</ejb-ref-name>
        <ejb-ref-type>Session</ejb-ref-type>
        <remote>ejbjpa.ejb.Cart</remote>
    </ejb-ref>
</web-app>
4.1.4 测试结果

编译、打包和部署测试应用程序后,启动 TestSampleServlet,正常情况下输出如下:

Cart initialized
Cart id: 2
Book id: 1430209631
Quantity: 1
Unit price: 29.69
----------
Cart id: 2
Book id: 1590595300
Quantity: 2
Unit price: 32.99
----------

启动 TestSampleServletCont,输出如下:

Remove the first item from the cart 
Cart id: 2
Book id: 1590595300
Quantity: 2
Unit price: 32.99
----------
1 item(s) removed

这表明 CartBean 实例被成功使用,并且购物车操作正常。

5. 问题排查

如果成功将 Bean 部署到应用服务器但启动调用该 Bean 的应用程序时出现错误,可以查看 JNDI 树浏览列表,检查服务器上可用资源对象的 JNDI 名称。如果部署的 Bean 不在 JNDI 树中,可以查看 glassfish_dir/domains/domain1/generated/xml/j2ee - modules/your_ejb_name/META - INF 目录,该目录包含应用服务器实际使用的部署描述符。

6. 在 NetBeans IDE 中继续示例项目

如果使用 NetBeans IDE 实现示例应用程序,可以按照以下步骤将本章创建的会话 Bean 添加到之前创建的 IDE 项目中:
1. 进入文件系统中 NetBeans IDE 创建示例项目(sampleappIDE)时生成的项目根目录,然后进入 sampleappIDE - ejb/src/java 目录。
2. 在 sampleappIDE/sampleappIDE - ejb/src/java/ejbjpa 目录下创建 ejb 目录,并复制之前创建的会话 Bean 源文件(Cart.java、CartBean.java、OrderSample.java 和 OrderBean.java)。
3. 从操作系统的开始菜单启动 NetBeans IDE。
4. 在项目窗口中,展开 sampleappIDE - ejb/Source Packages 节点,应看到 ejbjpa.entities 和 ejbjpa.ejb 两个包。
5. 在项目窗口中,双击 ejbjpa.ejb 包节点,应看到之前复制的会话 Bean 源文件。
6. 在项目窗口中,右键单击 sampleappIDE - ejb 节点,选择 Build Project。如果一切正常,输出窗口的最后一条消息应为 BUILD SUCCESSFUL。
7. 在项目窗口中,右键单击 sampleappIDE - ejb 节点,选择 Deploy Project。如果一切正常,输出窗口的最后一条消息应为 BUILD SUCCESSFUL。
8. 选择 File ➤ Exit 关闭 IDE。

部署部署归档文件后,可以按照前面介绍的方法创建 Servlet 应用程序来测试其中包含的会话 Bean。

通过以上步骤,成功构建了示例应用程序的业务逻辑层,使用了无状态和有状态会话 Bean,并对其进行了测试。后续将进一步探讨事务管理,并构建示例应用程序的表示层。

7. 会话 Bean 设计与实现总结

7.1 会话 Bean 类型对比

会话 Bean 类型 特点 适用场景 示例
无状态会话 Bean 不保持会话状态,多个客户端可共享同一实例 处理无状态业务逻辑,如订单处理 OrderBean
有状态会话 Bean 保持会话状态,每个客户端有独立实例 管理与客户端会话相关的状态,如购物车管理 CartBean

7.2 关键操作流程回顾

下面用 mermaid 流程图展示创建、部署和测试会话 Bean 的整体流程:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px

    A([开始]):::startend --> B(规划业务逻辑层):::process
    B --> C(创建会话 Bean):::process
    C --> D(编译会话 Bean):::process
    D --> E(创建 META - INF 目录并添加配置文件):::process
    E --> F(打包会话 Bean):::process
    F --> G(部署会话 Bean 到应用服务器):::process
    G --> H{部署是否成功?}:::decision
    H -->|是| I(测试会话 Bean):::process
    H -->|否| J(问题排查):::process
    J --> B
    I --> K([结束]):::startend

8. 会话 Bean 设计的最佳实践

8.1 业务逻辑分离

将会话 Bean 的业务逻辑与数据访问逻辑分离,提高代码的可维护性和可测试性。例如,在 OrderBean 中,placeOrder 方法通过操作 JPA 实体来间接处理数据,而不是直接操作数据库。

8.2 状态管理

对于有状态会话 Bean,合理管理会话状态。如 CartBean 中,使用 custId 实例变量来跟踪客户信息,并在方法调用之间保持购物车状态。同时,使用 @Remove 注解来确保在适当的时候销毁 Bean 实例,避免内存泄漏。

8.3 异常处理

在会话 Bean 的业务方法中,进行适当的异常处理。如 OrderBean 和 CartBean 中,捕获异常并抛出 EJBException,方便调用者处理错误。

9. 会话 Bean 与其他组件的交互

9.1 与 JPA 实体的交互

会话 Bean 通过 JPA 实体与数据库进行交互。例如,OrderBean 的 placeOrder 方法通过 JPA 实体 Customer、Employee 和 Order 来处理订单数据,利用 EntityManager 的方法进行持久化操作。

9.2 与 Servlet 的交互

会话 Bean 可以与 Servlet 结合使用,实现业务逻辑与表示层的分离。如使用 Servlet 测试 CartBean 时,Servlet 通过 HttpSession 对象管理 CartBean 实例,调用其业务方法完成购物车操作。

10. 未来展望

10.1 事务管理

后续将深入探讨事务管理,确保会话 Bean 在处理业务逻辑时的数据一致性和完整性。例如,在订单处理过程中,保证订单记录和相关细节记录的插入操作在一个事务中完成。

10.2 表示层构建

在完成事务管理后,将构建示例应用程序的表示层。可以使用 JSF 技术实现用户界面,与会话 Bean 进行交互,为用户提供友好的操作体验。

10.3 性能优化

随着应用程序的发展,可能需要对会话 Bean 进行性能优化。例如,优化数据库查询语句、合理配置事务隔离级别等,提高应用程序的响应速度和吞吐量。

通过对会话 Bean 的设计、实现、测试和优化,可以构建出高效、稳定的企业级应用程序。在实际开发中,应根据具体业务需求选择合适的会话 Bean 类型,并遵循最佳实践,确保代码的质量和可维护性。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值