본문 바로가기

SPRING/Spring 활용

[스프링부트| 스프링부트와 JPA 활용 1 | 웹 애플리케이션 개발 | 상품 도메인 개발] 상품 리포지토리,서비스 개발

@Repository // 이 클래스를 스프링의 리포지토리 컴포넌트로 등록하여 데이터 액세스 역할을 명시
@RequiredArgsConstructor // Lombok 라이브러리의 어노테이션으로 final 또는 @NonNull 필드에 대한 생성자를 자동으로 생성
public class ItemRepository {

    private final EntityManager em; // JPA의 EntityManager를 주입받아 데이터베이스 연산을 관리

    public void save(Item item) { // Item 객체를 저장하거나 업데이트하는 메소드
        if (item.getId() == null) { // Item의 ID가 null일 경우, 즉 새로 생성된 객체일 경우
            em.persist(item); // 객체를 영속화하며, 데이터베이스에 새로 저장
        } else {
            em.merge(item); // 이미 존재하는 객체일 경우, 변경된 정보를 데이터베이스에 업데이트
        }
    }

    public Item findOne(Long id) { // 주어진 ID를 가진 Item 객체를 찾는 메소드
        return em.find(Item.class, id); // ID를 기준으로 Item 객체를 데이터베이스에서 조회
    }

    public List<Item> findAll() { // 모든 Item 객체를 찾는 메소드
        return em.createQuery("select i from Item i", Item.class) // JPQL을 사용하여 모든 Item 객체를 조회하는 쿼리 생성
                 .getResultList(); // 쿼리 결과를 List로 반환
    }
}

persist와 merge는 Java Persistence API (JPA)에서 제공하는 두 가지 중요한 메서드입니다. 이들은 엔티티를 데이터베이스에 저장하거나 업데이트하는 데 사용되며, 각각의 메서드는 엔티티의 생명주기를 관리하는 데 특정 역할을 수행합니다.

persist 메서드

  • 목적: 새로운 엔티티를 영속성 컨텍스트에 추가하고, 결국에는 데이터베이스에 저장합니다.
  • 작동 방식: persist 메서드는 엔티티 매니저(EntityManager)를 통해 호출되며, 전달된 엔티티 객체를 영속성 컨텍스트에 관리하에 두게 합니다. 이 메서드는 주로 새로 생성된 객체를 데이터베이스에 저장할 때 사용됩니다.
  • 중요한 점: persist를 호출한 후, 해당 엔티티는 "영속 상태"가 됩니다. 이 상태의 엔티티는 영속성 컨텍스트에 의해 관리되며, 트랜잭션이 커밋되는 시점에 데이터베이스에 반영됩니다.
  • 제약 사항: persist 메서드는 이미 식별자가 있는 엔티티(예를 들어, 데이터베이스에서 이미 로드된 엔티티)에는 사용할 수 없습니다.

merge 메서드

  • 목적: 데이터베이스에 이미 존재하는 엔티티의 정보를 업데이트하거나, 준영속 상태의 엔티티를 영속성 컨텍스트에 다시 연결합니다.
  • 작동 방식: merge는 준영속 상태의 엔티티(영속성 컨텍스트의 관리를 벗어난 엔티티)를 받아서 그 상태를 새로운 영속 상태의 엔티티로 복사하고, 원본 엔티티는 여전히 준영속 상태를 유지합니다. merge를 통해 반환된 객체는 영속 상태의 새로운 객체입니다.
  • 중요한 점: merge는 엔티티의 모든 필드를 데이터베이스 행과 병합합니다. 만약 특정 필드가 merge 호출 시 null이라면, 데이터베이스의 해당 필드 값도 null로 업데이트될 수 있습니다.
  • 제약 사항: merge는 데이터베이스와의 동기화를 위해 엔티티의 모든 필드를 복사하기 때문에 성능상의 문제가 발생할 수 있습니다. 필요한 필드만 업데이트하고자 할 때는 다른 전략을 고려해야 할 수 있습니다.

결론

persist와 merge는 엔티티의 생명주기와 상태 관리에서 중요한 역할을 하며, 각각의 메서드는 사용되는 상황에 따라 선택하여 사용해야 합니다. 새로운 엔티티를 추가할 때는 persist를 사용하고, 이미 존재하거나 준영속 상태의 엔티티를 다룰 때는 merge가 적합합니다.

@Service // 스프링 컨테이너에서 이 클래스를 서비스 레이어의 컴포넌트로 관리하도록 설정
@Transactional(readOnly = true) // 클래스 레벨에서 읽기 전용 트랜잭션 설정, 데이터를 변경하지 않는 메서드에서 성능 최적화
@RequiredArgsConstructor // Lombok 라이브러리를 사용하여 final이나 @NonNull인 필드의 생성자를 자동으로 생성
public class ItemService {

    private final ItemRepository itemRepository; // ItemRepository에 대한 의존성 주입

    @Transactional // saveItem 메서드에 대해 읽기 전용이 아닌 트랜잭션 설정(데이터 변경이 있기 때문)
    public void saveItem(Item item) { // Item 객체를 저장하는 메서드
        itemRepository.save(item); // ItemRepository의 save 메서드를 호출하여 item 저장
    }
    
    public List<Item> findItems() { // 모든 Item 객체를 조회하는 메서드
        return itemRepository.findAll(); // ItemRepository의 findAll 메서드를 호출하여 모든 Item 조회
    }
    
    public Item findOne(Long itemId) { // ID를 기반으로 한 Item 객체를 조회하는 메서드
        return itemRepository.findOne(itemId); // ItemRepository의 findOne 메서드를 호출하여 특정 ID의 Item 조회
    }

}
  • @Service 어노테이션: 이 클래스가 비즈니스 로직을 담당하는 서비스 레이어의 컴포넌트임을 나타냅니다. 스프링은 이 클래스의 인스턴스를 자동으로 생성하고 관리합니다.
  • @Transactional(readOnly = true): 이 설정은 이 서비스 클래스의 모든 메서드가 기본적으로 읽기 전용 트랜잭션으로 실행됨을 의미합니다. 데이터 변경이 필요한 경우, 메서드 레벨에서 별도의 @Transactional 어노테이션을 사용하여 재정의할 수 있습니다.
  • @RequiredArgsConstructor: 이 클래스에 final 또는 @NonNull 어노테이션이 붙은 모든 필드에 대한 생성자를 Lombok이 자동으로 생성합니다. 이는 생성자를 통한 의존성 주입을 가능하게 합니다.
  • 메서드들: saveItem은 데이터를 저장하기 때문에 @Transactional 어노테이션이 붙어 있어 기본 readOnly 설정을 덮어씁니다. findItems와 findOne은 데이터 조회만을 수행하므로 클래스 레벨에서 설정된 readOnly 트랜잭션이 유지됩니다.