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);
}
}
중복된 코드가 상당량 제거된다.