개발

[공부] 공부하면서 왜 필요한지 생각해보기 (왜?).. @Transactional..

개발 공부하는 태준 2024. 12. 3. 13:43

@Transactional 어노테이션은 왜 사용하는가?

 

트랜잭션 관리가  필요한 메서드에 붙여주는 어노테이션이다.

트랜잭션은 여러 작업을 하나의 단위로 묵어서, 모든 작업이 성공적으로 완료되었을 때만 커밋하고, 그렇지 않으면 모두 롤백하는 방식이다.

언제 Transaction을 사용하는가?


그전에 트랜잭션이란 무엇일까?

 

트랜잭션은 "더이상 분할이 불가능한 업무의 처리 단위"를 의미한다고 한다. 

한꺼번에 수행되어야 할 일련의 연산모음을 의미한다고 한다. 

밑에 있는 예시와 같이 ATM기로 돈을 송급한다고 가정할 때 A에서 B로 송금을 했을 때 A에서는 차감이 되었지만 B에서 입금이 되지 않다면 그것은 심각한 문제일  것이다.

 

따라서 이 두 과정은 동시에 성공하거나 실패해야한다. 이러한 과정을 동시에 묶는 밥법이 바로 트랜잭션이다. 

 

즉 데이터베이스와 어플리케이션의 데이터 거래(Transaction)에 있어서 안전성을 확보하기 위한 방법이 트랜잭션인 것이다.

 

따라서 다른 테이블에 데이터를 입력하거나 갱신, 삭제하는 도중에 오류가 발생하면, 결과를 재반영하는 것이 아니라 모든 작업을 원상태로 복구하고, 처리 과정이 모두 성공하였을 떄만 그 결과를 반영한다. 

 

MYSQL 트랜잭션

 

MySQL에서 트랜잭션은 데이터베이스의 상태를 바꾸는 일종의 작업 단위이다.

 

INSERT, DELETE, UPDATE등의 SQL 명령문을 통해 데이터의 상태를 바꿀 때 마다 내부적으로 자동 Commit을 실행하여 변경된 내역을 데이터베이스에 반영하는 것이다. 

 

근데 여기서 작업의 단위는, 질의어 한문장이 아니다.

작업단위는 많은 잘의어 명령문을 사람이 정하는 기준에 따라 정하는 것을 의미한다.

 

예를들어 A(데이터베이스 설계자), B(백엔드), C(클라이언트)

 

만약에 A가 데이터 베이스 설계를

 

1. 결제 정보 저장

2. 재고 차감

3. 배송 정보 생성 이라는 트랜잭션을 설계 했을 때 

 

B가 구현

@Transactional
public void processOrder(Order order) {
    paymentService.processPayment(order.getPaymentInfo());
    inventoryService.decreaseStock(order.getProductId(), order.getQuantity());
    shippingService.createShipping(order.getShippingInfo());
}

 

모든 작업이 성공해야만 Commit 하고 하나라도 실패 한다면 Rollback 하도록 설정

 

C가 어떤 결제를 진행하고 재고가 없어서 실패했다고 가정하면 트랜잭션이 롤백되어 결제는 되고 주문은 되지않는 상황이 발생하지 않을 수 있다. 

 

START TRANSACTION;

-- 결제 정보 저장
INSERT INTO payments (order_id, amount) VALUES (1, 100);

-- 재고 차감
UPDATE inventory SET stock = stock - 1 WHERE product_id = 101;

-- 배송 정보 생성
INSERT INTO shipping (order_id, address) VALUES (1, '123 Main St');

COMMIT;  -- 모든 작업이 성공하면 변경 사항 적용

 

또한 여기서 작업의 단위는 insert문과 update문을 합친 것이고 이러한 작업단위를 하나의 트랜잭션이라고 한다. 

 

트랜잭션 특징

 

원자성 (Atomicity) 원자성은 트랜잭션이 데이터베이스에 모두 반영되거나, 혹은 전혀 반영되지 않아야 한다는 특성이다.
일관성 (Consistency) 일관성은 트랜잭션의 작업 처리 결과가 항상 일관성이 있어야 한다는 것이다.

