하이버네이트 VS iBatis 성능 비교: 이번에는 select

결과는 비슷하다. 차이 없다.

어제는 insert 성능 비교했었는데, 단일 insert 문이야 얼마나 차이가 나겠냐. 고작 3~4 밀리세컨 가지고 성능을 논하기는 뭐할 것 같아서, 1000건~5000건을 한꺼번에 insert 시키는 시간을 측정해 봤더니, 이건 뭐 하이버네이트가 한 5~6배는 빠르게 나왔었다. 코드까지 열어봐도 잘 모르겠지만, 배치 업데이트를 하는것처럼 보인다. 그러지 않고서는 저렇게 성능차이가 날 수 없다. 아니면 내가 뭔가 잘못 테스트 했거나….

코드는 github에 올려두었으니, 해보고 싶으신 분들은 직접 해보시면 됩니다.

https://github.com/keesun/his

어제 저녁에는 select 성능을 측정하다가 퇴근시간이 다되서 나갔는데, 오늘 오전에 마저 해봤더니, 성능이 비슷하게 나왔습니다. 예상했던 결과입니다. 하이버네이트나, iBatis나 결국은 DataSource 빈 통해서 가져오는거고, 그 위에서 맵핑좀 하고, 미리 만들어둔 쿼리 가져다가 쓰는것 뿐이니까요.

소스 코드에서는 MemberServiceIntegTest 클래스를 실행해보시면 됩니다.

하이버네이트와 아이바티스 DAO를 사용하는 서비스 구현체를 가지고 테스트했습니다. 처음엔 DAO를 직접 가져다가 테스트 했는데, 그때는 테스트 클래스 위에 붙인 @Transacional 때문에, 트랜잭션 단위가  테스트 메서드로 묶이는데, 그게 성능에 영향을 준건지, Hibernate가 iBatis보다 select 성능이 2배 정도 높게 측정됐었습니다. 조금 차이는건 이해하겠지만, 2배 정도 차이나는건 이해할 수가 없고, 원인도 모르겠더군요. 그러던 중 직장 동료분께서 트랜잭션 범위에 대해서 조언을 주셨고, 결국 서비스를 구현하여, 서비스 구현체에 @Transional을 붙인 상태로 테스트 해보게 됐습니다.

테스트 코드 찾기 귀찮으신 분들을 위해서…

[java]
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/testContext.xml")
public class MemberServiceIntegTest {
   
    @Autowired MemberServiceHibernateImpl memberServiceHibernate;
    @Autowired MemberServiceIbatisImpl memberServiceIbatis;

    @Before
    public void setUp(){
        memberServiceHibernate.flushAndClear();
        memberServiceIbatis.deleteAll();
    }

    @Test
    @Repeat(10)
    public void hibernate(){
        //GIVEN
        List<Member> member5000 = MemberTestUtils.makeMember5000();
        for(Member newMember : member5000) {
            memberServiceHibernate.add(newMember);
        }
        memberServiceHibernate.flushAndClear();
        System.out.println("Hibernate: insert 5000 complete!");

        //WHEN
        StopWatch watch = new StopWatch();
        watch.start();
        for(int i = 0 ; i < 100 ; i++) {
            List<Member> members = memberServiceHibernate.list();
        }
        memberServiceHibernate.flushAndClear();
        watch.stop();

        //THEN
        System.out.println(watch.prettyPrint());
    }

    @Test
    @Repeat(10)
    public void iBatis(){
        //GIVEN
        List<Member> member5000 = MemberTestUtils.makeMember5000();
        for(Member newMember : member5000) {
            memberServiceIbatis.add(newMember);
        }
        System.out.println("iBatis: insert 5000 complete!");

        //WHEN
        StopWatch watch = new StopWatch();
        watch.start();
        for(int i = 0 ; i < 100 ; i++) {
            List<Member> members = memberServiceIbatis.list();
        }
        watch.stop();
        //THEN
        System.out.println(watch.prettyPrint());
    }

}
[/java]

