Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

Transaction Manager - 2 본문

JPA/영속성

Transaction Manager - 2

Seung__ 2021. 7. 4. 17:56
 

Transaction Manager - 1

 1. Transaction이란? Data Base에서 다루는 개념. DB명령어들의 논리적인 묶음이다. Transaction의 성질 : ACID A : atomatic(원자성), 부분적의 성공을 허용하지 않는다. 중간 단계까지 실행되고 실패하는 일이

bsh-developer.tistory.com

 

Transaction 의 특징 : All or Nothing, DB에 전부 반영하거나 전부 안하거나, 원자성

 

1. @Transactional 없을 때 예외발생 시


<BookService>에 기존에 붙여준 @Transactional을 주석처리하자.

@Service
@RequiredArgsConstructor
public class BookService {

   // @Autowired //요즘에는 Autowired보다는...생성자 주입@RequiredArgsConstructor, final이기 때문에 생성자 반드시 필요.
    private final BookRepository bookRepository;

   // @Autowired
    private final AuthorRepository authorRepository;

    //@Transactional
    public void putBookAndAuthor(){

        Book book = new Book();
        book.setName("JPA 시작작");
        bookRepository.save(book); //DB insert

        Author author = new Author();
        author.setName("hyun");
        authorRepository.save(author); // DB insert

        throw new RuntimeException("오류 발생해서 DB commit 발생하지 않음.");

    }
}

<Test>는 예외처리를 위해 Try-Catch 로 변경해주자.

사실 이런식의 try-catch는 anti 패턴이다.

RuntimeException이 발생했을 때 해당 예외 모두가 test를 통과하게 된다...

@SpringBootTest
class BookServiceTest {

    @Autowired
    private BookService bookService;
    @Autowired
    private BookRepository bookRepository;
    @Autowired
    private AuthorRepository authorRepository;

    @Test
    void transactionTest(){

        try{
            bookService.putBookAndAuthor();
        } catch (RuntimeException e){
            System.out.println(">>>>" + e.getMessage());
        }
        
        System.out.println("books : " + bookRepository.findAll());
        System.out.println("Authors : " + authorRepository.findAll());
    }
}

 

테스트를 돌려보자! @Transaction이 없기 때문에 예외가 발생해도 각 save가 transaction이기 때문에

모두 DB에 반영이 된다.

>>>>오류 발생해서 DB commit 발생하지 않음.
books : [Book(super=BaseEntity(createdAt=2021-07-04T16:22:33.434986, updatedAt=2021-07-04T16:22:33.434986), id=1, name=JPA 시작작, authorId=null, category=null)]
Authors : [Author(super=BaseEntity(createdAt=2021-07-04T16:23:25.943198, updatedAt=2021-07-04T16:23:25.943198), Id=1, name=hyun, country=null)]

 

 

2. @Transactional 적용 시 예외발생


각 break point에서 DB에 반영이 되었는지 확인해 보자.

 

@Transactional이 적용되기 때문에 transaction 종료 후 DB에 한 번에 반영이 될 것이다.

 

예상대로 book과 author table에 아무 내용이 반영되지 않았다.

예외 발생단계로 넘어가보자 어떻게 바뀔까

>>>>오류 발생해서 DB commit 발생하지 않음.
Hibernate: 
    select
        book0_.id as id1_1_,
        book0_.created_at as created_2_1_,
        book0_.updated_at as updated_3_1_,
        book0_.author_id as author_i4_1_,
        book0_.category as category5_1_,
        book0_.name as name6_1_,
        book0_.publisher_id as publishe7_1_ 
    from
        book book0_
books : []
Hibernate: 
    select
        author0_.id as id1_0_,
        author0_.created_at as created_2_0_,
        author0_.updated_at as updated_3_0_,
        author0_.country as country4_0_,
        author0_.name as name5_0_ 
    from
        author author0_
Authors : []

test코드가 끝까지 돌고왔는데, 예외발생으로 인해 commit이 되지 않았다.

 

즉 book과 author모두 save() method가 실행 되었지만,

Transaction내에서 예외가 발생하여 commit이 되지 않아 DB에 반영되지 않았다.

 

 

 

3. Transaction의 잘못된 사용 : checked exception의 사용


