JPA에서의 트랜잭션과 엔티티의 DB 적용

Hyun·2023년 8월 3일
0

Spring

목록 보기
21/38

@Transactional
public void cancelOrder(Long orderId) {
	//주문 엔티티 조회
    Order order = orderRepository.findOne(orderId);
    //주문 취소
    order.cancel();
} 

본인

JPA 기술을 사용할때, 위 함수를 실행하여 order 엔티티의 특정 필드값을 변경시키면, 함수 종료후에 db에 변경사항이 자동으로 적용되는것 맞지?

ChatGPT

네, 맞습니다. 주어진 함수가 @Transactional로 표시되어 있다면, 함수 실행 중에 변경된 사항은 함수의 실행이 정상적으로 종료될 때 자동으로 데이터베이스에 적용됩니다. 이것이 JPA에서 트랜잭션의 특성입니다.

주문 엔티티를 조회하고, 해당 엔티티의 cancel 메서드를 호출하여 주문을 취소하는 경우, cancel 메서드 내에서 엔티티의 필드가 변경되고, 이 변경 사항은 영속성 컨텍스트에 기록됩니다. 함수가 트랜잭션 내에서 실행 중이기 때문에, 함수가 종료될 때 트랜잭션이 커밋되며 변경 사항이 데이터베이스에 자동으로 반영됩니다.

트랜잭션의 커밋은 @Transactional이 붙은 메서드의 정상 종료 시점에 발생합니다. 트랜잭션 커밋 시점에 영속성 컨텍스트에 있는 변경 내용이 데이터베이스에 동기화되며, 이때 변경된 필드값도 함께 데이터베이스에 반영됩니다.

따라서 위의 cancelOrder 메서드에서 order.cancel()을 호출하여 주문을 취소하면, 함수 실행 종료 시점에 주문 엔티티의 변경 사항이 데이터베이스에 자동으로 적용됩니다.

본인

그러니까 트랜잭션 내에서 엔티티를 조회하고, 해당 엔티티의 필드값을 변경시키면, 트랜젝션 종료 후에 엔티티의 변경사항이 DB에 자동으로 적용된다는 말이지?

ChatGPT

정확히 맞습니다! 트랜잭션 내에서 엔티티를 조회하고 해당 엔티티의 필드 값을 변경하면, 트랜잭션의 종료 시점에 엔티티의 변경 사항이 자동으로 데이터베이스에 적용됩니다.

JPA는 영속성 컨텍스트를 사용하여 엔티티를 관리합니다. 트랜잭션 내에서 엔티티를 조회하면 해당 엔티티는 영속성 컨텍스트에 의해 관리됩니다. 그리고 이후에 엔티티의 필드 값을 변경하면, 영속성 컨텍스트에 변경 사항이 기록됩니다.

트랜잭션이 종료되는 시점에는 트랜잭션 매니저가 트랜잭션을 커밋합니다. 이때, 영속성 컨텍스트에 있는 변경 사항이 데이터베이스에 자동으로 반영됩니다. 즉, 변경된 필드 값이 데이터베이스에 적용됩니다.

따라서 트랜잭션 내에서 엔티티를 변경하면, 트랜잭션 종료 후에 변경 사항이 데이터베이스에 자동으로 적용된다는 것이 맞습니다. 이러한 기능 덕분에 JPA를 사용하여 데이터베이스와 상호 작용할 때 객체 지향적인 방식으로 개발할 수 있고, 편리한 CRUD(생성, 읽기, 갱신, 삭제) 작업을 제공받을 수 있습니다.


동일한 맥락의 질문

@Test
public void 상품주문() throws Exception {
	//*******given*******
	//회원 등록
	Member member = new Member();
	member.setName("회원1");
    member.setAddress(new Address("서울", "강가", "123-123"));
    em.persist(member);

    //재고 등록
    Book book = new Book();
    book.setName("시골 JPA");
    book.setPrice(10000);
    book.setStockQuantity(10);
    em.persist(book);

    int orderCount = 2;
    //*******when*******
    Long orderId = orderService.order(member.getId(), book.getId(), orderCount);

    //*******then*******
    Order getOrder = orderRepository.findOne(orderId);
    assertEquals(OrderStatus.ORDER, getOrder.getStatus());//상품 주문시 상태는 ORDER
    assertEquals(1, getOrder.getOrderItems().size());//주문한 상품 종류 수가 정확해야 한다
    assertEquals(10000 * orderCount, getOrder.getTotalPrice());//주문 가격은 가격 * 수량이다.
    assertEquals(8, book.getStockQuantity());//주문 수량만큼 재고가 줄어야 한다.
}

