Java/Spring2014. 11. 5. 01:47
반응형

 스프링 프레임워크를 이용하여 서비스 레이어 개발을 진행하던중 아래와 같은 로그를 발견하고 문제점을 찾아보기 시작했다. 디버그모드에서 출력되는 메시지인 관계로 무시해도 될 듯 했지만 스택 트레이스가 출력되고 있는 관계로 문제를 명확히 하기로 하고 찾아보기 시작했다. 일단 단순히 출력된 메시지만 확인한 결과 명시적으로 JDBC 세이브포인트를 릴리스 할수 없다는 내용이었습니다.


DEBUG o.s.j.d.JdbcTransactionObjectSupport - Could not explicitly release JDBC savepoint
java.sql.SQLException: 지원되지 않는 기능입니다
    at oracle.jdbc.driver.PhysicalConnection.releaseSavepoint(PhysicalConnection.java:6356) ~[ojdbc6-11.2.0.4.jar:11.2.0.3.0]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.7.0_51]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) ~[na:1.7.0_51]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.7.0_51]
    at java.lang.reflect.Method.invoke(Method.java:606) ~[na:1.7.0_51]
    at org.apache.ibatis.datasource.pooled.PooledConnection.invoke(PooledConnection.java:246) ~[mybatis-3.2.7.jar:3.2.7]
    at com.sun.proxy.$Proxy92.releaseSavepoint(Unknown Source) ~[na:na]
    at org.springframework.jdbc.datasource.JdbcTransactionObjectSupport.releaseSavepoint(JdbcTransactionObjectSupport.java:142) ~[spring-jdbc-3.2.10.RELEASE.jar:3.2.10.RELEASE]
    at org.springframework.transaction.support.AbstractTransactionStatus.releaseHeldSavepoint(AbstractTransactionStatus.java:160) [spring-tx-3.2.10.RELEASE.jar:3.2.10.RELEASE]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:749) [spring-tx-3.2.10.RELEASE.jar:3.2.10.RELEASE]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:724) [spring-tx-3.2.10.RELEASE.jar:3.2.10.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:475) [spring-tx-3.2.10.RELEASE.jar:3.2.10.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:270) [spring-tx-3.2.10.RELEASE.jar:3.2.10.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94) [spring-tx-3.2.10.RELEASE.jar:3.2.10.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) [spring-aop-3.2.10.RELEASE.jar:3.2.10.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:633) [spring-aop-3.2.10.RELEASE.jar:3.2.10.RELEASE]
    ...
    ...



 중첩 트랜잭션이 내부적으로 세이브포인트를 사용하므로 중첩 트랜잭션 사용이 원인일 것으로 생각되었다. 로그의 좀더 살펴본 결과 중간 부분에 releaseSavepoint라는 메소드가 호출되고 있는 부분도 확인 되었다. 스택 트레이스를 따라서 호출한 메소드를 확인해 본 결과 역시나 해당 메소드의 트랜잭션 전파규칙은 중첩(NESTED)으로 선언되어 있었다.
※사실 이전의 세이브포인트 관련 포스팅이 중첩 트랜잭션을 사용하기전에 상세한 내용을 알아보기위해 작성한 글이다.


@Transactional(propagation=Propagation.NESTED)
public void methodName() {
    ......
}



 일단 releaseSavepoint 메소드 호출시 해당 예외가 발생하는 것을 확인였으므로 사용하는 JDBC인 오라클 관련해서 검색을 좀 해본결과 오라클 도큐멘트에서 아래와 같은 문구가 있었습니다.


public void releaseSavepoint(Savepoint savepoint) throws SQLException;

Not supported at this release. Always throws SQLException.


※출처 : http://docs.oracle.com/cd/B10501_01/java.920/a96654/jdbc30ov.htm







 오라클 JDBC3.0 구현체에서는 명시적으로 세이브포인트 해제 메소드 호출은 지원하지 않고있음에도 불구하고 스프링 프레임워크 내부적으로 아래와 같이 커밋 시점에 명시적으로 호출하기 때문에 예외가 발생하고 있음을 알게 되었습니다.


 org.springframework.transaction.support.AbstractPlatformTransactionManager 내부 커밋 구현

    private void processCommit(DefaultTransactionStatus status) throws TransactionException {
        try {
            boolean beforeCompletionInvoked = false;
            try {
                ...중략...
                if (status.hasSavepoint()) {
                    if (status.isDebug()) {
                        logger.debug("Releasing transaction savepoint");
                    }
                    status.releaseHeldSavepoint();
                }
                else if (status.isNewTransaction()) {
                    if (status.isDebug()) {
                        logger.debug("Initiating transaction commit");
                    }
                    doCommit(status);
                }
                 ...후략...



 실제 예외의 로그를 출력하는 부분의 소스코드도 살펴보았다. 아래처럼 단순히 예외를 캐치하고 로그를 출력하고만 있었다. 스프링 자체의 구현으로는 전후의 소스에 영향을 줄만한 부분은 없다는 것이 확인되었다.


 org.springframework.jdbc.datasource.JdbcTransactionObjectSupport 내부 릴리스 세이브포인트 구현

public void releaseSavepoint(Object savepoint) throws TransactionException {
        try {
            getConnectionHolderForSavepoint().getConnection().releaseSavepoint((Savepoint) savepoint);
        }
        catch (Throwable ex) {
            logger.debug("Could not explicitly release JDBC savepoint", ex);
        }
    }


 사실 로그만 출력되는 현상이었고 단위테스트상에서는 커밋 롤백모두 잘 실행되었으므로 문제가 있다고는 할 수 없었지만 일단 무엇인가 찜찜한 기분은 어쩔수 없어서 내부 구현을 확인해 보았다. 위와 같이 큰 문제가 없는 부분도 있지만 방치해 두면 알수없는 애매한 버그를 발생시키는 경우도 있으므로 명확하게 테스트를 진행하고 소스코드를 확인해 두는 것이 좋겠습니다. 프레임워크를 이용하으로 막연히 잘되겠지고 안이하게 생각한다면 뜻하지 않은 곳에서 어려움에 봉착할지도 모르므로 주의하도록 합시다.


Posted by Reiphiel