추상(abstract) 애스펙트 클래스를 상속받아 포인트컷 메소드나 어드바이스 메소드를 오버라이딩하는 경우에 무엇이 적용되는가에 대해서 정리합니다. (참고로 애스펙트는 abstract 애스펙트만 상속받을 수 있습니다)


포인트컷 메소드 오버라이딩


추상 애스펙트에 정의된 (non-abstract) 포인트컷 메소드를 하위 애스펙트에서 오버라이딩하는 경우


(1) 포인트컷을 재정의하지 않은 경우

=> 추상 애스펙트의 포인트컷이 적용됨


(2) 포인트컷을 재정의한 경우 

=> 하위 애스펙트의 포인트컷이 적용됨


결론 : 자식 애스펙트에서 부모 애스펙트에 정의된 포인트컷을 재정의할 수 있다.



어드바이스 메소드 오버라이딩


추상 애스펙트에 정의된 어드바이스 메소드를 하위 애스펙트에서 오버라이딩하는 경우


(1) 어드바이스를 재정의하지 않은 경우

=> 추상 애스펙트의 어드바이스가 적용되지만 동작(메소드)는 하위 애스펙트의 것이 적용됨


(2) 어드바이스도 재정의한 경우

=> 추상 애스펙트의 어드바이스도 적용되고 하위 애스펙트의 어드바이스도 적용됨. 동작(메소드)은 하위 애스펙트의 것이 적용됨


결론 : 자식 애스펙트에서는 부모 애스펙트에 정의된 어드바이스의 동작(메소드 내용)은 재정의할 수 있지만,

언제(before, after 등) 어드바이스를 적용할지는 수정할 수 없다(무조건 새로운 적용 위치가 추가됨).


Posted by 에코지오
,

포인트컷 재사용


공통 포인트컷만 모아놓은 애스펙트 작성를 작성합니다. 예를들어 MyPointcuts.

다른 애스펙트에서는 MyPointcuts.somePointcut() 처럼 static하게 액세스하여 참조합니다.


@Aspect

public class MyPointcuts {

 @Pointcut("within(my..On*Listener+) && execution(!private !static * on*(..) throws !Exception)")

 public void eventListenerMethods() {}

}


@Aspect

public class MyAspect {

    @Around("my.MyPointcuts.eventListenerMethods()")

    public Object handleListenerException(final ProceedingJoinPoint pjp) {

        try {

            return pjp.proceed();

        } catch (final Throwable e) {

            exceptionHandler.handle(e);

            return null;

        }

    }

}



어드바이스 재사용

abstract 포인트컷을 만들고 그 포인트컷을 사용하는 어드바이스를 작성합니다. 애스펙트를 abstract로 선언해야 합니다(여전히 @Aspect 어노테이션 필요함).


@Aspect

public abstract class MyAspect {

 @Pointcut("")

 protected abstract void toOverridePointcut(); 


 @After("toOverridePointcut()")

 public void myAdvice() {

    ... ...

 }

 ...

}


하위 애스펙트에서 abstract 포인트컷을 구현합니다.


@Aspect

public class YourAspect extends MyAspect {

 @Pointcut("within(android.app.Activity+) && execution(void onCreate())")

 protected void toOverridePointcut() {}


 ... ...

}


Posted by 에코지오
,

AspectJ 문서에는 아래와 같이 @within()과 @annotation()에 대해 설명합니다.



즉, Anno라는 어노테이션에 대해서(제가 제대로 해석했다면...)


- @within(Anno) : Anno 어노테이션을 갖는 타입(클래스) 안에 정의된 코드와 관련된 조인포인트

- @annotation(Anno) : 조인포인트 대상이 Anno 어노테이션을 갖는 조인포인트


http://whiteship.tistory.com/379 에서는 이렇게 설명합니다.


- @within(Type) : 선언된 타입에 @Type 어노테이션이 붙어있을 때 그 객체의 모든 execution Join point를 나타냅니다.

