0

Having the following entities:

@Entity
@Table
public class Employee {

  @Id
  @GeneratedValue(generator = "UUID")
  @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
  private UUID id;

  @NotBlank
  private String firstName;

  @NotBlank
  private String lastName;

  private Gender gender;
  private Instant birthDate;
  private String email;
  private String corporateEmail;

  @Embedded
  private Address address;

  // and many more 
  // + all getters and setters
}

@Entity
@Table
public class Discipline {

  @Id
  @GeneratedValue(generator = "UUID")
  @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
  private UUID id;

  @NotNull
  private Instant date;
  private String infraction;
}

A Discipline is for a specific Employee. An Employee may have 0 to several disciplines. A discipline is for only one Employee, no more no less. In the Discipline world (micro-service), it requires only a few attributes from the full Employee class (id, firstName and lastName). In the Employee World (micro-service), all Employee fields are relevant.

How do I properly set my relation between both entities without fetching the Employee entity for each Discipline but only the required fields as I would do with a projection?

Must I refactor my Employee entity by extracting a superclass with only the attributes' subset?

In a magical world, I would like to have Discipline entity defines as follow:

@Entity
@Table
public class Discipline {

  @Id
  @GeneratedValue(generator = "UUID")
  @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
  private UUID id;

  @NotNull
  private Instant date;
  private String infraction;

  @ManyToOne
  private EmployeeProfile employeeProfile;
}

public interface EmployeeProfile {

  UUID getId();
  String getFirstName();
  String getLastName();
}

Where EmployeeProfile here should look similar to what a Spring Data JPA interface based projection would use.

Aimed goals by doing this:

  • Avoid incoming issues when our entities will be versionned. In fact, we don't want an addDiscipline request to fail due to an outdated employee instance on an irrelevant attribute. This could happen if we link Discipline to the full `Employee
  • Improve performance by reducing load to our DB (slimmer entities the better)
  • Reduce coupling between our entities as much as possible.
  • Keep entities and DB design as simple as possible.
Jeep87c
  • 1,050
  • 16
  • 36
  • If exposing the domain model directly via a REST API doesn't work for you then use the DTO pattern. https://www.baeldung.com/entity-to-and-from-dto-for-a-java-spring-application – Alan Hay Jul 11 '19 at 07:25
  • How about a separate `@Immutable EmployeeProfile` entity, mapped to the `EMPLOYEE` table? – crizzis Jul 11 '19 at 08:55
  • @crizzis If I understand properly, you're suggesting on using a one-to-one association using shared primary keys linking both `Employee` and `EmployeeProfile` entities correct? If so, would it not result in two DB tables? – Jeep87c Jul 11 '19 at 14:47
  • 1
    I'm suggesting to create another entity mapped to the same `EMPLOYEE` table, but with only a subset of the columns mapped onto entity fields – crizzis Jul 11 '19 at 14:56
  • @AlanHay it was my first natural solution to this problem but, to do so, my Discipline entity would only have the employee id (linked properly with JPA which I don't know yet how) as `@ManyToOne(...) private UUID employeeId;`. This solution was challenged by stating it's not really JPA compliant having an entity persisted with only the id of another entity. I could still make it work by using property access strategy as explained here https://stackoverflow.com/a/2598301 since getting the identifier won't trigger a full fetch from DB. But it's kind of odd. – Jeep87c Jul 11 '19 at 15:04
  • @crizzis Ok I think I get what you meant. It would be as `@Entity @Table("Employee") public class Employee {}` with `@Entity @Table("Employee") public class EmployeeProfile {}` where the latter one has only a subset of all the `Employee` attributes. Correct? – Jeep87c Jul 11 '19 at 15:05
  • 1
    Yup. As I mentioned, you might also want `@Immutable` to prevent accidental modifications – crizzis Jul 11 '19 at 15:17
  • Feeling dumb asking this but which `@Immutable` you're refering too? `org.hibernate.annotations.Immutable`, `javax.annotation.concurrent.Immutable`, `org.hibernate.validator.internal.util.stereotypes.Immutable` or `org.springframework.data.annotation.Immutable`? And this is only to prevent modifications to this entity since anyway default cascade operation is "do nothing", correct? – Jeep87c Jul 11 '19 at 17:38

1 Answers1

0

Thanks to @crizzis who proposed what I was looking for. Here's the solution if anybody else is looking for this in future.

SOLUTION: Simply have two entities, one with all the attributes and another one with only the subset you're interested in and have both entities using same table as follow:

@Entity
@Table(name = "EMPLOYEE")
public class Employee {

  @Id
  @GeneratedValue(generator = "UUID")
  @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
  private UUID id;

  @NotBlank
  private String firstName;

  @NotBlank
  private String lastName;

  private Gender gender;
  private Instant birthDate;
  private String email;
  private String corporateEmail;

  @Embedded
  private Address address;

  ...
}

@Entity
@Table(name = "EMPLOYEE")
@Immutable
public class EmployeeProfile {

  @Id
  private UUID id;
  private String firstName;
  private String lastName;
}

Than you can have link other entities on this EmployeeProfile class as follow:

@Entity
@Table
public class Discipline {

  @Id
  @GeneratedValue(generator = "UUID")
  @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
  private UUID id;

  @NotNull
  private Instant date;
  private String infraction;

  @ManyToOne
  @JoinColumn(name = "employee_id")
  private EmployeeProfile employeeProfile;
}

As by default on ManyToOnerelationship no operations are cascaded, this suits perfectly our needs.

@AlanHay proposed to go down the REST way by having a REST endpoint returning this specific DTO. It is another great solution especially in a micro-service architecture.

As in our case, all our entities are still persisted in the same DB, we are going with the above solution as first step of doing micro-services is to build a great/decoupled monolithic application and because it will handle everything in only one DB query. And when the day come to split Discipline and Employee in different micro-services, it will be very simple to do so as Discipline table hold only the employee id, avoiding painful DB migration.

Jeep87c
  • 1,050
  • 16
  • 36