前些天,我遇到了一个有趣的映射挑战,我认为这可能值得分享。如果你是经验丰富的JPA用户,这对你来说可能不是什么新鲜事,但那些不太有经验的用户可能会觉得有帮助 :)
TL;DR - JPA 允许你覆盖嵌入式对象和嵌入式对象集合的数据库列;可以使用 @AttributeOverride
和 @AssociationOverride
来实现这一点。
让我们假设以下实体模型表示一个人及其家庭地址和商业地址

有趣的部分是,Person
有两个与 Address
的关联,一个与 "homeAddress" 角色相关,另一个与 "businessAddress" 角色相关。反过来,Address
有一个或多个地址行。
当使用JPA映射这些类型时,Person
自然成为了一个实体。由于 Person
和 Address
之间存在组合关系,所以后者使用 @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")
当然这是有道理的;由于 Address
在 Person
中嵌入两次,其属性必须在 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)
)