아래 코드는 transaction을 보기 위해 억지로  예외를 발생시킴

최근 경향에 맞게 runtime exception(unchecked exception)을 활용함

 

@SpringBootTest
class BookServiceTest {

    @Autowired
    private BookService bookService;
    @Autowired
    private BookRepository bookRepository;
    @Autowired
    private AuthorRepository authorRepository;

    @Test
    void transactionTest(){

        try{
            bookService.putBookAndAuthor();
        } catch (RuntimeException e){
            System.out.println(">>>>" + e.getMessage());
        }
        
        System.out.println("books : " + bookRepository.findAll());
        System.out.println("Authors : " + authorRepository.findAll());
    }
}

 

실제는 요렇게 만들지 않고 더욱 복잡하여

checked exception이 사용될 수도, unchecked exception이 사용될 수 도 또는 혼용될수 있다.

- checked exception : runtime exception의 상위(Exception), 명시적으로 exception처리를 해줘야 함

 

기존 RuntimeException을 Exception으로 바꾸면 Unhandled Exception이라고 오류가 발생한다.

 

아래와 같이 수정해 주자. 이 method는 Exception을 thorws할수 있다는 의미이다.

 

 @Transactional
    public void putBookAndAuthor() throws Exception {

 

<Test>도 Exception을 받을 수 있도록 수정해주자.

 

@SpringBootTest
class BookServiceTest {

    @Autowired
    private BookService bookService;
    @Autowired
    private BookRepository bookRepository;
    @Autowired
    private AuthorRepository authorRepository;

    @Test
    void transactionTest(){

        try{
            bookService.putBookAndAuthor();
        } catch (Exception e){
            System.out.println(">>>>" + e.getMessage());
        }

        System.out.println("books : " + bookRepository.findAll());
        System.out.println("Authors : " + authorRepository.findAll());
    }
}

 

앗 뭔가 다르다. 예외처리가 된 이후에 book과 author가 DB에 반영이 되었다.

 

 

뭐지????킹받는다. 

Runtime Exception은 Transaction내에서 발생하면 rollback이 일어나고

Checked Exception은 Transaction내에서 발생해도 rollback이 일어나지 않고 Commit이 가능하다.??

 

Checked Exception은 개발자가 해당 Exception 처리에 대한 책임을 가지게 된다.

개발자가 반드시 직접 핸들링 해야한다고 한다.

 

3-2. Checked Exeption은 왜 Rollback 처리가 안될까?


TransactionAspectSupport.java의 336번 line을 보면 invokeWithinTransaction에 대한 코드를 볼 수있다.

 

377번의 PlatformTransactionManager를 보자. 현재 기본적으로 사용하고 있다.

 

completeTransactionAfterThrowing method를 자세히 보자. Exception 발생 후 Transaction처리에 관한 내용이다.

 

 

TransactionAttribute가 rollbackOn이면 rollback을 진행한다 : (RuntimeExceptio인 경우)

 

그렇지 않은 경우에 대해서는 commit을 진행한다.

rollbackOn method를 자세히 보자. (DafaultTransactionAttribute.java)

 

Exception이 RuntimeException인지 Error타입인지 확인을 해준다.

 

즉, RuntimeException이 발생하면 Rollback, 그렇지 않은 예외는 Commit이 진행된다.

 

 

3-3. CheckedException 발생 시  Rollback을 하고 싶으면? @rollbackFor()


@Transactional 옵션 중 @rollbackFor()가 있다.

Defines zero (0) or more exception classes, which must be subclasses of Throwable, indicating which exception types must cause a transaction rollback.

By default, a transaction will be rolling back on RuntimeException and Error but not on checked exceptions (business exceptions).

See org.springframework.transaction.interceptor.DefaultTransactionAttribute.rollbackOn(Throwable) for a detailed explanation. This is the preferred way to construct a rollback rule (in contrast to rollbackForClassName), matching the exception class and its subclasses.

간단하게 아래처럼 설정해 주면, checkedException 발생 시에도 rollback을 할 수 있따.

@Transactional(rollbackFor = Exception.class)

 

같은 테스트를 다시 돌려보자. rollbackFor()를 반영했기 때문에 DB에 반영되지 않을 것이다.

>>>>오류 발생해서 DB commit 발생하지 않음.
Hibernate: 
    select
        book0_.id as id1_1_,
        book0_.created_at as created_2_1_,
        book0_.updated_at as updated_3_1_,
        book0_.author_id as author_i4_1_,
        book0_.category as category5_1_,
        book0_.name as name6_1_,
        book0_.publisher_id as publishe7_1_ 
    from
        book book0_
books : []
Hibernate: 
    select
        author0_.id as id1_0_,
        author0_.created_at as created_2_0_,
        author0_.updated_at as updated_3_0_,
        author0_.country as country4_0_,
        author0_.name as name5_0_ 
    from
        author author0_
Authors : []

book, authors 모두 비어있다. checkedException이 발생하였지만 commit이 진행되지 않고 rollback이 되었다. 

 

 

4. Transaction의 잘못된 사용 : private method에서의 @Transactional사용


Test 에서는 bookService의 put()을 호출하고

    @Test
    void transactionTest(){

        try{
//            bookService.putBookAndAuthor();
            bookService.put();
        } catch (RuntimeException e){
            System.out.println(">>>>" + e.getMessage());
        }

        System.out.println("books : " + bookRepository.findAll());
        System.out.println("Authors : " + authorRepository.findAll());
    }
}