트랜잭션이 진행되는 동안에 데이터베이스가 변경 되더라도 업데이트된 데이베이스로 트랜잭션이 진행되는 것이 아니라, 처음에 트랜잭션을 진행하기 위해 참조한 데이터베이스로 진행한다. 

데이터베이스에서의 시작과 끝에 항상 유효한 상태로 남도록 보장해야 한다. 
독립성 (Isolation) 어떤 하나의 트랜잭션이라도, 다른 트랜잭션의 연산에 끼어들 수 없다는 점을 가리킨다.
영구성 (Durability) 트랜잭션이 성공적으로 완료되었을 경우, 결과는 영구적으로 반영되어야 한다. 

 


 

 

- 여러 데이터베이스 작업을 하나의 단위로 묶고자 할 때

 

예 : 여러 엔티티에 대해 저장, 수정, 삭제 등의 작업을 동시에 처리해야 할 때, 중간에 문제가 생기면 모든 작업을 롤백해야 하므로 트랜잭션을 사용해야한다. 

 

- 데이터 일관성을 유지하고자 할 때 

 

예: 고객의 계좌에서 돈을 출금하고, 다른 계좌로 이체하는 작업이 있을 때, 중간에 실패하면 두 계좌 모두 원상복구 해야 하므로 트랜잭션이 필요하다.

 

트랜잭션이 필요한 경우

트랜잭션을 사용할 수 있는 예시를 정리하자면:

  • 두 개 이상의 엔티티 수정이 필요할 때 (ex. 계좌 간 송금)
  • 복잡한 비즈니스 로직에서 여러 작업을 처리할 때
  • 예외 발생 시 롤백해야 할 때 (ex. 데이터베이스의 장애 등)

트랜잭션을 붙이지 않아도 되는 경우

  • 단일 작업 (예: 한 번의 데이터 저장, 삭제)
  • 읽기 작업 (읽기 전용 작업은 트랜잭션을 사용하지 않아도 된다)

 


 

기본 생성자가 필요한 이유 

- 기본 생성자는 클래스가 인스턴스화될 때 호출되는 기본 생성 메서드임. Spring Bean으로 등록되는 클래스는 스프링 컨테이너에 의해 자동으로 관리되며, 이때 기본 생성자가 필요하다.

 

기본 생성자는 자동차의 기본 모델과 같다. 특정 옵션이 없는 경우에도 기본 모델은 반드시 존재해야 자동차를 생산할 수 있듯이 Spring은 기본 생성자를 통해 객체를 생성하고 관리한다. 

 

그이유?

 

Spring이 객체를 관리할 때 Relfection 방식으로 기본 생성자를 호출하여 인스턴스를 생성한다. 사용자 정의 생성자가 없거나 기본 생성자가 없으면 객체 생성이 실패할 수 있다.

 

리플렉션 방식이란?

프로그램이 실행 중에 클래스, 메서드, 필드 등의 정보를 동적으로 조회하고 수정할 수 있는 메커니즘이다. 즉 컴파일 시점이 아니라 런타임 시점에 클래스의 구조를 분석하고 객체를 생성하거나 메서드를 호출할 수 있도록 한다.

 

- 클래스 정보 조회: 클래스 이름, 메서드, 필드, 생성자 정보를 런타임에 확인할 수 있다.

- 객체 생성: Class 객체를 이용해 기본 생성자 또는 특정 생성자를 호출해 객체 생성 가능

- 메서드 호출: 메서드 이름을 동적으로 가져와 호출할 수 있습니다.

 

 

Spring에서 리플렉션의 활용

Spring 프레임워크는 리플렉션을 사용해 다음과 같은 작업을 수행한다.

1. 의존성 주입: 클래스의 생성자나 필드를 동적으로 찾아 주입할 객체를 생성한다.

2. 애노테이션 분석: @Autowired, @Service 같은 애노테이션을 분석해 적절한 동작을 수행한다.

3. 프록시 생성: AOP (Aspect-Oriented Programming)에서 동적으로 프록시 객체를 생성해 메서드를 가로챈다.

 

생성자 기반 의존성 주입

@Service
public class MyService {

    private final MyRepository myRepository;

