@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

results matching ""

    No results matching ""