๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐ŸŒพBackEnd/๐ŸŒฑ Spring

[ Object Mapper] Dozer์— ๋Œ€ํ•ด์„œ ๋“ค์–ด๋Š” ๋ดค๋‹ˆ?

by MuGeon Kim 2023. 3. 14.
๋ฐ˜์‘ํ˜•

 

Dozer

 

dozermapper.github.io

Dozer๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

Dozer๋Š” Java ๊ธฐ๋ฐ˜์˜ ๊ฐ์ฒด ๋งคํ•‘ ๋„๊ตฌ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. ๊ฐ์ฒด ๋งคํ•‘์€ ์„œ๋กœ ๋‹ค๋ฅธ ํด๋ž˜์Šค ๊ฐ„์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ณต์‚ฌํ•˜๊ฑฐ๋‚˜ ๋งคํ•‘ํ•˜๋Š” ํ”„๋กœ์„ธ์Šค๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์ฝ์€ ๋ฐ์ดํ„ฐ๋ฅผ Java ๊ฐ์ฒด์— ๋งคํ•‘ํ•˜๊ฑฐ๋‚˜, ์„œ๋กœ ๋‹ค๋ฅธ API์—์„œ ๋ฐ˜ํ™˜ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜์˜ ๊ฐ์ฒด๋กœ ๋งคํ•‘ํ•˜๋Š” ๋“ฑ์˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ฒ˜์Œ์€ ์ผ๋‹จ ์˜์กด์„ฑ ์ถ”๊ฐ€ํ•˜์ž

implementation'com.github.dozermapper:dozer-core:6.4.0'

 

Dozer๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ด์ „์— ์ฝ”๋“œ๋ฅผ ํ•œ๋ฒˆ ์‚ดํŽด๋ณด์ž

  • ๊ธฐ์กด์˜ ์ฝ”๋“œ๋Š” Builder๋ฅผ ์ด์šฉํ•˜์—ฌ ๋งคํ•‘์„ ํ•˜๊ฒŒ ๋„์™€์ค€๋‹ค. ์ด ๋ฐฉ์‹์€ ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•œ ๋ฐฉ์‹๋ณด๋‹ค ์ •ํ™•ํ•˜๊ฒŒ ๋งคํ•‘์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์•Œ ์ˆ˜ ์žˆ๊ฒŒ๋œ๋‹ค. ํ•˜์ง€๋งŒ ๋ฌธ์ œ์ ์€ ๋„ˆ๋ฌด ๋งŽ์€ ๊ฐ์ฒด๊ฐ€ ์žˆ์œผ๋ฉด ์ž‘์„ฑํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ๋„ˆ๋ฌด ๊ธธ์–ด์ง„๋‹ค.

ProductData(DTO)

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductData {
    private Long id;

    @NotBlank
    private String name;

    @NotBlank
    private String maker;

    @NotNull
    private Integer price;

    private String imageUrl;
}

Product

@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String maker;

    private Integer price;

    private String imageUrl;

    public void change(Product product) {
        this.name = product.getName();
        this.maker = product.getMaker();
        this.price = product.getPrice();
        this.imageUrl = product.getImageUrl();
    }
}

Service

    public Product createProduct(ProductData productData) {
        Product product = Product.builder()
                .name(productData.getName())
                .maker(productData.getMaker())
                .price(productData.getPrice())
                .build();
        return productRepository.save(product);
    }
  • ์ง€๊ธˆ์€ 3๊ฐœ๋ฅผ ๋งคํ•‘์„ ํ•˜์ง€๋งŒ ์ด๊ฒŒ ๋งŒ์•ฝ์— ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„์ง€๊ฒŒ ๋˜๋ฉด Builder ํŒจํ„ด์˜ ๊ฐ€๋…์„ฑ์ด ์ข‹์€ ์ฝ”๋“œ์˜ ํšจ๊ณผ๊ฐ€ ๋–จ์–ด์ง€๊ฒŒ ๋œ๋‹ค. ๊ทธ๋ž˜์„œ Dozer๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋” ๊ฐ„๊ฒฐํ•˜๊ฒŒ ํ‘œํ˜„์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

1. APP์— @Bean์„ ๋™๋กํ•œ๋‹ค.

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
    @Bean
    public Mapper dozermapper(){
        return DozerBeanMapperBuilder.buildDefault();
    }
}

 

2. Service์— Mapper๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

import com.github.dozermapper.core.Mapper;
@Service
@Transactional
@RequiredArgsConstructor
public class ProductService {
    private final ProductRepository productRepository;
    private final Mapper mapper;

    public Product createProduct(ProductData productData) {
        Product product = mapper.map(productData,Product.class);
        return productRepository.save(product);
    }

    private Product findProduct(Long id) {
        return productRepository.findById(id)
                .orElseThrow(() -> new ProductNotFoundException(id));
    }
}
  • ์ด๋•Œ mapper.map(`๋งคํ•‘ ๋ฐ์ดํ„ฐ(DTO)` , ๋ณ€ํ™˜) ํ˜•์‹์œผ๋กœ ๋งคํ•‘์„ ํ•ด์ฃผ๋ฉด ๋œ๋‹ค. 
  • ๊ทธ๋Ÿฌ๋ฉด Product product = mapper.map(productData,Product.class);๋Š” productData์—
  • @Mapping๋œ ๋ฐ์ดํ„ฐ๋ฅผ Product ๋ฐ์ดํ„ฐ ํƒ€์ž…์œผ๋กœ ๋งคํ•‘์ด ๋œ๋‹ค.

 

3. DTO์— ๋งคํ•‘์— ๋Œ€ํ•ด ๋ช…์‹œํ•œ๋‹ค. ProductData(DTO)

@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductData {
    private Long id;

    @NotBlank
    @Mapping("name")
    private String name;

    @NotBlank
    @Mapping("maker")
    private String maker;

    @NotNull
    @Mapping("price")
    private Integer price;

    @Mapping("imageUrl")
    private String imageUrl;
}

 

4. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ํ†ตํ•˜์—ฌ ํ™•์ธํ•˜์ž

@DataJpaTest
class ProductServiceTest {

    private ProductService productService;

    private ProductRepository productRepository = mock(ProductRepository.class);

    @BeforeEach
    void setUp() {
        Mapper mapper = DozerBeanMapperBuilder.buildDefault();
        productService = new ProductService(productRepository, mapper);

        Product product = Product.builder()
                .id(1L)
                .name("๊น€๋ฌด๊ฑด")
                .maker("๋‚˜์ดํ‚ค")
                .price(1000)
                .build();

        given(productRepository.save(any(Product.class))).will(invocation -> {
            Product source = invocation.getArgument(0);
            return Product.builder()
                    .id(2L)
                    .name(source.getName())
                    .maker(source.getMaker())
                    .price(source.getPrice())
                    .build();
        });
    }
    @Test
    void createProduct() {
        ProductData productData = ProductData.builder()
                .name("์ฅ๋Œ์ด")
                .maker("๋ƒฅ์ด์›”๋“œ")
                .price(5000)
                .build();

        Product product = productService.createProduct(productData);

        verify(productRepository).save(any(Product.class));

        assertThat(product.getId()).isEqualTo(2L);
        assertThat(product.getName()).isEqualTo("๊น€๋ฌด๊ฑด");
        assertThat(product.getMaker()).isEqualTo("๋‚˜์ดํ‚ค");
    }
}

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๋™์ž‘์„ ํ•œ๋‹ค.

๋ฐ˜์‘ํ˜•