ActiveJPA 將 Martin Fowler 的 Active Record 模式帶入 Java JPA 生態,讓實體類直接繼承 Model 即擁有 CRUD 能力,無需額外 DAO/Repository 類。支援 Hibernate、EclipseLink、OpenJPA 等所有 JPA 實現。
快速開始
1. 實體類轉換
@Entity
public class Order extends Model { // 僅需繼承 Model
@Id @GeneratedValue Long id;
String customerEmail;
String status;
BigDecimal billingAmount;
// ... 其他欄位、關聯
}
2. Maven 依賴
<dependency>
<groupId>org.activejpa</groupId>
<artifactId>activejpa-core</artifactId>
<version>0.1.5</version>
</dependency>
<repository>
<id>activejpa-repo</id>
<url>https://raw.github.com/ActiveJpa/activejpa/mvn-repo/releases</url>
</repository>
3. 初始化(ServletContextListener)
public class AppInitializer implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent e) {
ActiveJpaAgentLoader.instance().loadAgent(); // 載入 Java Agent
JPA.addPersistenceUnit("order"); // 對應 persistence.xml
}
}
核心 CRUD API
| 操作 | API 範例 |
| 主鍵查詢 | Order.findById(12345L) |
| 條件查詢 | Order.where("customerEmail", "a@b.com", "status", "shipped") |
| 首筆/唯一 | Order.first(...) / Order.one(...) |
| 全表查詢 | Order.all() |
| 存在性檢查 | Order.exists(1234L) |
| 持久化 | order.persist() |
| 刪除 | order.delete() |
| 刷新/合併 | order.refresh() / order.merge() |
進階查詢:Filter 與分頁
Filter filter = new Filter();
filter.setPageNo(1);
filter.setPerPage(25);
filter.addCondition(new Condition("customerEmail", Operator.eq, "a@b.com"));
filter.addCondition(new Condition("billingAmount", Operator.gt, 1000.00));
List<Order> orders = Order.where(filter);
Long count = Order.count(filter);
Order.deleteAll(filter); // 批量刪除
巢狀關聯查詢
// 訂單項包含產品分類為 books 的訂單
Filter filter = new Filter();
filter.addCondition(new Condition("orderItems.product.category", Operator.eq, "books"));
List<Order> orders = Order.where(filter);
集合操作
// 訂單項集合操作
order.collections("orderItems").findById(123L);
order.collections("orderItems").first("status", "shipped");
order.collections("orderItems").where("status", "cancelled");
order.collections("orderItems").all();
order.collections("orderItems").add(newOrderItem);
order.collections("orderItems").remove(orderItem);
// 集合分頁
Filter f = new Filter(); f.setPageNo(1); f.setPerPage(10);
f.addCondition(new Condition("status", Operator.eq, "shipped"));
order.collections("orderItems").where(f);
order.collections("orderItems").count(f);
動態屬性更新
// 簡單屬性
Map<String, Object> attrs = new HashMap<>();
attrs.put("billingAmount", 1000.0);
order.updateAttributes(attrs);
// 巢狀物件
Map<String, Object> attrs = new HashMap<>();
Map<String, Object> addr = new HashMap<>();
addr.put("city", "Bangalore"); addr.put("state", "Karnataka");
attrs.put("shippingAddress", addr);
attrs.put("billingAmount", 1000.0);
order.updateAttributes(attrs);
事務控制
JPAContext ctx = JPA.instance.getDefaultConfig().getContext();
ctx.beginTxn();
try {
// 業務邏輯
ctx.closeTxn(false); // 提交
} catch (Exception e) {
ctx.closeTxn(true); // 回滾
throw e;
}
Spring 整合
public class CustomContextListener extends ContextLoaderListener {
@Override
public void contextInitialized(ServletContextEvent e) {
ActiveJpaAgentLoader.instance().loadAgent();
super.contextInitialized(e);
JPA.instance.addPersistenceUnit("default",
getCurrentWebApplicationContext().getBean(EntityManagerFactory.class), true);
}
}
測試支援
public class OrderTest extends BaseModelTest { // TestNG 基類
@Test
public void testCreateOrder() {
Order order = new Order();
order.setCustomerEmail("a@b.com");
order.persist();
Assert.assertEquals(Order.where("customerEmail", "a@b.com").get(0), order);
}
}
優缺點評估
| 優點 | 缺點/注意 |
| 消除 DAO 層樣板代碼 | 耦合持久化邏輯於實體,違反純粹 DDD |
| 語法簡潔、開發效率高 | 複雜查詢仍需 JPQL/Criteria |
| 支援所有 JPA 實現 | Java Agent 需啟動參數 -javaagent |
| 動態屬性更新適合表單綁定 | 集合/陣列屬性更新尚不支援 |