|
역할 분리를 위한 Entity와 DTO 작성의 이유는 다음과 같다.
역할 분리를 위한 Entity, DTO 개념과 차이점 https://wildeveloperetrain.tistory.com/101
* Entity와 DTO 분리 * Entity는 데이터베이스와 연관이 있으며 데이터베이스의 영속성을 관리하기 위한 역할을 한다. DTO는 클라이언트와 서버 간의 데이터 전송을 관리하고, 필요한 데이터를 포함하는 구조로 클라이언트 요청 및 응답을 처리하는 데 사용된다. Entity는 데이터베이스 영속성(persistent)의 목적으로 사용되는 객체이며, 때문에 요청(Request)이나 응답(Response) 값을 전달하는 클래스로 사용하는 것은 좋지 않다. 즉, Entity를 직접 사용하는 방식보다 DTO를 정의하여 요청과 응답을 하기 위한 객체로 사용하는 것이 더 권장된다. 예를 들어 Jikwon Entity 객체에 password 정보가 있을 때 Jikwon 자체를 return하게 되면 다른 사용자에게 보여주면 안 되는 password 정보가 같이 넘어가는 문제가 있을 수 있다. 그러므로 필요한 정보만 넘기기 위해서는 DTO를 사용해야 한다. 분리해서 얻는 이점은 ... - Entity 내부 구현을 캡슐화 처리 가능 - 화면에 필요한 데이터만 선별 처리 가능 - 순환 참조 예방 - Validation 코드 분리 가능 Dto와 Entity를 분리하는 이유, 분리하는 방법 참조 https://velog.io/@0sunset0/Dto%EC%99%80-Entity%EB%A5%BC-%EB%B6%84%EB%A6%AC%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-%EB%B6%84%EB%A6%AC%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95 |
👓 알고 가기 :
Entity에서는 setter 메서드의 사용은 바람직하지 않다. 왜냐하면 변경되지 않는 인스턴스에 대해서도 setter로 접근이 가능해지기 때문에 객체의 일관성, 안전성을 보장하기 힘들어진다. setter가 있다는 것은 불변하지 않다는 것이 된다. 따라서 Entity에는 setter 대신 Constructor 또는 롬복의 Builder를 사용하게 되는데, 이는 setter 메서드가 아닌 Constructor를 이용해서 초기화하는 경우 불변 객체로 활용할 수 있고, 불변 객체로 만들면 데이터를 전달하는 과정에서 데이터가 변조되지 않음을 보장할 수 있기 때문이다.
그리고 아래 예처럼 Builder를 사용하면 멤버 변수가 많아지더라도 어떤 값을 어떤 필드에 넣는지 코드를 통해 확인할 수 있고, 필요한 값만 넣는 것이 가능하다는 장점이 있다.
@Builder @Getter @Entity @NoArgsConstructor public class Membmer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String email; public Member(Long id, String name, String email) { this.id = id; this.name = name; this.email = email; } } // 사용 방법 Member member = Member.builder() .name("tom") .email("tom@test.com") .build(); |
* 역할 분리를 위한 Entity와 DTO 작성 방법
Entity 작성 : 엔티티 클래스는 데이터베이스 테이블과 매핑된다. 엔티티 클래스는 @Entity와 @Table 어노테이션을 사용한다.
예)
import javax.persistence.*;
import lombok.*;
@Entity
@Table(name = "sangdata")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SangpumEntity {
@Id
@Column(name = "code")
private int code;
@Column(nullable = false)
private String sang;
private int su;
private int dan;
}
DTO 작성 : DTO 클래스는 주로 데이터를 전달하는 데 사용되며, 비즈니스 로직과 프레젠테이션 계층 간의 데이터 전송을 담당한다. DTO 클래스는 @Data 어노테이션을 사용하여 Getter와 Setter를 자동 생성할 수 있다.
예)
import javax.persistence.*;
import lombok.*;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SangpumDto {
private int code;
private String sang;
private int su;
private int dan;
}
Entity와 DTO 간의 변환 : 변환 메서드를 작성할 수 있다. 이는 수동으로 하거나, 모델 매퍼 라이브러리(예: ModelMapper)를 사용하여 자동으로 할 수 있다.
예(수동 변환):
public class SangpumMapper {
public static SangpumDto toDto(SangpumEntity entity) {
return new SangpumDto(
entity.getCode(),
entity.getSang(),
entity.getSu(),
entity.getDan()
);
}
public static SangpumEntity toEntity(SangpumDto dto) {
return new SangpumEntity(
dto.getCode(),
dto.getSang(),
dto.getSu(),
dto.getDan()
);
}
}
요약
- 역할 분리 : Entity는 데이터베이스와 매핑되고, DTO는 데이터를 전달하는 역할을 한다.
- 유지보수성, 보안, 성능, 유연성을 이유로 Entity와 DTO를 분리한다.
- Entity와 DTO를 작성한 후 변환 메서드를 통해 데이터를 전환할 수 있다.
이렇게 Entity와 DTO 간의 역할을 분리하면 애플리케이션의 구조가 더욱 명확해지고, 유지보수가 용이해진다.
예(자동 변환):
ModelMapper를 사용하면 엔티티와 DTO 간의 변환을 자동으로 처리할 수 있어, 변환 메서드를 수동으로 작성하는 부담을 줄일 수 있다. 다음은 ModelMapper를 활용하여 엔티티와 DTO를 변환하는 방법이다.
1. ModelMapper 의존성 추가
: pom.xml 파일에 ModelMapper 의존성을 추가한다.
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.1.0</version> <!-- 적당한 버전 확인 필요 -->
</dependency>
: gradle 파일에 ModelMapper 의존성을 추가한다.
implementation 'org.modelmapper:modelmapper:3.1.0'
2. ModelMapper 설정 : Spring Boot 애플리케이션에서 ModelMapper를 빈으로 설정한다. 이를 위해 설정 클래스를 작성한다.
import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ModelMapperConfig {
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
}
3. Entity와 DTO 클래스 작성 : 위와 동일
4. 변환 메서드 작성 : ModelMapper를 사용하여 Entity와 DTO 간의 변환을 수행하는 변환 메서드를 작성한다. 보통 서비스 클래스에서 변환을 수행한다.
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class SangpumService {
@Autowired
private ModelMapper modelMapper;
public SangpumDto convertToDto(SangpumEntity entity) {
return modelMapper.map(entity, SangpumDto.class);
}
public SangpumEntity convertToEntity(SangpumDto dto) {
return modelMapper.map(dto, SangpumEntity.class);
}
}
5. Controller에서 사용 : 서비스 클래스에서 변환 메서드를 사용하여 컨트롤러에서 Entity와 DTO 간의 변환을 수행한다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/sangpum")
public class SangpumController {
@Autowired
private SangpumService sangpumService;
@PostMapping("/convertToDto")
public SangpumDto convertToDto(@RequestBody SangpumEntity entity) {
return sangpumService.convertToDto(entity);
}
@PostMapping("/convertToEntity")
public SangpumEntity convertToEntity(@RequestBody SangpumDto dto) {
return sangpumService.convertToEntity(dto);
}
}
정리 :
ModelMapper 의존성 추가 : pom.xml 파일에 ModelMapper 의존성을 추가한다.
ModelMapper 설정 : ModelMapper를 Spring Bean으로 설정한다.
Entity와 DTO 클래스 작성 : Lombok을 사용하여 Entity와 DTO 클래스를 작성한다.
변환 메서드 작성 : 서비스 클래스에서 ModelMapper를 사용하여 Entity와 DTO 간의 변환을 수행하는 메서드를 작성한다.
Controller에서 사용 : 컨트롤러에서 서비스 클래스의 변환 메서드를 사용한다.
이렇게 하면 ModelMapper를 활용하여 Entity와 DTO 간의 변환을 간편하게 처리할 수 있다.
----- ----- ----- ----- ----- ----- ----- ----- ----- -----
간단한 CRUD 애플리케이션을 예로 들어 엔티티, DTO, 서비스, 컨트롤러를 역할 분리하여 사용하는 방법을 확인해 보겠다.
1. build.gradle 파일 설정
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.projectlombok:lombok'
implementation 'org.modelmapper:modelmapper:3.1.0'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
2. ModelMapper 설정 : ModelMapperConfig 클래스를 작성하여 ModelMapper를 Spring Bean으로 설정한다.
import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ModelMapperConfig {
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
}
3. 엔티티 클래스 작성 : SangpumEntity 엔티티 클래스를 작성한다.
import javax.persistence.*;
import lombok.*;
@Entity
@Table(name = "sangdata")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SangpumEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "code")
private int code;
@Column(nullable = false)
private String sang;
private int su;
private int dan;
}
4. DTO 클래스 작성 : SangpumDto 클래스를 작성한다.
import lombok.*;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SangpumDto {
private int code;
private String sang;
private int su;
private int dan;
}
5. 리포지토리 인터페이스 작성 : Spring Data JPA를 사용하여 리포지토리를 작성한다.
import org.springframework.data.jpa.repository.JpaRepository;
public interface SangpumRepository extends JpaRepository<SangpumEntity, Integer> {
}
6. 서비스 클래스 작성 : 서비스 클래스를 작성하여 비즈니스 로직을 구현한다.
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class SangpumService {
@Autowired
private SangpumRepository repository;
@Autowired
private ModelMapper modelMapper;
public SangpumDto convertToDto(SangpumEntity entity) {
return modelMapper.map(entity, SangpumDto.class);
}
public SangpumEntity convertToEntity(SangpumDto dto) {
return modelMapper.map(dto, SangpumEntity.class);
}
public List<SangpumDto> getAllSangpum() {
return repository.findAll().stream()
.map(this::convertToDto)
.collect(Collectors.toList());
}
public SangpumDto getSangpumById(int id) {
return repository.findById(id)
.map(this::convertToDto)
.orElseThrow(() -> new RuntimeException("Item not found"));
}
public SangpumDto createSangpum(SangpumDto dto) {
SangpumEntity entity = convertToEntity(dto);
return convertToDto(repository.save(entity));
}
public SangpumDto updateSangpum(int id, SangpumDto dto) {
SangpumEntity entity = repository.findById(id)
.orElseThrow(() -> new RuntimeException("Item not found"));
entity.setSang(dto.getSang());
entity.setSu(dto.getSu());
entity.setDan(dto.getDan());
return convertToDto(repository.save(entity));
}
public void deleteSangpum(int id) {
repository.deleteById(id);
}
}
7. 컨트롤러 작성 : 컨트롤러를 작성하여 HTTP 요청을 처리한다.
jimport org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/sangpum")
public class SangpumController {
@Autowired
private SangpumService sangpumService;
@GetMapping
public List<SangpumDto> getAllSangpum() {
return sangpumService.getAllSangpum();
}
@GetMapping("/{id}")
public SangpumDto getSangpumById(@PathVariable int id) {
return sangpumService.getSangpumById(id);
}
@PostMapping
public SangpumDto createSangpum(@RequestBody SangpumDto dto) {
return sangpumService.createSangpum(dto);
}
@PutMapping("/{id}")
public SangpumDto updateSangpum(@PathVariable int id, @RequestBody SangpumDto dto) {
return sangpumService.updateSangpum(id, dto);
}
@DeleteMapping("/{id}")
public void deleteSangpum(@PathVariable int id) {
sangpumService.deleteSangpum(id);
}
}
요약하면
이렇게 역할을 분리하면 각 계층의 책임이 명확해지고, 유지보수와 테스트가 편리해질 수 있다.