- @annotation(Type) : 실행되는 메소드에 @Type 어노테이션이 붙어있을 때 그 메소드의 execution Join point를 나타냅니다.


http://www.egovframe.org/wiki/doku.php?id=egovframework:rte:fdl:aop:aspectj 에서는 이렇게 설명하네요.


- @within(Transactional) : 대상 객체의 선언 타입이 @Transactional 어노테이션을 갖는 모든 결합점

- @annotation(Transactional) : 실행 메소드가 @Transactional 어노테이션을 갖는 모든 결합점



그러니까 @within()은 어노테이션이 붙은 클래스에 적용되고, @annotation()은 어노테이션이 붙은 메소드에 적용된다고 심플하게 이해하고 사용하면 될듯합니다만, 정확히 하자면 @annotation()은 조인포인트가 클래스건 메소드건 따지지 않는 것으로 보입니다.


(1) @annotation(Anno) && within(my.aop.*)

=> my.aop 패키지의 클래스 중에서 Anno 어노테이션이 붙은 클래스


(2) @annotation(Anno) && within(my.aop.*) && execution(* test*(..))
=> my.aop 패키지의 클래스에 정의된 test* 메소드 중에서 Anno 어노테이션이 붙은 메소드

(3) @annotation(Anno) && execution(* test*(..)) 
=> test* 메소드 중에서 Anno 어노테이션이 붙은 메소드

(4) @annotation(Anno) && @within(Anno) && execution(* test*(..))
=> Anno 어노테이션이 붙은 클래스의 test* 메소드 중에서 Anno 어노테이션이 붙은 메소드


대충 감이 오네요.


참고로 @annotation(Anno) && execution(* test*(..)) 포인트컷은 이렇게도 표현할 수 있습니다.

  

execution(@Anno * test*(..))




Posted by 에코지오
,

하이버네이트 매핑설정에는 lazy 옵션이 있습니다. 연관객체를 언제 로딩할거냐에 대한 옵션입니다. true로 설정하면 하이버네이트는 부모객체를 로딩할 때 그에 딸린 연관객체(자식객체)를 미리 로딩하지 않고 프록시 객체만 만들어둡니다. 그리고는 실제로 연관객체가 사용될 때 연관객체를 로딩하죠. false이면 부모객체가 로딩될 때 연관객체도 함께 미리 로딩합니다.

Employee(부모)와 Achievement(자식) 가 있습니다. 아래처럼 lazy=true 로 설정되어 있기 때문에 Achievement 목록은 실제 쓰이지 않는다면 로딩되지 않습니다.

<class name="Employee" table="EMP">
... ...
<set name="achievements"

       lazy="true" 

      ... ... >

    <key column="EMP_ID" />
    <one-to-many class="Achievement" />
</set>

그러나 연관객체를 미리 로딩하고 싶을 때도 있습니다. 크게 세가지 방법이 있습니다.

 (1) HQL fetch 키워드 사용
left outer join fetch 키워드를 사용하면 lazy=true, fetch=select 이더라도
조인을 통해 1번의 쿼리로 연관객체를 모두 가져옴.
 
Query q = session.createQuery(" from Employee as emp "
        + " left outer join fetch emp.achievements "
        + " where emp.id = :employeeId");

q.setParameter("employeeId", employeeId);
employee = (Employee) q.list().get(0);
 
(2) Criteria에서 fecth mode를 JOIN으로 설정
 
Criteria c = session.createCriteria(Employee.class);
c.setFetchMode("achievements", FetchMode.JOIN);
c.add(Restrictions.idEq(employeeId));
employee = (Employee) c.uniqueResult();
 
(3) Hibernate.initialize 메소드 이용
연관객체를 임의로 미리 로딩. 단, 세션안에서만 사용가능.
 
employee = (Employee) session.get(Employee.class, employeeId);
Hibernate.initialize(employee.getAchievements());