그럼 결국에
Book book = new Book();
book.setName("시골 JPA");
book.setPrice(10000);
book.setStockQuantity(10);

이 코드의 book 과 assertEquals(8, book.getStockQuantity()); 이 코드의 book 이 영속성 context 에서 관리되는 동일한 book 엔티티라는거지?

ChatGPT
네, 맞습니다. 코드 블록에서 생성한 Book 엔티티와 assertEquals(8, book.getStockQuantity()) 문에서 사용하는 book은 영속성 컨텍스트에서 관리되는 동일한 엔티티입니다.

영속성 컨텍스트는 JPA에서 엔티티 매니저(em)에 의해 관리되는 엔티티들의 컬렉션으로, 영속성 컨텍스트에 포함된 엔티티들은 데이터베이스와의 동기화를 자동으로 처리해줍니다.

주어진 테스트 코드에서 Book book = new Book();을 통해 생성된 book 엔티티는 JPA의 EntityManager를 통해 영속성 컨텍스트에 저장됩니다. 그리고 assertEquals(8, book.getStockQuantity()) 문에서 사용된 book 엔티티도 이전에 생성된 book 엔티티를 의미합니다.

따라서 주문 테스트를 수행하기 전에 재고 수량(stockQuantity)이 10으로 설정된 book 엔티티가 주문 처리 후 재고 수량이 8로 변경된 것을 확인하는 것입니다. 이런 방식으로 JPA는 영속성 컨텍스트를 통해 엔티티들의 상태를 관리하고, 테스트 메서드에서는 이러한 엔티티들의 상태를 검증하고 테스트할 수 있습니다.

User
book 엔티티가 영속성 context 에 등록되는 시점이 Book book = new Book(); 이때야 아니면 em.persist(book); 이때야?

ChatGPT
Book book = new Book();과 em.persist(book);는 서로 다른 시점에 엔티티를 영속성 컨텍스트에 등록하는 작업입니다.

Book book = new Book();: 이 코드는 단순히 새로운 Book 엔티티를 생성하는 작업입니다. 이 시점에서는 엔티티 객체가 생성되었지만, 아직 영속성 컨텍스트에는 등록되지 않았습니다.

em.persist(book);: 이 코드는 위에서 생성한 book 엔티티를 영속성 컨텍스트에 등록하는 작업입니다. em.persist() 메서드를 호출하면 JPA는 해당 엔티티를 영속성 컨텍스트에 등록합니다. 이후에는 엔티티가 영속성 컨텍스트에 포함되어 관리되게 됩니다.

따라서, Book book = new Book();이 실행되었다고 해서 바로 영속성 컨텍스트에 등록되는 것은 아닙니다. 엔티티를 영속성 컨텍스트에 등록하려면 em.persist() 메서드를 호출하여야 합니다. 일반적으로 JPA 작업은 엔티티를 생성하고 필요한 설정을 한 후에 em.persist()를 호출하여 영속성 컨텍스트에 등록하는 방식으로 처리됩니다.

=> 엔티티를 영속성 컨텍스트에 등록하려면 em.persist() 메서드를 호출하여야 합니다.

User
jpa 가 자동으로 데이터베이스와의 동기화를 관리해준다는게, @TEST 메서드가 종료되고 트랜젝션이 끝나야 DB에 반영되는거지?

ChatGPT
네, 맞습니다. JPA가 자동으로 데이터베이스와의 동기화를 관리하는 방식에는 여러 가지가 있지만, 일반적인 경우에는 @Test 메서드가 종료되고 해당 트랜잭션이 커밋되는 시점에 데이터베이스에 변경사항이 반영됩니다.

