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 。

results matching ""

    No results matching ""