Simplifying CRUD Web Applications

Spring @MVC

2003년부터 있었고, 2.5에서 많이 바꼈다.
세 부분으로 구성되어있다.
– 요청을 다루기 위한 부분
    – DataBinder
    – Validator
    – Tag library macros
– 확장 가능한 dispatcher 프레임워크
    – routing engine
– 컨트롤러 상속구조
    – Controller, ThrowawayController
    – SimpleFromController 어렵다.

Controller -> @Controller
<context:component-scan>

@RequestMapping, HTTP method를 명시할 수 있다. 패턴을 사용할 수 있고, Class에도 선언할 수 있다.

DEMO

리턴값
    – String : 뷰이름
    – ModelAndView
    – Map(ModelMap): 뷰이름은 URL로 판단
    – 아무개arbitrary 타입 객체: 뷰이름은 URL로 판단, 이때는 @ModelAttrubute(“뷰에서 이 객체를 참조할 이름”)를 사용해야 된다.

DEMO
    – JSP를 정말 간단하게 작성했다. 발표 주제 전달에 필요한 만큼만 코딩한다.

아규먼트는 Spring에 의해서 자동으로 채워진다.
    – @RequestParam(“reauest 파라미터”)을 아규먼트 앞에 붙여준다.
    – 자동 바인딩.

DEMO
    – Portlet 패키지에 있는 ModelAndView를 import하는 실수 보여줌.
    – 청중이 에러를 적극적으로 잡아준다. 리액션이 좋다.

@Controller를 사용하면 테스트하기 좋다.

@MVC 사용해서 CRUD하기

TYpe || HTTP method || REST(아직 안됨) || Now
Create | POST | /account | /account
Read(one) | GET | /account/1 | /account?id=1
Read(all) | GET | account | account
Update | POST | /account/1/update | /account/update?id=1
Delete | POST | /account/1/1delete | /account/delete?id=1

DEMO
    – 청중이 앞서서 다음에 나올 것을 물어본다.
    – Wait a minute. I will show you.로 대답한다.

CRUD 코딩

    – Get으로 폼뷰 보여주기
    – 바인더 등록하기
@InitBinder public void initBinder(WebDataBinder binder){
// 바인더에 추가.
}
    – POST로 저장하기
@RequestMapping public String createAccount(Account acc, BindingRresult result){
// BindingResult 타입의 변수를 인자로 받아서 거기에 에러가 있으면 ~~페이지로 보내고, 에러 없으면 저장.
}
    – 에러 났을 때 FROM으로 다시 보내기
@RequestMapping public String createAccount(@ModelAttribute(“account”) Account acc, BindingRresult result){
// 입력한 account 정보를 다시 보여줘야 하니까 @ModelAttribute를 사용한다.

}
   – validation 추가하기
지금은 Validator를 만들고 직접 validate까지 호출해줘야 하지만, 나중에는 세터에 애노테이션만 붙이면 되는 방식으로 바뀔 듯.
    – 세션사용하기
폼뷰 보여주는 메소드에서 new 해서 새로 만든 객체를 세션에 저장한다.
저장하는 메소드에서는 SesstionStatus 타입의 객체를 인자에 추가하고, setComplete()를 호출해서 세션에서 모델을 날려줘야 한다.

Future directions

@PathElements

@RequestMapping(value=”/confiirm/{orderId}”)
public Date confirm( @PathElement(“orderId”) String id) {
    // REST 스타일의 URL을 사용할 수 있게 될 것이다.
}

HTML || GET || /account/1
XML || GET | /account/1.xml
JSON || GET | /account/1.json

Flow

FlowContext 객체를 사용해서 @Controller에서 플로우를 시작하도록 할 수 있게된다.

JDBC 함수 실행 전에, Hibernate 세션 플러시하기

요약/번역/참조 : Before a JDBC operation, flush the Hibernate Session (includes TSE example code)

문제 상황

ORM을 사용하는 코드와 ORM을 사용하지 않는 코드(생 SQL)를 한 뭉탱이 한 트랜잭션으로 처리할 때, 데이터베이스에 있는 데이터를 사용할 수 없는 이슈가 발생할 수 있다.

섞어서 사용해야 하는 이유

많은 엔터프라이즈 애플리케이션에서 도메인 모델을 저장하고 (심지어 복잡한 도메인 모델을) 가져올 때 ORM을 사용하고 있다.

