實體類常需映射多張資料表:主表儲存核心屬性,擴展表/字典表儲存分類、代碼等共享資料。EJB 3.0 (JPA 1.0) 提供 `@SecondaryTable` / `@SecondaryTables` 實現單一實體跨表映射。
@Target(TYPE) @Retention(RUNTIME)
public @interface SecondaryTable {
String name(); // 表名
String catalog() default ""; // 目錄
String schema() default ""; // 模式
JoinColumn[] join() default {}; // 聯合主鍵欄位映射
UniqueConstraint[] uniqueConstraints() default {};
}
@Target(TYPE) @Retention(RUNTIME)
public @interface SecondaryTables {
SecondaryTable[] value() default {};
}
package com.kuaff.ejb3.secondary;
import javax.persistence.*;
import java.io.Serializable;
@Entity
@Table(name = "STUDENT")
@SecondaryTables({
@SecondaryTable(name = "GENDER",
join = @JoinColumn(name = "GENDER_ID", referencedColumnName = "ID"))
})
public class Student implements Serializable {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@Embedded
private Name name; // 嵌入式值物件
@Column(name = "GRADE")
private String grade;
@Column(name = "EMAIL")
private String email;
// 映射至 GENDER 表的 gender 欄位
@Column(name = "GENDER", table = "GENDER")
private String gender;
// Getter/Setter 省略
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public Name getName() { return name; }
public void setName(Name name) { this.name = name; }
public String getGrade() { return grade; }
public void setGrade(String grade) { this.grade = grade; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getGender() { return gender; }
public void setGender(String gender) { this.gender = gender; }
}
@Embeddable
public class Name implements Serializable {
@Column(name = "FIRST")
private String first;
@Column(name = "LAST")
private String last;
// Getter/Setter 省略
}
@Local
public interface StudentDAO {
Student create(Student s);
Student findById(int id);
void update(Student s);
void delete(int id);
}
@Stateless
public class StudentDAOBean implements StudentDAO {
@PersistenceContext(unitName = "ejb3PU")
private EntityManager em;
public Student create(Student s) { em.persist(s); return s; }
public Student findById(int id) { return em.find(Student.class, id); }
public void update(Student s) { em.merge(s); }
public void delete(int id) { Student s = findById(id); if (s != null) em.remove(s); }
}
| 屬性 | 映射規則 |
|---|---|
| 主鍵共享 | `@SecondaryTable.join` 指定 `GENDER_ID` 對應主表 `ID`,JPA 自動內連結 |
| 欄位指定表 | `@Column(table = "GENDER")` 明確宣告該欄位寫入副表 |
| 嵌入物件 | `@Embedded` + `@AttributeOverrides` 可將 `Name` 的 `first/last` 映射至主表 `FIRST/LAST` |
| 多副表 | `@SecondaryTables({ @SecondaryTable(...), @SecondaryTable(...) })` 支援任意數量 |
CREATE TABLE STUDENT ( ID INT AUTO_INCREMENT PRIMARY KEY, FIRST VARCHAR(50), LAST VARCHAR(50), GRADE VARCHAR(20), EMAIL VARCHAR(100), GENDER_ID INT ) ENGINE=InnoDB; CREATE TABLE GENDER ( ID INT PRIMARY KEY, GENDER VARCHAR(10) -- 'M', 'F', 'U' ) ENGINE=InnoDB; ALTER TABLE STUDENT ADD FOREIGN KEY (GENDER_ID) REFERENCES GENDER(ID);
Properties props = new Properties();
props.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
props.put("java.naming.provider.url", "jnp://localhost:1099");
InitialContext ctx = new InitialContext(props);
StudentDAO dao = (StudentDAO) ctx.lookup("StudentDAO/local");
Student s = new Student();
s.setName(new Name("John", "Doe"));
s.setGrade("Senior");
s.setEmail("john@example.com");
s.setGender("M"); // 對應 GENDER 表 ID=1
dao.create(s);
Student found = dao.findById(s.getId());
System.out.println("Gender from GENDER table: " + found.getGender());