@org.hibernate.annotations.Any 定义了一个 class 映射到多个 table 的关联。映射需要多个 column ,其中一个 column 表示 entity type 其它 columns 表示 identifier
@Any 不能在数据库中定义外键关联,因此只应该用于特殊情况,例如 audit logs, user session data 等等。
@Any 的属性
- metaDef - 指向 @AnyMetaDef.name
- metaColumn - 存储 metadata information
@AnyMetaDef 属性 - 将 metadata information 与不同的 entity type 关联
- metaType - @Any.metaColumn 的 hibernate type
- idType - entity type 的 identifier properties 的 hibernate type
@Any mapping usage
package-info.java
@AnyMetaDef(name = "PropertyMetaDef", metaType = "string", idType = "long", metaValues = {
@MetaValue(value = "S", targetEntity = StringProperty.class),
@MetaValue(value = "I", targetEntity = IntegerProperty.class) })
package org.example.demo.hibernate;
import org.example.demo.hibernate.Test.IntegerProperty;
import org.example.demo.hibernate.Test.StringProperty;
import org.hibernate.annotations.AnyMetaDef;
import org.hibernate.annotations.MetaValue;
package org.example.demo.hibernate;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Persistence;
import javax.persistence.Table;
import org.hibernate.annotations.Any;
public class Test {
public static void main(String[] args) throws Exception {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("test");
EntityManager em = factory.createEntityManager();
IntegerProperty ageProperty = new IntegerProperty();
ageProperty.name = "age";
ageProperty.value = 23;
StringProperty nameProperty = new StringProperty();
nameProperty.name = "name";
nameProperty.value = "John Doe";
PropertyHolder namePropertyHolder = new PropertyHolder();
namePropertyHolder.property = nameProperty;
em.getTransaction().begin();
em.persist(ageProperty);
em.persist(nameProperty);
em.persist(namePropertyHolder);
em.getTransaction().commit();
em.clear();
PropertyHolder ph = em.find(PropertyHolder.class, namePropertyHolder.id);
// name
System.out.println(ph.property.getName());
// John Doe
System.out.println(ph.property.getValue());
em.close();
factory.close();
}
public interface Property<T> {
String getName();
T getValue();
}
@Entity
@Table(name = "integer_property")
public static class IntegerProperty implements Property<Integer> {
@Id
@GeneratedValue
private Long id;
@Column(name = "`name`")
private String name;
@Column(name = "`value`")
private Integer value;
@Override
public String getName() {
return name;
}
@Override
public Integer getValue() {
return value;
}
}
@Entity
@Table(name = "string_property")
public static class StringProperty implements Property<String> {
@Id
@GeneratedValue
private Long id;
@Column(name = "`name`")
private String name;
@Column(name = "`value`")
private String value;
@Override
public String getName() {
return name;
}
@Override
public String getValue() {
return value;
}
}
@Entity
@Table(name = "property_holder")
public static class PropertyHolder {
@Id
@GeneratedValue
private Long id;
@Any(metaDef = "PropertyMetaDef", metaColumn = @Column(name = "property_type"))
@JoinColumn(name = "property_id")
private Property<?> property;
}
}
生成 SQL
drop table if exists integer_property cascade
drop table if exists property_holder cascade
drop table if exists string_property cascade
drop sequence if exists hibernate_sequence
create sequence hibernate_sequence start 1 increment 1
create table integer_property (id int8 not null, "name" varchar(255), "value" int4, primary key (id))
create table property_holder (id int8 not null, property_type varchar(255), property_id int8, primary key (id))
create table string_property (id int8 not null, "name" varchar(255), "value" varchar(255), primary key (id))
select nextval ('hibernate_sequence')
select nextval ('hibernate_sequence')
select nextval ('hibernate_sequence')
insert into integer_property ("name", "value", id) values ('age', 23, 1)
insert into string_property ("name", "value", id) values ('name', 'John Doe', 2)
insert into property_holder (property_type, property_id, id) values ('S', 2, 3)
select test_prope0_.id as id1_1_0_, test_prope0_.property_type as property2_1_0_, test_prope0_.property_id as property3_1_0_ from property_holder test_prope0_ where test_prope0_.id=3
select test_strin0_.id as id1_2_0_, test_strin0_."name" as name2_2_0_, test_strin0_."value" as value3_2_0_ from string_property test_strin0_ where test_strin0_.id=2
IntegerProperty 和 StringProperty 不必有共同的接口
package org.example.demo.hibernate;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Persistence;
import javax.persistence.Table;
import org.hibernate.annotations.Any;
public class Test {
public static void main(String[] args) throws Exception {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("test");
EntityManager em = factory.createEntityManager();
IntegerProperty ageProperty = new IntegerProperty();
ageProperty.name = "age";
ageProperty.value = 23;
StringProperty nameProperty = new StringProperty();
nameProperty.name = "name";
nameProperty.value = "John Doe";
PropertyHolder namePropertyHolder = new PropertyHolder();
namePropertyHolder.property = nameProperty;
em.getTransaction().begin();
em.persist(ageProperty);
em.persist(nameProperty);
em.persist(namePropertyHolder);
em.getTransaction().commit();
em.clear();
PropertyHolder ph = em.find(PropertyHolder.class, namePropertyHolder.id);
// class org.example.demo.hibernate.Test$StringProperty_$$_jvst6fc_0
System.out.println(ph.property.getClass());
// name
System.out.println(StringProperty.class.cast(ph.property).getName());
// John Doe
System.out.println(StringProperty.class.cast(ph.property).getValue());
em.close();
factory.close();
}
@Entity
@Table(name = "integer_property")
public static class IntegerProperty {
@Id
@GeneratedValue
private Long id;
@Column(name = "`name`")
private String name;
@Column(name = "`value`")
private Integer value;
public String getName() {
return name;
}
public Integer getValue() {
return value;
}
}
@Entity
@Table(name = "string_property")
public static class StringProperty {
@Id
@GeneratedValue
private Long id;
@Column(name = "`name`")
private String name;
@Column(name = "`value`")
private String value;
public String getName() {
return name;
}
public String getValue() {
return value;
}
}
@Entity
@Table(name = "property_holder")
public static class PropertyHolder {
@Id
@GeneratedValue
private Long id;
@Any(metaDef = "PropertyMetaDef", metaColumn = @Column(name = "property_type"))
@JoinColumn(name = "property_id")
private Object property;
}
}
生成的 SQL
drop table if exists integer_property cascade
drop table if exists property_holder cascade
drop table if exists string_property cascade
drop sequence if exists hibernate_sequence
create sequence hibernate_sequence start 1 increment 1
create table integer_property (id int8 not null, "name" varchar(255), "value" int4, primary key (id))
create table property_holder (id int8 not null, property_type varchar(255), property_id int8, primary key (id))
create table string_property (id int8 not null, "name" varchar(255), "value" varchar(255), primary key (id))
select nextval ('hibernate_sequence')
select nextval ('hibernate_sequence')
select nextval ('hibernate_sequence')
insert into integer_property ("name", "value", id) values ('age', 23, 1)
insert into string_property ("name", "value", id) values ('name', 'John Doe', 2)
insert into property_holder (property_type, property_id, id) values ('S', 2, 3)
select test_prope0_.id as id1_1_0_, test_prope0_.property_type as property2_1_0_, test_prope0_.property_id as property3_1_0_ from property_holder test_prope0_ where test_prope0_.id=3
select test_strin0_.id as id1_2_0_, test_strin0_."name" as name2_2_0_, test_strin0_."value" as value3_2_0_ from string_property test_strin0_ where test_strin0_.id=2
FIXME 但是怎么根据 property 的类型(IntegerProperty/StringProperty)查询 PropertyHolder 呢?测试 "from PropertyHolder ph where type(ph.property) = StringProperty" 不可行,抛异常 org.hibernate.QueryException: could not resolve property: class of: org.example.demo.hibernate.Test$PropertyHolder
出错的代码如下
package org.example.demo.hibernate;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Persistence;
import javax.persistence.Table;
import org.hibernate.annotations.Any;
public class Test {
public static void main(String[] args) throws Exception {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("test");
EntityManager em = factory.createEntityManager();
IntegerProperty ageProperty = new IntegerProperty();
ageProperty.name = "age";
ageProperty.value = 23;
StringProperty nameProperty = new StringProperty();
nameProperty.name = "name";
nameProperty.value = "John Doe";
PropertyHolder namePropertyHolder = new PropertyHolder();
namePropertyHolder.property = nameProperty;
em.getTransaction().begin();
em.persist(ageProperty);
em.persist(nameProperty);
em.persist(namePropertyHolder);
em.getTransaction().commit();
em.clear();
PropertyHolder ph = em.find(PropertyHolder.class, namePropertyHolder.id);
// name
System.out.println(ph.property.getName());
// John Doe
System.out.println(ph.property.getValue());
em.createQuery("from PropertyHolder ph where ph.id = 1").getResultList();
// em.createQuery("from PropertyHolder ph where type(ph.property) = StringProperty").getResultList();
em.createQuery("from PropertyHolder ph where type(ph.property) = :type", PropertyHolder.class)
.setParameter("type", StringProperty.class).getResultList();
em.close();
factory.close();
}
public interface Property<T> {
String getName();
T getValue();
}
@Entity(name = "IntegerProperty")
@Table(name = "integer_property")
public static class IntegerProperty implements Property<Integer> {
@Id
@GeneratedValue
private Long id;
@Column(name = "`name`")
private String name;
@Column(name = "`value`")
private Integer value;
@Override
public String getName() {
return name;
}
@Override
public Integer getValue() {
return value;
}
}
@Entity(name = "StringProperty")
@Table(name = "string_property")
public static class StringProperty implements Property<String> {
@Id
@GeneratedValue
private Long id;
@Column(name = "`name`")
private String name;
@Column(name = "`value`")
private String value;
@Override
public String getName() {
return name;
}
@Override
public String getValue() {
return value;
}
}
@Entity(name = "PropertyHolder")
@Table(name = "property_holder")
public static class PropertyHolder {
@Id
@GeneratedValue
private Long id;
@Any(metaDef = "PropertyMetaDef", metaColumn = @Column(name = "property_type"))
@JoinColumn(name = "property_id")
private Property<?> property;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Property<?> getProperty() {
return property;
}
public void setProperty(Property<?> property) {
this.property = property;
}
}
}
@org.hibernate.annotations.AnyMetaDef 可以跟随 @Any 一起,但通常放在 class or package-level 以重用。建议放在 package 上。
@ManyToAny mapping
@Any 相当于 @ManyToOne, 而 @org.hibernate.annotations.ManyToAny 相当于 @OneToMany
package org.example.demo.hibernate;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.Persistence;
import javax.persistence.Table;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.ManyToAny;
public class Test {
public static void main(String[] args) throws Exception {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("test");
EntityManager em = factory.createEntityManager();
IntegerProperty ageProperty = new IntegerProperty();
ageProperty.name = "age";
ageProperty.value = 23;
StringProperty nameProperty = new StringProperty();
nameProperty.name = "name";
nameProperty.value = "John Doe";
PropertyRepository propertyRepository = new PropertyRepository();
propertyRepository.properties.add(ageProperty);
propertyRepository.properties.add(nameProperty);
em.getTransaction().begin();
em.persist(ageProperty);
em.persist(nameProperty);
em.persist(propertyRepository);
em.getTransaction().commit();
em.clear();
PropertyRepository pr = em.find(PropertyRepository.class, propertyRepository.id);
// [org.example.demo.hibernate.Test$IntegerProperty@107e5441, org.example.demo.hibernate.Test$StringProperty@62315f22]
System.out.println(pr.properties);
em.close();
factory.close();
}
@Entity
@Table(name = "integer_property")
public static class IntegerProperty {
@Id
@GeneratedValue
private Long id;
@Column(name = "`name`")
private String name;
@Column(name = "`value`")
private Integer value;
public String getName() {
return name;
}
public Integer getValue() {
return value;
}
}
@Entity
@Table(name = "string_property")
public static class StringProperty {
@Id
@GeneratedValue
private Long id;
@Column(name = "`name`")
private String name;
@Column(name = "`value`")
private String value;
public String getName() {
return name;
}
public String getValue() {
return value;
}
}
@Entity
@Table(name = "property_repository")
public static class PropertyRepository {
@Id
@GeneratedValue
private Long id;
@ManyToAny(metaDef = "PropertyMetaDef", metaColumn = @Column(name = "property_type"))
@Cascade({ CascadeType.ALL })
@JoinTable(name = "repository_properties", joinColumns = @JoinColumn(name = "repository_id"), inverseJoinColumns = @JoinColumn(name = "property_id"))
private List<Object> properties = new ArrayList<>();
}
}
生成 SQL
alter table repository_properties drop constraint FKa4y4y2h0tlpk2k7ffgupg7sja
drop table if exists integer_property cascade
drop table if exists property_repository cascade
drop table if exists repository_properties cascade
drop table if exists string_property cascade
drop sequence if exists hibernate_sequence
create sequence hibernate_sequence start 1 increment 1
create table integer_property (id int8 not null, "name" varchar(255), "value" int4, primary key (id))
create table property_repository (id int8 not null, primary key (id))
create table repository_properties (repository_id int8 not null, property_type varchar(255), property_id int8 not null)
create table string_property (id int8 not null, "name" varchar(255), "value" varchar(255), primary key (id))
alter table repository_properties add constraint FKa4y4y2h0tlpk2k7ffgupg7sja foreign key (repository_id) references property_repository
select nextval ('hibernate_sequence')
select nextval ('hibernate_sequence')
select nextval ('hibernate_sequence')
insert into integer_property ("name", "value", id) values ('age', 23, 1)
insert into string_property ("name", "value", id) values ('name', 'John Doe', 2)
insert into property_repository (id) values (3)
insert into repository_properties (repository_id, property_type, property_id) values (3, 'I', 1)
insert into repository_properties (repository_id, property_type, property_id) values (3, 'S', 2)
select test_prope0_.id as id1_1_0_ from property_repository test_prope0_ where test_prope0_.id=3
select properties0_.repository_id as reposito1_2_0_, properties0_.property_type as property2_2_0_, properties0_.property_id as property3_2_0_ from repository_properties properties0_ where properties0_.repository_id=3
select test_integ0_.id as id1_0_0_, test_integ0_."name" as name2_0_0_, test_integ0_."value" as value3_0_0_ from integer_property test_integ0_ where test_integ0_.id=1
select test_strin0_.id as id1_3_0_, test_strin0_."name" as name2_3_0_, test_strin0_."value" as value3_3_0_ from string_property test_strin0_ where test_strin0_.id=2