put은 동일 클래스 내에서 putBookAndAuthor()를 호출한다.

@Service
@RequiredArgsConstructor
public class BookService {

   // @Autowired //요즘에는 Autowired보다는...생성자 주입@RequiredArgsConstructor, final이기 때문에 생성자 반드시 필요.
    private final BookRepository bookRepository;

   // @Autowired
    private final AuthorRepository authorRepository;

    public void put(){
        this.putBookAndAuthor();;
    }

    @Transactional
    void putBookAndAuthor(){

        Book book = new Book();
        book.setName("JPA 시작작");
        bookRepository.save(book); //DB insert

        Author author = new Author();
        author.setName("hyun");
        authorRepository.save(author); // DB insert

        throw new RuntimeException("오류 발생해서 DB commit 발생하지 않음.");

    }


}

 

 

테스트를 돌려보자!

 

정상적이라면 RuntimeException이 발생했기 때문에 rollback이 일어나서DB에 반영이 되지 않아야 한다.

>>>>오류 발생해서 DB commit 발생하지 않음.

books : [Book(super=BaseEntity(createdAt=2021-07-04T17:41:41.230715, updatedAt=2021-07-04T17:41:41.230715), id=1, name=JPA 시작작, authorId=null, category=null)]

Authors : [Author(super=BaseEntity(createdAt=2021-07-04T17:41:41.328529, updatedAt=2021-07-04T17:41:41.328529), Id=1, name=hyun, country=null)]

그런데 rollback이 되지않고 commit되어 DB에 반영이 되었다..?

 

@Transactional이 무시당했나 ㅜ

 

이 문제는  spring container와 관련이 있다.

 

spring container는 bean으로 진입할때 걸려있는 annotation을 처리하도록 되어있다.

 

이미 put이라는 method로 진입하는 순간 bean 내부로 들어왔고,

 

bean 내부에 있는 다른 method를 호출하게 되면 그 method의 @annotaion을 읽지 않고 넘어가게 된다.

 

bean으로 진입할 때 annotation을 처리하기 때문! 이미 진입햇으니 annotaion 처리가 불가능함.

 

 

해당 @Transactional이 적용된 method가 public이던, private던 간에 bean class내부에서 내부 method를 호출 할 때

 

@Transactional 효과가 없어진다.

 

 

GitHub


https://github.com/bsh6463/BookManager/blob/main/README.md

 

bsh6463/BookManager

Contribute to bsh6463/BookManager development by creating an account on GitHub.

github.com

 

'JPA > 영속성' 카테고리의 다른 글

Transaction Manager - 4 : Isolation  (0) 2021.07.05
Transaction Manager-3 : Isolation  (0) 2021.07.04
Transaction Manager - 1  (0) 2021.07.04
Entity 생애 주기  (0) 2021.07.04
Entity Cache  (0) 2021.07.02
Comments