JPA 2.0 added support for derived identifiers which allow an entity to borrow the identifier from a many-to-one or one-to-one association.
Derived identifier with @MapsId
package org.example.demo.hibernate;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
import javax.persistence.Persistence;
public class Test {
public static void main(String[] args) {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("test");
EntityManager em = factory.createEntityManager();
Person p = new Person();
PersonDetails pd = new PersonDetails();
pd.person = p;
pd.nickName = "bill";
em.getTransaction().begin();
em.persist(p);
em.persist(pd);
em.getTransaction().commit();
em.clear();
PersonDetails detail = em.find(PersonDetails.class, pd.id);
// org.example.demo.hibernate.Test$Person@1b1637e1
System.out.println(detail.person);
em.close();
factory.close();
}
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
}
@Entity(name = "PersonDetails")
public static class PersonDetails {
@Id
private Long id;
String nickName;
@ManyToOne
@MapsId
private Person person;
}
}
生成 SQL
alter table PersonDetails drop constraint FK8c9ip9bcimb1rv32v062tm7qb
drop table if exists Person cascade
drop table if exists PersonDetails cascade
drop sequence if exists hibernate_sequence
create sequence hibernate_sequence start 1 increment 1
create table Person (id int8 not null, primary key (id))
create table PersonDetails (nickName varchar(255), person_id int8 not null, primary key (person_id))
alter table PersonDetails add constraint FK8c9ip9bcimb1rv32v062tm7qb foreign key (person_id) references Person
select nextval ('hibernate_sequence')
insert into Person (id) values (1)
insert into PersonDetails (nickName, person_id) values ('bill', 1)
select test_perso0_.person_id as person_i2_1_0_, test_perso0_.nickName as nickName1_1_0_ from PersonDetails test_perso0_ where test_perso0_.person_id=1
select test_perso0_.id as id1_0_0_ from Person test_perso0_ where test_perso0_.id=1
两条 select 语句是连续执行的,中间没有运行时代理。
The @MapsId annotation can also reference columns from an @EmbeddedId identifier as well.
Derived identifier @PrimaryKeyJoinColumn
package org.example.demo.hibernate;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Persistence;
import javax.persistence.PrimaryKeyJoinColumn;
public class Test {
public static void main(String[] args) {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("test");
EntityManager em = factory.createEntityManager();
Person p = new Person();
PersonDetails pd = new PersonDetails();
pd.nickName = "bill";
em.getTransaction().begin();
em.persist(p);
pd.person = p;
pd.id = p.id;
em.persist(pd);
em.getTransaction().commit();
em.clear();
PersonDetails detail = em.find(PersonDetails.class, pd.id);
// org.example.demo.hibernate.Test$Person@df4b72
System.out.println(detail.person);
em.close();
factory.close();
}
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
}
@Entity(name = "PersonDetails")
public static class PersonDetails {
@Id
private Long id;
String nickName;
@ManyToOne
@PrimaryKeyJoinColumn
private Person person;
}
}
生成 SQL
alter table PersonDetails drop constraint FK8c9ip9bcimb1rv32v062tm7qb
drop table if exists Person cascade
drop table if exists PersonDetails cascade
drop sequence if exists hibernate_sequence
create sequence hibernate_sequence start 1 increment 1
create table Person (id int8 not null, primary key (id))
create table PersonDetails (id int8 not null, nickName varchar(255), person_id int8, primary key (id))
alter table PersonDetails add constraint FK8c9ip9bcimb1rv32v062tm7qb foreign key (person_id) references Person
select nextval ('hibernate_sequence')
insert into Person (id) values (1)
insert into PersonDetails (nickName, person_id, id) values ('bill', 1, 1)
select test_perso0_.id as id1_1_0_, test_perso0_.nickName as nickName2_1_0_, test_perso0_.person_id as person_i3_1_0_, test_perso1_.id as id1_0_1_ from PersonDetails test_perso0_ left outer join Person test_perso1_ on test_perso0_.person_id=test_perso1_.id where test_perso0_.id=1
注意,这时 PersonDetails 是有自己的 id 栏位的,与 person_id 不是同一个栏位。
注意,使用 @PrimaryKeyJoinColumn 时,需要自己设置 pd.id = p.id;
即第 25 行。只有第 24 行的 pd.person = p;
是不够的,会抛异常 org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned beforeQuery calling save(): org.example.demo.hibernate.Test$PersonDetails
注意,只有第 25 行,没有第 24 行会导致写入的 person_id = null ,然后读取时第 33 行输出的 person 就是 null
如果在 PersonDetails.id 上加上 @GeneratedValue 则不需要设置 pd.id = p.id;
@PrimaryKeyJoinColumn 与 @ManyToOne 一起使用时和 @JoinColumn 好像没有差别!而与 @MapsId 有很大不同。
@PrimaryKeyJoinColumn 与 OneToOne 一起使用
package org.example.demo.hibernate;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Persistence;
import javax.persistence.PrimaryKeyJoinColumn;
public class Test {
public static void main(String[] args) {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("test");
EntityManager em = factory.createEntityManager();
Person p = new Person();
PersonDetails pd = new PersonDetails();
pd.nickName = "bill";
em.getTransaction().begin();
em.persist(p);
pd.id = p.id;
em.persist(pd);
em.getTransaction().commit();
em.clear();
PersonDetails detail = em.find(PersonDetails.class, pd.id);
// org.example.demo.hibernate.Test$Person@7cf7aee
System.out.println(detail.person);
em.close();
factory.close();
}
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
}
@Entity(name = "PersonDetails")
public static class PersonDetails {
@Id
private Long id;
String nickName;
@OneToOne
@PrimaryKeyJoinColumn
private Person person;
}
}
生成 SQL
drop table if exists Person cascade
drop table if exists PersonDetails cascade
drop sequence if exists hibernate_sequence
create sequence hibernate_sequence start 1 increment 1
create table Person (id int8 not null, primary key (id))
create table PersonDetails (id int8 not null, nickName varchar(255), primary key (id))
select nextval ('hibernate_sequence')
insert into Person (id) values (1)
insert into PersonDetails (nickName, id) values ('bill', 1)
select test_perso0_.id as id1_1_0_, test_perso0_.nickName as nickName2_1_0_, test_perso1_.id as id1_0_1_ from PersonDetails test_perso0_ left outer join Person test_perso1_ on test_perso0_.id=test_perso1_.id where test_perso0_.id=1
注意这时 PersonDetails 没有建立外键!
总之, @PrimaryKeyJoinColumn 不如 @MapsId 。