Posted by 에코지오
,
iBATIS용 코드 제너레이션 툴인 Abator의 이름이 iBATOR로 변경되었다.(2008년 4월)

예전에 잠깐 Abator를 써봤는데... 그리 만족스럽지 못했던 기억이 있는데,

이름만 바꾸지 말고 좀 제대로 그럴싸하게 잘 만들어주었으면 하는 바램이 있다.

iBATOR 홈 : http://ibatis.apache.org/ibator.html
Posted by 에코지오
,
* agimatec-validation  http://code.google.com/p/agimatec-validation/

JSR 303: bean-validation 스펙을 구현한 라이브러리. 아래는 agimatec-validation 사용예제.

javax.validation.Validator customerValidator = new ClassValidator(Customer.class);
Set<InvalidConstraint<Customer>> violations = customerValidator.validate(customer);
public class Customer{
    @NotEmpty(groups = "last")
    private String firstName;
    @NotEmpty(groups = "first")
    private String lastName;
    @Length(max = 30, groups = "last")
    private String company;
    @Valid
    private List<Address> addresses;
   
    ....
}

그 밖에 다음 글도 참고할 것.

- Bean Validation Sneak Peek http://in.relation.to/Bloggers/BeanValidationSneakPeekPartI
- 기선님 Spring MVC Validation 정리(스프링 validator와 valang)
- 기선님 Really easy field validation 사용하기(이건 css를 통해 브라우저단에서 입력값을 검증하는 것)
- 스트러츠2에서 어노테이션방식 입력값검증 http://struts.apache.org/2.x/docs/validation-annotation.html
- 하이버네이트 Validator http://www.hibernate.org/hib_docs/validator/reference/en/html/validator-defineconstraints.html
- springmodules https://springmodules.dev.java.net/docs/reference/0.8/html/validation.html#d0e9699
Posted by 에코지오
,
지금까지 트랜잭션 안에 있기만하면 준영속 상태의 객체에서 늦은 로딩이 가능한 줄 알았는데, 그게 아니었다.

늦은 로딩은 영속 상태에서만 즉, 객체가 세션과 연결된 상태에서만 적용된다.

이런 기초적인 것도 모르고 하이버네이트로 코딩을 한다는 것이 부끄럽다.

하이버네이트 객체 생명주기


그럼 준영속 상태의 객체를 파라미터로 받는 메소드(A)에서는 어떻게 늦은 로딩으로 설정된 연관 데이터를 갖고와야할까?

1. 준영속 객체를 session.update(), saveOrUpdate()를 이용하여 영속 상태로 만든 후에 늦은 로딩하는 방법.
- 이 방법은 필요없는 UPDATE 또는 SELECT 쿼리가 추가적으로 실행되는 문제점이 있다.

2. 준영속 객체의 키를 이용하여 session.get()을 통해 영속 객체를 로딩한 후 늦은 로딩하는 방법.
- 이것도 SELECT 쿼리가 추가적으로 실행이 된다.

3. 처음부터 연관데이터를 미리 로딩한 뒤 메소드에 넘겨주는 방법.
- 영속 객체를 메소드 A에 넘기기 전에 Query에서 fetch 키워드를 쓰거나 또는 Hibernate.initialize()를 이용하여 연관 데이터를 미리 객체에 담아둔다. 만약 메소드 A가 시간이 좀 걸리는 작업이고 연관 데이터가 수시로 변한다면 연관 데이터의 싱크가 문제가 될수 있다.

아마 대부분의 경우 별거 아닐텐데 내 경우에는 3번은 불가하고, 1,2번 중에서 골라야 한다. 음...
Posted by 에코지오
,

아래 글의 트랜잭션 처리와 관련하여 내가 겪었던 상황을 살펴볼까한다.

- 삽질했던 상황 -

DB로부터 목록을 조회한 후 목록안의 개별 아이템에 대한 작업을 수행한 뒤
개별 아이템별로 작업결과를 DB에 반영한다.