JPA에서는 트랜잭션 단위로 작업을 처리합니다. @Test 메서드에서 실행되는 JPA 작업도 하나의 트랜잭션으로 간주됩니다. 따라서 @Test 메서드 내에서 JPA 엔티티들의 상태를 변경하거나 새로운 엔티티를 생성하더라도, 이러한 변경사항은 메서드가 완료되는 시점인 트랜잭션의 커밋(commit) 시점까지는 데이터베이스에 영향을 주지 않습니다.

일반적으로 JPA는 @Transactional 어노테이션을 사용하여 트랜잭션을 관리합니다. @Transactional 어노테이션이 메서드에 적용되면, 해당 메서드에서 실행되는 JPA 작업은 하나의 트랜잭션 내에서 실행되며, 메서드가 정상적으로 완료되면 트랜잭션은 커밋됩니다. 그렇기 때문에 트랜잭션이 커밋되는 시점에 JPA는 변경된 엔티티들을 데이터베이스에 반영하여 영속성 컨텍스트와 데이터베이스를 동기화합니다.

만약 트랜잭션 내에서 예외가 발생하면, JPA는 변경사항을 롤백하여 이전 상태로 되돌립니다. 이렇게 함으로써 데이터베이스와 영속성 컨텍스트를 항상 일관된 상태로 유지하고 데이터 무결성을 보장할 수 있습니다.


동일한 맥락의 질문

OrderServiceTest.java

@Test
public void 주문취소() throws Exception {
	//given
    Member member = createMember();
    Book item = createBook("시골 JPA", 10000,10);

    int orderCount = 2;

    //when
    Long orderId = orderService.order(member.getId(), item.getId(), orderCount);
    //then
}

OrderService.java

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;
    private final MemberRepository memberRepository;
    private final ItemRepository itemRepository;

    //주문
    @Transactional
    public Long order(Long memberId, Long itemId, int count) {
        //cascade = CascadeType.All => order 만 delivery, orderItem 사용하기 때문에 cascade 사용할 수 있었음, 헷갈리면 안쓰는게 낫다. 나중에 리팩토링 하는걸로

        //엔티티 조회
        Member member = memberRepository.findOne(memberId);
        Item item = itemRepository.findOne(itemId);

        //배송정보 생성
        Delivery delivery = new Delivery();
        delivery.setAddress(member.getAddress());

        //주문 상품 생성, 단순화를 위해 주문 상품의 종류를 하나만 선택할 수 있도록 함!
        OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(), count);

        //주문 생성
        Order order = Order.createOrder(member, delivery, orderItem);

        //주문 저장
        orderRepository.save(order);//orderItem 과 delivery 는 cascade 옵션에 의해 함께 persist 되면서 db 테이블에 insert 된다
        return order.getId();
    }

    //취소
    @Transactional
    public void cancelOrder(Long orderId) {
        //주문 엔티티 조회
        Order order = orderRepository.findOne(orderId);
        //주문 취소
        order.cancel();
    }

    //검색
//    public List<Order> findOrders(OrderSearch orderSearch) {
//        return orderRepository.findAll(orderSearch);
//    }
}

User
내가 이해하기로는 주문취소 테스트 함수가 끝나기전에는 Book 엔티티가 데이터 베이스에 저장되는게 아니라 영속성 컨텍스트에 존재하는걸로 아는데 어떻게 orderService.order 함수에서 Book 엔티티의 id 를 이용해 itemRepository 에 존재하는 item 엔티티를 찾을 수 있지?

GPT
주문 취소 테스트 함수에서 주문을 생성할 때와 주문 취소를 수행할 때에 itemRepository에서 item 엔티티를 찾을 수 있는 이유는 JPA의 영속성 컨텍스트(persistence context)가 관리하는 메커니즘 때문입니다.

영속성 컨텍스트는 JPA에서 엔티티들을 관리하는 컨테이너로서, 엔티티 매니저(EntityManager)에 의해 관리됩니다. 영속성 컨텍스트는 트랜잭션 범위 내에서 엔티티들을 보관하고, 엔티티들의 상태를 추적하며, 엔티티들과 데이터베이스 사이의 데이터 동기화를 관리합니다.

