I need a standard approach to compare a JPA Entity to it's DTOs and determine if they represent the same business object. I can think of three approaches, a custom method on each DTO, an interface with a static method or a comparator.
Building on the answer from João Dias, approach 4 - Inheritance.
Pros/Cons
- Approach 1 - Bad all the way around
- Approach 2 - Uses interface to favor composition over inheritance but requires semantics using custom method name (
businessKeysMatch()
) - Approach 3 - Does not require the source code to be modified
- Approach 4 - Simplifies semantics as it uses standard
equals()
but requires "boilerplateequals()/hashcode()
Any other pros/cons of the approaches or suggestions on other approaches?
In the end, I chose to use approach 2 (Interface). The Inheritance approach showed promise but one of the classes being a JPA Entity made the mapping more complex than I wanted.
Thanks for reading and thinking about my question!
Background
Entity
a database key
a business key that is unique enforced at ORM and database that determines equality (
equals()
/hashcode()
)public attributes (name, address, age, etc)
non-public / confidential attributes (password, refresh token, SSN, etc)
@Data @EqualsAndHashCode(onlyExplicitlyIncluded = true) @Entity @Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "businessKey1", "businessKey2" }) }) class UserEntity { @Id Long id; @NotNull @EqualsAndHashCode.Include Long businessKey1; @NotNull @EqualsAndHashCode.Include Long businessKey2; String name; Integer age; String password; String refreshToken; String SSN; }
DTO (full)
business key that determines equality (
equals()
/hashcode()
)public attributes (name, address, age, etc)
@Data @EqualsAndHashCode(onlyExplicitlyIncluded = true) class UserDto { @EqualsAndHashCode.Include @NotNull Long businessKey1; @EqualsAndHashCode.Include @NotNull Long businessKey2; String name; String address; Integer age; }
DTO (limited)
business key that determines equality (
equals()
/hashcode()
)selected public attributes (name)
@Data @EqualsAndHashCode(onlyExplicitlyIncluded = true) class UserNameDto { @EqualsAndHashCode.Include @NotNull Long businessKey1; @EqualsAndHashCode.Include @NotNull Long businessKey2; String name; }
Approach 1 - Custom method added to each User*Dto
boolean businessKeysMatch(UserEntity entity) {
if((this.getBusinessKey1() == entity.getBusinessKey1()) && (this.getBusinessKey2() == entity.getBusinessKey2()))
return true;
return false;
}
Approach 2 - Add static method to common interface
interface UserKeys {
Long getBusinessKey1();
Long getBusinessKey2();
static boolean businessKeysMatch(UserKeys o1, UserKeys o2) {
if((o1.getBusinessKey1() == o2.getBusinessKey1()) && (o1.getBusinessKey2() == o2.getBusinessKey2()))
return true;
return false;
}
}
class UserEntity implements UserKeys {
// no other changes
}
class UserDto implements UserKeys {
// no other changes
}
class UserEntity implements UserKeys {
// no other changes
}
Approach 3 - Comparator
interface UserBusinessKey {
Long getBusinessKey1();
Long getBusinessKey2();
}
class UserDto implements UserCompare {
// no other changes
}
class UserEntity implements UserCompare {
// no other changes
}
class UserCompare implements Comparator<UserBusinessKey> {
public int compare(UserBusinessKey o1, UserBusinessKey o2) {
int key1Compare = o1.getBusinessKey1().compareTo(o2.getBusinessKey1());
if (key1Compare == 0)
return o1.getBusinessKey2().compareTo(o2.getBusinessKey2());
return key1Compare;
}
}
Approach 4 - Inheritance with equals/hashcode of base class only
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
@Data
abstract class UserBase {
@NotNull
Long businessKey1;
@NotNull
Long businessKey2;
// lombok generates a standard equals() / hashcode() pair
}
@SuperBuilder
@Getter
@Setter
@ToString
@Entity
@Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "businessKey1", "businessKey2" }) })
class UserEntity extends UserBase {
@Id
Long id;
String name;
Integer age;
String password;
String refreshToken;
String SSN;
// handcoded equals/hashcode that only call super
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public int hashCode() {
return super.hashCode();
}
}
@SuperBuilder
@Getter
@Setter
@ToString
class UserDto extends UserBase {
String name;
String address;
Integer age;
// handcoded equals/hashcode that only call super
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public int hashCode() {
return super.hashCode();
}
}
CodePudding user response:
If you have multiple User*Dto
I would create an abstract AbstractUserDto
that would be then extended by all you concrete User DTOs. There you could place the method that you show in your Approach 1 (so that you don't duplicate the same code over and over again):
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public abstract class AbstractUserDto {
@EqualsAndHashCode.Include
@NotNull
Long businessKey1;
@EqualsAndHashCode.Include
@NotNull
Long businessKey2;
String name;
public final boolean businessKeysMatch() {
return (this.getBusinessKey1() == entity.getBusinessKey1()) && (this.getBusinessKey2() == entity.getBusinessKey2());
}
}