그렇다고해서 일반 SQL을 사용하는 것이 완전히 사라지진 않을 것이며, 다음의 경우에는 일반 SQL이 필요하다.
– 테스트 코드 : ORM 툴을 사용하여 객체를 DB에 넣었을 때, SQL을 사용하여 전체 레코드 수를 가져와서 비교하는 등의 테스트를 할 때 용이하다.
– 스토어드 프로시져 사용시 : 프로젝트에서 스토어드 프로시져를 사용하는 경우가 많았고 이것을 ORM과 혼용하길 원하는 경우가 많았다. 예를 들어, 새로운 객체들을 집어넣은 뒤에, 가용한 레코드들과 새로운 넣은 레코드들의 연관이 필요한 경우가 있다.(뭔소린지..흠;;)
– 상당히 많은 객체에 대한 함수 사용시 : 백만개의 주문을 true 에서 false로 변경할 때, 저자는 ORM보다 SQL을 선호한다고 합니다.(하긴 뭐 이게 더 편할 수도 있겠지..)

ORM과 SQL 혼용시 발생하는 이슈

DB는 비어있다고 가정하고, 다음의 수도Pseudo 코드를 보자.

start transaction

create part with name Bolt
associate with ORM engine (i.e. save using entity manager)

update part set stock = 15 where name=’Bolt’

end transaction

위에서 두 수도 코드의 update문(SQL문)은 위에서 Bolt라는 부품을 Entity Manager에게 저장하라고 했는데도 불구하고 실패하게 됩니다.

Entity Manager가 저장을 요청한 순간에 부품 객체를 저장하는 것이 아니라 트랜잭션이 끝날 때 저장하기 때문입니다.

ORM의 이런 속성을 write-behind 라고 합니다. 이 개념때문에, 사용할 수 있을 것 같다고 생각한 순간인데도 실제로는 사용하지 못하는 경우가 발생합니다.(위 상황으로 유추할 때, write-behind는 DB에 접속하는 빈도수를 줄이기 위해 사용하는 기술 같습니다.)

올바른 해결책

단순하게 다음과 같이 위의 수도 코드를 두 개의 트랜잭션으로 나누는 것을 생각할 수 있습니다.

start transaction
create part with name Bolt
associate with ORM engine (i.e. save using entity manager)
end transaction

start transaction
update part set stock = 15 where name=’Bolt’
end transaction

하지만 이 것은 틀린 답입니다. 원자성을 깨트렸습니다. (그렇다면, 위의 두 트랜잭션을 nested transaction으로 가지는 하나의 트랜잭션으로 묶으면 되지 않을까?)

올바른 해결책은 SQL 쿼리를 실행하기 전에, ORM 엔진이 변경사항을 DB에 저장하도록 하는 것입니다. JPA도 하이버네이트 처럼 이런 방법을 제공하는데, 이게 바로  Flushing 입니다. 이것을 사용하여 수도 코드를 다음과 같이 고칠 수 있습니다.

start transaction

create part with name Bolt
associate with ORM engine (i.e. save using entity manager)

*** flush

update part set stock = 15 where name=’Bolt’

end transaction

해결책 적용하기

사용자 삽입 이미지수도 코드를 시퀀스 다이어그램으로 나타낸 것입니다.

자 이제 flush() 메소드를 추가해야 하는데, 어디에 추가해야 할까요? addPart() 메소드 안의 맨 뒤에 추가할까요? 아니면 updateStock() 메소드 안에서 UPDATE 문 바로 뒤에 둘 까요?

두 방법 다 안 좋습니다.
– addPart() 메소드 안에 넣는 방법은 write-behind 방식을 깨트립니다. 그러면 여러 개의 부품을 하나의 트랜잭션에서 추가할 때 최적화를 할 수 없습니다.
– updateStock() 메소드 안에 넣는 방법이 조금 더 나아보이겠지만, 만약 한 트랜잭션에 SQL 문이 여러 개면 어떨까요? flush()가 의미가 있나요?
사용자 삽입 이미지결론적으로 필요한 것은 세 가지(부품 추가, 부품 수정, 세션 플러시)인데, 이 요구 사항을 만족시키기 위해 코드를 수정할 수 있는 부분은 두 곳 뿐입니다. 바로 여기가 Aspect-Oriented 가 제 몫을 하는 지점입니다. AOP 기술은 코드를 추가할 수 있는 추가적인 공간을 제공합니다. 즉 별도의 모듈로 위의 요구사항을 해결할 수 있도록 해줍니다.

세 개의 요구사항을 각각의 모듈로 처리하기

새로운 부품 추가하기

private SessionFactory sessionFactory;

public void insertPart(Part p) {
        sessionFactory.getCurrentSession().save(p);
}

부품 가격 수정하기

private SimpleJdbcTemplate jdbcTemplate;

public void updateStock(Part p, int stock) {
        jdbcTemplate.update(“update stock set stock = stock + ? where number=?”,
                stock, p.getNumber());
}

세션 동기화하기

