JPA Embeddables – Multiple per Entity, Single Table for all Entities



I’m exploring options for being able to store specific addresses (home, work etc.) for multiple entities, and having a single table that holds all addresses, probably with some kind of discriminator per record. The primary keys for all tables are UUIDs.

I’m using Spring Boot 2.3.6 and JPA/Hibernate.

Ideally I’d like to use named properties per entity, rather than holding a collection of entities as it will make DTO mapping and updates easier.

It’s not an issue for me if there are entries in the shared Address table with all NULL values for each entity & property pair if no data is entered.

In pseudo code, I’d like to be able to define the entities as:

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

@Entity
class Incident {
   private Address incidentLocation;
}

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

I’ve researched using JPA options such as @Embeddable‘s and the options I have seen are to either a) have a single embeddable per entity (I want multiples) b) use @CollectionTable (I want specific named properties) or c) use @AttributeOverride which will mean repeated & renamed columns in the table for each property.

I’ve also looked at @JoinTable and @OneToMany but again this is geared towards using collections.

I get the feeling that @Embeddable is what I need, but need to be able to specify a discriminator for each property that uses this type (homeAddress, workAddress, incidentLocation) so that the data in the Address table follows the format

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   

As a bonus, I’d also like (if I could) to be able to create a JpaRepository<Address> that allows me to query/update the addresses independently of the parent entity.

With all the options available I wondered if anyone knew if there was a way to achieve what I want, or will I have to go down the collection route in order to achieve this? Thanks

Answer

Thanks to for the assistance, I think a combination of crizzis and latterly Jens’s suggestions led me to this JPA implementation.

@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;
}

with the UUID type def defined as follows in case anyone also needs it

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);

    }
}

This generates the following DDL in the MySQL database

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)


Source: stackoverflow