Generic type 을 이용한 RestController 코드 중복제거

By | 2020년 12월 27일
Table of Contents

Generic type 을 이용한 RestController 코드 중복제거

RestController 를 작성하다 보면 95% 의 코드가 반복되는 것을 느끼게 된다.

Generic type 을 이용해 코드중복을 제거해 보자.

CustomGenericService

/**
 *
 * CustomGenericService - 서비스의 공통 기능 추상화(CRUD)
 * 재정의가 필요한 기능은 @Override해서 사용할것
 *
 * @author skyer9@gmail.com
 *
 * @param <D> DTO Type
 * @param <E> Entity Type
 * @param <KD> Key DTO Type
 * @param <KE> Key Entity key type
 */

public abstract class CustomGenericService<D, E, KD, KE> {

    protected JpaRepository<E, KE> repository;
    protected final String title;

    public CustomGenericService(JpaRepository<E, KE> repository, String title) {

        this.repository = repository;
        this.title = title;
    }

    // 생성
    @Transactional
    public D create(D d) {

        E e = newEntity();
        updateFromDto(d, e);

        return toDto(repository.save(e));
    }

    // 체크 및 생성
    @Transactional
    public D create(KD id, D d) throws DataExistsException {

        KE ke = toKeyEntity(id);

        if (get(ke, false) != null) {
            throw new DataExistsException(title + "이(가) 이미 존재합니다.");
        }

        E e = newEntity(ke);
        updateFromDto(d, e);

        return toDto(repository.save(e));
    }

    // 수정
    @Transactional
    public void update(KD id, D d) {

        E e = get(toKeyEntity(id), true);
        updateFromDto(d, e);
        repository.save(e);
    }

    // DTO 조회
    @Transactional(readOnly = true)
    public D select(KD id) {

        return toDto(get(toKeyEntity(id), true));
    }

    // Entity 반환
    @Transactional
    public E get(KE id, boolean throwExceptionIfNotExists) {

        E e = repository.findById(id).orElse(null);

        if (throwExceptionIfNotExists && (e == null)) {
            throw new DataNotFoundException(title + "이(가) 존재하지 않습니다.");
        } else {
            return e;
        }
    }

    @Transactional
    public E get(KE id) {
        Optional<E> o = repository.findById(id);
        return o.orElseGet(() -> newEntity(id));
    }

    public abstract SearchResponseDto search(Map<String, String> params);

    protected List<D> toDto(List<E> lst) {

        return lst.stream().map(this::toDto).collect(Collectors.toList());
    }

    protected abstract D toDto(E e);
    protected abstract E toEntity(D e);
    // protected abstract KD toKeyDto(KE e);
    protected abstract KE toKeyEntity(KD e);
    protected abstract void updateFromDto(D d, E e);
    protected abstract E newEntity();
    protected abstract E newEntity(KE e);
}

BaseRestController

/**
 *
 * CustomGenericService - 서비스의 공통 기능 추상화(CRUD)
 * 재정의가 필요한 기능은 @Override해서 사용할것
 *
 * @author skyer9@gmail.com
 *
 * @param <D> DTO Type
 * @param <E> Entity Type
 * @param <KD> Key DTO Type
 * @param <KE> Key Entity key type
 */

public class BaseRestController<D, E, KD, KE> {

    protected final CustomGenericService<D, E, KD, KE> service;

    public BaseRestController(CustomGenericService<D, E, KD, KE> service) {
        this.service = service;
    }

    // PK 자동생성
    public ResponseEntity<?> create(D dto) {

        D created = service.create(dto);

        return ResponseEntity.ok(new ApiResponseWithData(ResponseCode.OK, created));
    }

    // PK 수동부여(PK 체크)
    public ResponseEntity<?> create(KD kd, D dto) {

        D created = service.create(kd, dto);

        return ResponseEntity.ok(new ApiResponseWithData(ResponseCode.OK, created));
    }

    public ResponseEntity<?> update(KD keyDto, D dto) {

        service.update(keyDto, dto);
        return ResponseEntity.ok(new ApiResponseMessage(ResponseCode.OK));
    }