따라서, orderService.order() 메서드에서 itemRepository.findOne(itemId)를 호출하여 item 엔티티를 조회할 때, 해당 엔티티가 이미 주문 생성 테스트 함수(주문취소() 메서드)에서 영속성 컨텍스트에 저장(persist)되어 있는 경우, 영속성 컨텍스트에서 먼저 해당 엔티티를 찾습니다. 즉, 영속성 컨텍스트는 이미 조회된 엔티티들을 메모리에 보관하고 있기 때문에 다시 데이터베이스에 쿼리를 날리지 않고도 해당 엔티티를 찾아올 수 있습니다.

따라서 주문 생성 테스트 함수가 끝나기 전에 영속성 컨텍스트에 item 엔티티가 존재한다면, 주문 취소 테스트 함수에서도 해당 엔티티를 찾을 수 있게 됩니다.

이는 영속성 컨텍스트의 기능 중 하나로서, 트랜잭션 범위 내에서 엔티티들을 캐시하고 관리하여 데이터베이스 조회를 최소화하는 효율적인 방법입니다.

User
아 그러면 실제 데이터 베이스에는 아직 item 엔티티가 저장되어있지 않은 상태이고, 현재 관리되는 영속성 컨택스트에 있는 item 값을 itemRepository.findOne(itemId) 로 조회한다는거지?

ChatGPT
맞습니다. 주문 취소 테스트 함수(주문취소())가 실행되는 시점에서 실제 데이터베이스에는 아직 item 엔티티가 저장되어 있지 않을 수 있습니다. 이 때, itemRepository.findOne(itemId)을 통해 해당 itemId에 해당하는 item 엔티티를 조회하려고 하면, 영속성 컨텍스트가 관리하는 메커니즘에 의해 영속성 컨텍스트 내부에서 해당 item 엔티티를 찾습니다.

따라서 주문 취소 테스트 함수가 실행되는 동안 영속성 컨텍스트에 이미 item 엔티티가 등록되어 있다면, 데이터베이스에 쿼리를 날리지 않고도 해당 엔티티를 찾아올 수 있습니다. 이는 영속성 컨텍스트가 엔티티들을 메모리에 캐시하여 트랜잭션 범위 내에서 조회 성능을 최적화하는 장점을 가지고 있기 때문입니다.

만약 영속성 컨텍스트에 해당 엔티티가 없는 경우라면, itemRepository.findOne(itemId)를 호출하면 데이터베이스에서 실제로 조회 쿼리가 수행될 것입니다. 이때 조회된 엔티티는 영속성 컨텍스트에 등록되어 관리되며, 이후 같은 itemId를 가지고 다시 조회하면 데이터베이스에 쿼리를 날리지 않고 영속성 컨텍스트 내부에서 해당 엔티티를 찾아올 수 있습니다.

User
근데 itemRepository, OrderRepository 등 데이터베이스의 테이블이 다양하게 존재하는데, 그 중에서 item 엔티티의 값을 영속성 컨텍스트에서 찾을 수 있는 방법이 뭐야. itemId 에 해당하는 item 엔티티가 영속성 컨텍스트에 하나만 존재하니까 바로 찾을 수 있는거야?

GPT
맞습니다. itemId에 해당하는 item 엔티티가 영속성 컨텍스트에 이미 존재하는 경우, 해당 엔티티는 영속성 컨텍스트 내에서 유일하게 존재하게 됩니다. 따라서 itemRepository.findOne(itemId)과 같이 해당 엔티티를 조회하면, 영속성 컨텍스트에서 바로 찾을 수 있습니다.

영속성 컨텍스트는 엔티티의 식별자(primary key)를 기준으로 엔티티를 관리하며, 식별자가 동일한 엔티티는 하나의 인스턴스만을 유지합니다. 따라서 같은 itemId에 해당하는 item 엔티티는 주문 생성 테스트 함수(주문취소())에서 영속성 컨텍스트에 등록되어 있다면, 주문 취소 테스트 함수에서도 영속성 컨텍스트 내에서 해당 엔티티를 찾을 수 있게 됩니다.

이렇게 영속성 컨텍스트는 조회한 엔티티들을 메모리에 캐시하여 트랜잭션 범위 내에서 조회 성능을 최적화합니다. 따라서 같은 엔티티를 여러 번 조회하는 경우, 데이터베이스에 쿼리를 날리지 않고도 영속성 컨텍스트 내에서 해당 엔티티를 찾을 수 있습니다.

profile
better than yesterday

0개의 댓글