정리해보면, ‘JDBC을 할 떄마다, 세션에 dirty 상태이면 flush해야한다.’ 이것을 좀 더 정리하면, ‘JDBC 작업을 수행하기 전에, 세션이 dirty 상태이면 flush 하라.’ 이 것을 다음과 같이 언제, 어디서, 무엇을로 쪼갤 수 있습니다.

    * 언제: before
    * 어디서: a call to a JDBC operation
    * 무엇을: flush a dirty Hibernate session

이렇게 쪼개두면, AspectJ로 구현하기 쉽습니다. 이것을 AspectJ로 구현하면 다음과 같습니다.

public aspect HibernateStateSynchronizer {

        private SessionFactory sessionFactory;
      
        public void setSessionFactory(SessionFactory sessionFactory() {
                this.sessionFactory = sessionFactory;
        }

        pointcut jdbcOperation() :
                call(* org.springframework.jdbc.core.simple.SimpleJdbcTemplate.*(..));
              
        before() jdbcOperation() {
                Session session = sessionFactory.getCurrentSession();
                if (session.isDirty()) {
                        session.flush();
                }
        }
}

생각해 볼 것

먼저, 위의 Aspect는 SimpleJdbcTemplate의 모든 메소드 호출에 적용을 했는데, 특정 애노테이션이 붙은 메소드에만 적용할 수 있도록 포인트컷을 수정할 수 있습니다. (예) execution(@JdbcOperation *(..))

두 번째로 생각해 볼 것은 가용한 하이버네이트 세션이 없을 때 입니다. SessionFactory.getCurrentSession() 은 항상 새로운 세션을 생성해 줍니다. 그런데 SessionFactory 없거나, 세션이 아예 만들어지지 않았을 때에도 위의 Aspect가 동작하려면, 스프링에서 제공하는 SessionFactoryUtils 를 사용해서 세션을 가져오도록 할 수 있습니다. (어지럽…)

소스 코드

HibernateStateSynchronizer 는 AspectJ를 사용해서 구현했고, 간단하게 Spring AOP를 사용하여 구현할 수도 있습니다.

HibernateCarPartsInventoryTests 는 위의 수도 코드를 테스트 했고, aspect가 가용하면 테스트가 통과하고 가용하지 않다면 테스트는 실패합니다.

현재는 before 어드바이스를 주석처리 해놨기 때문에 테스트가 fail 할 것이고, pom.xml을 보면 Maven AspectJ 플러그인이 있는데, 이게 버전 충돌이 나지만 무시해도 됩니다.

ek21.zip
감상문

대단합니다. 어쩜 이리 깔끔하게 정리할 수 있는지, 사진도 멋있네요.(전 여자를 좋아합니다.) Spring AOP를 공부하면서 AspectJ로 살짝 공부하긴 했지만, 실제 언제 어떻게 Aspect를 적용할지는 많이 고민해보지를 않았었습니다. 그냥 막연하게 레퍼런스에 나와있는 정도의 로깅, 트랜잭션, 보안.. 그나마 이러한 것들도 이미 스프링에서 만들어서 제공해주고 있기 때문에 더더욱 별 고민이 없었죠. 이 글은 AOP 초딩같은 저에게 진짜 Aspect Orient Programming이 뭔지 한 수 보여주는 것 같습니다. Alef Arendsen 당케쉔.

Spring Security 2 조만간 볼 수 있을 듯

참조 : http://blog.interface21.com/main/2007/12/06/whats-new-in-spring-security-2/

다음 주 쯤에 배포가 될 것 같습니다. 벌써부터 기다려지네요. Ben Alext가 올린 글을 보면, 설정이 상당히 간결해 진것 같은데, 너무 간결해져서 다시 공부해야 할 것 같습니다. 수 많은 필터들을 직관적인 엘리먼트와 속성이름을 사용해서 감춰주길 기대하고 있습니다.

대강 보니, 사용법은 일단 web.xml에 Filter To Chain Proxy를 등록하던 저번과 비슷한데, 대신 등록하는  클레스가 Filter To Chain Proxyr가 아니라 org.springframework.web.filter.DelegatingFilterProxy 로 바뀐 것 같습니다. 그리고 수 많은 필터를 빈으로 등록해야 했던 부분이 다음과 같은 한 덩어리로 뭉쳐진 것 같습니다.

    <http autoConfig=”true”>
        <intercept-url pattern=”/**” access=”IS_AUTHENTICATED_REMEMBER” />
    </http>

    <repository>
        <user-service hash=”md5:hex”>
            <user name=”rod” password=”a564de63c2d0da68cf47586ee05984d7″ authorities=”ROLE_SUPERVISOR,ROLE_USER” />
        </user-service>
    </repository>

TSE 2007에서 Spring Security와 관련된 세션이 세 개나 된다고 하는데, 내년에도 올해처럼 재밌는 발표들이 가득한 TSE가 되면 좋겠습니다.