【问题标题】:JPA Embeddables - Multiple per Entity, Single Table for all EntitiesJPA Embeddables - 每个实体多个,所有实体的单个表
【发布时间】:2021-03-17 06:41:17
【问题描述】:

我正在探索能够为多个实体存储特定地址(家庭、工作等)的选项,并拥有一个包含所有地址的表,每个记录可能带有某种鉴别器。所有表的主键都是 UUID。

我正在使用 Spring Boot 2.3.6 和 JPA/Hibernate。

理想情况下,我希望为每个实体使用命名属性,而不是持有实体集合,因为它会使 DTO 映射和更新更容易。

如果没有输入数据,如果共享地址表中有每个实体和属性对的所有 NULL 值的条目,这对我来说不是问题。

在伪代码中,我希望能够将实体定义为:

@Entity
class Person {
   private Address homeAddress;
   private Address workAddress;
}

@Entity
class Incident {
   private Address incidentLocation;
}

@Entity
class Address {
   private String street;
   private String zip;
}

我已经使用 JPA 选项(例如 @Embeddable's 进行了研究,我看到的选项是 a)每个实体有一个可嵌入的(我想要多个)b)使用 @CollectionTable(我想要具体命名properties) 或 c) 使用 @AttributeOverride,这意味着每个属性在表中重复和重命名列。

我还查看了 @JoinTable@OneToMany,但这同样适用于使用集合。

我感觉@Embeddable是我需要的,但需要能够为每个使用这种类型的属性(homeAddress、workAddress、incidentLocation)指定一个鉴别器,以便地址表中的数据遵循格式

id        type      street          zip
=========================================
UUID-1    HOME      1 Main St       30002
UUID-1    WORK      10 North St     30005
UUID-2    INCIDENT  5 West Ave      30008   

作为奖励,我还希望(如果可以的话)能够创建一个JpaRepository<Address>,它允许我独立于父实体查询/更新地址。

有了所有可用的选项,我想知道是否有人知道是否有办法实现我想要的,还是我必须沿着收集路线走才能实现这一目标?谢谢

【问题讨论】:

  • 能否提供您的架构的 ER 图。
  • 如果把外键放在父表中,让Address有自己的id,会更容易实现。然后,您可以使用简单的单向@OneToOne。至于type 列,您可以将其作为常规属性映射到Address,或者为Addresses 引入SINGLE_TABLE 继承层次结构,type 是鉴别器。如果这对您不起作用,那么恐怕您需要使用集合(也许带有@MapKeyColumn 的地图会更有意义)。另外,如果你需要一个存储库,那么你需要@OneToMany,而不是@ElementCollection
  • @crizzis - 这看起来可能是一个很有前途的解决方案。我会试一试并报告。谢谢!

标签: java hibernate jpa hibernate-mapping


【解决方案1】:

只有一堆属性(homeAddressworkAddress ...),每个属性都以一对一的关系引用Address,并在设置器中设置鉴别器。

【讨论】:

    【解决方案2】:

    如果地址与许多实体有关系,最好的解决方案是使用从任何实体到​​Address的单向关系,如下所示:

        @Entity
    class Person {
       @OneToOne
       private Address homeAddress;
       @OneToOne
       private Address workAddress;
    }
    
    @Entity
    class Incident {
       @OneToOne
       private Address incidentLocation;
    }
    
    @Entity
    class Address {
       private String street;
       private String zip;
    }
    

    如果同一个地址被多次使用,你也可以使用@ManyToOne。例如homeAddressworkAddress 是一样的,并且你希望没有可为空的字段。

    在这种情况下,实体Address 不知道属于哪个实体,但关系的第二方(PersonIncident)知道哪个地址是自己的。在表中PersonIncident 将是具有地址ID 的列

    【讨论】:

      【解决方案3】:

      感谢您的帮助,我认为结合 crizzis 和最近 Jens 的建议,我实现了这个 JPA。

      @Data
      @Entity
      @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
      @DiscriminatorColumn(name = "address_type", discriminatorType = DiscriminatorType.STRING)
      @Table(name = "address")
      @TypeDef(name = UUID_CUSTOM_TYPE_NAME, typeClass = com.example.onetoone.domain.entity.UUIDCustomType.class, defaultForType = UUID.class)
      public class AddressEntity
      {
          @Id
          private UUID uuid;
      
          private String address1;
      
          private String city;
      
          private String zip;
      }
      
      
      @Data
      @EqualsAndHashCode(callSuper = true)
      @Entity
      @DiscriminatorValue("HOME")
      public class HomeAddressEntity extends AddressEntity
      {
          @OneToOne(mappedBy = "homeAddress", fetch = FetchType.LAZY)
          private PersonEntity personHome;
      }
      
      
      @Data
      @EqualsAndHashCode(callSuper = true)
      @Entity
      @DiscriminatorValue("WORK")
      public class WorkAddressEntity extends AddressEntity
      {
          @OneToOne(mappedBy = "workAddress", fetch = FetchType.LAZY)
          private PersonEntity personWork;
      }
      
      
      
      @Data
      @EqualsAndHashCode(callSuper = true)
      @Entity
      @DiscriminatorValue("INCIDENT")
      public class IncidentAddressEntity extends AddressEntity
      {
          @OneToOne(mappedBy = "incidentAddress", fetch = FetchType.LAZY)
          private IncidentEntity incident;
      }
      
      
      @Data
      @Entity
      @Table(name = "person")
      @TypeDef(name = UUIDCustomType.UUID_CUSTOM_TYPE_NAME, typeClass = com.example.onetoone.domain.entity.UUIDCustomType.class, defaultForType = UUID.class)
      public class PersonEntity
      {
          @Id
          private UUID uuid;
      
          private String name;
      
          @OneToOne(cascade = CascadeType.ALL)
          private HomeAddressEntity homeAddress;
      
          @OneToOne(cascade = CascadeType.ALL)
          private WorkAddressEntity workAddress;
      }
      
      
      @Data
      @Entity
      @Table(name = "incident")
      @TypeDef(name = UUIDCustomType.UUID_CUSTOM_TYPE_NAME, typeClass = UUIDCustomType.class, defaultForType = UUID.class)
      public class IncidentEntity
      {
          @Id
          private UUID uuid;
      
          private String name;
      
          @OneToOne(cascade = CascadeType.ALL)
          private IncidentAddressEntity incidentAddress;
      }
      

      UUID 类型 def 定义如下,以防万一有人也需要它

      public class UUIDCustomType extends AbstractSingleColumnStandardBasicType<UUID> implements LiteralType<UUID>
      {
      
          private static final long serialVersionUID = -540308541695243812L;
      
          public static final String UUID_CUSTOM_TYPE_NAME = "uuid-custom";
      
          public UUIDCustomType()
          {
      
              // https://stackoverflow.com/questions/42559938/hibernate-uuid-with-postgresql-and-sql-server
              super(VarcharTypeDescriptor.INSTANCE, UUIDTypeDescriptor.INSTANCE);
      
          }
      
          @Override
          public String getName()
          {
      
              return UUID_CUSTOM_TYPE_NAME;
      
          }
      
          @Override
          public String objectToSQLString(UUID value, Dialect dialect) throws Exception
          {
      
              return StringType.INSTANCE.objectToSQLString(value.toString(), dialect);
      
          }
      }
      

      这会在 MySQL 数据库中生成以下 DDL

      CREATE TABLE `address` (
        `address_type` varchar(31) NOT NULL,
        `uuid` varchar(255) NOT NULL,
        `address1` varchar(255) DEFAULT NULL,
        `city` varchar(255) DEFAULT NULL,
        `zip` varchar(255) DEFAULT NULL,
        PRIMARY KEY (`uuid`)
      )
      
      CREATE TABLE `person` (
        `uuid` varchar(255) NOT NULL,
        `name` varchar(255) DEFAULT NULL,
        `home_address_uuid` varchar(255) DEFAULT NULL,
        `work_address_uuid` varchar(255) DEFAULT NULL,
        PRIMARY KEY (`uuid`),
        KEY `FKoqa1ado547ntt2lc6ppx1lvr4` (`home_address_uuid`),
        KEY `FKjc3ayqtduyx0l342uu9ti32hl` (`work_address_uuid`)
      )
      
      CREATE TABLE `incident` (
        `uuid` varchar(255) NOT NULL,
        `name` varchar(255) DEFAULT NULL,
        `incident_address_uuid` varchar(255) DEFAULT NULL,
        PRIMARY KEY (`uuid`),
        KEY `FKosj0m7i6beq7ijwh68tjpfaa7` (`incident_address_uuid`)
      )
      
      alter table incident add constraint FKosj0m7i6beq7ijwh68tjpfaa7 foreign key (incident_address_uuid) references address (uuid)
      alter table person add constraint FKoqa1ado547ntt2lc6ppx1lvr4 foreign key (home_address_uuid) references address (uuid)
      alter table person add constraint FKjc3ayqtduyx0l342uu9ti32hl foreign key (work_address_uuid) references address (uuid)
      

      【讨论】:

        猜你喜欢
        • 2014-09-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-02-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多