Whiteship's Note


[스프링 3.0] @Value 이용해서 기본값 설정하기

Spring/3.0 : 2010.03.16 18:04


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class ValueTest {

    @Autowired Whiteship whiteship;

    @Test
    public void defaultName(){
        assertThat(whiteship.getName(), is("keesun"));
    }

}

@Component
public class Whiteship {

    @Value("keesun")
    private String name;

    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

악.. 저녁 약속이 있어서 길게 정리는 못하겠네요. 


이것도 DI용 애노테이션이라는거..


top


봄싹 9월 특강 Completed!! 후기랄까나...

모하니?/Thinking : 2009.09.15 01:48



켄트벡 세미나와 KSUG 번개에 나갔었다면, 볼 수 있었겠지만 개인 사정으로 그동안 토비님을 못 뵈었다가 드디어 봄싹 스터디에 초대하여 3시간짜리 스터디 진행을 부탁드렸습니다. 커피 한 잔과 감자탕 한 끼로 너무 많이 부려먹은듯(?)해서 죄송스럽지만, 뭐.. 제자에게 이정도쯤은.. 해주셔야.. ㅎㅎㅎ 그저 감사 할 따름입니다. (__)/

이번에 사부님을 만나 느낀점은 많지만 그 중에서 개발자로서의 고민이 좀 더 심화되었습니다. 사실 다음 DebDay를 다녀왔을 때부터 느끼던 것인데 털어놓진 않고 속으로 앓고 있었지요.

문제의 핵심은 제가 작성한 코드가 개떡같다는 거였습니다.

그 개떡같은 코드는 현재 제 노트북에만 있고 그 어디에도 공개하지 않았습니다. 저번 달인가 이번 달 초에 다음 DevDay 때문에 제주도에  갔었을 때 작성한 코드가 정말 최악이었습니다. 스프링 코드를 거의 사용하지 않고 Smack과 java.net 패키지를 주로 이용하여 코드를 작성했었는데 정말 끔찍했습니다. 조금씩 계속 지져분해지기 시작하더니 어느 순간 걷잡을 수 없는 형태의 코드가 되어버렸고... 그 뒤는... 동작하긴 하지만 속은 다 썪어서 도무지 남에게 보여줄 수 없는 코드가 되었습니다.

그러던 중... 봄싹 스터디에서 스프링의 가장 기초이자 핵심은 DI가 어떤 것인지 보여주는 명쾌한 코드와 설명을 보면서 다시 한 번 자극을 받을 수 있었습니다. 스프링의 핵심인 DI가 어떤 과정으로 탄생되는 것인지 살펴보았는데, 그 과정이 굉장히 논리적이고 깔끔했습니다.

거기에다, 비밀리에 베타리딩 중인 책에서도 스프링이 어떤 문제를 어떤 방법으로 해결해나가는지 순차적으로 보여주는 내용이 저에게 많은 도움이 되고 있습니다.

스프링의 가장 초기 모습을 볼 수 있는 빨간책 1권과 코드를 중심으로 살펴보면서 스프링 DI 감각을 익히는 것이 중요하고 재밌겠다고 생각했습니다. 그래서 갑작스래 봄싹 스터디에 '오리지널 스프링' 스터디를 만들었는데, 생각보다 많은 분들이 참여하고 계십니다. 슬슬 스터디 홍보로 전환되는 듯 한데 어서 마무리하고 좀 더 공부하다 자야겠습니다.

밤늦게 글을 써서 그런지 두서가 없는데, 결론은 토비님 덕분에 스프링을 좀 더 진지하게 공부하게 되었다는 것입니다. 부디.. 개떡같은 코드가 찰떡같은 코드로 거듭나길 바라며~~ 열공!!
top


스프링의 이메일 기능 지원과 테스트를 살펴보자

Spring/etc : 2009.07.27 12:20


스프링이 지원하는 이메일은 JavaMail과 JAF라는 것이 있습니다. 사용법은 간단하니.. 다음에 심심할 때 살펴보기로 하고, 지금은 사부님이 올리신 글과 관련 된 부분을 찾아보는게 급선무입니다.

스프링 이메일 기능은 context.support 모듈에 들어있습니다. 주요 클래스는 o.s.mail.javamail에 들어있는 JavaMailSender 인터페이스와 그 구현체인 JavaMailSenderImpl 클래스입니다. 인터페이스의 API를 읽어보면 다음과 같은 내용이 있습니다.

...

Clients should talk to the mail sender through this interface if they need mail functionality beyond SimpleMailMessage. The production implementation is JavaMailSenderImpl; for testing, mocks can be created based on this interface. Clients will typically receive the JavaMailSender reference through dependency injection.

...

The entire JavaMail Session management is abstracted by the JavaMailSender. Client code should not deal with a Session in any way, rather leave the entire JavaMail configuration and resource handling to the JavaMailSender implementation. This also increases testability.

A JavaMailSender client is not as easy to test as a plain MailSender client, but still straightforward compared to traditional JavaMail code: Just let createMimeMessage() return a plain MimeMessage created with a Session.getInstance(new Properties()) call, and check the passed-in messages in your mock implementations of the various send methods.

즉, 이 API를 만든 의도에 사용성 편의 뿐만 아니라, 테스트 편의성도 포함되어 있다는 암시를 읽어낼 수 있습니다. JavaMail의 Session API를 사용하지 않고 스프링의 JavaMailSender를 목킹해서 테스트 하라는 것인데, 왜 그렇게 했는지는 맨 마지막 부분의 JavaMail의 Session API 사용법(Session.getInstance(new Properties())에서 볼 수 있습니다. 바로 static 메서드입니다.

JavaMail 레퍼런스에서 그 사용법을 보면, 다음과 같은 코드로 JavaMail을 이용하는 모습을 볼 수 있습니다.

//메시지를 만들고,,
     Properties props = new Properties();
     props.load(new FileInputStream(propfile));

     Session session = Session.getInstance(props, null);
     MimeMessage msg = new MimeMessage(session);

...

//전송합니다.
    // Set the content for the message and transmit
    msg.setContent(mp);
    Transport.send(msg);

코드 대부분을 생략했습니다. 중요한 부분은 위에 다 나와있습니다. 바로 static 메서드를 사용한다는 것이 중요한 부분입니다.

이런 API 사용을 클래스를 단위 테스트 하려면 막막합니다. 도무지 static 메서드를 호출하는 부분을 mock으로 바꿀 수가 없습니다. 그렇다고 테스트를 하는데 실제로 메일을 매번 보내기도 뭐하고 말이죠. 그래서 테스트 하려면 static 메서드 호출을 사용한 클래스를 거의 다시 만들다시피 구현한 stub을 만들어서 테스트 해야 하는데 이건 엄청난 수고가 필요합니다. JavaMail 예를 들면 거의 Transport를 테스트용으로 다시 구현해서.. sendMessage에서 실제로 메일을 보내지 않고 그냥 보내는 메시지 목록에 메시지만 모아두는 식의 작업이 필요해집니다. 그리고 테스트 할 떄는 그런 스텁 Transport를 사용하는 또 다른 스텁 MailSender가 필요해지겠죠.(실제로 이 작업들은 스프링의 JavaMailSenderTests에서 수행하고 있습니다.)

하지만, 테스트를 편하게 하는 방법이 아주 없는 건 아니었습니다. 오늘 톱님께서 올리신 글을 보면 static 메서드를 호출하는 코드를 비교적 편하게 테스트하는 방법 세 가지를 알 수 있습니다.

하나는 JavaMailSender처럼 static 메서드 호출 부분을 랩핑한 클래스를 만들고, 그 클래스를 목킹한 다음 해당 메서드가 호출되는지 테스트하는 것입니다. 그렇게 만들어 두면, 테스트 하려는 대상이 JavaMail의 Transport.send 같은 static 메서드를 호출하지 않고, 그것을 사용한 JavaMailSender를 사용하기 때문에 JavaMailSender의 mock을 만들고 그 객체의 send가 호출될 때 어떤걸 하라고 mocking 한다던지 해서 호출이 제대로 됐는지, 어떤 메시지가 넘어갔는지 등을 확인할 수 있겠습니다.

두 번째 방법은 로드존슨이 만들었다는 AspectJ를 이용하는 방법이고, 세 번째 방법은 PowerMock을 이용하는 방법인데, 둘 다 결국 바이트코드를 조작해서 static 호출 부분을 mock으로 호출로 교체하는 기술 인듯합니다. 이 두 가지는 일단 논외로 치

결론은.. 스프링의 JavaMailSender를 사용하면 JavaMail의 static 메서드 호출과 관련하여 테스트를 어떻게 작성할까 고민할 필요없이, JavaMailSender를 목킹해서 테스트를 만들면 된다는 것입니다.

덤으로 JavaMail의 static 메서드 호출을 사용한 JavaMailSender의 테스트 클래스를 보면, static 메서드 호출을 하는 클래스에 대한 테스트 실마리를 얻을 수 있습니다. static 메서드를 호출해서 가져오는 객체를 가져오는 부분을 별도의 메서드로 분리하고 그 부분을 테스트 용으로 재구현하고 그것을 테스트에서 사용하는 방법인데.. 이거 이거.. 귀찮아서 원... @_@..  그래서 static은 테스트의 적인가 봅니다. 그래도 이길 수 있는 적이라는거..
top


하이버네이트, 스프링 MVC에서 enum 사용하기 2

모하니?/Coding : 2009.07.03 16:37


어제에 이어 오늘도 물고 늘어진다. 이번에 해결한 문제는, DB에 저장할 값을 int 타입 뿐만이 아니라 String 타입을 사용해도 무방하도록 코드를 수정했다.

public enum UserCate implements PersistentEnum {
   
    ADMIN("admina", "관리자"), STAFF("staff", "직원"), SUPP("supp", "협력업체");
   
    private final String value;
    private final String descr;
   
    private UserCate(String value, String descr) {
        this.value = value;
        this.descr = descr;
    }

    public String getValue() {
        return value;
    }
    public String getDescr() {
        return descr;
    }
   
...

}

이건 DB에 저장할 값으로 String 값을 사용할 enum 이고..

public enum CodeCate implements PersistentEnum {

    COLOR(10, "색상"), SIZE(20, "사이즈"), PAYTERM(30, "지불조건"), SHIPVIA(40, "운송방식");
   
    private final Integer value;
    private final String descr;
   
    private CodeCate(Integer value, String descr) {
        this.value = value;
        this.descr = descr;
    }
   
    public int getValue() {
        return value;
    }
    public String getDescr() {
        return descr;
    }

...

}

이건 DB에 저장할 값으로 int 값을 사용할 enum이다.

하이버네이트가 필요로 하는 UserType을 만들어 보자.

public class CodeCateType extends GenericEnumUserType<CodeCate>{

    public CodeCateType() {
        super(CodeCate.class);
    }
   
}

public class UserCateType extends GenericEnumUserType<UserCate>{

    public UserCateType() {
        super(UserCate.class);
    }
   
}

끝이다. 도메인 객체 타입에 설정해보자. 구현에 필요한 코드만 파란색으로 강조를 했다. 생성자 만드는 부분까지 없앨 수 있을 것 같다. 3차 구현에서 해보자.

Code.java

    @Column
    @Type(type="koma.domain.usertype.CodeCateType")
    CodeCate codeCate;

User.java

    @Column
    @Type(type="koma.domain.usertype.UserCateType")
    UserCate userCate;

자.. 이러면 하이버네이트가 CodeCate는 Integet SQL 타입 컬럼을 만들어 주고 UserCate는 String SQL 타입(varchar)를 만들어 줄 것이다.

스프링 MVC에서 바인딩 할 때 사용할 PropertyEditor는 어떨까? 한 줄 씩이다.

        binder.registerCustomEditor(CodeCate.class, new GenericEnumPropertyEditor<CodeCate>(CodeCate.class));
        binder.registerCustomEditor(UserCate.class, new GenericEnumPropertyEditor<UserCate>(UserCate.class));

끝인가?? 글쎄.. 모르곘다. Charater를 추가하는 것도 별 문제 없을 듯 하다. 해보자. 이러다가 모든 타입을 지원해야 하는건 아니겠지?? Char까지만 해보자. 여기서도 생성자에 클래스를 주고 있는데 3차에서는 저 코드를 없앨 수 있게 구현해보자.

자.. 하이버, 스프링에서 자바 enum 사용 간편화 3차 구현 ㄱㄱㅆ
top


하이버네이트, 스프링 MVC에서 enum 사용하기

모하니?/Coding : 2009.07.02 18:19


하이버네이트와 스프링 풀셋으로 구성되어 있는 웹 애플리케이션에서 자바 enum을 사용할 때 생기는 이슈가 뭘까?

1. DB에 어떤 값을 넣을 것이고,
2. 화면에는 어떤 값을 보여주고 어떻게 바인딩 할 것인가?

이 두 가지라고 한다. 그 밖에 이슈 될만한 것은.. 흠.. 뭐.. 없지 않을까 싶다. 왜 이슈일까?

1번 문제를 보자. DB에 잘 안 들어갈까? 하이버네이트로 맵핑을 해보자.

enum UserType {
 MEMBER, MANAGER
}

@Entity
class Member {
...
  @Column
  UserType userType;
}

이 상태로도 SessionFactory를 생성하는데 별 문제도 없을 뿐더러, 읽고 저장하기가 잘 된다. 문제는 DB에 들어가는 값이다. DB에 들어가는 값을 보면, UserType.MEMBER는 0, UserType.MANAGER는 1이 integer 컬럼에 저장된다. enum의 ordinal() 메서드가 반환해주는 값을 그대로 저장한 것이다. 문제는 ordinal() 값이 고정이 아니라, enum 순서에 따라 바뀐다는 것이다. 이런... 그럼 안 되겠다. ordinal 말고, String을 저장하고 싶다면, JPA의 @Enumerated 애노테이션을 추가해주면 된다. @Enumerated(EnumType.STRING) 이렇게 말이다. 이 것을 사용하면 방금 말한 ordinal 문제는 사라질 것이다. DB에는 ordinal이 반환하는 Integer대신 enum의 name이 저장될 것이다.

그런데.. 어떤 이유에선가 굳이 integer 값을 DB에 저장하고 싶다면 어찌해야 될까? 이제부터 복잡해진다. 일단 enum에 필드를 하나 추가하고, EJ2가 추천하는 방법으로 enum을 구현한다. 다음에는 하이버네이트의 UserType 인터페이스를 구현한 클래스를 하나 만들고,

    @Type(type="koma.domain.usertype.CodeCateUserType")
    @Column
    CodeCate codeCate;

이런식으로 하이버네이트의 @Type 애노테이션을 이용하여 맵핑 방법(db에 어떻게 저장하고, db에서 어떻게 꺼내 올 것인가)을 담고 있는 UserType 구현체를 지정해주어야 한다. 이 구현체를 만들 때는 UserType 인터페이스가 제공하는 메서드 10개 정도를 구현해야 된다. 귀찮은 일이다. 그래서 GenericEnumUserType 이라는 클래스를 만들었다. 간단하게 상속만 하고, 생성자만 만들면 되도록 귀찮을 일을 줄여놨다. 자 그럼 일단 첫 번째 문제는 해결이다.

두 번째 문제는 첫 번째에 비하면 비교적 쉽다. 지난 프로젝트에서 PropertyEditor와 씨름을 했던탓에 면역이 생긴 것 같다. 화면에 Enum을 보여줄 떄 enum의 name을 보여주고 싶진 않을것이다. 역시 새로운 필드를 추가해야겠다. 그리고 화면에 보여줄 때는 그 값을 출력하고, 화면에서 어떤 것을 선택했을 때에는 아까 DB에 입력한 값을 선택해서 가져오도록 화면 코드를 작성했다.

다음은 그렇게 해서 가져온 integer 값을 Enum 객체로 샥 바꿔주는 일을 할 PropertyEditor를 만드는 것이다. 간단하다. getAsText()에서는 getValue()로 가져온 객체를 내가 사용하는 enum으로 타입을 변환 한 다음 아까 추가한 필드의 getter를 사용하여 String 값을 넘겨주었다. 이제 화면에서 사용자 친화적인 문구를 볼 수 있을 것이다. 다음은 setAsTest(String text)를 재정의 하여 text는 화면에서 선택한 enum이 DB에 입력하는 값인 integer 값일 것이다, 일단 Integer.parseInt()를 해야 겠다. 아.. 이런.. Enum 클래스에서 valueOf(Class, String) 메서드를 제공해준다. 하지만 난 int 값을 사용하기로 마음 먹었으니 저 클래스는 사용하지 못하겠다. 유틸을 하나 만들었다. Enum 클래스와 int 값을 받아서 해당 int 값을 가지고 있는 Enum을 돌려받는.. 그런 클래스다. 자 그럼 이제 이 유틸을 이용해서 setAsText(String text) 구현도 마칠 수 있다. 이러한 PropertyEditor 역시 매번 만들어 쓰기 귀찮으니깐, 아예 클래스를 만들지 않고 객체만 만들어 사용할 수 있는 GenericEnumPropertyEditor를 만들었다. 두 번째 문제도 해결됐다.

오늘 내가 할 일은 이게 끝인 듯 하다. 자 그럼 잠깐 회고를 해보자.

DB에 int 값이 아닌 enum의 name 문자열을 저장한다면 어떻게 될까?

일단, UserType을 만들 필요가 없어진다. 아까도 이야기 했듯이 @Column과 @Enumerated(EnumType.STRING)를 사용하면 UserType 없이고, 문자열로 enum을 DB에 저장할 수 있다. GenericEnumUserType도 필요가 없고, 매번 UserType 클래스를 만들어야 하는 수고도 줄어든다.

다음, 화면에서 enum 목록(Arrays.asList(enum.values());를 사용하면 간단)을 보여줄 때, enum에 추가한 사용자 친화적인 설명을 담고 있는 descr 속성에 담겨있는 값을 보여주고, 실제로 선택하는 값이 DB에 저장하는 int값이 아닌 enum의 name이라면 어떻게 바뀔까? getAsText() 구현은 동일하고, setAsText()에서 받아오는 값이 Enum의 name이니깐, Enum.valueOf(Class, String)을 사용할 수 있다. 굳이 Util 클래스를 만들 필요도 없고, setAsText() 구현도 간단해진다. 다만, Enum 마다 PropertyEditor 객체를 지정해 줘야 하는 건 어쩔 수 없다. 하지만 이건 정말 일도 아니다. 새로운 클래스를 추가하는 것도 아닌데 이 일이 뭐 크게 대수겠는가.

결국.. DB에 어떤 이유로 인해 enum의 interger 값을 저장하는 것이 enum의 name 문자열을 저장하는 것보다 훨씬 복잡하고, 귀찮은 것 같다.

DB에 int를 저장하는게 좋을까 string을 저장하는게 좋을까? integer 값을 저장해야 하는 별다른 이유가 없다면 나는 enum의 name을 저장하고 싶다.

수정은 내일.. 오늘은 이만 퇴근..

===========================

할려고 했으나.. 이게 끝이 아니란다. DB에 저장할 enum 필드를 선택할 수 있게 해야 되고(결국 위에서 실컷 고민한게.. 물거품처럼 하얘지는 느낌이다.),

enum 목록을 가져올 때 정렬을 할 수 있어야 한단다.(그럼 이것도 Arrays.asList(enum.values()); 만으로는 어림 없을 듯 하다.)

또한 i18n까지도..

@_@
top


스프링 2.5 환경에서 하이버네이트 사용하기



참조 요약: Spring One 2008 Wokring With Hibernate in a Spring 2.5 Environment

스프링의 HibernateTemplate
(Hibernate 3.1 이전)

- 스프링이 관리하는 트랜잭션을 사용한다.
- 예외 번역 제공

public class HibernateClinic extends HibernateDaoSupport {
...
}

Native Hibernate DAO
(Hibernate 3.1+_

- 트랜잭션 훅(hook)을 제공한다.
- transactional session을 찾는 로직을 제공할 수 있게 한다. sessionFactory.getCurrentSession()
- 예외 번역 제공 @Repository PersistenceExceptionTranslation post processor
- 순수 하이버네이트 API만 사용할 수 있다.

단위 테스트 종류
- "Logical" 단위 테스트
- 통합 테스트

스프링으로 통합 테스트하기
- DI
- 애플리케이션 컨텍스트 로딩 줄이기
- 자동 롤백
- 하이버네이트 + JDBC 조합

AbstractTransactionalDataSourceSpringContextTests
- Ctrl + Shift + T ATDSSCT
- 애플리케이션 컨텍스트 로딩, 캐슁
- DI
- JdbcTemplate 제공

WAR에서 하이버네이트 VS OSGi 개발
- 일반 WAR 배포
  - SessionFactory가 클래스패스에 도메인 타입들을 찾는다.
  - 저장소(DAO, repositoru)들이 SessionFactory를 사용한다.
- OSGi 개발
  - 여러 도메인 번들로 쪼갠다.
  - SessionFactory는 도메인 타입에 접근해야 한다.
  - 저장소는 SessionFactory에 접근해야 한다.

OSGi 환경에서 하이버네이트
- "infrastructure" 번들
  - SessionFactory 서비스를 제공하고
  - 모든 도메인 타입을 가져온다.
- 여러 도메인 모델
  - 도메인 타입을 공개한다.
  - SessionFactory 서비스를 사용한다.
- Petclinic 예제를 제공한다.

=====

발표에서 다루는 내용이 너무 많아서 다소 산만했습니다. 하이버네이트만 집중적으로 다뤘으면 어땠을까 싶더군요. @Repository 얘기가 나오니까 컴포넌트 스캔 얘기로 새고.. 도메인 객체에 repository 객체 주입하는 얘기가 나오니까 @Configurable이랑 aspectj 얘기로 새고.. 테스트 얘기 나오니까 Test Context 쪽으로 또 얘기가 새버렸다가 나중엔 다시 하이버네이트랑 OSGi 얘기 조금 꺼내고 끝~~

멋진건 54분이라는 짧은 발표 시간 중에도 저렇게 많은 내용과 중간 중간 전부 데모까지 보여줬다는 겁니다.
top


스프링과 디자인 패턴

Spring/etc : 2009.01.16 10:22


참조, 요약: 프로 스프링 2.5

인터페이스 기반 프로그래밍
생성 패턴
- Singleton: BeanFactory
- Factory
- Builder: BeanDefinitionBuilder
- Prototype: 스프링 없이 만들려면 별도의 추상 클래스 만들고 makeCopy() 같은 매서드 필요해
구조 패턴
- Proxy: 스프링 AOP
- Adapter: MessageListenerAdapter, MessageListenerAdapter102(어댑터), MessageListener
- Wrapper와 Decorator: DisplayTag
- Facade
행동 패턴
- Observer: ApplicationListener
- Strategy
템플릿 매서드
- JdbcTemplate, HibernateTemplate, HibernateCallback, ...

흠~ 여기서 좀 걸리는건 템플릿 매서드 패턴의 예로 든 JdbcTemplate 및 기타 Template 들인데요. 빨간책에서는 템플릿 매서드 패턴으로 XXSqlQuery 클래스들을 꼽고 있고 XXTemplate들은 Stategy 패턴의 일종인 콜백이라고 하고 있는데 좀 혼란을 주네요. @_@
top


스프링을 사용하는 애플리케이션의 성능 최적화 방안

Spring/etc : 2008.11.18 13:38


참조: Spring In Production White Paper
번역, 요약, 편역: 백기선

어떻게 하면 스프링을 사용한 애플리케이션의 성능을 향상 시킬 수 있을까?

처음으로 할 일은 성능을 측정하여 핫스팟을 발견하고 변경으로 인해 얻을 수 있는 이점을 정량화 한다. 최적화는 두 분류 효율적인 청사진 만들기(설정 튜닝하기)와 효율적인 런타임 기능 사용하기(애플리케이션 설계 최적화)로 나뉘어진다.

측정하기

튜닝은 측정부터.. 아파치 JMeter, Selenium 그리고 프로파일러를 사용한다. 스프링소스 컨설턴트들은 JAMon을 Spring AOP나 ApsectJ와 함께 사용해서 컴포넌트의 동작이나 요청 처리 경로를 프로파일링 할 때 좋은 성과를 봤다.

효율적인 청사진 만들기

효율적인 청사진 만들기의 비밀은 배포 플랫폼의 장점을 충분히 활용하는것에 있다. 스프링이 환경 종속적인 정보를 애플리케이션 코드 밖으로 빼내어 관리하기 때문에 이렇게 하는 것이 훨씬 쉽다.

데이터베이스 커넥션 풀의 경우, 애플리케이션 서버 위에서 실행하고 있다면 풀 설정을 관리자 콘솔에서 하고 스프링에는 JNDI를 통해 참조하도록 할 수 있다.

<jee:jndi-lookup id="dataSource"
    jndi-name="jdbc/MyDataSource"/>

이렇게 하면 두 가지 장점이 생긴다. 하나는 애플리케이션을 애플리케이션 서버 콘솔을 통해 운영팀이 관리하기 쉬워진다. 두 번째는 애플리케이션 서버 밴더가 커넥션 풀을 최적화 할 것이다.

같은 이유로 JMS ConnectionFactory 와 Destinationeh 애플리케이션 서버에 설정하고 JNDI로 얻어올 수 있다.

트랜잭션 관리의 경우도, 스프링은 커스텀 플랫폼 트랜잭션 매니저를 제공하여 여러분 배포 환경에 맞는 걸 사용할 수 있도록 해준다. ex) WebLogicJtaTransactionManager, Oc4jJtaTransactionManager,
스프링 2.5에 추가된 <tx:jta-transaction-manager/> 태그가 자동으로 기반으로 하고 있는 플랫폼을 찾아서 적절한 구현체를 선택할 것이다.

스프링 JMX를 사용하여 애플리케이션 자원을 노출할 때, 제품 플랫폼이 제공하는 MBeanServer와 연동하고 싶을 것이다. ex)  웹로직-JNDI (“java:comp/env/jmx/runtime”), WebSphereMBeanServerFactoryBean,
스프링 2.5에 추가된  <context:mbean-server ... /> 태그를 사용하면 자동으로 적절한 MBeanServer를 찾아준다.

이런 장점이 있지만, 통합 테스트를 애플리케이션 서버 밖에서 하고 싶을 것이다. 예를 들어, 기본적인 메시징 테스트는 ActiveMQ를 사요해서 하지만 실제 제품을 배포할 때는 IBM의 MQSeries를 사용할 수 있다. 스프링이 여러 설정 파일을 사용하는 기능이 있기 때문에 쉽게 처리할 수 있다. 우리는 모든 환경-독립적인 설정을 핵심 애플리케이션 설정에서 분리하는 것을 추천한다. 최선책은 애플리케이션 모듈 마다 하나의 설정 파일을 유지하는 것이다. 여기에 추가로 integration-test.xml 설정과 proeduction.xml 같은 설정을 정의할 수 있을 것이다. 통합 테스트를 할 때는 적절한 모듈 설정 파일과 integration-test.xml을 사용하여 application context를 만들면 된다. 배포할 때는 production.xml 파일을 사용하면 될 것이다.

이와 관련있는 설정으로 PropertyPlaceholderConfigurer는 설정 값을 외부화 하여 운영 팀에서 변경할 수 있도록 할 때 매우 유용하다. 스프링소스 컨설턴트가 성공적으로 사용하고 있는 방법은 다음과 같이 properties 파일을 연쇄적으로 사용하는 것이다.

1. classpath*:*.properties.local: 이에 해당하는 프로퍼티 파일들은 소스 코드 관리 시스템에 포함시키지 않느다. 개발자 마다 재정의해서 쓰도록 한다.

2. classpath*:META-INF/*.properties.default: 이 속성 파일들은 빌드에 의해 생성되는 애플리케이션 요소에 포함되며 기본 설정 값들을 가지고 있다. 프로젝트의 요구사항에 따라 이 수준은 생략할 수도 있다.

3. classpath*:*.properties: 이 파일들은 애플리케이션 요소들 밖에 존재두고, 운영팀에서 쉽게 수정할 수 있게 한다.

<context:property-placeholder
  location="classpath*:META-INF/*.properties.default,
classpath*:*.properties,
            classpath*:*.properties.local"/>

효율적인 청사진 만들기에 추가적으로 생각해 봐야 할 것들
  • 최적의 JDBC 커넥션 풀 갯수 찾아보기. 테스트 할 때 실제 배포 시나리오대로 해볼 것
  • 쓰레드 풀 구현체를 사용하는 스프링의 TaskExecutor를 사용할 때 최적의 쓰레드 풀 크기를 찾아볼 것. 처음은 CPU 갯수와 동일한 쓰레드 갯수로 설정해두고, 로컬 파일 I/O를 쓰면 쓰레드 하나를 추가하고. 네트워크 기반 I/O를 할 떈 또 몇 개를 추가하는 식으로..
  • read-only 트랜잭션일 경우에는 read-only 속성을 선언할 것. 이렇게 하면 하이버네이트를 사용하여 많은 객체를 데이터베이스에서 읽은 다음 아무 일도 하지 않았을 때 정말로 성능이 향상된다. 이렇게 하면 FlushMode.NEVER로 설정되기 때문에 세션에서 불필요한 dirth checking을 하지 않는다.
  • 만약 2단 커밋이 필요 없다면, JTA 대신에 로컬 트랜잭션 매니저 사용을 고려해 보라. 스프링은 HibernateTransactionManager를 통해서 정말 쉽게 JDBC와 Hibernate 데이터 접근을 동일한 트랜잭션에서 처리하게 해준다. 둘은 다른 방법으로 DB에 접근하지만 동일한 트랜잭션을 사용한다.
  • acknowledge=”transacted" 설정을 통해 메시지 리스너 컨테이너에 네이티브 JMS 트랜잭션 사용을 고려하라.

런타임 최적화하기

대부분의 엔터프라이즈 애플리케이션 성능 문제는 영속 계층으로부터 기인한다. Good performance here is often a function of sound design choices. 몇 가지 팁을 살펴보자.
  • ORM 툴을 사용할 때, eager와 lazy 로딩 전략의 균형을 잘 맞춰야 한다. 기본으로 로딩 지연을 사용하고, 특정 경우에만 fetch-join으로 튜닝하여 이른 로딩 장점을 활용한다. 쿼리를 튜닝할 때는 데이터 셋을 제품이 지금부터 향후 1년 간을 기준으로 하라.
  • ORM 툴이나 데이터베이스를 사용하여 로그에 SQL문이 보이도록 하라. 너무 많은 쿼리가 발생하는 이규가 생길 때 이를 쉽게 찾을 수 있다.
  • 하이버네이트를 사용할 때, 하이버네이트 Statistics 객체를 사용하여 런타임에 무슨 일이 벌어지는지 알 수 있게 하라. 프로그래밍을 통해 statics에 접근하거나, 스프링을 사용하여 하이버네이트 Statics MBean을 여러분의 MBean 서버에 노출시킬 수 있다. 프로그래밍을 통한 statics 객체 사용을 JUnit 테스트에 활용하여 여러분이 예측 가능한 쿼리가 얼마나 많이 발생하는지 확인하거나, 허용하는 쿼리 수를 기술 할 수 있다. 그 수를 벗어나면 테스트가 실패하도록.
  • 배치 스타일의 기능, 벌크 업데이트 또는 추가, 스토어드 프로시저는 보통 ORM 보다는 JDBC를 사용하는 것이 최선책이다. 스프링은 이들을 혼용하기 쉽게 해준다. 예를 들어 하이버네이트와 JDBC 데이터 접근을 동일한 트랜잭션으로 할 수 있다. 이 때 동일한 테이블을 사용하는 JDBC가 제대로 동작하려면 하이버네이트 세션을 적절한 시기에 flush 해주어야 한다.
  • 데이터베이스가 제공하는 기능을 활용하라.
    • 엑셀 스프레드시트를 일겅야 하는 애플리케이션에서 간단하게 변환하고 각 행을 SQL 서버 테이블에 넣어야 한다면 3시간이 걸리는 일도 SQL 서버 lined 쿼리를 사용하면 17초 만에 뚝딱.
    • 하이버네이트로 데이터 트리를 특정 뎁쓰로 변환하는 작업을 아무리 튜닝해도 시간도 오래 걸리고 메모리로 쫑나는데, 이걸 오라클의 스토어드 프로시저로 오라클의 계층 쿼리 기능으로 하니까 5초 미만으로 해결 됨.
    • flat 파일을 오라클 데이터베이스로 읽을 필요가 있을 때, 오라클 SQL 로더를 사용하여 데이터를 staging 테이블로 읽어들인다음, 스토어드 프로시저로 변경하고 데이터를 복사하여 원하는 테이블에 넣을 수 있다.
  • 만약 (비즈니스 로직은 전혀 없고)완전한 영속 로직만 있는 메소드가 있다면 데이터베이스의 스토어드 프로시저로 옮기고 스프링 JDBC를 사용하여 그것을 호출하라.
  • 읽기 전용 참조 데이터는 메모리 내부의 캐시에 둘 수 있다.
배치 애플리케이션은 추가적으로 고려할 것이 있다. 메소드 사용이 중요하기 때문이다. 스트림-기반 알고리즘이 최선의 선택이다. 예를 들어 컬렉션 보다는 이터레이터를 사용하라. 파일을 가지고 작업할 때, 만약 줄을 나눠야 한다면 스프링-기반이 아니라 캐릭터-기반을 사용하라. 우리는 이런 접근 방법을 사용하여 2백 50만 줄을 읽어 들인 적이 있다. 파싱하고 처리하는데 4초 미만이 걸렸고 메모리는 102K만 사용했다.

XML을 사용하는 배치 애플리케이션도 스트리밍을 사용하라. 우리는 280mb 파일에 들어있는 100,000개의 복잡한 XML 이벤트를 처리해야 할 필요가 있었다. DOM 기반의 접근 방법으로 2.5 시간이 걸렸고, 가비지 컬렉팅으로 9분이 필요했다. XML pull-parshing 기반 접근 방법으로 바꾸었더니 3초 만에 처리가 끝났고 200k 메모리를 사용했다.

또 다른 팁으로 단위 테스트와 통합 테스트를 할 때 java.lang.management 패키지에 있는 JVM 통계정보를 사용하는 것이다. 그것을 사용하면 CPU와 가비지 컬렉션 시간들을 확인할 수 있다.

데이터 계층을 위한 마지막으로 조언으로  every team benefits from access to a good DBA.

이밖에 스프링소스 컨설턴트로부터 얻은 다른 최적화 방안은 다음과 같다.
  • 스프링 배치 프로젝트가 제공하는 retry 기능을 사용하여 실패시 재시도가 필요할 때 사용할 수 있다.(예를 들어, 오라클 RAC의 개별 노드에서 실패한 기능의 경우) 사용자가 에러를 만나는 부담을 덜어줄 수 있다.
  • 웹 요소 랜더링 비용을 과소평가 하지 말아라. 트랜잭션 밖에서 되길 원할 것이다.(이부분 때문에 OSIV 패턴 이야기가 나왔군..)
  • application context를 요청 마다 새로 생성하지 말아라.
  • 스프링의 비동기 task executer를 사용하여 백그라운드에서 실행해도 될 작업을 사용자가 기다리게 하지 말아라.
  • 적절한 리모팅 프로토콜을 선택하라. 만약 SOAP 호환이 필요없다면, 스프링의 HttpInvoker 같이 간단한 스키마를 사용하는 것이 더 간단하고 빠를 것이다.
  • 스프링 AOP를 애플리케이션의 굉장히 여러 부분에 적용하고 있다면 ApsectJ 사용을 고려하라
스프링소스 컨설턴트들이 최적화에 도움을 얻은 참고자료는 다음과 같다.
  • Thomas Kyte's “Runstats.sql” test harness
  • “Effective Oracle by Design” (Thomas Kyte)
  • “Java Performance Tuning” (Jack Shirazi)
  • Sun's Java Performance Guides


top


하이버네이트, 스프링, 트랜잭션, OSIV(Open Session In View) 패턴

Spring/Chapter 12 : 2008.11.17 15:10


참조

No Hibernate Session bound to thread 에러로 시작한 OSIV 얘기
http://forum.springframework.org/archive/index.php/t-33082.html

논쟁에서 언급한 HibernateTemplate API
http://static.springframework.org/spring/docs/2.5.x/api/org/springframework/orm/hibernate3/HibernateTemplate.html

스프링의 OpenSessionInViewFilter API
http://static.springframework.org/spring/docs/2.5.x/api/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.html

하이버네이트 위키 OSIV
http://www.hibernate.org/43.html

손권남님의 OSIV 정리
http://kwon37xi.springnote.com/pages/1075048

InfoQ에서 Spring In Production 요약한 것
http://www.infoq.com/news/2007/11/spring-production

댓글에 보면 OSIV 패턴이 안티 패턴인가에 대한 내용이 있음
http://blog.springsource.com/2007/11/09/download-the-spring-in-production-white-paper/

Tip4에서 OSIV 패턴은 안티 패턴이라고 한다.
http://www.realsolve.co.uk/site/tech/orm-performance.php

이미 세션이 닫힌 상태에서 뷰에서 Detached 객체에서 아직 로딩하지 않은 객체에 접근하면 에러(LazyInitializationException)가 발생합니다. 이 에러는 정상적인 에러죠. 하이버네이트 맵핑을 Lazy loading으로 최소한의 객체만 로딩하도록 하고 필요할 때 연관을 맺고 있는 객체를 추가로 가져오도록할 때 흔히 발생하는 아주 정상적인 에러입니다.

이런 상황을 극복하려면 필요한 객체를 미리 팻치(Eager Fatch)해둔 상태로 로딩하는 메소드를 DAO쪽에 구현해서 뷰에서 추가로 세션을 사용할 필요없이 뷰 랜더링을 마치도록 하면 되기도 하고..

아니면 OSIV 패턴을 사용해서, 인터셉터나 필터를 적용해서 뷰를 완전히 랜더링 할 때까지 세션을 열어 두는 겁니다. 그럼 뷰를 랜더링 하다가 필요한 객체가 있으면 세션을 사용해서 로딩할 수 있겠죠. 그럼 위의 방법처럼 DAO에 이른 팻치를 해서 가져오는 별도의 메소드나 쿼리를 만들지 않아도 되겠죠.

흠.. 하지만 뷰에서 자신도 예측하지 못한 쿼리가 계속 발생할 여지도 있고, 웹 단에서 데이터 캡슐화를 깨기 때문에 OSIV를 안티패턴으로 보고 사용을 최소화 해야 한다는 주장도 있습니다.

스프링 + 하이버네이트 조합을 쓸 때 OpenSessionInViewFilter를 자주 사용하는데, 이 API는 아주 잘 봐둘 필요가 있습니다. 그 중에서 가장 주목해야 할 부분은 다음과 같습니다.

1. 기본 플러시 모드는 FlushMode.NEVER입니다. NEVER는 deprecated 됐는데, 아직 사용하고 있습니다. 이 걸 왜 주의해야 하냐면, OSIV를 등록하고 만약 서비스 계층에 @Trasaction을 설정하지 않았다고 해봅시다. 그럴 때 트랜잭션 처리는 됩니다. 왜냐면 OSIV 때문이죠. 요청이 들어오면 해당 요청을 처리할 쓰레드에 새로운 세션 만들고 그걸로 트랜잭션 만들어서 쓰기 때문에 트랜잭션은 있는데 문제는 플러시 모드가 NEVER라서 명시적으로 flush()를 호출하기 전까지는 절대로 DB에 반영이 안 됩니다.

2. Conversation을 하나의 세션을 늘리는 방법(extending a sesion for conversation)으로 구현할 경우에, persistence 객체의 reassociation을 최대한 요청 처리 초기에 해야 합니다. 안 그럼 나중에 동일한 객체가 또 있다고 충돌 날 수가 있습니다. 이 문제에 대응하기 위해서 singleSession이라는 옵션에 false 값을 줄 수도 있지만, 그렇게 하면 매 요청 마다 새로운 Session 만들어서 사용하겠다는 것이고 Conversation을 구현하는 다른 방법(detached 객체를 가지고 구현하는 방법)으로 구현해야 될 것 같습니다. 즉 코딩할 때 조금 주의해서 단일 세션으로 Conversation을 사용할 것이냐, 아님 단순하게 요청 마다 새로운 세션으로 만들고 객체들을 붙였다 띄었다 할 것이냐 인데.. 필요한 객체를 계속 하나의 세션에 들고 유지하는게 좀 더 효율적이지 않을까 싶네요.


top


새로운 스프링소스 웨비나 일정

Spring/Webinar : 2008.11.16 12:38


참조: http://www.springsource.org/node/828

일시: 한국 시간으로 11월 25일 밤 11시~12시
주제: Apache Tomcat Tips and Tricks from the Pros
내용: 아파치 톰캣을 보다 빠르고, 쉽고, 생산적으로 관리하는 방법
세부내용
    * Setting up your Apache Tomcat infrastructure for large scale deployments
    * How to upgrade and easily rollback different Apache Tomcat and JVM versions
    * How to migrate your Apache Tomcat configurations during upgrades
    * Apache Tomcat connector configurations
    * Best practices around virtual host configurations in Apache Tomcat
    * Undocumented configurations options

top


@Repository를 쓴다면 하이버네이트 예외 변환기 직접 만들 필요 없습니다.

Spring/Chapter 12 : 2008.11.12 10:52


스프링이 2.0부터 제공하고 있었는데, 이제서야 알게 됐습니다. 저는 그동안 뭘...;;;; 한 거죠.. ㅋㅋㅋ 이 것 참..;; 혹시 저만 빼고 다들 알고 계셨던건 아니겠죠? 저는 게다가 항상 @Repository 애노테이션을 쓰고 있었거든요. 그런데도 몰랐습니다. @Repository 애노테이션 API에도 안 나와있네요.

하이버네이트 예외 변환기가 언제 필요하냐면.
1. 하이버네이트 DAO 구현을 스프링 API에서 독립적으로 구현하고 싶을 때. 다른 말로 하이버네이트 SessionFacotry를 직접 주입해서 사용하고 싶을 때.
2. 하이버네이트 예외를 스프링 DataAccessException으로 변환하고 싶을 때.

위 두 가지 조건이 만족한다면 사용하고 싶을 겁니다. 그럴 때 만약 @Reposity라는 애노테이션으로 빈 스캔을 사용해서 빈을 등록하고 있다면 예외 처리기를 만들 필요 없이 빈 하나만 등록해주면 끝납니다.

<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

끝~ 저 클래스는 이름이 암시하듯이 스프링의 빈 포스트 프로세서로, 등록된 빈 중에 @Repository가 붙어있는 빈에 persistenceExceptionTranslationAdvisor 이런 어드바이저를 적용한 프록시 빈으로 교체해주는 멋진 녀석입니다. AOP와 빈 포스프 프로세서의 조합. 캬오... 스프링엔 이렇게 멋진 코드가 곳곳에 숨어있군요.

핵심코드 감상 하기...
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> targetClass =
                (bean instanceof Advised ? ((Advised) bean).getTargetSource().getTargetClass() : bean.getClass());
        if (targetClass == null) {
            // Can't do much here
            return bean;
        }
       
        if (AopUtils.canApply(this.persistenceExceptionTranslationAdvisor, targetClass)) {
            if (bean instanceof Advised) {
                ((Advised) bean).addAdvisor(this.persistenceExceptionTranslationAdvisor);
                return bean;
            }
            else {
                ProxyFactory proxyFactory = new ProxyFactory(bean);
                // Copy our properties (proxyTargetClass etc) inherited from ProxyConfig.
                proxyFactory.copyFrom(this);
                proxyFactory.addAdvisor(this.persistenceExceptionTranslationAdvisor);
                return proxyFactory.getProxy(this.beanClassLoader);
            }
        }
        else {
            // This is not a repository.
            return bean;
        }
    }

위 코드는 유겐 휄러와 로드 좐슨이 작성한 코드입니다.

ProxyFactory를 사용해서 직접 프록시 만드는 방법은 어제 올린 스크린 캐스팅에 포함되어 있었죠. Advised 인터페이스에 대해서도 언급 했었구요. 스프링 AOP 스캐를 보신 분들이라면 무리 없이 이해하실 수 있을 겁니다. 귿~
top


OSAF 1.0.0-M2는 스프링 2.5.6 기반 프레임워크~

OSAF : 2008.11.05 11:52


국내 최초 아니 어쩌면 세계 최초 스프링 2.5.6 기반 애플리케이션 프레임워크가 될지도 모릅니다. 크하하하. 최첨단 프레임워크니까 이 정도는 해줘야겠죠?

나오자마자 바로 업그레이드. 이럴 땐 메이븐이 편하긴 합니다. pom.xml에서 프로퍼티 사용해서 버전만 바꿔주면 되거든요.

    <properties>
        <slf4j.version>1.4.3</slf4j.version>
        <spring.version>2.5.6</spring.version>
        <java.version>1.5</java.version>
    </properties>

        <!-- spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
...

뭐 이런 식입니다. 어떻게 사용하는지 대충 아시겠죠?

인증샷 올립니다



top


스프링에서 하이버네이트와 JDBC 같이 사용할 때 트랜잭션 처리는?

Hibernate/etc : 2008.10.08 16:23


별로 할 일이 없습니다.

PlatformManager는 하이버네이트가 사용하는
org.springframework.orm.hibernate3.HibernateTransactionManager

이걸 그대로 사용하면 되고, JDBC 코딩을 할 때는 그냥 JdbcTemplate을 사용하면 알아서 트랜잭션이 적용됩니다.

그런데 만약에 JdbcTemplate을 사용하지 못하고, DataSource를 직접 사용해야 할 경우에는 다음과 같이 TransactionAwareDataSourceProxy를 사용하면 된다고 합니다.

<bean id="rawDataSource" class="whatover youuse"/>

<bean id="dataSource" class="TransactionAwareDataSourceProxy">
  <constructor-arg ref="rawDataSource" />
</bean>

DataSource를 직접 사용하는 코드가 엄청나게 많아서 손을 못댈 경우에는 저렇게 dataSource를 스프링이 관리하는 트랜잭션을 알고 있는 데이터소스로 바꾸면 된다고 하는데, 해보진 않았습니다. 제가 해보고 싶었던 건 하이버네이트가 flush()를 하지 않은 데이터에 대한 JDBC쿼리로 인한 예외 상황인데...

테스트를 잘못 짠건지.. 잘 안 되더군요.

@Repository
public class MemberDao {

    @Autowired
    SessionFactory sessionFactory;
   
    SimpleJdbcTemplate jdbcTemplate;
   
    @Autowired
    public MemberDao(DataSource dataSource) {
        jdbcTemplate = new SimpleJdbcTemplate(dataSource);
    }
   
    public void add(Member member){
        sessionFactory.getCurrentSession().save(member);
    }
   
    public int update(Member member){
        return jdbcTemplate.update(
                "UPDATE Member SET age = ? WHERE name = ?", member.getAge(), member.getName());
    }

}

이런 DAO를 만들었습니다. add()는 하이버네이트로하고 update()는 JdbcTemplate으로 했습니다.

@Service
@Transactional
public class MemberService {

    @Autowired
    MemberDao memberDao;
   
    public void foo(){
        Member member = new Member();
        member.setName("keesun");
        memberDao.add(member);
       
        member.setAge(20);
        memberDao.update(member);
    }
   
}


그리고 서비스 코드는 저렇게 트랜잭션 처리를 하고, keesun이라는 객체를 하나 만들어서 저장하고, 나이를 추가한다음에 JDBC로 update문을 날립니다.

제가 원했던 결과는..

에러가 나는 겁니다.

그러나..

Hibernate: select nextval ('hibernate_sequence')
Hibernate: insert into Member (age, name, id) values (?, ?, ?)
Hibernate: update Member set age=?, name=? where id=?

에러가 나질 않고, 너무도 자연스럽게 동작해버려서 당황했습니다. 특히 마지막 줄의 쿼리는 제가 JdbcTemplate으로 날린 쿼리랑은 완전 다른 하이버네이트가 만든 쿼리가 날아갔습니다. 이게 대체;;; 무슨 일인지..  흠..

결과적으로는 아~무 걱정없이 하이버네이트랑 JdbcTemplate을 같이 사용할 수 있다는 것이지만, 제가 원했던 상황이 발생하지 않아서 좀 우울합니다.
top


스프링은 CI를 어떻게 하고 있을까?

Good Tools : 2008.09.08 19:49


스프링은 Bamboo를 사용하고 있습니다.
http://build.springframework.org:8085/start.action
여기서 확인할 수 있죠.

사용자 삽입 이미지

하나의 프로젝트에 여러 Plan을 두고 빌드하고 있는 모습입니다. 오른쪽에 잘 보면 시간이 나와있는데, 스프링 개발자들 무진장 열심히 개발하고 있습니다. 모든 빌드가 거의 한 시간을 넘은것들이 없네요. 완전 따끈따끈한 프로젝트입니다.

Plan을 나눠 둔걸 보면 빌드를 어떻게 구성해야 할지 대강 짐작이 옵니다. 특히 Spring OSGi를 보면, OSGi 플랫폼 삼형제에 각각의 Plan을 두고 빌드 하고 있습니다. 배포 환경이 여러 개 일 때 각각에 해당하는 빌드를 구성해서 CI 활용을 극대화 하고 있는 모습입니다.

캬~ 스프링 멋져.
top


스프링을 사용한 트랜잭션 관리 방법

모하니?/Coding : 2008.06.04 22:56


크게 두 가지로 나눌 수 있습니다. 프록시를 사용하는 방법과 그렇치 않은 방법. 좀 더 세밀하게 나누면, 여러 방법이 있지만 구현되어 있는 특징을 기준으로 나누면 AOP를 사용한 방법과 그렇치 않은 방법으로 나눌 수 있고 그 두 방법의 대표자로 각각 @Transactional과 PlatformTransactionManager을 꼽을 수 있습니다.

PlatformTransactionManager
1. 트랜잭션 범위를 세밀하게 조정할 수 있다.
2. 코드가 지져분해진다. 템플릿을 이용하면 어느 정도는 청소할 수 있지만 한계는 있다.
3. 메소드 접근 지시자를 신경쓰지 않아도 된다.
4. 롤백 시킬 예외를 명시적으로 catch 블럭에서 잡아줘야 한다.

@Transactional을 사용할 때 주의
1. 코드가 깔끔해진다.
2. public 메소드만 트랜잭션 처리가 된다는 것을 알아둬야 한다.
3. 세밀하게 적용하려면 해당 부분을 별도의 public 메소드로 빼내야 한다.
4. RuntimeException은 기본으로 롤백해버리고 Catched Exception중에서도 롤백하고 싶은게 있으면 별도의 설정을 필요로 한다.

여기에 하나더, @Transactional을 사용할 때 만약 인터페이스가 아니라 클래스의 프록시 객체를 사용할 때, CGLib을 사용하게 되는데, CGLib 좀 별로임. 성능도 떨어지고 리플랙션 할 떄 문제도 있는 것 같고.. 따라서 프록시 사용할 때는 무조건 인터페이스 기반으로 만들어서 JDK의 프록시 사용하도록 할 것.




top