Spring-@Transactional-백그라운드에서 어떤 일이 발생합니까?


334

@Transactional? 로 메소드에 주석을 달 때 실제로 어떤 일이 발생하는지 알고 싶습니다 . 물론 Spring이 해당 메소드를 Transaction에 래핑한다는 것을 알고 있습니다.

그러나 다음과 같은 의심이 있습니다.

  1. Spring이 프록시 클래스를 생성한다고 들었습니다 . 누군가가 이것을 더 깊이 설명 할 수 있습니까 ? 그 프록시 클래스에 실제로 상주하는 것은 무엇입니까? 실제 수업은 어떻게 되나요? Spring이 만든 프록시 클래스를 어떻게 볼 수 있습니까?
  2. 나는 또한 Spring 문서에서 다음과 같이 읽었습니다.

참고 :이 메커니즘은 프록시를 기반으로 하므로 프록시를 통해 들어오는 '외부'메소드 호출 만 인터셉트 됩니다. 즉, 'self-invocation', 즉 대상 객체의 다른 메소드를 호출하는 대상 객체 내의 메소드는 호출 된 메소드가 @Transactional! 로 표시 되더라도 런타임시 실제 트랜잭션으로 이어지지 않습니다 .

출처 : http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html

자체 호출 메소드가 아닌 외부 메소드 호출 만 트랜잭션 아래에있는 이유는 무엇입니까?


2
관련 토론은 여기에 있습니다 : stackoverflow.com/questions/3120143/…
dma_k

답변:


255

이것은 큰 주제입니다. Spring 레퍼런스 문서는 여러 장을 다루고 있습니다. Spring의 선언적 트랜잭션 지원은 AOP를 기반으로하므로 Aspect-Oriented Programming and Transactions 에 대한 내용을 읽는 것이 좋습니다 .

그러나 매우 높은 수준에서 Spring은 클래스 자체 또는 멤버에서 @Transactional 을 선언 하는 클래스에 대한 프록시를 만듭니다 . 프록시는 대부분 런타임에 보이지 않습니다. Spring이 프록시 화되는 객체에 메소드 호출 전, 후 또는 주위에 동작을 주입하는 방법을 제공합니다. 트랜잭션 관리는 연결될 수있는 동작의 한 예일뿐입니다. 보안 검사도 있습니다. 그리고 로깅과 같은 것들을 위해 당신 자신도 제공 할 수 있습니다. 따라서 @Transactional을 사용 하여 메소드에 주석을 달면 Spring은 주석을 추가하는 클래스와 동일한 인터페이스를 구현하는 프록시를 동적으로 만듭니다. 클라이언트가 객체를 호출하면 호출이 차단되고 동작은 프록시 메커니즘을 통해 주입됩니다.

그런데 EJB의 트랜잭션도 비슷하게 작동합니다.

살펴본 바와 같이 프록시 메커니즘은 일부 외부 객체에서 전화가 올 때만 작동합니다. 개체 내에서 내부 전화를 걸 때 실제로 " this 프록시를 우회하는 "참조를 . 그러나이 문제를 해결하는 방법이 있습니다. 이 포럼 게시물 에서 BeanFactoryPostProcessor 를 사용 하여 프록시 인스턴스를 런타임에 "자기 참조"클래스에 삽입하는 한 가지 방법에 대해 설명 합니다. 이 참조를 " me " 라는 멤버 변수에 저장합니다 . 그런 다음 스레드의 트랜잭션 상태를 변경해야하는 내부 호출을해야하는 경우 프록시를 통해 호출을 전달합니다 (예 : " me.someMethod () "를 통해 전화를 겁니다 .) 포럼 게시물에 자세한 내용이 설명되어 있습니다. BeanFactoryPostProcessor 코드는 Spring 1.x 타임 프레임에서 다시 작성 되었기 때문에 이제 조금 달라 졌다는 점에 유의하십시오 . 그러나 그것이 당신에게 아이디어를 줄 수 있기를 바랍니다. 사용 가능한 것으로 업데이트 된 버전이 있습니다.


4
>> 프록시는 런타임에 대부분 보이지 않습니다. 나는 그들이 궁금합니다 :) 휴식. 당신의 대답은 매우 포괄적이었습니다. 이것은 당신이 나를 도와 두 번째입니다. 모든 도움을 주셔서 감사합니다.
peakit

17
문제 없어요. 디버거를 단계별로 실행하면 프록시 코드를 볼 수 있습니다. 아마도 가장 쉬운 방법 일 것입니다. 마술은 없습니다. 그것들은 스프링 패키지 내의 클래스 일뿐입니다.
Rob H

@Transaction 어노테이션이있는 메소드가 인터페이스를 구현하는 경우 스프링은 동적 프록시 API를 사용하여 트랜잭션을 주입하고 프록시를 사용 하지 않습니다 . 트랜잭션 처리 클래스가 인터페이스를 구현하는 것을 선호합니다.
Michael Wiles

1
나는 "me"구성표를 찾았습니다 (명시 적 배선을 사용하여 생각하는 방식으로 수행). 그렇게하면 리팩토링을 사용하는 것이 더 좋을 것입니다. 해야합니다. 그러나 그렇습니다. 때로는 매우 어색 할 수도 있습니다.
Donal Fellows

2
2019 : 이 답변이 오래됨에 따라 추천 포럼 게시물을 더 이상 사용할 수 없으며을 사용하여 프록시 우회 하지 않고 객체 내에서 내부 호출을해야하는BeanFactoryPostProcessor 경우를 설명합니다 . 그러나이 답변에 설명 된 매우 비슷한 방법이 있습니다 : stackoverflow.com/a/11277899/3667003 ... 그리고 전체 스레드에서 추가 솔루션.
Z3d4s

196

Spring이 Bean 정의를로드하고 @Transactional주석 을 찾도록 구성된 경우 실제 Bean 주위에 이러한 프록시 오브젝트를 작성합니다 . 이 프록시 객체는 런타임에 자동 생성되는 클래스의 인스턴스입니다. 메소드가 호출 될 때 이러한 프록시 오브젝트의 기본 작동은 "대상"Bean (예 : Bean)에서 동일한 메소드를 호출하는 것입니다.

그러나 프록시에는 인터셉터가 제공 될 수 있으며, 존재하는 경우 이러한 인터셉터는 대상 Bean의 메소드를 호출하기 전에 프록시에 의해 호출됩니다. 로 주석이 달린 대상 Bean의 @Transactional경우 Spring은을 생성 TransactionInterceptor하고 생성 된 프록시 객체로 전달합니다. 따라서 클라이언트 코드에서 메소드를 호출하면 프록시 오브젝트에서 메소드를 호출합니다. 프록시 오브젝트는 먼저 TransactionInterceptor(트랜잭션을 시작하는) 호출하여 대상 Bean에서 메소드를 호출합니다. 호출이 완료되면 TransactionInterceptor트랜잭션을 커밋 / 롤백합니다. 클라이언트 코드에 투명합니다.

"외부 메소드"에 관해서는, Bean이 자체 메소드 중 하나를 호출하면 프록시를 통해 수행되지 않습니다. Spring은 당신의 bean을 프록시로 감싸고 당신의 bean은 그것을 알지 못한다는 것을 기억하십시오. 당신의 bean을 "외부"로부터의 호출 만이 프록시를 통과합니다.

도움이 되나요?


36
스프링이 프록시에 당신의 콩을 감싸고 있다는 것을 기억하십시오. 당신의 콩은 그것을 알지 못합니다 . 좋은 답변입니다. 도움을 주셔서 감사합니다.
peakit

프록시와 인터셉터에 대한 훌륭한 설명. 이제 스프링이 프록시 객체를 구현하여 대상 Bean에 대한 호출을 가로 채는 것을 이해합니다. 감사합니다!
dharag

나는 당신이 Spring 문서의 그림을 묘사하려고 노력하고 있고이
WesternGun

44

시각적 인 사람으로서 나는 프록시 패턴의 시퀀스 다이어그램으로 무게를 측정하고 싶습니다. 당신이 화살표를 읽는 방법을 모르는 경우,이 같은 첫 번째 읽기 : Client이 실행을 Proxy.method().

  1. 클라이언트는 자신의 관점에서 대상에 대한 메소드를 호출하고 프록시에 의해 자동으로 가로 채집니다.
  2. 이전 측면이 정의되면 프록시가이를 수행합니다.
  3. 그런 다음 실제 방법 (대상)이 실행됩니다.
  4. 복귀 후 및 투사 후는 메소드 반환 후 및 / 또는 메소드에서 예외가 발생하는 경우 실행되는 선택적 측면입니다.
  5. 그 후 프록시는 이후 측면 (정의 된 경우)을 실행합니다.
  6. 마지막으로 프록시는 호출 클라이언트로 돌아갑니다.

프록시 패턴 시퀀스 다이어그램 (저는 출처를 언급 한 조건으로 사진을 게시 할 수있었습니다. 저자 : Noel Vaes, 웹 사이트 : www.noelvaes.eu)


27

가장 간단한 대답은 다음과 같습니다.

어떤 메소드에서든 @Transactional트랜잭션의 경계 를 선언 하고 메소드가 완료되면 경계가 종료됩니다.

JPA 호출을 사용하는 경우 모든 커밋이이 트랜잭션 경계에 있습니다.

entity1, entity2 및 entity3을 저장한다고 가정하겠습니다. 이제 entity3을 저장하는 동안 예외가 발생 하지만 enitiy1과 entity2가 동일한 트랜잭션으로 들어 오면 entity1과 entity2가 entity3으로 롤백 됩니다.

거래 :

  1. 엔티티 1. 저장
  2. entity2.save
  3. entity3.save

예외가 발생하면 DB와의 모든 JPA 트랜잭션이 롤백됩니다. 내부 JPA 트랜잭션은 Spring에서 사용됩니다.


2
"A̶n̶y̶ 예외로 인해 DB와의 모든 JPA 트랜잭션이 롤백됩니다." 참고 RuntimeException 만 롤백됩니다. 확인 된 예외는 롤백되지 않습니다.
Arjun

2

늦었을 수도 있지만 프록시와 관련된 문제를 설명하는 무언가를 발견했습니다 (프록시를 통해 들어오는 '외부'메소드 호출 만 차단됩니다).

예를 들어 다음과 같은 클래스가 있습니다.

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
    }

    public void doSomethingSmall(int x){
        System.out.println("I also do something small but with an int");    
  }
}

그리고 당신은 다음과 같은 양상을 가지고 있습니다 :

@Component
@Aspect
public class CrossCuttingConcern {

    @Before("execution(* com.intertech.CoreBusinessSubordinate.*(..))")
    public void doCrossCutStuff(){
        System.out.println("Doing the cross cutting concern now");
    }
}

다음과 같이 실행할 때 :

 @Service
public class CoreBusinessKickOff {

    @Autowired
    CoreBusinessSubordinate subordinate;

    // getter/setters

    public void kickOff() {
       System.out.println("I do something big");
       subordinate.doSomethingBig();
       subordinate.doSomethingSmall(4);
   }

}

위 코드에서 킥오프를 호출 한 결과.

I do something big
Doing the cross cutting concern now
I did something small
Doing the cross cutting concern now
I also do something small but with an int

그러나 코드를 다음과 같이 변경하면

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
        doSomethingSmall(4);
    }

    public void doSomethingSmall(int x){
       System.out.println("I also do something small but with an int");    
   }
}


public void kickOff() {
  System.out.println("I do something big");
   subordinate.doSomethingBig();
   //subordinate.doSomethingSmall(4);
}

보시다시피, 메소드는 내부적으로 다른 메소드를 호출하므로 인터셉트되지 않으며 출력은 다음과 같습니다.

I do something big
Doing the cross cutting concern now
I did something small
I also do something small but with an int

그렇게함으로써 우회 할 수 있습니다

public void doSomethingBig() {
    System.out.println("I did something small");
    //doSomethingSmall(4);
    ((CoreBusinessSubordinate) AopContext.currentProxy()).doSomethingSmall(4);
}

https://www.intertech.com/Blog/secrets-of-the-spring-aop-proxy/ 에서 가져온 코드 스 니펫


1

기존의 모든 대답은 정확하지만이 복잡한 주제 만 줄 수는 없습니다.

포괄적이고 실제적인 설명을 위해이 Spring @Transactional In-Depth 안내서를 살펴보고자합니다.이 안내서는 많은 코드 예제와 함께 ~ 4000 간단한 단어로 트랜잭션 관리를 다루기 위해 최선을 다합니다.


대단해 ...
Alpit Anand
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.