    // 의존성은 생성자를 통해 주입된다.
    public MyService(MyRepository myRepository) {
        this.myRepository = myRepository;
    }
}

 

필드 기반 의존성 주입

 

@Service
public class MyService {

    @Autowired
    private MyRepository myRepository;  // 의존성이 자동 주입되지만 명시성이 떨어짐
}

 

즉, 코드에서 의존성 주입이 직접적으로 드러나지 않는 문제가 있다.

코드가 개발자나 리뷰어에게 명확하게 의도를 드러내는 정도를 의미한다.

결국 코드 가독성과 유지보수성이 낮아질 수 있기 때문에 스프링에서는 생성자 기반 주입을 선호하고 있다.


ResponseEntity.status(HttpStatus.CREATED).body(postDto) 가 왜필요할까?

 

RESTful API에서는 HTTP 상태 코드가 중요한 역할을 한다.

각 상태 코드는 서버가 요청을 어떻게 처리했는지에 대한 정보를 제공한다. 

 

-200 OK: 요청이 성공적으로 처리되었음을 나타낸다.

-201 Created: 요청이 성공적으로 처리되어 새로운 리소스가 생성되었음을 나타낸다. 이는 POST 요청에 적합한 상태 코드이다.

-400 Bad Request: 클라이언트가 잘못된 요청을 보냈을 때

-404 Not Found: 요청한 리소스를 찾을 수 없을 때

-500 Internal Server Error: 서버에서 처리 중에 오류가 발생했을 때

 

ResponseEntity의 필요성

ResponseEntity는 Spring에서 HTTP 응답을 더욱 세밀하게 제어할 수 있게 해주는 클래스임.

ResponseEntity를 사용하면 응답의 상태 코드, 헤더, 본문을 모두 제어할 수 있음.

 

ResponseEntity는 status() 메서드를 통해 상태 코드를, body() 메서드를 통해 응답 본문을 설정할 수 있음 

 


 

왜 요청에 대해 상태 코드와 리소스의 데이터를 응답으로 보낼까? 

 

이 방식은 클라이언트와 서버 간의 상호작용을 더 명확하고 효율적으로 만들기 위한 RESTful API 설계 원칙에 기반한다. 

 

1. 클라이언트에게 명확한 피드백을 제공 

 

POST 요청은 새로운 리소스를 생성하는 요청이므로, 요청 후 생성된 리소스에 대한 정보가 응답으로 전달되는 것은 클라이언트가 어떤 일이 일어났는지 명확히 알 수 있게 해준다.

상태코드 201 Created는 클라이언트에게 리소스가 성공적으로 생성되었음을 알려준다.

 

2. REST API 설계 원칙에 맞추기 위해

 

RESTful API에서는 각 HTTP 메서드가 의미하는 바를 정확하게 반영해야 한다. 

POST는 리소스를 생성하는 작업을 나타내므로 요청이 성공적으로 처리되면 201 Created 상태 코드와 함께 리소스에 대한 정보를 반환하는 것이 규칙이다. 

 

 
 public PostDto updatePost(Long id, PostDto postDto) {
        PostEntity postEntity = postRepository.findById(id).orElse(null);
        if (postEntity == null) {
            throw new  EntityNotFoundException("EntityNotFound, Entity id = "+id);
        }
        postEntity.updatePost(postDto);
        PostEntity updatedEntity = postRepository.save(postEntity);

//        PostEntity updatedEntity = postRepository.save(postEntity.updatePost(postDto));
        return new PostDto(id, updatedEntity.getContent(), updatedEntity.getTitle(), updatedEntity.getCreatedAt());



    }

 

update를 하려고 했는데 update는 되지 않고 업데이트 하려는게 따로 Database 에 저장됨

 

이유?

 

주석 처리한 부분을 보면 postEntity를 수정해주는 것이 아닌 entity 내부 메서드인  updatePost가 new Entity로 반환해주고 있었기 때문에 기존의 entity객체를 수정해주는 것이 아니라 또 다른 객체를 반환받아서 저장해주고 있었음.


 

 

'개발' 카테고리의 다른 글

Spring Cloud..?  (0) 2024.11.27
[개발] 클라우드 네이티브?... MSA...?  (1) 2024.11.25