首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Session Bean 實戰教學 (五):建立資料庫驅動的無狀態定價元件

     編輯:Java 相關知識

前言

延續前四篇 Session Bean 基礎教學,本文實作一個實用的 無狀態 Session Bean——Pricing,從資料庫讀取產品代碼與價格,展示企業級 Bean 開發完整流程:介面設計、實作類別、資源管理、部署配置、客戶端測試。

資料庫結構


CREATE TABLE price (
    product_code VARCHAR(10) NOT NULL PRIMARY KEY,
    price DECIMAL(10,2) NOT NULL
);
-- 測試資料
INSERT INTO price (product_code, price) VALUES ('A1', 1.59);

元件介面設計

Remote 介面:Pricing.java


package usingj2ee.pricing;
import java.rmi.*;
import javax.ejb.*;

public interface Pricing extends EJBObject {
    String[] getProductCodes() throws RemoteException;
    double getPrice(String productCode) throws RemoteException, InvalidProductCodeException;
}

Home 介面:PricingHome.java


package usingj2ee.pricing;
import java.rmi.*;
import javax.ejb.*;

public interface PricingHome extends EJBHome {
    Pricing create() throws RemoteException, CreateException;
}

無狀態 Bean 的 Home 介面僅需無參 create(),因不維護客戶狀態。

實作類別:PricingImpl.java


package usingj2ee.pricing;
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
import java.sql.*;
import javax.sql.*;
import javax.naming.*;

public class PricingImpl implements SessionBean {
    private SessionContext context;
    private Connection conn;

    public PricingImpl() {}

    // 1. 設定 Session Context
    public void setSessionContext(SessionContext aContext) {
        context = aContext;
    }

    // 2. 建立時取得資料庫連線
    public void ejbCreate() throws CreateException {
        try { conn = getConnection(); }
        catch (Exception e) { throw new CreateException("DB連線失敗: " + e); }
    }

    // 3. Passivate 時關閉連線 (容器可能序列化 Bean)
    public void ejbPassivate() throws EJBException {
        try { if (conn != null) { conn.close(); conn = null; } }
        catch (Exception e) { throw new EJBException("關閉連線失敗: " + e); }
    }

    // 4. Activate 時重新取得連線
    public void ejbActivate() throws EJBException {
        try { conn = getConnection(); }
        catch (Exception e) { throw new EJBException("重新連線失敗: " + e); }
    }

    // 5. 移除時清理資源
    public void ejbRemove() throws EJBException {
        try { if (conn != null) { conn.close(); conn = null; } }
        catch (Exception e) { throw new EJBException("關閉連線失敗: " + e); }
    }

    // 業務方法:取得所有產品代碼
    public String[] getProductCodes() throws EJBException {
        Statement s = null;
        try {
            s = conn.createStatement();
            ResultSet rs = s.executeQuery("SELECT product_code FROM price");
            Vector v = new Vector();
            while (rs.next()) v.addElement(rs.getString("product_code"));
            String[] codes = new String[v.size()];
            v.copyInto(codes);
            return codes;
        } catch (Exception e) { throw new EJBException("查詢失敗: " + e); }
        finally { try { if (s != null) s.close(); } catch (Exception ignore) {} }
    }

    // 業務方法:取得指定產品價格
    public double getPrice(String productCode) throws EJBException, InvalidProductCodeException {
        PreparedStatement ps = null;
        try {
            ps = conn.prepareStatement("SELECT price FROM price WHERE product_code = ?");
            ps.setString(1, productCode);
            ResultSet rs = ps.executeQuery();
            if (rs.next()) return rs.getDouble("price");
            else throw new InvalidProductCodeException(productCode);
        } catch (SQLException e) { throw new EJBException("查詢價格失敗: " + e); }
        finally { try { if (ps != null) ps.close(); } catch (Exception ignore) {} }
    }

    // 取得連線:透過 JNDI 查找 DataSource
    protected Connection getConnection() throws SQLException, NamingException {
        InitialContext ic = new InitialContext();
        DataSource ds = (DataSource) ic.lookup("java:comp/env/jdbc/PriceDB");
        return ds.getConnection();
    }
}

關鍵設計模式解析

1. 生命週期管理資料庫連線

方法時機動作
ejbCreateBean 建立取得連線
ejbPassivate容器閒置 Bean關閉連線 (不可序列化)
ejbActivate容器重新啟用 Bean重新取得連線
ejbRemoveBean 銷毀關閉連線

2. JNDI 邏輯名稱解耦

java:comp/env/jdbc/PriceDB 為邏輯名稱,部署時綁定至實體 DataSource (如 jdbc/Cloudscape、jdbc/Oracle),Bean 程式碼無需修改即可切換資料庫。

3. PreparedStatement 防 SQL 注入

使用 ? 佔位符 + setString,自動處理引號跳脫,提升安全性與效能。

4. 例外處理規範

  • 系統層級錯誤 (SQLException、NamingException) → 包裝為 EJBException (RuntimeException),容器自動回滾交易
  • 業務邏輯錯誤 (找不到產品) → 拋出應用例外 InvalidProductCodeException,客戶端可捕捉處理

部署配置要點 (J2EE SDK deploytool)

  1. 新增 Enterprise Bean,選擇 PricingImpl.class、PricingHome.class、Pricing.class
  2. Bean 類型:Stateless Session Bean
  3. 資源參考:新增 jdbc/PriceDB,類型 javax.sql.DataSource,認證 Container
  4. JNDI 名稱頁籤:將 jdbc/PriceDB 對應至伺服器實際 DataSource (如 jdbc/Cloudscape)

客戶端測試程式:TestPricing.java


package usingj2ee.pricing;
import java.util.*;
import javax.naming.*;
import javax.rmi.*;

public class TestPricing {
    public static void main(String[] args) {
        try {
            Context ctx = new InitialContext();
            PricingHome home = (PricingHome) PortableRemoteObject.narrow(
                ctx.lookup("Pricing"), PricingHome.class);
            
            Pricing pricing = (Pricing) home.create();
            String[] codes = pricing.getProductCodes();
            
            for (String code : codes) {
                System.out.println(code + ": " + pricing.getPrice(code));
            }
            
            // 測試例外處理
            try { pricing.getPrice("f00b4r"); }
            catch (InvalidProductCodeException e) {
                System.out.println("預期例外: " + e);
            }
            
            pricing.remove();
        } catch (Exception e) { e.printStackTrace(); }
    }
}

常見部署問題排查

問題可能原因解決方向
EAR/JAR 未生成實作類別違反 EJB 限制 (finalize、static 欄位等)啟用 Verifier 檢查相容性,查看 logs 目錄錯誤訊息
客戶端找不到 JNDI 服務EJB 伺服器未啟動、Context Factory 未設定、JNDI 名稱不符確認伺服器運行、命令列設定 naming.factory.initial、大小寫完全一致
DataSource 存取錯誤未設定資源參考、邏輯名稱未綁定實體 DataSource檢查 deploytool 資源參考頁籤、JNDI 名稱頁籤綁定

結語

Pricing Bean 展示了無狀態 Session Bean 封裝資料庫存取的標準模式:Remote/Home 介面定義契約、實作類別管理資源生命週期、JNDI 解耦資源配置、PreparedStatement 安全查詢、標準例外處理。此模式可直接套用至任何「查詢型」業務邏輯,是企業級 Java 開發的基石範本。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved