Eclipse가 빌드경로에 추가해주는 JAR 파일 바꾸기

스프링 2.5를 사용하시는 분들은 애노테이션 기반의 테스트를 작성하실 때 한 가지 귀찮은 에러를 맞닥드리게 되어 있습니다. 그건 바로 @RunWith라는 JUnit 애노테이션과 관련이 있습니다.

Spring-Test는 JUnit 4.4를 필요로 합니다. lib/junit 폴더에 가시면 junit4.4.jar 파일이 보이는 걸 보아 짐작할 수 있습니다. 그러나 Eclipse 3.3에서 기본으로 제공하는 JUnit은 4.3 버전입니다. 따라서 Eclipse가 빌드경로에 추가해주는 JUnit4를 사용하면 spring-test.jar가 사용해야 하는 라이브러리 버전이 아니기 때문에 에러가 발생합니다.

해결하는 방법은 간단합니다. Eclipse에서 자동완성으로 Junit 4를 넣지 않고 직접 lib/junit 폴더에 가서 jar 파일을 복사해서 넣어주면 됩니다.

이게 귀찮고 이전처럼 자동완성에서 JUnit 4를 빌드패스에 추가해서 사용하고 싶으신 분들은 한 번의 약간 큰 고생을 해주셔야 합니다.
사용자 삽입 이미지
바로 Eclipse 설치 폴더의 eclipse\plugins\org.junit4_4.3.1 폴더로 이동하셔서 그곳에 위치한 junit.jar 파일을 lib/junit 폴더의 junit4.4.jar 파일로 교채해 주는 것입니다. 물론 파일 이름도 junit4.4.jar 에서 junit.jar 로 바꿔줘야겠죠.
사용자 삽입 이미지
그러면 이제 이전처럼 퀵 픽스를 이용해서 JUnit4를 빌드패스에 추가하더라도 에러가 발생하지 않습니다.
사용자 삽입 이미지
이런 노가다도 Eclispe 3.4가 나오면 하지 않아도 될 것 같습니다.

이렇게 라이브러리 가지고 씨름을 하다보면 정말 Maven의 Archetype이 부러울따름입니다. 문제는 Maven을 처음 실행하고 첫 프로젝트의 Archetype 받을 때 시간이 너무 오래걸려서 탈입니다. pom.xml로 정의해둔 라이브러리들 마저 꼬이는 경우도 있고, 그냥 베이스 프로젝트 하나를 zip파일로 묶어 두던가 하는게 속편할 듯 합니다. 이상하게 얘기가 새고 있는데 이쯤에서 멈추고 자야겠습니다.

4. @AspectJ 사용하는 초간단 AOP 예제

사용자 삽입 이미지
JDK 6에서도 테스트 해봤습니다.
Spring 2.5 jar 파일을 사용했으며, AspectJ 관련 라이브러리는 lib폴더에 있는 것들을 사용했습니다.

결과화면
사용자 삽입 이미지오랜만에 배치기 노래도 듣을 수 있고 좋군요.

저 예제를 돌리고 나서 얼마나 신났었는지 그 때의 기분을 고대로 느낄 수 있었습니다.
댓글 주셔서 감사합니다.
================================================================================

Spring Reference 6장에 있는 코드들을 테스트 해보기 위해 초간단 예제를 만들어 봅니다.
참조 : http://www.infoq.com/articles/Simplifying-Enterprise-Apps

소스 코드 보기

[#M_ more.. | less.. |

// Human.java
public interface Human {    
     
public void sayName();

}


// Keesun.java

public class Keesun implements Human {

       public void sayName() {

             System.out.println(저는 ~~선입니다.”);

       }

}


//MannerAOP.java

@Aspect

public class MannerAOP {

 

       @Pointcut(“execution(public * Human.sayName(..))”)

       public void greeting() {

       }

 

       @Before(“greeting()”)

       public void hi() {

             System.out.println(만나서 ~~습니다.”);

       }

 

       @AfterReturning(“greeting()”)

       public void doBeforeOne() {

             System.out.println(“AOP ~ ~ 줍니다~”);

       }

}


//aopAppContext.xml

<?xml version=“1.0” encoding=“UTF-8”?>

<beans xmlns=“http://www.springframework.org/schema/beans”

       xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”

       xmlns:aop=“http://www.springframework.org/schema/aop”

       xsi:schemaLocation=

http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd

http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd”>

 

       <!– this is the object that will be proxied by Spring’s AOP infrastructure –>

       <bean id=“keesun” class=“firstSpringAOP.Keesun” />

 

       <aop:aspectj-autoproxy />

 

       <!– this is the actual advice itself –>

       <bean id=“mannerAOP” class=“firstSpringAOP.MannerAOP” />

</beans>


// TestFirstAOP.java

public class TestFirstAOP {

       public static void main(String[] args) {

 

             BeanFactory bf = new ClassPathXmlApplicationContext(

                           “firstSpringAOP/aopAppContext.xml”);

             Human human = (Human) bf.getBean(“keesun”);

             human.sayName();

       }

}


_M#]실행 결과

만나서 반~갑~습니다.
저는 백~기~선입니다.
AOP 죽~ 여~ 줍니다~

이 프로그램이 돌아가려면 Spring을 사용하기 때문에 이 전 글에서 추가 했던 spring.jar파일과 commons-logging.jar가 필요하며 AspectJ를 사용하고 있기 때문에 ‘AspectJ 설치 폴더’/lib or Spring-with-dependencies를 설치하셨다면 ‘Spring 설치 폴더’/lib/aspectj/ 안에 있는 aspectjrt.jar 와 aspectjweaver.jar 파일을 classpath에 추가해야 합니다.

예제를 돌렸네요. 아고 기뻐라.


bl162.mp3bl163.zip


Chapter 14: Case Study: Building a Product Catalog

참조 : The Programmer’s Guide to SQL

오라클에서 테이블 만들기

CREATE TABLE Category (
CategoryID INT NOT NULL PRIMARY KEY,
DepartmentID INT NOT NULL,
Name VARCHAR(50) NOT NULL,
Description VARCHAR (200) NULL,
FOREIGN KEY (DepartmentID) REFERENCES Department (DepartmentID));

CREATE SEQUENCE CategoryIDSeq;

CREATE OR REPLACE TRIGGER CategoryAutonumberTrigger
BEFORE INSERT ON Category
FOR EACH ROW
BEGIN
   SELECT CategoryIDSeq.NEXTVAL
   INTO :NEW.CategoryID FROM DUAL;
END;
/
– 시퀀스 만들고, 트리거 만들어서 자동 증가값처럼 사용하기.

CREATE TABLE ProductCategory (
ProductID INT NOT NULL,
CategoryID INT NOT NULL,
PRIMARY KEY (ProductID, CategoryID),
FOREIGN KEY (ProductID) REFERENCES Product (ProductID),
FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID)
);
– Junction 테이블.

데이터 가져오기

SELECT Name, Price FROM
      (SELECT Name, Price
       FROM Product
       ORDER BY Price DESC, Name ASC)
WHERE ROWNUM<=5;
– 상위 다섯개의 데이터 가져오기.
– id를 사용하지 않는다. id가 꼭 순서대로 매겨진다는 보장이 없으니까..

SELECT ProductID, Name FROM
(
SELECT RANK() OVER (ORDER BY ProductID) As Ranking, ProductID, Name
FROM Product
ORDER BY PRODUCTID
)
WHERE Ranking BETWEEN 6 AND 8;
– 특정 범위의 값 가져오기.

SELECT C.Name as “Category Name”, P.Name as “Product Name”
FROM Product P
INNER JOIN ProductCategory PC ON P.ProductID = PC.ProductID
INNER JOIN Category C ON PC.CategoryID = C.CategoryID
ORDER BY C.Name, P.Name;
=> WHERE 절을 사용하도록 변경하면…
SELECT C.Name as “Category Name”, P.Name as “Product Name”
FROM Product P, ProductCategory PC, Category C
WHERE P.ProductID = PC.ProductID AND PC.CategoryID = C.CategoryID
ORDER BY C.Name, P.Name;
=> 연관된 서브쿼리를 사용하도록 변경하면..
SELECT C.Name as “Category Name”, Product.Name as “Product Name”
FROM Product, Category C
WHERE Product.ProductID IN
  (SELECT ProductID FROM ProductCategory
   WHERE ProductCategory.CategoryID = C.CategoryID)
ORDER BY C.Name, Product.Name;

데이터 검색

SELECT Name, Description
FROM Product
WHERE (Description || Name LIKE ‘%devil%’)
  AND (Description || Name LIKE ‘%mask%’);
– 여러 컬럼에서 특정 문자열 검색할 때 꼼수로 모든 검색 대상 필드를 문자열로 변환하여 붙인다음에 검색.

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 당케쉔.