Mapstruct @ObjectFactory 활용시 주의점

devcha·2024년 11월 16일
0

TIL

목록 보기
2/2
post-thumbnail

@ObjectFactory

Mapstruct Mapper의 구현체는 일반적으로 기본생성자로써 객체를 생성하고 setter를 통해서 필드들을 초기화 한다.

target class의 기본생성자를 사용할수 없거나 다른 생성 로직을 통해 객체를 생성하고 싶을때 @ObjectFactory 어노테이션이 붙은 메서드를 구현하여 직접 객체 생성로직을 지정할수있다.

Mapstruct mapper 사용시 @ObjectFactory 어노테이션 활용 과정에서 겪은 이슈

  • User 엔티티를 초기화하는 과정에서 입력받은 파라미터들을 검증하는 로직을 태우기 위해 팩토리 메서드 형태로 생성하도록 구현해둔 상태
  • JPA의 경우 리플렉션을 통해서 객체를 초기화하니 문제가 없었지만,
    mapper를 통해 dto -> entity 변환 과정에서 문제 발생
@Mapper(componentModel = "spring")
interface UserMapper {  
    fun toEntity(userDto: UserDto): User  
  
    fun toDto(user: User): UserDto  
  
    @ObjectFactory  
    fun createUser(userDto: UserDto): User {  
        return User.create(userDto.name)  
    }  
}

위처럼 구현하고 빌드했을때 자동 생성되는 구현체는 다음과 같다.


@Component  
public class UserMapperImpl implements UserMapper {  
  
   ...
   
    @Override
    public User toEntity(UserDTO userDTO) {
        if ( userDTO == null ) {
            return null;
        }
		// 문제가 되는 부분
        User user = createUser( userDTO );

        return user;
    }

    @Override
    public User createUser(UserDTO userDTO) {
        if ( userDTO == null ) {
            return null;
        }
		// createUser가 toEntity로직이랑 동일한 코드로 override됨
        User user = createUser( userDTO );

        return user;
    }
    
    ...
}

createUser 메서드가 override되었으나 구현 자체가 이상하다, createUser라는 매퍼 메서드로 인식하고 구현된것 같다. (UserDTO -> User)

mapstruct 동작까지 살펴보진 않았지만 User를 생성할때 기본생성자를 사용하지 않고 createUser를 호출하는 방식으로 구현된것으로 보아 @ObjectFactory 자체를 인식하지 못하진 않은것 같고, createUser를 일반 매퍼 메서드로써 인식하며 구현해버리는것이 문제이지 않을까 싶다.

결과적으로 UserMapper interface의 createUser를 default 메서드로써 호출할것이라 생각했으나 실제 구현체는 의도와 달랐고, 다르게 문제를 해결했다.

해결방법

1. abstract class 활용

-> 객체간 매핑이 단순하지 않고 복잡한 경우엔 직접 매퍼 메서드를 구현하는 경우가 더 편한 경우가 있다. 그럴때 abstract class를 사용하여 일부 매퍼 메서드만 직접 구현해서 사용하는 경우가 있었는데.
그때 기억을 떠올려 createUser를 concrete method로 정의하였다.

@Mapper(componentModel = "spring")
abstract class UserMapper {  
	abstract fun toEntity(userDto: UserDto): User  
  
	abstract fun toDto(user: User): UserDto  
  
	@ObjectFactory  
	fun createUser(userDto: UserDto): User {  
		return User.create(userDto.name)  
	}  
}

UserMapper를 abstract class로 변경하여 빌드했을때는 구현체가 createUser를 override하고 있지 않고 UserMapper의 createUser를 호출하고있다.

실제 구현체를 살펴보면

@Component
public class UserMapperImpl extends UserMapper {

    ...
   
    @Override
    public User toEntity(UserDTO userDTO) {
        if ( userDTO == null ) {
            return null;
        }
		// UserMapper.createUser를 호출함
        User user = createUser( userDTO );

        return user;
    }
    
}

createUser가 override되지 않고, 정상적으로 UserMapper.createUser를 호출하여 정상적으로 동작했다.

0개의 댓글