애플리케이션 개발에서 데이터의 효율적인 관리와 계층 간의 명확한 역할 분리는 매우 중요합니다. 특히 DAO(Data Access Object), DTO(Data Transfer Object), 그리고 Entity는 이러한 역할 분리를 실현하는 핵심 요소입니다. 이 글에서는 이 세 가지 개념의 정의와 역할, 그리고 Spring JPA를 사용한 구현 방법에 대해 자세히 알아보겠습니다.
DAO는 데이터베이스와 같은 영구 저장소와의 상호작용을 담당하는 객체입니다. 데이터베이스 연산을 추상화하여 애플리케이션의 비즈니스 로직이 데이터베이스와 독립적으로 작동할 수 있도록 합니다.
Spring Data JPA를 사용하면 별도의 DAO 클래스를 작성하지 않고도 Repository
인터페이스를 통해 DAO 패턴을 구현할 수 있습니다.
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
List<Customer> findByName(String name);
}
위의 CustomerRepository
인터페이스는 JpaRepository
를 상속받아 기본적인 CRUD 메서드를 자동으로 제공합니다.
DTO는 계층 간 데이터 전송을 위한 객체로, 주로 데이터를 캡슐화하여 네트워크를 통해 전송하거나 다른 계층으로 전달할 때 사용됩니다. 비즈니스 로직이나 데이터베이스 연산을 포함하지 않고 순수하게 데이터 저장 용도로만 사용됩니다.
public record CustomerDTO(
Long id,
String name,
String address,
String phoneNumber
) {
}
Record를 이용하여 DTO 생성 시 모든 필드 값이 final
로 생성 이후 값 변경이 불가능하여 불변성을 보장할 수 있습니다.
Entity는 데이터베이스의 테이블과 매핑되는 클래스입니다. 데이터의 구조를 정의하며, 데이터베이스 테이블의 각 필드에 해당하는 속성을 갖습니다. Entity는 비즈니스 로직을 포함하지 않고 순수한 데이터 상태를 표현합니다.
@Entity
어노테이션을 통해 데이터베이스 테이블과 매핑import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
@Table(name = "customers")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 100)
private String name;
@Column
private String address;
@Column(name = "phone_number")
private String phoneNumber;
// 기본 생성자 (JPA 요구 사항)
protected Customer() {}
// 생성자
public Customer(String name, String address, String phoneNumber) {
this.name = name;
this.address = address;
this.phoneNumber = phoneNumber;
}
public Long getId() { return id; }
public String getName() { return name; }
public String getAddress() { return address; }
public String getPhoneNumber() { return phoneNumber; }
}
Entity 클래스는 불변성을 유지하기 위해 setter
메서드를 제공하지 않으며, 데이터의 일관성을 보장합니다.
구분 | DAO (Repository) | DTO | Entity |
---|---|---|---|
역할 | 데이터베이스와의 상호작용 로직 캡슐화 | 계층 간 데이터 전송 | 데이터 구조와 상태 표현 |
포함 내용 | CRUD 메서드 및 데이터 접근 로직 | 전송할 데이터의 필드 | 데이터베이스 테이블과 매핑되는 필드 |
특징 | JpaRepository 상속으로 기본 기능 제공 | 불변 객체로 설계 | setter를 제공하지 않음 |
사용 위치 | 서비스 계층에서 사용 | 컨트롤러와 서비스 계층 간 | JPA를 통해 자동으로 관리 |
관계:
Spring JPA를 사용하면 DAO와 Entity를 명확하게 분리하여 구현할 수 있습니다. 특히 Entity 객체는 불변성을 유지하기 위해 Setter 메서드를 제공하지 않고, 필요할 경우 업데이트 메서드를 별도로 정의합니다.
왜 Entity는 Setter 메서드를 제공하지 않을까?
서비스 계층에서의 사용 예시:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class CustomerService {
private final CustomerRepository customerRepository;
public CustomerService(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
// 고객 등록
@Transactional
public CustomerDTO createCustomer(CustomerDTO customerDTO) {
Customer customer = new Customer(
customerDTO.getName(),
customerDTO.getAddress(),
customerDTO.getPhoneNumber()
);
customer = customerRepository.save(customer);
return new CustomerDTO(
customer.getId(),
customer.getName(),
customer.getAddress(),
customer.getPhoneNumber()
);
}
// 고객 조회
@Transactional(readOnly = true)
public CustomerDTO getCustomerById(Long id) {
Customer customer = customerRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Customer not found"));
return new CustomerDTO(
customer.getId(),
customer.getName(),
customer.getAddress(),
customer.getPhoneNumber()
);
}
}
서비스 계층에서는 Repository
를 통해 데이터 접근을 수행하고, Entity와 DTO 간의 변환을 담당합니다.
DAO, DTO, 그리고 Entity는 애플리케이션 개발에서 각각의 역할이 명확하게 분리되어야 합니다.
Spring JPA를 활용하면 이러한 패턴들을 더욱 효율적으로 구현할 수 있으며, 코드의 재사용성과 유지보수성을 높일 수 있습니다. 각 객체의 역할을 명확히 분리하여 설계하면 애플리케이션의 확장성과 안정성을 향상시킬 수 있습니다.