Home > OS >  Compare JPA Entity and DTO
Compare JPA Entity and DTO

Time:12-08

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 "boilerplate equals()/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());
    }
}
  • Related