로직은 별로 복잡하지 않다. 근데 문제는 이거 2가지다.

- 전체 작업이 아니라 개별 작업결과를 커밋하길 원한다.
- 개별 작업은 1~10초 정도 시간이 걸리는 네트워크 작업이다.

처음에 아래처럼 코드를 작성했다.

public void 전체작업() {
  전체 목록 조회;
  for (아이템 : 전체목록) {
      try {
          아이템작업(아이템);
      } catch(에러) {
         //로깅
      }
  }
}

public void 아이템작업(아이템) throws 네트워크에러, DB에러 {
    네트워크작업(아이템);
    작업결과처리(아이템);
}

@Transactional
private void 작업결과처리(아이템) {
     // 아이템 가공
     dao.save(아이템);
}

나름 트랜잭션 스코프도 최소화하고 독립적으로 개별 아이템작업을 처리하기 위해 신경썼는데,

무지막지하게 삽질을 해댔으니... RTFM이 떠오른다.

이제 위 코드에서 왜 트랜잭션이 작동하지 않는지 이유를 알았으니 해결방안을 찾아야겠다.

Posted by 에코지오
,
http://static.springframework.org/spring/docs/2.0.x/reference/transaction.html#transaction-declarative

위 사이트의 9.5.6. Using @Transactional 부분.

첫째, @Transactional은 인터페이스(및 메소드), 구현클래스(및 public 메소드)에 붙일 수 있다.
특히나 public 메소드에만 적용되는 점은 아래와 같이 설명하고 있다.
Method visibility and @Transactional

When using proxies, the @Transactional annotation should only be applied to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error will be raised, but the annotated method will not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

둘째, self-invocation 상황(내부 호출)에서는 적용되지 않는다. 즉 외부의 클래스에서 그 메소드를
호출해야 트랜잭션이 걸린다.

Note: Since this mechanism is based on proxies, only 'external' method calls coming in through the proxy will be intercepted. This means that 'self-invocation', i.e. a method within the target object calling some other method of the target object, won't lead to an actual transaction at runtime even if the invoked method is marked with @Transactional!

며칠 동안 두가지 상황을 모두 겪으면서 삽집한 생각을 하면... 으 ...... 바보같다.. ㅠ.ㅠ

궁금점 : @Transactional을 Class 선언부에 안붙이고 메소드에만 붙이면 어떻게 되는거지?
여기저기 찾아보면 당최 메소드에만 붙어있는 예제가 없다.


Posted by 에코지오
,
하이버네이트는 항상 느끼는 거지만 코딩할 때마다 헷갈리는 부분이 많다.

늦은로딩, 페칭, 세션, 캐쉬, 객체연관, 객체상태, DB와 동기화 시점 등등.

어떡하지...? 뭘 어떡하나. 부지런히 자료찾아보고 테스트하고 메모해나가는 수밖에....

이미 그런 분들이 꽤 계시다...  특히 기선님하이버네이트3 책 요약 시리즈

나처럼 우매한 대중에게는 너무 고마운 자료다.

일단 기초적인거 하나 붙잡고 읽어보자.
 
하이버네이트 API: 저장하고 읽어들이기

session.load() 메소드 사용팁을 잘 정리해주셨다.

DB에 있는 객체 하나를 꺼내서 다른 객체에 세팅해줘야 하는 경우. Comment.setForAuction(item). 이 경우 굳이 item은 load()로 가져와도 된다. 굳이 DB에서 전부 가져올 필요가 없다. Comment를 저장할 때, item의 id를 외례키로 저장하게 되는데, load()로 가져온 Proxy가 딱 그 id만 가지고 있기 때문이다

session.delete() 호출후 식별자를 제거하기 위한 팁도 있다.

Transient 상태가 될 때, 식별자도 제거하려면 hibernate.use_identifier_rollback 이 설정을 해줘야돼.

다른 글도 읽어보자. 다 피가 되고 살이 된다.
 

Posted by 에코지오
,