在Java的JPA(Java Persistence API)中,复合主键是一个常见的需求,尤其是在处理多对多关系或需要多个字段共同作为主键的场景中。传统上,我们可以通过@IdClass来实现复合主键,但这种方式需要在实体类和主键类中重复定义相同的字段,显得有些冗余。相比之下,@EmbeddedId提供了一种更为简洁和直观的解决方案,它通过对象组合的方式,将复合主键类嵌入到实体类中,避免了字段的重复定义。本文将通过一个具体的实例,详细介绍如何使用@EmbeddedId来实现复合主键。
@Embeddable
public class CompositeTaskId implements Serializable {
private int employeeId;
private int taskId;
public CompositeTaskId() {
}
public CompositeTaskId(int employeeId, int taskId) {
this.employeeId = employeeId;
this.taskId = taskId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CompositeTaskId taskId1 = (CompositeTaskId) o;
if (employeeId != taskId1.employeeId) return false;
return taskId == taskId1.taskId;
}
@Override
public int hashCode() {
return Objects.hash(employeeId, taskId);
}
}
在这个类中,employeeId和taskId是作为主键的两个字段。我们还重写了equals和hashCode方法,这是为了确保在使用复合主键时,能够正确地比较和哈希这些字段。
2. 定义实体类
接下来,我们定义一个实体类Task,并使用@EmbeddedId注解将复合主键类嵌入其中。以下是Task类的定义:
java复制
@Entity
public class Task {
@EmbeddedId
private CompositeTaskId taskId;
private String taskName;
private Date date;
public Task() {
}
public Task(CompositeTaskId taskId) {
this.taskId = taskId;
}
// Getter and Setter methods
public CompositeTaskId getTaskId() {
return taskId;
}
public void setTaskId(CompositeTaskId taskId) {
this.taskId = taskId;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
@Override
public String toString() {
return "Task{" +
"taskId=" + taskId +
", taskName='" + taskName + '\'' +
", date=" + date +
'}';
}
}
在这个类中,taskId字段被标记为@EmbeddedId,表示它是一个复合主键。其他字段如taskName和date则是一些普通的业务字段。
3. 数据库操作示例
为了展示如何使用Task类和CompositeTaskId类进行数据库操作,我们编写了一个简单的主类ExampleMain。以下是一个示例代码,展示了如何插入数据、执行原生SQL查询以及通过复合主键查找实体:
java复制
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
import java.util.Date;
import java.util.List;
public class ExampleMain {
public static void main(String[] args) throws Exception {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory(“example-unit”);
try {
persistEntity(emf);
runNativeQuery(emf);
findEntityById(emf);
} finally {
emf.close();
}
}
private static void persistEntity(EntityManagerFactory emf) throws Exception {
System.out.println("-- Persisting entity --");
EntityManager em = emf.createEntityManager();
CompositeTaskId taskId = new CompositeTaskId(5, 10);
Task task = new Task(taskId);
task.setTaskName("coding");
task.setDate(new Date());
em.getTransaction().begin();
em.persist(task);
em.getTransaction().commit();
em.close();
}
private static void runNativeQuery(EntityManagerFactory emf) {
System.out.println("-- Native query --");
EntityManager em = emf.createEntityManager();
Query query = em.createNativeQuery("Select * from Task");
List list = query.getResultList();
for (Object o : list) {
if (o instanceof Object[]) {
System.out.println(Arrays.toString((Object[]) o));
} else {
System.out.println(o);
}
}
em.close();
}
private static void findEntityById(EntityManagerFactory emf) {
System.out.println("-- Finding entity --");
EntityManager em = emf.createEntityManager();
CompositeTaskId taskId = new CompositeTaskId(5, 10);
Task task = em.find(Task.class, taskId);
System.out.println(task);
em.close();
}
}
输出结果
运行上述代码后,我们可能会得到以下输出:
复制
– Persisting entity –
– Native query –
Select * from Task
[5, 10, 2025-01-23 12:34:56.789, coding]
– Finding entity –
Task{taskId=CompositeTaskId{employeeId=5, taskId=10}, taskName=‘coding’, date=2025-01-23 12:34:56.789}
4. 总结
通过使用@EmbeddedId,我们可以以一种更为简洁和直观的方式实现复合主键,避免了在实体类和主键类中重复定义字段的麻烦。同时,这种方式也更符合面向对象的设计思想,通过对象组合的方式将主键类嵌入到实体类中。在实际开发中,@EmbeddedId是一个非常实用的工具,可以帮助我们更好地管理复杂的数据库关系。
希望本文的介绍和示例能够帮助你更好地理解和使用@EmbeddedId来实现复合主键。