5천 건을 넣고, selecrt를 100번한 시간을 측정하는 겁니다. 그렇게 하이버 10번, iBatis 10번. 해본 결과는 다음과 같습니다.

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 6428
—————————————–
ms     %     Task name
—————————————–
06428  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 5399
—————————————–
ms     %     Task name
—————————————–
05399  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 3758
—————————————–
ms     %     Task name
—————————————–
03758  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 3773
—————————————–
ms     %     Task name
—————————————–
03773  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 3741
—————————————–
ms     %     Task name
—————————————–
03741  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 3735
—————————————–
ms     %     Task name
—————————————–
03735  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 3719
—————————————–
ms     %     Task name
—————————————–
03719  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 4156
—————————————–
ms     %     Task name
—————————————–
04156  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 3829
—————————————–
ms     %     Task name
—————————————–
03829  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 3785
—————————————–
ms     %     Task name
—————————————–
03785  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3753
—————————————–
ms     %     Task name
—————————————–
03753  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3568
—————————————–
ms     %     Task name
—————————————–
03568  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3521
—————————————–
ms     %     Task name
—————————————–
03521  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3558
—————————————–
ms     %     Task name
—————————————–
03558  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3471
—————————————–
ms     %     Task name
—————————————–
03471  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3497
—————————————–
ms     %     Task name
—————————————–
03497  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3667
—————————————–
ms     %     Task name
—————————————–
03667  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3460
—————————————–
ms     %     Task name
—————————————–
03460  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3524
—————————————–
ms     %     Task name
—————————————–
03524  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3467
—————————————–
ms     %     Task name
—————————————–
03467  100%

JDK 6.0에서 CGLib과 JDK 프록시 성능 비교

테스트 환경

JDK 1.6
CGLib 2.1_3 nodep
Spring 2.5.5

테스트 클래스

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=”springContext.xml”)
public class HelloAspectTest{

    @Autowired
    Hello goodHelloImpl;
   
    @Test
    public void createProxy() throws Exception {
        assertNotNull(goodHelloImpl);
        StopWatch stopWatch = new StopWatch();
        testProxiedMethod(10, stopWatch);
        System.out.println(“total ” + stopWatch.getTotalTimeMillis());
    }

    private void testProxiedMethod(int count, StopWatch stopWatch) {
        while(count > 0){
            stopWatch.start();
            for(int i = 0 ; i < 1000000 ; i++)
                goodHelloImpl.hi();
            stopWatch.stop();
            System.out.println(stopWatch.getLastTaskTimeMillis());
            count–;
        }
       
    }
}

CGLib을 사용할 때는 Concrete class 타입을 써도 상관없는데, JDK 프록시를 사용할 때는 인터페이스 타입을 써야 합니다. 이유는 아시죠? 설명은 패스합니다.

StopWatch 클래스는 스프링이 제공해주는건데, 위처럼 요긴하게 쓸 수 있습니다. API는 설명이 필요 없을 만큼 작명을 잘 해뒀기 때문에 그냥 읽어보시면 어떤 일을 하는지 알 수 있으실 겁니다.

스프링 설정 파일

    <context:component-scan base-package=”org.opensprout.sandbox.proxy”>
        <context:include-filter type=”annotation” expression=”org.aspectj.lang.annotation.Aspect” />
    </context:component-scan>

    <aop:aspectj-autoproxy proxy-target-class=”true” />

윗 부분에 주의해야 합니다. proxy-target-class=”true” 이 설정을 삭제하면, JDK 프록시를 사용하는 것이고, 저 설정 그대로면, CGLib을 사용하는 겁니다.

어드바이스 적용 대상 인터페이스 => 타겟 인터페이스

public interface Hello {
   
    public String hi();

}

어드바이스 적용 대상 구현체 => 타겟 클래스

@Component
public class GoodHelloImpl implements Hello {
   
    public String hi(){
        return “”;
    }

}

클래스 이름이나 메소드 이름 등에 너무 신경쓰지 말아주세요. 흑흑..ㅠ.ㅠ 대충 만든거란 말에요. 그것 보단, 이 녀석이 인터페이스를 구현하고 있기 때문에, 스프링이 프록시를 만들 때 proxy-target-class=”true” 이 설정이 없으면 기본적으로 JDK 프록시를 생성한다는 사실.. 아시죠?

자 그럼 이제부터 쇼타임.

1. CGLib 사용시

위 설정 그대로 입니다.

1250
1204
1203
1218
1204
1203
1203
1219
1218
1219
total 12141

2. JDK 프록시 사용시

스프링 설정에서  proxy-target-class=”true” 이걸 삭제합니다.

1687
1657
1656
1656
1656
1672
1672
1672
1656
1656
total 16640

3. CGLib 프록시를 서버 모드로

위 설정 그대로 사용합니다.
테스트 실행시 VM 인자에 -server 추가합니다.

813
796
797
813
797
797
812
797
797
total 8172

4. JDK 프록시를 서버 모드로

스프링 설정에서  proxy-target-class=”true” 이걸 삭제합니다.
테스트 실행시 VM 인자에 -server 추가합니다.

1078
922
922
937
906
938
906
922
937
938
total 9406

캬요~ 자바지기님께서 테스트 했을 때에 비하면 JDK 프록시 성능 많이 좋아졌죠?