[Java] dto와 entity 변환하기 -3 <Generic Method>

개발개발·2021년 6월 10일
1

제네릭 메소드는 stream을 사용하면서 자동완성칸에서 자주 보던 내용들이었다. 간단하게 살펴봤던 것들이 떠올랐고 반복되는 코드를 줄여줄 방법이라는 생각이 들었다.

  • Util.class
@Component
public class Utils<T> {
    static ObjectMapper objectMapper = new ObjectMapper();
    static Random random=new Random();
    public Utils() {
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS,false);
    }
    public static<F,T> T to(Class<T> to, F from){
            if(from == null) return null;
            return objectMapper.convertValue(from,to);
        }
    }

아주 간단하다. 생성하면서 ObjectMapper에 설정값을 넣어주면서 생성하고 제네릭 메소드를 통해서 매번 dto에 생성하지 않아도 변환할 수 있게 되었다.

하지만 위의 방법으로도 여전히 관계맺어진 테이블을 변환하기 위해서는 번거로움음 감수해야 한다. 그래서 관계가 복잡한 테이블의 경우 dto안에 다시 메소드를 만들고 있다.

 - usersDto -> 외래키 추가
```java
@Data
@NoArgsConstructor
public class UsersDto {

    private int id;
    private String token;
    private String password;
    private Date regDate;
    private String email;
    
    private TestDto testDto;

    @Builder
    public UsersDto(int id, String token, String password, Date regDate, String email,TestDto testDto) {
        this.id = id;
        this.token = token;
        this.password = password;
        this.regDate = regDate;
        this.email = email;
        this.testDto = testDto;
    }
// 1편에서 사용한 방식
public static UsersEntity converter(UsersDto usersDto){
        // OneToOne관계의 테이블
        TestDto testDto = usersDto.getTestEntity();
    	TestEntity testEntity = testDto.builder()
                .i1(testDto.getI1())
                .s1(testDto.getS1())
                .f1(testDto.getF1())
                .build();
                
        return UsersEntity.builder()
                .token(usersDto.getToken())
                .password(usersDto.getPassword())
                .regDate(usersDto.getRegDate())
                .email(usersDto.getEmail())
                .testDto(testDto)
                .build();
    }
    
   public static UsersEntity mapper(UsersDto usersDto){
        if(usersDto==null) return null;
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS,false);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
        UsersEntity usersEntity = objectMapper.convertValue(usersDto,UsersEntity.class);
        TestDto testDto = objectMapper.convertValue(testDto, TestEntity);
        usersEntity.setTestDto(testDto);
        return usersEntity;
    }
    
    public static UsersDto convert(USersEntity usersEntity){
        UsersDto usersDto = Utils.to(UsersDto.class,usersEntity);
        usersDto.setTestDto(Utils.to(TestDto.class,usersEntity.getTestEntity()));
        return usersDto;
    }
}

조금씩 편리해지고 있는것 같다. 이 방식으로 사용하던 중 변환당할 대상만 있으면 무엇으로 변환할지 충분히 유추할 수 있었다. suffix만 dto나 entity로 바꿔주면 변환할 클래스를 작성하지 않아도 될것 같았다. 이런 방식을 만들기 위해서 공부하다 자바 reflection에 대해서 알게 되었다.

reflection은 간단하게 말하면 메타데이터다. 클래스가 어떤 디렉토리에 있고 필드값, 메소드, 어노테이션 등 다양한 정보를 알 수 있다. 리플렉션을 통해서 개별적을 생성된 객체에 대해서 메소드를 실행시킬 수만 있다면 Entity 속의 Entity(관계 맺는 테이블)도 재귀적으로 처리할 수 있을거라고 생각했다. 하지만 메타데이터라 그런지 class에 선언된 메소드는 알수 있지만 개별적인 객체에 대해서는 메소드를 실행하는 방법을 찾지 못했다.(혹시 아시는 분이 계신다면 댓글로 알려주시면 감사하겠습니다.)
reflection은 추후에 포스팅하기로 하고 지금은 변환할 객체만 넣어주면 변환할 클래스를 알아서 찾아주는 기능을 만들었다.

@Component
public class Utils<T> {
    static ObjectMapper objectMapper = new ObjectMapper();
    static Random random=new Random();
    public Utils() {
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS,false);
    }
    
    // 변환할 클래스를 직접 입력, 변환 당하는 객체만 알면 추론이 가능하다.
    public static<F,T> T to(Class<T> to, F from){
            if(from == null) return null;
            return objectMapper.convertValue(from,to);
    }
    
    public static<F,T> T to(F from){
            if(from == null) return null;
            return objectMapper.convertValue(from,opponentClass(from.getClass()));
    }
    // List를 변환해준다.
    public static<F,T> List<T> to(List<F> froms){
    	List<T> ts = new ArrayList<>();
        if(froms.size() == 0) return ts;
        //froms.stream().forEach(f -> ts.add(objectMapper.convertValue(f,to)));
        for (F f : froms) {
            ts.add(objectMapper.convertValue(f,to));
        }
        return ts;
    }
    
    // 같은 쌍의 dto나 entity 찾기
    // 디렉토리이름을 어떻게 지었는지에 따라서 찾는 방법은 다르다.
    private static<T> Class opponentClass(Class<T> from){
        Class objectClass = null;
        String className = from.getClass().getName();
        // 경로를 포함한 클래스 이름이 나온다.
        className =className.substring(className.lastIndexOf(".")+1).toLowerCase();
        
        // 클래스의 전체 이름(경로를 포함)
        // com.test.example.dto.UsersDto
        // com.test.example.entity.UsersEntity
        // 디렉토리 이름과 클래스 이름을 수정해서 ClassLoader를 통해 변환할 클래스를 찾아낸다.
        try {
            if(className.contains("entity")){
                objectClass = from.getClass().getClassLoader().loadClass(from.getClass().getName().replace("Entity","Dto").replace("entity","dto"));
            }else if(className.contains("dto")) {
                objectClass = from.getClass().getClassLoader().loadClass(from.getClass().getName().replace("Dto","Entity").replace("dto","entity"));
            }else {
                return null;
            }
        } catch (ClassNotFoundException e) {
            return null;
        }
        return objectClass;
    }
}

지금은 위와같은 방법으로 dto와 entity를 변환하면서 사용하고 있다. 계속 공부하면서 코드를 많이 줄였고 새로운 내용들도 많이 알게 되었다.

코드량은 많이 줄어들었지만 관계맺는 테이블이 있는 경우 여전히 일일히 변환하고 값을 넣어줘야하는 번거로움이 있다. 이것도 편하게 수정하는 방법이 있을지 찾아봐야겠다.

profile
청포도루이보스민트티

0개의 댓글