Converter란 Entitiy를 dto로 변환시켜주는 역할
-> 왜냐? Entity를 view로 내리면 무한 루프에 빠지는 등의 문제가 생길 수 있음.
CRUD 추상화를 위해서 아래와 같이 Converter 인터페이스를 작성하였다.
구현 순서:
Entity에 상응하는 Dto 클래스를 만들고 값을 변환해줄 Converter를 구현해준다.
이어서 인터페이스를 상속받는다 (Controller, Service)
public interface Converter<DTO, ENTITY> {
DTO toDTO(ENTITY entity);
ENTITY toEntity(DTO dto);
}
인터페이스를 구현하였다.
@Service
@RequiredArgsConstructor
public class ReplyConverter implements Converter<ReplyDto, ReplyEntity> {
private final PostRepository postRepository;
@Override
public ReplyDto toDTO(ReplyEntity replyEntity) {
return ReplyDto.builder()
.id(replyEntity.getId())
.postId(replyEntity.getPost().getId())
.title(replyEntity.getTitle())
.content(replyEntity.getContent())
.status(replyEntity.getStatus())
.userName(replyEntity.getUserName())
.password(replyEntity.getPassword())
.repliedAt(replyEntity.getRepliedAt())
.build();
}
@Override
public ReplyEntity toEntity(ReplyDto replyDto) {
var postEntity= postRepository.findById(replyDto.getPostId());
return ReplyEntity.builder()
.id(replyDto.getId())
.post(postEntity.orElse(null))
.status((replyDto.getStatus() != null) ? replyDto.getStatus() : "REGISTERED")
.title(replyDto.getTitle())
.content(replyDto.getContent())
.userName(replyDto.getUserName())
.repliedAt((replyDto.getRepliedAt() != null) ? replyDto.getRepliedAt(): LocalDateTime.now()) // request로 추가할 때는 현재시간으로 저장이 되었지만 //변환시에는 null이 가능
.password(replyDto.getPassword())
.build();
}
}
CRUD 추상화
CRUD 추상화를 통해 간단하게 기능들을 구현할 수 있다 -> 개발 속도와 코드 재사용성 향상
주의할 점:
추상클래스의 경우 인스턴스 생성이 안되기 때문에(싱글톤 객체 생성 x)
스프링 빈으로 등록될 수 없다.
-> Autowired를 통한 DI 불가
대신 추상클래스를 상속받은 구체 클래스가 Component로 컨테이너에 등록되어 있으면 가능하다.
public interface CRUDInterface<DTO> {
DTO create(DTO dto);
Optional<DTO> read(Long id);
DTO update(DTO dto);
void delete(Long id);
Api<List<DTO>> list(Pageable pageable);
}
public abstract class CRUDAbstractService<DTO, ENTITY> implements CRUDInterface<DTO> {
@Autowired(required = false)
private JpaRepository<ENTITY, Long> jpaRepository; //
@Autowired(required = false)
private Converter<DTO, ENTITY> converter; // dto와 entity 변환 역할
@Override
public DTO create(DTO dto) {
// dto -> entity
var entity = converter.toEntity(dto);
// entity -> save
jpaRepository.save(entity);
// save -> dto
var returnDto= converter.toDTO(entity);
return returnDto;
}
@Override
public Optional<DTO> read(Long id) {
var optionalEntity = jpaRepository.findById(id);
var dto=optionalEntity.map(
entity -> converter.toDTO(entity)
).orElseGet(()->null); // orElseGet은 value가 없는 경우
return Optional.of(dto);
}
@Override
public DTO update(DTO dto) {
var entity = converter.toEntity(dto);
jpaRepository.save(entity);
var returnDto= converter.toDTO(entity);
return returnDto;
}
@Override
public void delete(Long id) {
jpaRepository.deleteById(id);
}
@Override
public Api<List<DTO>> list(Pageable pageable) { // pageable이 무엇인가
var list=jpaRepository.findAll(pageable); // Page를 리턴하는데 이 페이지가 무엇인가
//Page<Entity> 객체에는 페이징 된 데이터 목록, 페이지 정보
//즉 Page<Entity>는 List<Entity> + 페이지 정보
var pagination = Pagination.builder() // 페이지 정보 추출
.page(list.getNumber())
.size(list.getSize())
.currentElements(list.getNumberOfElements())
.totalElements(list.getTotalElements())
.totalPage(list.getTotalPages())
.build();
var dtoList= list.stream() // 엔티티를 추출하여 DTO로 변환
.map(it->{
return converter.toDTO(it);
}).collect(Collectors.toList());
var response = Api.<List<DTO>>builder() // DTO와 페이지 정보를 Api 객체에 넣어서 리턴
.body(dtoList)
.pagination(pagination)
.build();
return response;
}
}
구현체 코드:
@Service
@RequiredArgsConstructor
public class ReplyService extends CRUDAbstractService<ReplyDto, ReplyEntity> {}
클라이언트로 부터 요청받을 Controller도 추상 클래스로 구현하였다.
public abstract class CRUDAbstractApiController<DTO, ENTITY> implements CRUDInterface<DTO>{
@Autowired(required = false) // 해당 타입의 빈이 컨테이너에 없어도 오류가 x
private CRUDAbstractService<DTO, ENTITY> crudAbstractService;
@PostMapping("")
@Override
public DTO create(
@Valid
@RequestBody
DTO dto
) {
return crudAbstractService.create(dto);
}
@GetMapping("/id/{id}")
@Override
public Optional<DTO> read(
@PathVariable
Long id
) {
return crudAbstractService.read(id);
}
@PutMapping("")
@Override
public DTO update(
@Valid
@RequestBody
DTO dto
) {
return crudAbstractService.update(dto);
}
@DeleteMapping("")
@Override
public void delete(
@PathVariable
Long id
) {
crudAbstractService.delete(id);
}
@GetMapping("/all")
@Override
public Api<List<DTO>> list(
@PageableDefault()
Pageable pageable
) {
return crudAbstractService.list(pageable);
}
}
이 또한 구현 클래스를 만들어 컨테이너에 등록한다.
@RestController
@RequestMapping("/api/reply")
@RequiredArgsConstructor
public class ReplyApiController extends CRUDAbstractApiController<ReplyDto, ReplyEntity> {}
'Springboot' 카테고리의 다른 글
[스프링 부트] 멀티 모듈 Bean 등록 (0) | 2024.07.08 |
---|---|
[스프링 부트] 자바 파일에서 스프링 부트 세팅 (0) | 2024.07.08 |
[스프링 부트] AOP, Pointcut (0) | 2024.07.04 |
[스프링 부트] Filter (0) | 2024.07.04 |
[스프링 부트] 인터셉터 (0) | 2024.07.04 |