Java 開發面臨多種持久化選擇,各有痛點:
| 技術 | 痛點 |
|---|---|
| 序列化 | 不支援事務、查詢、多用戶共享;粒度固定,難維護 |
| JDBC | 雙重範式(物件 vs 關聯),手工映射繁瑣,喪失 OO 優勢 |
| EJB CMP | 分散式架構重、資源消耗大、需容器、建模限制多 |
JDO 目標:專注物件模型設計,透明持久化,支援多種資料源,輕量級、可嵌入、可整合 EJB。
package com.mediamania.prototype;
import java.util.*;
import java.util.Date;
import java.text.*;
public class Movie {
// JDO 要求:無參建構子(可 private)
private Movie() {}
public Movie(String title, Date release, int duration, String rating, String genres) {
this.title = title;
this.releaseDate = release;
this.runningTime = duration;
this.rating = rating;
this.genres = genres;
this.cast = new HashSet<>();
}
// 基本屬性(標準 Java 型別)
private String title;
private Date releaseDate;
private int runningTime;
private String rating;
private String webSite;
private String genres;
// 關聯:一對多(Movie -> Role)
private Set cast; // 元素型別: Role
// 業務方法(非 JDO 強制,但建議封裝集合操作)
public void addRole(Role role) { cast.add(role); }
public Set getCast() { return Collections.unmodifiableSet(cast); }
// 靜態欄位不持久化
private static SimpleDateFormat yearFmt = new SimpleDateFormat("yyyy");
public static final String[] MPAA_RATINGS = {"G","PG","PG-13","R","NC-17","NR"};
// Getter/Setter 省略...
public String getTitle() { return title; }
public Date getReleaseDate() { return releaseDate; }
public String getRating() { return rating; }
public int getRunningTime() { return runningTime; }
public void setWebSite(String site) { webSite = site; }
public String getWebSite() { return webSite; }
public String getGenres() { return genres; }
// 工具方法
public static Date parseReleaseDate(String val) {
try { return yearFmt.parse(val); } catch (ParseException e) { return null; }
}
public String formatReleaseDate() { return yearFmt.format(releaseDate); }
}
// Actor.java
public class Actor {
private Actor() {}
public Actor(String name) { this.name = name; }
private String name;
private Set roles = new HashSet<>();
public void addRole(Role role) { roles.add(role); }
public Set getRoles() { return Collections.unmodifiableSet(roles); }
// getter/setter...
}
// Role.java (關聯實體,攜帶屬性)
public class Role {
private Role() {}
public Role(Movie movie, Actor actor, String roleName) {
this.movie = movie; this.actor = actor; this.roleName = roleName;
movie.addRole(this); actor.addRole(this); // 雙向維護
}
private Movie movie;
private Actor actor;
private String roleName; // 屬性:角色名
// getter/setter...
}
可用 .jdo XML 檔或 Annotation(JDO 2.0+)定義映射。XML 範例:
import javax.jdo.*;
import java.util.Date;
public class JdoDemo {
public static void main(String[] args) {
// 1. 取得 PersistenceManagerFactory (通常單例、線程安全)
PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory("transactions-optional");
// 2. 開啟 PersistenceManager (非線程安全,請求範圍)
PersistenceManager pm = pmf.getPersistenceManager();
Transaction tx = pm.currentTransaction();
try {
tx.begin();
// 建立物件圖
Movie movie = new Movie("Inception", new Date(), 148, "PG-13", "Sci-Fi,Action");
Actor leo = new Actor("Leonardo DiCaprio");
Actor ellen = new Actor("Elliot Page");
new Role(movie, leo, "Cobb");
new Role(movie, ellen, "Ariadne");
// 持久化:透過達到關聯自動級聯
pm.makePersistent(movie);
tx.commit();
System.out.println("持久化成功,Movie ID: " + movie.getId()); // 假設有身份欄位
} finally {
if (tx.isActive()) tx.rollback();
pm.close();
}
// 查詢範例 (JDOQL)
pm = pmf.getPersistenceManager();
try {
Query query = pm.newQuery(Movie.class, "genres == 'Sci-Fi'");
List results = (List) query.execute();
for (Movie m : results) {
System.out.println(m.getTitle() + " - " + m.getCast().size() + " roles");
}
} finally {
pm.close();
}
}
}
| 特性 | JDO | JPA/Hibernate | JDBC | EJB CMP |
|---|---|---|---|---|
| 物件透明度 | 高 (增強後無侵入) | 高 (Annotation) | 無 | 中 |
| 查詢語言 | JDOQL (物件導向) | JPQL/Criteria | SQL | EJB QL |
| 資料源支援 | RDBMS, ODBMS, XML, 檔案... | 主要 RDBMS | RDBMS | RDBMS |
| 事務支援 | 本地/全域 (JTA) | 本地/全域 | 手工 | 全域 (容器管理) |
| 運行環境 | Java SE/EE, 嵌入式 | Java SE/EE | Java SE/EE | Java EE 容器 |
| 學習曲線 | 中 | 中低 | 低 | 高 |
JDO 開創了「透明物件持久化」先河,讓開發者專注領域模型而非 SQL 映射。雖然 JPA 成為主流標準,但理解 JDO 核心概念(增強、PM、JDOQL、多資料源抽象)有助於深度掌握現代 ORM 框架底層原理。新專案建議優先採用 JPA/Hibernate,除非有明確非關聯資料源需求。