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

JBoss EJB 3.0 Entity Bean 完整範例:註解驅動的 O/R 映射實戰

     編輯:Java 相關知識

前言:EJB 3.0 的註解革命

EJB 3.0 (JSR 220) 以「配置由註解取代 XML」為核心,徹底簡化了實體 Bean 開發。本文基於 JBoss EJB 3.0 Preview 5 官方教學範例,完整呈現 Order-LineItem 雙向關聯、ShoppingCart 狀態型 Session Bean、EntityManager 操作的端到端實作。

領域模型:Order ↔ LineItem (一對多雙向)

Order.java — 訂單實體 (擁有方)


package org.jboss.tutorial.entity.bean;

import javax.persistence.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;

@Entity
@Table(name = "PURCHASE_ORDER")  // 指定資料表名,預設為類別名
public class Order implements Serializable {

    private int id;
    private double total;
    private Collection lineItems;

    @Id(generate = GeneratorType.AUTO)  // 自動遞增主鍵,對應資料庫 IDENTITY/SEQUENCE
    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 (lineItems == null) lineItems = new ArrayList();
        LineItem item = new LineItem();
        item.setOrder(this);                    // 設定反向關聯 (關鍵!)
        item.setProduct(product);
        item.setQuantity(quantity);
        item.setSubtotal(quantity * price);
        lineItems.add(item);
        total += quantity * price;
    }

    // 一對多雙向關聯:Order 為擁有方,LineItem 為被擁有方
    @OneToMany(
        cascade = CascadeType.ALL,      // 全級聯:PERSIST/MERGE/REMOVE/REFRESH
        fetch = FetchType.EAGER,        // 急切載入 (預設 LAZY)
        mappedBy = "order"              // 關聯由 LineItem.order 欄位維護
    )
    public Collection getLineItems() { return lineItems; }
    public void setLineItems(Collection lineItems) { this.lineItems = lineItems; }
}

LineItem.java — 訂單明細實體 (被擁有方)


package org.jboss.tutorial.entity.bean;

import javax.persistence.*;

@Entity  // 無 @Table,資料表名預設為 LINEITEM
public class LineItem implements Serializable {

    private int id;
    private double subtotal;
    private int quantity;
    private String product;
    private Order order;  // 反向導航屬性

    @Id(generate = GeneratorType.AUTO)
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }

    public double getSubtotal() { return subtotal; }
    public void setSubtotal(double subtotal) { this.subtotal = subtotal; }

    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
    @JoinColumn(name = "order_id")  // LINEITEM 表的外鍵欄位
    public Order getOrder() { return order; }
    public void setOrder(Order order) { this.order = order; }
}

業務邏輯:ShoppingCart 狀態型 Session Bean

ShoppingCart.java — 遠端業務介面


package org.jboss.tutorial.entity.bean;

import javax.ejb.Remote;
import javax.ejb.Remove;

@Remote
public interface ShoppingCart {
    void buy(String product, int quantity, double price);
    Order getOrder();
    @Remove void checkout();  // 標記移除:方法完成後 Bean 實例銷毀
}

ShoppingCartBean.java — 實作類


package org.jboss.tutorial.entity.bean;

import javax.ejb.*;
import javax.persistence.EntityManager;

@Stateful  // 狀態型:維護用戶會話狀態 (購物車內容)
public class ShoppingCartBean implements ShoppingCart {

    @Inject  // EJB 3.0 早期註解,後標準化為 @PersistenceContext
    private EntityManager manager;  // 核心:查詢、建立、尋找、移除實體

    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
    public void checkout() {
        manager.persist(order);  // 交易邊界結束時同步寫入資料庫
    }
}

客戶端測試程式


package org.jboss.tutorial.entity.client;

import org.jboss.tutorial.entity.bean.*;
import javax.naming.InitialContext;

public class Client {
    public static void main(String[] args) throws Exception {
        InitialContext ctx = new InitialContext();
        ShoppingCart cart = (ShoppingCart) ctx.lookup(ShoppingCart.class.getName());

        System.out.println("Buying 2 memory sticks");
        cart.buy("Memory stick", 2, 500.00);

        System.out.println("Buying a laptop");
        cart.buy("Laptop", 1, 2000.00);

        System.out.println("Print cart:");
        Order order = cart.getOrder();
        System.out.println("Total: $" + order.getTotal());
        for (LineItem item : order.getLineItems()) {
            System.out.println(item.getQuantity() + " " + item.getProduct() + " " + item.getSubtotal());
        }

        System.out.println("Checkout");
        cart.checkout();  // 觸發 @Remove,Bean 銷毀,持久化完成
    }
}

關鍵註解深度解析

註解位置語義關鍵屬性
@Entity類別標記為 JPA 實體name (實體名稱,JPQL 用)
@Table類別指定資料表映射name, schema, catalog, uniqueConstraints
@IdGetter主鍵欄位generate: AUTO/IDENTITY/SEQUENCE/TABLE
@OneToManyGetter一對多關聯cascade, fetch, mappedBy, targetEntity
@ManyToOneGetter多對一關聯cascade, fetch, optional
@JoinColumnGetter指定外鍵欄位name, referencedColumnName, nullable
@Stateful類別狀態型 Session Beanname, mappedName, description
@Inject / @PersistenceContext欄位注入 EntityManagerunitName, type (TRANSACTION/EXTENDED)
@Remove方法標記 Bean 移除retainIfException (預設 false)

EntityManager 核心操作速查


// 持久化新實體
em.persist(entity);

// 根據主鍵查詢
Order order = em.find(Order.class, orderId);

// JPQL 查詢
Query q = em.createQuery("SELECT o FROM Order o WHERE o.total > :min");
q.setParameter("min", 1000.0);
List orders = q.getResultList();

// 合併離脫實體
Order merged = em.merge(detachedOrder);

// 移除實體
em.remove(managedOrder);

// 刷新同步資料庫狀態
em.refresh(entity);

// 取得交易同步狀態
em.flush();  // 強制同步到資料庫

執行與驗證


# 1. 啟動 JBoss (含 HSQLDB)
cd jboss_home/bin
run.bat -c all

# 2. 建置與部署 (Ant)
ant build
ant deploy

# 3. 執行客戶端
ant run-client

# 4. 驗證資料庫
# HSQL Database Manager: 啟動腳本中找到 startDatabaseManager
# 執行查詢:SELECT * FROM PURCHASE_ORDER; SELECT * FROM LINEITEM;

Log4j 配置 (解決 JBoss Preview 5 缺少 appender 警告)


log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=record.log
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %d{hh:mm:ss} %t %c{1} -%m%n
log4j.appender.R.MaxBackupIndex=1
log4j.appender.R.MaxFileSize=100KB

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) -%m%n

log4j.rootLogger=stdout,R

常見坑點與避雷

  • 雙向關聯雙邊維護:`Order.addPurchase()` 中必須 `item.setOrder(this)`,否則外鍵為 NULL
  • mappedBy 指向正確欄位名:`@OneToMany(mappedBy="order")` 對應 `LineItem.getOrder()`
  • CascadeType.ALPHA 包含 REMOVE:刪除 Order 會級聯刪除所有 LineItem,謹慎使用
  • EAGER 載入效能陷阱:大量訂單查詢時會連帶載入所有明細,建議預設 LAZY,需要時用 JOIN FETCH
  • @Inject vs @PersistenceContext:早期 JBoss 專用,標準化請用 `@PersistenceContext`
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved