Spring @Transactional 속성은 개인 메소드에서 작동합니까?


196

나는이있는 경우 @Transactional Spring 빈에서 개인 방법에 -annotation을, 주석이 어떤 영향이 있습니까?

@Transactional주석이 공용 메소드에있는 경우 주석이 작동하고 트랜잭션을 엽니 다.

public class Bean {
  public void doStuff() {
     doPrivateStuff();
  }
  @Transactional
  private void doPrivateStuff() {

  }
}

...

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();

답변:


163

질문은 비공개 또는 공개가 아니며 질문은 어떻게 호출되며 어떤 AOP 구현을 사용합니까!

(기본) Spring 프록시 AOP를 사용하는 경우 Spring에서 제공하는 모든 AOP 기능 (예 :) @Transational은 호출이 프록시를 통과하는 경우에만 고려됩니다. -일반적으로 주석이 달린 메소드가 다른 Bean 에서 호출 된 경우 입니다.

여기에는 두 가지 의미가 있습니다.

  • 개인 메소드는 다른 Bean에서 호출되지 않아야하기 때문에 (예외는 리플렉션 임) @Transactional주석은 고려되지 않습니다.
  • 메소드가 공용이지만 동일한 Bean에서 호출 된 경우이 메소드도 고려되지 않습니다 (이 명령문은 (기본) Spring 프록시 AOP가 사용되는 경우에만 정확함).

@See Spring Reference : Chapter 9.6 9.6 프 락싱 메커니즘

IMHO 문제를 극복 할 Spring 프록시 대신 aspectJ 모드를 사용해야합니다. 그리고 AspectJ Transactional Aspects는 개인적인 방법으로도 짜여져있다 (Spring 3.0에서 확인).


4
두 가지 모두 반드시 사실은 아닙니다. 첫 번째는 올바르지 않습니다. 개인 메소드 반사적으로 호출 수 있지만 프록시 발견 논리는 그렇지 않습니다. 두 번째 요점은 인터페이스 기반 JDK 프록시에만 적용되지만 CGLIB 서브 클래스 기반 프록시에는 적용되지 않습니다.
skaffman

@skaffman : 1-나는 나의 진술을 더 정확하게 만든다. 2. 그러나 기본 프록시는 인터페이스 기반이다-그렇지 않습니까?
Ralph

2
대상이 인터페이스를 사용하는지 여부에 따라 다릅니다. 그렇지 않으면 CGLIB가 사용됩니다.
skaffman

canu는 왜 cglib가 aspectj가 할 수 없지만 reson 또는 어떤 참조를 말해 줄 수 있습니까?
phil

1
응답 블록의 링크에서 참조, Spring 프록시 [기본 환경]를 사용하려면 doStuff ()에 주석을 넣고 ((Bean) AopContext.currentProxy ()). doPrivateStuff ();를 사용하여 doPrivateStuff ()를 호출하십시오. 전파가 [기본 환경] 인 경우 동일한 트랜잭션에서 두 메소드를 모두 실행합니다.
Michael Ouyang

219

귀하의 질문에 대한 대답은 아니오입니다- @Transactional개인 메소드에 주석을 달 때 사용하면 효과가 없습니다. 프록시 생성기는 무시합니다.

이것은 스프링 매뉴얼 10.5.6 장에 설명되어 있습니다 .

분석법 가시성 @Transactional

프록시 @Transactional를 사용하는 경우 공개 가시성이있는 메소드에만 주석을 적용해야합니다 . 주석이있는 보호 된, 개인용 또는 패키지 표시 가능 메소드에 @Transactional주석을 달면 오류가 발생하지 않지만 주석이 달린 메소드는 구성된 트랜잭션 설정을 표시하지 않습니다. 비공개 메소드에 주석을 달아야하는 경우 AspectJ (아래 참조) 사용을 고려하십시오.


이거 확실하니? 나는 그것이 변화를 기대하지 않을 것입니다.
willcodejavaforfood

프록 싱 스타일이 Cglib이면 어떻습니까?
백합

32

기본적 @Transactional으로이 속성은 applicationContext에서 얻은 참조에 주석이 달린 메소드를 호출 할 때만 작동합니다.

public class Bean {
  public void doStuff() {
    doTransactionStuff();
  }
  @Transactional
  public void doTransactionStuff() {

  }
}

거래가 열립니다 :

Bean bean = (Bean)appContext.getBean("bean");
bean.doTransactionStuff();

이것은하지 않습니다 :

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();

스프링 레퍼런스 : @Transactional 사용

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

자체 호출이 트랜잭션으로 랩핑 될 것으로 예상되는 경우 AspectJ 모드 (아래 참조)의 사용을 고려하십시오. 이 경우 처음에는 프록시가 없습니다. 대신, @Transactional모든 종류의 메소드에서 런타임 동작으로 전환하기 위해 대상 클래스가 '위빙'됩니다 (예 : 바이트 코드가 수정 됨) .


bean = new Bean ();을 의미합니까?
willcodejavaforfood

아니. new Bean ()으로 Bean을 작성하면 Aspect-J를 사용하지 않으면 주석이 작동하지 않습니다.
Juha Syrjälä

2
감사! 이것은 내가 관찰하고있는 이상한 행동을 설명합니다. 아주 카운터 ...이 내부 메소드 호출 제한 직관적
마누엘 알다

나는 하드 방법을 "프록시를 통해 오는 유일한 외부 메서드 호출이 차단 될 것"배운
asgs

13

예, 개인 메소드에서 @Transactional을 사용할 수는 있지만 다른 사람들이 언급했듯이 즉시 사용할 수는 없습니다. AspectJ를 사용해야합니다. 작동 방법을 알아내는 데 시간이 걸렸습니다. 결과를 공유하겠습니다.

로드 타임 직조 대신 컴파일 타임 직조를 사용하기로 결정했습니다. 왜냐하면 그것이 전반적으로 더 나은 옵션이라고 생각하기 때문입니다. 또한 Java 8을 사용하고 있으므로 일부 매개 변수를 조정해야 할 수도 있습니다.

먼저 aspectjrt에 대한 종속성을 추가하십시오.

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.8</version>
</dependency>

그런 다음 AspectJ 플러그인을 추가하여 Maven에서 실제 바이트 코드 직조를 수행하십시오 (최소한의 예는 아님).

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.8</version>
    <configuration>
        <complianceLevel>1.8</complianceLevel>
        <source>1.8</source>
        <target>1.8</target>
        <aspectLibraries>
            <aspectLibrary>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
        </aspectLibraries>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

마지막으로 이것을 구성 클래스에 추가하십시오.

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)

이제 개인 메소드에서 @Transactional을 사용할 수 있습니다.

이 접근 방식의 한 가지주의 사항 : Eclipse를 통해 앱을 실행하는 경우 작동하지 않을 수있는 경우 AspectJ를 인식하도록 IDE를 구성해야합니다. 위생 검사로 직접 Maven 빌드에 대해 테스트하십시오.


프록 싱 메소드가 cglib 인 경우, 메소드가 공용이어야하는 인터페이스를 구현할 필요가 없으면 개인 메소드에서 @Transactional을 사용할 수 있습니까?
백합

예, 개인 메소드에서 작동하며 인터페이스없이 작동합니다! AspectJ가 올바르게 구성되어 있으면 기본적으로 작업 방법 데코레이터를 보장합니다. 그리고 user536161은 자신의 답변에서 자체 호출에서도 작동 할 것이라고 지적했습니다. 정말 시원하고 조금 무섭습니다.
제임스 왓킨스

12

트랜잭션 내부에 전용 메소드를 랩핑해야하고 aspectj를 사용하지 않으려는 경우 TransactionTemplate 을 사용할 수 있습니다 .

@Service
public class MyService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    private void process(){
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                processInTransaction();
            }
        });

    }

    private void processInTransaction(){
        //...
    }

}

TransactionTemplate사용법 을 보여 주면 좋지만, ..RequiresTransaction대신 두 번째 방법을 호출하십시오 ..InTransaction. 일년 후에 읽은 내용을 항상 말하십시오. 또한 실제로 두 번째 개인 메소드가 필요하다고 생각할 것입니다. 익명 execute구현에 직접 내용을 넣거나 지저분 해지면 구현을 다른 서비스로 분할하여 주석을 달 수 있음을 나타낼 수 있습니다 @Transactional.
스턱

@Stuck, 두 번째 방법은 실제로 필요하지 않지만 개인 방법으로 봄 거래를 적용하는 방법에 대한 원래 질문에 대한 답변입니다.
loonis

예, 나는 이미 답을 올렸지 만 아키텍처 관점 에서이 상황이 디자인 결함의 잠재적 인 표시라고 생각하기 때문에 적용 방법에 대한 컨텍스트와 생각을 공유하고 싶었습니다.
스턱

5

Spring Docs는 다음과 같이 설명합니다.

프록시 모드 (기본값)에서는 프록시를 통해 들어오는 외부 메소드 호출 만 인터셉트됩니다. 즉, 호출 된 메소드에 @Transactional이 표시되어 있어도 실제로 자체 호출은 대상 오브젝트의 다른 메소드를 호출하는 대상 오브젝트 내의 메소드가 런타임시 실제 트랜잭션으로 이어지지 않음을 의미합니다.

자체 호출도 트랜잭션으로 랩핑되도록하려면 AspectJ 모드 (아래 표의 모드 속성 참조)를 사용하십시오. 이 경우 처음에는 프록시가 없습니다. 대신 @Transactional을 모든 종류의 메소드에서 런타임 동작으로 변환하기 위해 대상 클래스가 직조됩니다 (즉, 바이트 코드가 수정 됨).

다른 방법은 사용자 BeanSelfAware입니다.


에 대한 참조를 추가 할 수 BeanSelfAware있습니까? 그것은 봄의 클래스처럼 보이지 않는
asgs

@asgs 자기 주입에 관한 것이라고 가정 해 봅시다 (자체 인스턴스가 프록시에 래핑 된 빈을 제공합니다). stackoverflow.com/q/3423972/355438 에서 예제를 볼 수 있습니다 .
Lu55


1

@loonis가 TransactionTemplate 을 사용하도록 제안한 것과 같은 방법 으로이 도우미 구성 요소 (Kotlin)를 사용할 수 있습니다.

@Component
class TransactionalUtils {
    /**
     * Execute any [block] of code (even private methods)
     * as if it was effectively [Transactional]
     */
    @Transactional
    fun <R> executeAsTransactional(block: () -> R): R {
        return block()
    }
}

용법:

@Service
class SomeService(private val transactionalUtils: TransactionalUtils) {

    fun foo() {
        transactionalUtils.executeAsTransactional { transactionalFoo() }
    }

    private fun transactionalFoo() {
        println("This method is executed within transaction")
    }
}

TransactionTemplate기존 트랜잭션의 재사용 여부를 모르지만이 코드는 확실합니다.

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