能找到我吗 - 高级可嵌入映射

发布者:    |       Hibernate ORM

前些天,我遇到了一个有趣的映射挑战,我认为这可能值得分享。如果你是经验丰富的JPA用户,这对你来说可能不是什么新鲜事,但那些不太有经验的用户可能会觉得有帮助 :)

TL;DR - JPA 允许你覆盖嵌入式对象和嵌入式对象集合的数据库列;可以使用 @AttributeOverride@AssociationOverride 来实现这一点。

让我们假设以下实体模型表示一个人及其家庭地址和商业地址

Entity model

有趣的部分是,Person 有两个与 Address 的关联,一个与 "homeAddress" 角色相关,另一个与 "businessAddress" 角色相关。反过来,Address 有一个或多个地址行。

当使用JPA映射这些类型时,Person 自然成为了一个实体。由于 PersonAddress 之间存在组合关系,所以后者使用 @Embeddable 映射。同样适用于 AddressLine,它也是一个 @Embeddable,包含在由 Address 拥有的元素集合中。

所以你会得到以下这些类

@Entity
public class Person {

    @Id
    private long id;
    private String name;
    private Address homeAddress;
    private Address businessAddress;

    // constructor, getters and setters...
}
@Embeddable
public class Address {

    private boolean active;

    @ElementCollection
    private List<AddressLine> lines = new ArrayList<AddressLine>();

    // constructor, getters and setters...
}
@Embeddable
public class AddressLine {

    private String value;

    // constructor, getters and setters...
}

@AttributeOverride

让我们启动一个使用这些类型的会话工厂(这使用了Hibernate ORM 5中引入的新引导API)并看看效果如何

StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
    .applySetting( AvailableSettings.SHOW_SQL, true )
    .applySetting( AvailableSettings.FORMAT_SQL, true )
    .applySetting( AvailableSettings.HBM2DDL_AUTO, "create-drop" )
    .build();

SessionFactory sessionFactory = new MetadataSources( registry )
    .addAnnotatedClass( Person.class )
    .buildMetadata()
    .buildSessionFactory();

嗯,这并不顺利

org.hibernate.MappingException: Repeated column in mapping for entity:
org.hibernate.bugs.Person column: active (should be mapped with insert="false" update="false")

当然这是有道理的;由于 AddressPerson 中嵌入两次,其属性必须在 Person 表中的唯一列名中进行映射。可以使用 @AttributeOverride 注解来实现这一点

@Entity
public class Person {

    @Id
    private long id;
    private String name;

    @AttributeOverride(name = "active", column = @Column(name = "home_address_active"))
    private Address homeAddress;

    @AttributeOverride(name = "active", column = @Column(name = "business_address_active"))
    private Address businessAddress;

    // constructor, getters and setters...
}

有了这些,会话工厂成功启动。但让我们看看创建的表

create table Person (
    id bigint not null,
    business_address_active boolean,
    home_address_active boolean,
    name varchar(255),
    primary key (id)
)

create table Person_lines (
    Person_id bigint not null,
    "value" varchar(255)
)

@AssociationOverride

Person 表看起来没问题,但只有一个表用于地址行。这是一个问题,因为它意味着在从数据库中读取人时,无法区分商业地址行和家庭地址行。

那么怎么办呢?这次@AttributeOverride没有帮助,因为它不是需要重新定义的单列,而是整个表。但幸运的是,@AttributeOverride有一个同伴,@AssociationOverride。这个注解可以用来配置这种情况所需的表

@Entity
public class Person {

    @Id
    private long id;
    private String name;

    @AttributeOverride(name = "active", column = @Column(name = "home_address_active"))
    @AssociationOverride(name = "lines", joinTable = @JoinTable(name = "Person_HomeAddress_Line"))
    private Address homeAddress;

    @AttributeOverride(name = "active", column = @Column(name = "business_address_active"))
    @AssociationOverride(name = "lines", joinTable = @JoinTable(name = "Person_BusinessAddress_Line"))
    private Address businessAddress;

    // constructor, getters and setters...
}

就这样,现在你会得到创建Person表和两个不同地址行的DDL

create table Person (
    id bigint not null,
    business_address_active boolean,
    home_address_active boolean,
    name varchar(255),
    primary key (id)
)

create table Person_BusinessAddress_Line (
    Person_id bigint not null,
    "value" varchar(255)
)

create table Person_HomeAddress_Line (
    Person_id bigint not null,
    "value" varchar(255)
)

回到顶部