EJB 3.0 引入 JPA(Java Persistence API),以註解替代 XML 描述符,實現領域模型與關係數據庫的聲明式映射。本文通過「學生-老師(多對多)、學生-檔案(一對一)」經典案例,系統講解關聯映射核心註解與完整工程代碼。
| 實體 | 關係 | 說明 |
|---|---|---|
| Student | 多對多 | 一學生多老師,一老師多學生,需關聯表 |
| Teacher | ||
| Student | 一對一 | 一學生一檔案,雙向導航 |
| Dossier | 一對一 | 檔案屬於單一學生 |
@ManyToMany(
cascade = {CascadeType.CREATE, CascadeType.MERGE},
fetch = FetchType.EAGER,
isInverse = true // 標識關係擁有方,由 Teacher 維護關聯表
)
@AssociationTable(
table = @Table(name = "STUDENT_TEACHER"),
joinColumns = @JoinColumn(name = "TEACHER_ID"), // 關聯表中指向 Teacher 的外鍵
inverseJoinColumns = @JoinColumn(name = "STUDENT_ID") // 關聯表中指向 Student 的外鍵
)
public Set<Teacher> getTeachers() { ... }
關鍵點:
cascade:持久化操作級聯(創建、合併)fetch:EAGER 立即加載 / LAZY 延遲加載isInverse:雙向關係中,僅一方為擁有方(維護關聯表),另一方設 mappedBy 或 isInverse=true@AssociationTable 定義關聯表名、雙向外鍵列@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "DOSSIER_ID") // Student 表中外鍵列,指向 Dossier 主鍵
public Dossier getDossier() { ... }
雙向導航:Dossier 端同樣定義 @OneToOne(mappedBy="dossier"),實現:
com.kuaff.ejb3.relationships ├── Student.java # 核心實體,雙向關聯樞紐 ├── Teacher.java # 實體,維護多對多擁有方 ├── Dossier.java # 實體,一對一被動端 ├── EntityTest.java # @Remote 業務接口 ├── EntityTestBean.java # @Stateless 會話 Bean 實現 ├── Client.java # 測試客戶端 ├── jndi.properties # JNDI 配置 └── build.xml # Ant 構建腳本
@Entity
@Table(name = "STUDENT")
public class Student implements Serializable {
@Id(generate = GeneratorType.AUTO)
private int id;
private String first, last;
// 一對一:Student 為擁有方,外鍵在 STUDENT 表
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "DOSSIER_ID")
private Dossier dossier;
// 多對多:非擁有方,isInverse=true
@ManyToMany(cascade = {CascadeType.CREATE, CascadeType.MERGE},
fetch = FetchType.EAGER, isInverse = true)
@AssociationTable(table = @Table(name = "STUDENT_TEACHER"),
joinColumns = @JoinColumn(name = "TEACHER_ID"),
inverseJoinColumns = @JoinColumn(name = "STUDENT_ID"))
private Set<Teacher> teachers;
// getter/setter...
}
@Entity
public class Teacher implements Serializable {
@Id(generate = GeneratorType.IDENTITY)
private Long id;
private String name, info;
// 擁有方:mappedBy 指向 Student 端屬性名,或配置 @AssociationTable
@ManyToMany(cascade = {CascadeType.CREATE, CascadeType.MERGE},
fetch = FetchType.EAGER)
@AssociationTable(table = @Table(name = "STUDENT_TEACHER"),
joinColumns = @JoinColumn(name = "TEACHER_ID", referencedColumnName = "ID"),
inverseJoinColumns = @JoinColumn(name = "STUDENT_ID", referencedColumnName = "ID"))
private Set<Student> students;
// getter/setter...
}
@Stateless
public class EntityTestBean implements EntityTest {
@Inject EntityManager manager; // 容器注入持久化上下文
public void createData() {
// 構建對象圖:Teacher ←→ Students、Student ←→ Dossier
Teacher t1 = new Teacher(); t1.setName("hushisheng"); ...
Teacher t2 = new Teacher(); t2.setName("liyongchi"); ...
Student s1 = new Student(); s1.setFirst("晁"); s1.setLast("岳攀");
Dossier d1 = new Dossier(); d1.setResume("這是晁岳攀的檔案");
s1.setDossier(d1);
// ... s2, s3, d2, d3
t1.setStudents(new HashSet<>(Arrays.asList(s1, s2)));
t2.setStudents(new HashSet<>(Collections.singletonList(s3)));
manager.persist(t1); manager.persist(t2); // 級聯持久化所有關聯對象
}
public List<Teacher> findByName(String name) {
return manager.createQuery(
"FROM Teacher t WHERE t.name = :name")
.setParameter("name", name).getResultList();
}
}
mappedBy/isInverse,一對一用 @JoinColumn 所在端)CascadeType.ALL 謹慎用於多對多(刪除教師會刪除所有關聯學生?通常不合理),建議 PERSIST, MERGE@AssociationTable(JBoss/Hibernate 擴展)或標準 JPA @JoinTable,明確表名、列名、引用列student.setDossier(dossier); dossier.setStudent(student)),保證對象圖一致性EJB 3.0 / JPA 以註解聲明式映射徹底簡化了關係建模。掌握擁有方、級聯、抓取、關聯表四大核心概念,配合EntityManager 管理對象圖生命週期,即可優雅實現複雜領域模型持久化。代碼即文檔,模型即資產。