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