    public ResponseEntity<?> select(KD keyDto) {

        D dto = service.select(keyDto);

        return ResponseEntity.ok(new ApiResponseWithData(ResponseCode.OK, dto));
    }

    public ResponseEntity<?> search(Map<String, String> params) {

        SearchResponseDto searchResponseDto = service.search(params);

        return ResponseEntity.ok(
                new ApiResponseWithPaging(ResponseCode.OK,
                        searchResponseDto.getResults(),
                        searchResponseDto.getPageable(),
                        searchResponseDto.getTotalCount()
                )
        );
    }
}

BrandsService

@Service
public class BrandsService extends CustomGenericService<BrandsDto, Brands, String, String> {

    private final BrandsMapper mapper = Mappers.getMapper(BrandsMapper.class);

    @PersistenceContext
    private final EntityManager entityManager;

    public BrandsService(JpaRepository<Brands, String> repository, EntityManager entityManager) {
        super(repository, "브랜드");
        this.entityManager = entityManager;
    }

    @Override
    @Transactional(readOnly = true)
    public SearchResponseDto search(Map<String, String> params) {

        BrandSearchDto dto = new BrandSearchDto(params);
        Pageable pageable = PageRequest.of(dto.getPageNo(), dto.getPageSize());

        StoredProcedureQuery sp =
                entityManager.createNamedStoredProcedureQuery(Brands.NamedQuery_GetBrandList);
        sp.setParameter("_pageNo", pageable.getPageNumber());
        sp.setParameter("_pageSize", pageable.getPageSize());
        sp.setParameter("_partnerId", dto.getPartnerId());
        sp.setParameter("_brandId", dto.getBrandId());
        sp.setParameter("_useyn", dto.getUseyn());
        sp.execute();

        @SuppressWarnings("unchecked")
        List<Object> results = toDto(sp.getResultList());
        int totalCount = (int) sp.getOutputParameterValue("RESULT");

        return new SearchResponseDto(results, pageable, totalCount);
    }

    @Override
    protected BrandsDto toDto(Brands brands) {
        return mapper.to(brands);
    }

    @Override
    protected Brands toEntity(BrandsDto e) {
        return mapper.to(e);
    }

    @Override
    protected String toKeyEntity(String e) {
        return e;
    }

    @Override
    protected void updateFromDto(BrandsDto dto, Brands brands) {
        mapper.updateFromDto(dto, brands);
    }

    @Override
    protected Brands newEntity() {
        return new Brands();
    }

    @Override
    protected Brands newEntity(String e) {
        return new Brands(e);
    }
}

BrandsApiController

@Api(tags = { "12. 브랜드 리스트" })
@RestController
@RequestMapping("/v1/brands")
public class BrandsApiController extends BaseRestController<BrandsDto, Brands, String, String> {

    public BrandsApiController(BrandsService service) {
        super(service);
    }

    @Override
    @ApiOperation(value = "브랜드 등록", notes = "신규 브랜드를 등록등록합니다.")
    @PostMapping("/")
    public ResponseEntity<?> create(@RequestBody BrandsDto dto) {
        return super.create(dto.getBrandId(), dto);
    }

    @Override
    @ApiOperation(value = "브랜드 수정", notes = "브랜드를 수정합니다.")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "id", value = "브랜드 ID", required = true, dataType = "string", paramType = "path", defaultValue = ""),
    })
    @PutMapping("/{id}")
    public ResponseEntity<?> update(@PathVariable String id, @RequestBody BrandsDto dto) {
        return super.update(id, dto);
    }

    @Override
    @ApiOperation(value = "브랜드 조회", notes = "브랜드을 조회합니다.")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "id", value = "브랜드 ID", required = true, dataType = "string", paramType = "path", defaultValue = ""),
    })
    @GetMapping("/{id}")
    public ResponseEntity<?> select(@PathVariable String id) {
        return super.select(id);
    }

    @Override
    @ApiOperation(value = "브랜드 검색", notes = "브랜드를 검색합니다.")
    @GetMapping("/")
    public ResponseEntity<?> search(@RequestParam Map<String, String> params) {
        return super.search(params);
    }
}

중복된 코드가 상당량 제거된다.

답글 남기기