2009년 9월 7일 월요일

iBATIS 간단한 예제

iBATIS를 이용해서 간단한 예제를 만들어봅시다.

웹이랑 섞으면 복잡하니까 콘솔에서 아주 간단하게 해보겠습니다-_-;

iBATIS를 들어보기만 하고 저처럼 어려울 것 같아서 거부하신분들에게 바칩니다-_-; 저는 처음에 상당히 어려울 줄 알았는데 공식홈페이지가니까 이동국님이 번역해 놓으신 문서가 있더라구요. 약간 이상하게(?) 번역해 놓긴 했지만 영어울렁증이 있는 저에게는 정말 도움이 되는 자료 였습니다 ^^

iBATIS공식홈페이지입니다.( 개발자 가이드와 튜토리얼 메뉴얼도 있습니다.)
for Java를 클릭하면 자바용이 나옵니다.
http://ibatis.apache.org/

접근하기 쉽게 하기 위해서 원더걸스멤버를 DB에 저장해놓고 해봅시다-_-;


CREATE TABLE
`WONDERGIRLS` (
 
`NUM` int(11) NOT NULL,
 
`NAME` varchar(10) NOT NULL,
 
`AGE` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=euckr;

INSERT INTO
`WONDERGIRLS` (`NUM`, `NAME`, `AGE`) VALUES
(1, '선예', 18),
(2, '선미', 15),
(3, '소희', 15),
(4, '예은', 18),
(5, '유빈', 99);


사실 원더걸스가 이렇게 어린지 몰랐습니다-_-; 자 우선 넘어가고-_-;

이클립스를 실행하고 JAVA PROJECT로 프로젝트를 하나 만듭시다.
그리고 패키지를 jyp라고 해서 만듭시다-_-;
iBATIS lib파일인 ibatis-2.3.0.677.jar과 OracleJDBC를 LIB으로 추가합시다.

iBATIS를 사용하기 위한 설정파일을 구성해봅시다.
jyp에다가 new해서 xml파일을 하나 만듭시다. 이름은 SqlMapConfig.xml 이라고 합시다.

SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">


<sqlMapConfig>

 
<properties resource="./jyp/SqlMapConfig.properties" />
 
 
<settings
     
cacheModelsEnabled="true"
     
enhancementEnabled="true"
     
lazyLoadingEnabled="true"
     
maxRequests="32"
     
maxSessions="10"
     
maxTransactions="5"
     
useStatementNamespaces="false"
 
/>
   
 
<transactionManager type="JDBC" >
   
<dataSource type="SIMPLE">
     
<property name="JDBC.Driver" value="${driver}" />
     
<property name="JDBC.ConnectionURL" value="${url}" />
     
<property name="JDBC.Username" value="${username}" />
     
<property name="JDBC.Password" value="${password}" />
   
</dataSource>
 
</transactionManager>
 
 
<sqlMap resource="./jyp/WonderGirls.xml" />
 
</sqlMapConfig>

내용을 보면 환경을 설정하고 JDBC와 연결하기 위한 작업을 하는 듯 합니다. JDBC연결작업은 SIMPLE이라고 되어있는데 DBCP로도 됩니다. 메뉴얼에 보시면 개발자가이드 부분에 보면 하는 법이 있습니다.
그리고 마지막에 sqlMap의 resource를 WonderGirls.xml을 추가해놨는데요 차후 설명하겠습니다.

그다음에 저기 JDBC설정에 ${driver}등 변수형식으로 되어있는데 맨위에 properties파일을 하나 설정한게 있는데 저기에 정의 하면 됩니다.

SqlMapConfig.properties

driver
=oracle.jdbc.driver.OracleDriver
url
=jdbc:oracle:thin:@127.0.0.1:1521:XE
username
=MUDCHOBO
password
=1234

오라클은 저렇게 하면 되구요. mysql은 mysql에 맞게 바꾸면 돼요.
만약 한 프로그램에서 여러개의 db를 접근한다면 저 파일들을 또 만들면 되겠죠?

그 다음 클래스를 하나 만듭시다. MyAppSqlmapConfig라는 클래스를 만듭시다.

package jyp;

import java.io.Reader;

import com.ibatis.common.resources.Resources;
import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;

public class MyAppSqlMapConfig {

 
private static final SqlMapClient sqlMap;
 
 
static {
 
try {
   
String resource = "./jyp/SqlMapConfig.xml";
   
Reader reader = Resources.getResourceAsReader(resource);
   sqlMap
= SqlMapClientBuilder.buildSqlMapClient(reader);
 
} catch (Exception e) {
   e
.printStackTrace();
   
throw new RuntimeException("Error initializing class. Cause:" + e);
 
}
 
}

 
public static SqlMapClient getSqlMapInstance() {
 
return sqlMap;
 
}
}

방금 설정한 설정파일을 이용해서 sqlMapClient를 만드는 듯 합니다. 저 sqlMap객체를 통해서 db에 접근할 수 있는 듯 합니다. 저도 잘은 모르겠네요 ^^

원더걸스 getter, setter를 만들어 봅시다. WonderGirls라는 이름으로 클래스를 만듭니다.
WonderGirls.java

package jyp;

public class WonderGirls {

 
private int num;
 
private String name;
 
private int age;
 
 
public int getNum() {
 
return num;
 
}
 
public void setNum(int num) {
 
this.num = num;
 
}
 
public String getName() {
 
return name;
 
}
 
public void setName(String name) {
 
this.name = name;
 
}
 
public int getAge() {
 
return age;
 
}
 
public void setAge(int age) {
 
this.age = age;
 
}
}

이제 가장 중요한 부분인 Wondergirls에 관련된 sqlMap을 작성해봅시다.
이름은 아까 정의한 WonderGirls.xml파일로 만듭시다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-2.dtd">


<sqlMap namespace="WonderGirls">
 
 
<resultMap id="WondergirlsResult" class="jyp.WonderGirls">
 
<result column="NUM" property="num" />
 
<result column="NAME" property="name" />
 
<result column="AGE" property="age" />
 
</resultMap>
 
 
<select id="getWondergirls" resultMap="WondergirlsResult">
  SELECT NUM, NAME, AGE
  FROM WONDERGIRLS
  ORDER BY NUM
 
</select>

 
<select id="selectWondergirl" parameterClass="Integer"
             
resultClass="jyp.WonderGirls">
  SELECT NUM, NAME, AGE
  FROM WONDERGIRLS
  WHERE NUM = #num#
 
</select>
 
 
<insert id="insertWondergirl" parameterClass="jyp.WonderGirls">
  INSERT INTO
  WONDERGIRLS (NUM, NAME, AGE)
  VALUES (#num#, #name#, #age#)
 
</insert>
 
 
<update id="updateWondergirl" parameterClass="java.util.Map">
  UPDATE WONDERGIRLS SET
  NAME = #name#,
  AGE = #age#
  WHERE NUM = #num#
 
</update>
 
 
<delete id="deleteWondergirl" parameterClass="Integer">
  DELETE WONDERGIRLS
  WHERE NUM = #num#
 
</delete>
 
</sqlMap>

sql문을 xml로 뺐습니다. 나중에 고칠 때 편할 듯 싶네요.
sql이 총 5가지가 있네요.
전체리스트가져오기, 멤버번호받아서 한명멤버정보 가져오기, 멤버추가하기, 멤버수정하기, 멤버삭제하기

select해서 List를 가져올 때에는 원래 자동맵핑이 되서 알아서 가져오지만 그래도 명시적으로 맵핑을 해줘야돼요. 그래서 resultMap태그를 이용해서 명시해준뒤에 select를 선언해서 그 resultMap의 id를 resultMap속성값에 넣어주면 됩니다.
insert할 때에도 WonderGirls클래스형으로 받아서 넣어주면 되구요.
update할 때 좀 틀린게 parameterClass를 Map으로 선언합니다. 이름, 나이, 번호를 각각 객체로 받아서 Map에 저장한 것을 받아서 사용하는 것이죠.
delete할 때에는 Integer클래스형으로 숫자하나 받아서 해당 멤버를 지우게 됩니다.

이번엔 DAO를 만들어봅시다. WondergirlsDao라는 이름의 클래스를 만들어봅시다.

package jyp;

import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

import com.ibatis.sqlmap.client.SqlMapClient;

public class WondergirlsDao {

 
@SuppressWarnings("unchecked")
 
public void viewMember(SqlMapClient sqlMap) throws SQLException {
 
List<WonderGirls> wonderGirlsList = (List<WonderGirls>) sqlMap
   
.queryForList("getWondergirls");
 
for (int i = 0; i < wonderGirlsList.size(); i++) {
   
WonderGirls wonderGirl = (WonderGirls) wonderGirlsList.get(i);
   
System.out.print("번호 : " + wonderGirl.getNum());
   
System.out.print("   이름 : " + wonderGirl.getName());
   
System.out.println("   나이 : " + wonderGirl.getAge());
 
}
 
}

 
public void insertMember(SqlMapClient sqlMap, WonderGirls wonderGirls)
   
throws SQLException {  
  sqlMap
.insert("insertWondergirl", wonderGirls);
 
System.out.println("추가에 성공했습니다.");
 
}

 
public void modifyMember(SqlMapClient sqlMap, Map<String, Object> map)
   
throws SQLException {
  sqlMap
.update("updateWondergirl", map);
 
System.out.println("수정에 성공했습니다.");
 
}

 
public void deleteMember(SqlMapClient sqlMap, int num) throws IOException,
   
SQLException {
  sqlMap
.delete("deleteWondergirl", num);
 
System.out.println("삭제에 성공했습니다.");
 
}
 
 
public boolean validateMember(SqlMapClient sqlMap, int num) throws SQLException {
 
WonderGirls wonderGirls =
   
(WonderGirls) sqlMap.queryForObject("selectWondergirl", num);
 
 
if (wonderGirls == null) {
   
return false;
 
}
 
return true;
 
}
}

DAO를 보면 이상하게 간단합니다. 위에 xml에 다 쿼리를 선언해줬기때문에 그냥 파라메터만 넘겨주면 됩니다.
List보기는 sqlMap.queryForList("getWondergirls"); getWondergirls는 위에 선언한 쿼리의 id입니다.
insert할 때에는 객체를 받아서 넘겨주고, 수정할 때에는 Map을 넘겨주고, 삭제 할때에는 숫자를 넘겨주면 됩니다.
아 간단해요. 한줄이면 끝나요-_-;
예전에 Connection을 close()안해줬더니 서버가 다운되려고 한 경험이 있는데 이런일은 절대 없을 것 같군요.

이제 main함수를 작성해봅시다. 이름은 WondergirlsApplication으로 해봅시다.
public static void main(String[] args)에 체크해주시는 것 잊지 마시구요-_-;
WondergirlsApplication.java

package jyp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import com.ibatis.sqlmap.client.SqlMapClient;

public class WondergirlsApplication {

 
public static void main(String[] args) throws SQLException, IOException {

 
BufferedReader bufferedReader = new BufferedReader(
   
new InputStreamReader(System.in));
 
SqlMapClient sqlMap = MyAppSqlMapConfig.getSqlMapInstance();
 
WondergirlsDao wondergirlsDao = new WondergirlsDao();
 
WonderGirls wonderGirls = new WonderGirls();
 
 
Integer age, num;
 
String name;
 
 
while (true) {
   
try {
   
System.out.println("\n1.멤버출력\n2.멤버추가\n3.멤버수정\n4.멤버삭제\n5.종료");
   
System.out.print("--> ");
   
String temp = bufferedReader.readLine();
   
int MenuNum = Integer.parseInt(temp);
   
switch (MenuNum) {
   
case 1:
     wondergirlsDao
.viewMember(sqlMap);
     
break;
     
   
case 2:
     wonderGirls
= new WonderGirls();
     
System.out.print("번호을 입력하세요 : ");
     wonderGirls
.setNum(Integer.parseInt(bufferedReader.readLine()));
     
System.out.print("이름을 입력하세요 : ");
     wonderGirls
.setName(bufferedReader.readLine());
     
System.out.print("나이을 입력하세요 : ");
     wonderGirls
.setAge(Integer.parseInt(bufferedReader.readLine()));
     wondergirlsDao
.insertMember(sqlMap, wonderGirls);
     
break;
     
   
case 3:
     
Map<String, Object> map = new HashMap<String, Object>(3);

     
System.out.print("수정할 번호을 입력하세요 : ");
     num
= new Integer(Integer.parseInt(bufferedReader.readLine()));
     
if (!wondergirlsDao.validateMember(sqlMap, num)) {
     
System.out.println("없는 멤버번호입니다.");
     
break;
     
}
     
System.out.print("이름을 입력하세요 : ");
     name
= bufferedReader.readLine();
     
System.out.print("나이을 입력하세요 : ");
     age
= new Integer(Integer.parseInt(bufferedReader.readLine()));

     map
.put("num", num);
     map
.put("name", name);
     map
.put("age", age);
     wondergirlsDao
.modifyMember(sqlMap, map);
     
break;
     
   
case 4:
     
System.out.print("삭제할 멤버번호를 입력하세요 : ");
     num
= Integer.parseInt(bufferedReader.readLine());
     
if (!wondergirlsDao.validateMember(sqlMap, num)) {
     
System.out.println("없는 멤버번호입니다.");
     
break;
     
}
     wondergirlsDao
.deleteMember(sqlMap, num);
     
break;
     
   
case 5:
     
System.exit(0);
     
   
default:
     
System.out.println("1~5중에서 선택하세요!");
     
break;
   
}
   
} catch (NumberFormatException e) {
   
System.out.println("잘못 입력했습니다. 다시 입력하세요");
   
}
 
}
 
}
}

자세히 보시면 뭐 별거없는데 거창하게 만들어놨군요(제가 좀 허접해서-_-) 아 참고로 예외처리 잘 안해놨으니 알아서 버그많아요 알아서 보세요-_-;

1번은 멤버 리스트를 가져오는군요.

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 1
번호 : 1   이름 : 선예   나이 : 18
번호 : 2   이름 : 선미   나이 : 15
번호 : 3   이름 : 소희   나이 : 15
번호 : 4   이름 : 예은   나이 : 18
번호 : 5   이름 : 유빈   나이 : 5

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
-->

2번은 멤버를 추가하는데 추가해봅시다-_-;

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 2
번호을 입력하세요 : 6
이름을 입력하세요 : 성종천
나이을 입력하세요 : 25
추가에 성공했습니다.

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 1
번호 : 1   이름 : 선예   나이 : 18
번호 : 2   이름 : 선미   나이 : 15
번호 : 3   이름 : 소희   나이 : 15
번호 : 4   이름 : 예은   나이 : 18
번호 : 5   이름 : 유빈   나이 : 5
번호 : 6   이름 : 성종천   나이 : 25

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
-->

아 멤버가 추가되었어요-_-; (이거 욕먹겠군요-_-;)
멤버를 수정해봅시다. 유빈양의 나이가 잘못 되었으니 수정해봅시다.

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 3
수정할 번호을 입력하세요 : 5
이름을 입력하세요 : 유빈
나이을 입력하세요 : 20
수정에 성공했습니다.

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 1
번호 : 1   이름 : 선예   나이 : 18
번호 : 2   이름 : 선미   나이 : 15
번호 : 3   이름 : 소희   나이 : 15
번호 : 4   이름 : 예은   나이 : 18
번호 : 5   이름 : 유빈   나이 : 20
번호 : 6   이름 : 성종천   나이 : 25

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
-->

아.... 유빈양이 제일 마지막에 들어왔는데 나이가 제일 많군요-_-;
그리고 이제 말도 안되는 성종천이라는 멤버를 지워봅시다-_-;

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 4
삭제할 멤버번호를 입력하세요 : 6
삭제에 성공했습니다.

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 1
번호 : 1   이름 : 선예   나이 : 18
번호 : 2   이름 : 선미   나이 : 15
번호 : 3   이름 : 소희   나이 : 15
번호 : 4   이름 : 예은   나이 : 18
번호 : 5   이름 : 유빈   나이 : 20

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
-->

아.....잘 되는군요-_-;

iBATIS 란?


iBATIS의 개념
 

iBATIS는 간단한 XML서술자를 사용해서 간단하게 자바빈즈를 SQL statement에 맵핑시킨다. 간단함(Simplicity)이란 다른 프레임워크와 객체관계맵핑툴에 비해 iBATIS의 가장 큰 장점이다. iBATIS Data Mapper를 사용하기 위해서 당신은 자바빈즈와 XML 그리고 SQL에 친숙할 필요가 있다. 여기엔 배워야 할것도 거의 없고 테이블을 조인하거나 복잡한 쿼리문을 수행하기 위해 필요한 복잡한 스키마도 없다. Data Mapper를 사용하면 당신은 실제 SQL문의 모든 기능을 가질수 있다. JDBC 로만 프로그래밍 할 때의 번거로움을 줄여주기 위해 재사용 모듈로 개발된 것인데 그 주요한 어려움을 ibatis 개발자 가이드에서 다음과 같이 정리하고 있다.

 
 
iBATIS의 역사와 사용
  • SQL 문장과 프로그래밍 코드의 분리
  • JDBC 라이브러리를 통해 매개변수를 전달하고 결과를 추출하는 일
  • 데이터베이스 접근 클래스와 비즈니스 로직을 담은 클래스의 분리
  • 자주 쓰이는 데이터를 변경되지 않는 동안에 임시 보관(Cache)
  • 트랜젝션과 쓰레드 관리
 
이러한 사항들에 대해서 한번쯤 생각해볼 필요가 있다.
  • SQL 문장과 프로그래밍 코드의 분리
    SQL문과 프로그램 코드는 꼭 분리해야 할까? 우선 프로그램이 작은 경우는 굳이 분리할 필요가 없어 보인다. 파일만 두개로 분리되기 때문에 관리에 불편함이 가중될 수 있다. 프로그램이 커져도 분리하는 것이 꼭 유리한 것만은 아니다. SQL을 사용하는 클래스가 1000개라면 분리했을 때 2000개가 되어서 더 복잡해질 수도 있다. 그럼에도 불구하고 ibatis에서는 SQL을 분리하는 이유는 무엇일까?
    Separation of Concern!! 할 일의 명확한 분리. 아마도 이것이라 짐작된다. 자바 코드에서 SQL을 없애서 순수 OO로 만들어놓기. 이것은 결벽증이나 지나친 원리집착이 아니라 체계가 다른 것을 나누어 보관하는 것이다. 논리적으로 정연한 흐름을 만들어 둘 수 있고, 객체지향 입장에서 데이터에 해당하는 도메인 모델(Domain model) 혹은 도메인 객체(Domain object)와 관계형 데이터베이스의 테이블(Table)을 느슨한 관계(loosely coupled)로 만들어서 변화에 대한 유연성을 확보할 수도 있다. 즉, 정규화를 위해서 혹은 데이터 접근의 효율성을 위해서 테이블 구조를 바꾸어도 프로그램 코드 안에 있는 SQL을 수정할 필요가 없어진다.
     
  • JDBC 라이브러리를 통해 매개변수를 전달하고 결과를 추출하는 일
    select 등를 할 때 조건에 해당하는 매개변수를 설정하거나, insert 문에서 값을 할당하기 위해 매개변수를 설정하는 일은 무척 번거로운 일이다. 단순한 일이면서도 오류가 잦은 부분이다. 때문에 프로젝트를 몇 차례 겪고 나면 자신만의 라이브러리를 만들거나 공통팀에서 유틸리티를 제공하는 일을 흔하게 볼 수 있다. Apache Commons의 DB Utils 나 Spring의 JDBC Template 등은 이러한 작업을 용이하게 하려고 등장했고, ibatis 역시 이러한 작업을 훨씬 부드럽게 해준다.
     
  • 데이터베이스 접근 클래스와 비즈니스 로직을 담은 클래스의 분리
    이른바 DAO(Data Access Object) 패턴이 이러한 일을 담당한다. ibatis는 DAO 계층 구현을 위한 유틸리티 성격이면서 동시에 best practice 역할도 수행한다.
     
  • 자주 쓰이는 데이터를 변경되지 않는 동안에 임시 보관(Cache)
    ibatis 에선 XML 설정만으로 캐시를 할 수 있다.
     
  • 트랜젝션과 쓰레드 관리
    트랜젝션 처리 역시 용이하다.


    iBATIS의 주요기능 

    Data Mapper (com.ibatis.sqlmap.*)


    개념
    iBATIS Data Mapper API는 프로그래머에게 자바빈즈 객체를 PreparedStatement파라미터와 ResultSets으로 쉽게 맵핑할 수 있도록 한다. Data Mapper의 기본적인 생각은 간단함(simple)이다. 이는 자바코드의 20%를 사용하여 JDBC기능의 80%를 제공하는 간단한 프레임워크라는 뜻이다.


    작동원리
    Data Mapper는 자바빈즈, Map구현, 원시래퍼타입(String, Integer…) 그리고 SQL문을 위한 XML문서를 맵핑하기 위한 XML서술자를 사용하는 매우 간단한 프레임워크를 제공한다.

    다음은 생명주기에 대한 높은 레벨의 서술이다.


    1) 파라미터(자바빈즈, Map 또는 원시래퍼)로써 객체를 제공한다. 파라미터 객체는 update문내에 입력값을 셋팅하기 위해 사용되거나 쿼리문의 where절을 셋팅하기 위해서 사용된다.

    2) 맵핑된 statement을 실행한다. 이 단계는 마법이 일어나는곳이다. Data Mapper프레임워크는
    PreparedStatement 인스턴스를 생성할것이고 제공된 파라미터객체를 사용해서 파라미터를 셋팅한다. 그리고 statement를 실행하고 ResultSet으로부터 결과 객체를 생성한다.

    3) update의 경우에 영향을 미친 rows의 숫자를 반환한다. 조회문일경우에 한 개(single)의 객체 또는 컬렉션 객체를 반환한다. 파라미터처럼 결과 객체는 자바빈즈, Map 원시타입래퍼또는 XML이 될수 있다.

    참조 : http://blog.empas.com/ahnyounghoe/11535652

  • 2009년 9월 4일 금요일

    Persistance Support of Spring Framework


    Spring에서 DB에 연결하는 방법에 대해서 알아보려 합니다. 다음과 같이 세가지 방법이 있습니다.
     - JDBC를 이용하는 방법
     - JdbcDaoSupport 클래스를 이용해서 JDBC를 조금 편하게 템플릿화하는 방법
     - SqlMapClientDaoSupport 클래스를 이용해서 iBatis Framework를 연동하는 방법

    각 방법이 어떻게 다른지를 설명드리고 튜토리얼 수준의 예제를 보여드리겠습니다.

    1. JDBC
    일반적으로 JDBC를 이용할 경우 아래의 순서를 따라서 구현됩니다. 
     - DriverManager에 해당 DBMS Driver를 등록
     - 해당 Driver로 부터 Connection 객체 획득
     - Connection 객체로부터 Statement 객체 획득
     - Statement의 method를 이용하여 SQL실행
     - ResultSet 으로 받아서 처리(executeUpdate 의 경우엔 제외)
     - 객체 close() (ResultSet, Statement, Connection)

    여기서 실제로 개발자의 비지니스 로직이 들어가는 부분은 4번과 5번의 일부입니다. 나머지는 음... 기계적인 코드입니다. 실제 코드를 예로 들면 아래와 같습니다.

    Connection.java

    .........
    Connection con = null;
    Statement st = null;
    ResultSet rs = null;
     
    try {
        con=DriverManager("jdbc:mysql://localhost/dbname","root","1234");  // dbname에는 사용하는 database 이름, root 계정, 패스워드는 1234

        Statement st=con.createStatement();
        ResultSet rs=st1.executeQuery("select * form names");
     
        if( rs.next() ) {
            do {
                // result set을 잘 정리합니다. 물론 일일이
                ...
                // 비지니스 로직을 수행합니다.
                ...
            } while( rs.next() )
        }
    } catch (Exception ex) {
        // 적절한 예외 처리
    } finally {
        if( rs != null) rs.close();
        if( st != null ) st.close();
        if( con != null ) conn.close();
    }
    .........

    의외로 너무 많은 부분인 반복되는 느낌입니다. 이런 부분은 Spring을 사용하면 많이 줄어들게 됩니다. 다음절에서 확인해 보지요.

    2. DaoSupport in Spring Framework
    Spring에는 Template이라는 것을 이용해서 이 과정을 알아서 처리해 줍니다.
     - JdbcTemplate
     - NamedParameterJdbcTemplate
     - SimpleJdbcTemplate
     
    이를 편하게 사용하기 위해서 주로 아래의 Dao 클래스를 이용합니다.
     - JdbcDaoSupport
     - NamedParameterJdbcDaoSupport
     - SimpleJdbcDaoSupport

    여기서 가장 다양한 기능을 가진 SimpleJdbcDaoSupport 클래스의 사용법을 코드에서 확인해 보도록 하겠습니다. (전체 코드는 이전 4번 튜토리얼 분석 포스트에서 사용했던 예제를 보시면 됩니다.)

    applicationContext.xml

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
     
    <bean id="productDao" class="springapp.repository.JdbcProductDao">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    ProductDao.java

    public class JdbcProductDao extends SimpleJdbcDaoSupport implements ProductDao {
        public int selectCount() {
            return getSimpleJdbcTemplate().queryForInt(
                    "select count(*) from products");
        }
    }

    음..확실히 간편해 진것 같습니다.

    하지만, 쿼리문이 코드 내부에 하드코딩되어 있다는 점이 약간 아쉬운 부분입니다. 쿼리문 변경 시에 코드도 같이 변경되고 재컴파일도 되어야 합니다. (뭐 솔직히 컴파일 하면 되긴 해요... ^^) 그리고 따로 설명하지 않겠지만 ParameterizeRowMapper 인터페이스를 이용해서 domain 객체와 결과를 연결하는 방법이 있는데 이 역시 하드코딩 되기 때문에 유지 보수의 유연성이 떨어지는 면이 없지 않습니다.

    그래서, 쿼리 및 쿼리의 결과와 객체간의 연결을 외부에서 따로 관리하는 기능을 요구하게 되고 이런 기능을 가진 iBatis가 최근 많이 사용되고 있습니다. 다음 절에서 iBatis의 연동에 대해 알아보겠습니다.
     
    3. Spring Framework와 iBatis 연동
    1) iBatis
    국내 SI 현실에 가장 잘 어울린다고 평가받고 있는 persistance framework 입니다. 그 이유는 SQL 쿼리문을 그대로 사용하기 때문에 기존 JDBC 개발자들에게 거부감이 덜하고  경험적(?)인 시스템의 이해가 빠르기 때문일 겁니다. 여타의 ORM framwork는 배우기도 힘들고 또는 제 3자 입장에서 이해하기도 힘들어서 우리나라 SI 업계처럼 개발팀이 이합집산하는 문화에는 어울리지 않는다는 군요.

    각설하고 간단한 예를 보도록 하지요. iBatis에 대해서는 제가 따로 설명하지 않을 것입니다. 아래의 자료들을 보시면 간단하게 이해하실 수 있을 겁니다. 그리고, googling 하시면 JPetStore라는 예제가 있습니다. 이를 한번 실행시켜 보시는 것도 도움이 될 것입니다.

    다음은 iBatis 관련해서 많은 자료를 만드신 이동국님의 Tutorial 변역본입니다. 8장밖에 안되지만 iBatis의 동작에 대해서 이해하는데 많은 도움이 되실 것입니다.
    별첨으로 하나 더 첨부합니다. 이 파일은 sqlMap의 사용법에 대해 설명된 문서이고 역시 이동국님이 번역하신 자료입니다.

    자~! 이제 iBatis의 사용법에 대해서 간단히 감을 잡으셨나요?
    앞 절에서 언급한 것처럼 제 생각에는 2가지의 큰 장점을 가진다고 생각됩니다. (다른 장점이 있으시면 사알짝 좀 알려주세요~~)
     - SQL 쿼리문이 외부 설정 파일에서 관리된다.
     - domain 객체와 SQL result set의 연결이 외부 설정 파일에서 관리된다.

    그런데 JDBC와 마찬가지로 iBatis에도 단순하게 기계적으로 반복되는 코드가 있습니다. 바로 SqlMapClient를 선언하는 부분이지요! (아래 코드 참조)
    public MyAppSqlConfig {
        private static final SqlMapClient sqlMap;
        static {
            try {
                String resource = “com/ibatis/example/sqlMap-config.xml”;
                Reader reader = Resources.getResourceAsReader (resource);
                sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
            } catch (Exception e) {
                .......
        }
    }
     
    JDBC를 지원하기 위해 JdbcDaoSupport 클래스가 있지요? 그럼 SqlMapClient를 지원해 주는 것은? 네! SqlMapClientDaoSupport 클래스 입니다. 그럼 드디어 sqlMapClientDaoSupport 클래스에 대해 알아보겠습니다.

    2) sqlMapClientDaoSupport
    앞서 알아본 JdbcDaoSupport와 이름도 비슷하지만 사용법도 매우 유사합니다. 설정 파일과 관련된 자바 코드를 보시지요.
    applicationContext.xml - sqlMapConfig.xml - Product.xml 이 연쇄적인 관계를 가지고 있습니다. 여기서 applicationContext.xml은 spring 설정 파일이고 나머지는 iBatis 설정 파일입니다.
    비지니스 로직 수행 중 iBatisMessageDao의 함수들이 요청될 것이고 이떄 Product.xml에 있는 쿼리문들을 호출해서 수행하게 됩니다.
    주석문을 달고 색깔로 구분해 놓았습니다. 연관 관계에 유의하시면서 주의 깊게 확인하시길 바랍니다. (오랜만에 칼라풀한 포스트 입니다. ^^)

    applicationContext.xml

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
            <property name="driverClassName" value="${jdbc.driverClassName}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
    </bean>

    // sqlMapClient를 선언해야 합니다.
    <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
         <property name="dataSource" ref="dataSource" />
         <property name="configLocation" value="WEB-INF/sqlmap/sqlMapConfig.xml" />
    </bean>

    // DAO 입니다.
    <bean id="productDao" class="springapp.repository.iBatisProductDao">
         <property name="sqlMapClient" ref="sqlMapClient" />
    </bean>

    <bean id="productManager" class="springapp.service.SimpleProductManager">
            <property name="productDao" ref="productDao" />
    </bean>

    sqlMapConfig.xml

    // classpath가 root 디렉토리 임에 유의 하셔야 합니다.
    <sqlMapConfig>
        <sqlMap resource="../sqlmap/Product.xml" />
    </sqlMapConfig>

    Product.xml

    <sqlMap namespace="ProductNamespace">
        // Product 도메인 객체 입니다. 여기저기서 사용되게 됩니다.
        <typeAlias alias="Product" type="springapp.domain.Product" />

        <!-- getProductList -->
        // getProductList와 관련된 결과 값의 형태를 정의합니다.
        <resultMap id="getProductResultMap" class="Product">
            <result property="id" column="id" />
            <result property="description" column="description" />
            <result property="price" column="price" />
        </resultMap>
     
        <select id="getProductList" resultMap="getProductResultMap" resultClass="Product">
            select id, description, price from products
        </select>

        <!-- saveProduct -->
        // saveProduct와 관련된 입력 값의 형태를 정의합니다.
        <parameterMap id="saveProductParamMap" class="Product">
            <parameter property="id"  />
            <parameter property="description"  />
            <parameter property="price"  />
        </parameterMap>
     
        // parameter를 지정하는 방식에 유의 하시길 바랍니다.
        <update id="saveProduct" parameterClass="Product" >
            update products set description = (#description#), price = (#price#) where id = (#id#)
        </update>
    </sqlMap>

    iBatisMessageDao.java
     
    // 코드 내부에는 전혀 쿼리 문이 없습니다!! 여기에 별 5개 꼭 확인하세요!
    public class iBatisProductDao extends SqlMapClientDaoSupport implements ProductDao {
        @SuppressWarnings("unchecked")
        public List<Product> getProductList() {
            List<Product> products = getSqlMapClientTemplate().queryForList("getProductList");
            return products;
        }

        public void saveProduct(Product prod) {
            getSqlMapClientTemplate().update("saveProduct", prod);
        }
    }

    2절에서 설명한 JdbcDaoSupport 클래스의 사용법과 비교했을 때 iBatis는 이를 한단계 더 추상화 되어 있음을 알 수 있습니다. 모든 쿼리문과 해당 쿼리 결과와 객체간의 관계를 XML 외부 설정으로 따로 관리하고 있다는 것이죠.
    그렇기 때문에, 앞에서 말씀드린데로 이 방법은 java 코드내에 하드코딩하는 것보다 개발의 유연성을 높이고 추후 관리시에 유리한 장점을 가집니다. (추상화란 처음에는 귀찮고 나중에는 편리한 것이지요!)

    다음은 이전 4번째 spring 프레임워크 포스트에서 사용했던 튜토리얼을 iBatis로 연동시킨 예제입니다. 위에 설명드린 내용이 어떻게 적용되어 있는지 한번 더 확인하실 수 있을 것으로 생각됩니다.


    4. 맺음말
    휴..정말 긴 튜토리얼 리뷰였습니다. 매일 생각없이 JDBC로 개발하던 저에게는 참으로 재미난 것이 아닐 수 없었습니다.
    물론 때에 따라서는 JDBC 자체가 빛나는 순간이 있겠지만, 대부분의 경우에는 iBatis가 우리를 편하게 해주지 않을까 생각됩니다. iBatis 관련 공부 중 이런 글이 있었습니다. "iBatis는 80%의 JDBC 기능을 20%의 자바 코드로 구현한 간단한 프레임워크이다."
    맞습니다. 이는 만병통치약이 아닙니다. 다만 대부분에 경우에 잘 먹히는 고마운 녀석인 것이지요. 뭐 종합감기약 정도 ^^;
    그렇기 때문에 여러분도 한번 직접 경험해 보시고 개인적인 판단을 세우시길 바랍니다. 여러분에게 맞지 않을 수 있으니까요. 이상입니다. 감사합니다.

    5. 참고자료
    1) 웹 개발자를 위한 스프링 2.5 프로그래밍 (가메출판사, 최범균 지음)

    Developing a Spring Framework MVC application step-by-step


    다음의 내용은 spring framework 공식 홈페이지(www.springframework.org)의 튜토리얼을 정리한 것입니다. 저와 같은 spring 초보자를 위해서 개인적인 방식으로 재구성하였습니다. 처음 spring을 접하는 분들이 학습에 참고 자료로 활용하시길 바랍니다.  

    코드 자체의 세세한 부분은 다루지 않을 것입니다. spring 프레임워크을 이용해서 기본적인 웹 서비스를 구현하는 방법을 보여주는 것에 주력할 것입니다. 따라서, 이 글에서는 설정 파일의 작성에 대해서 중점적으로 논의하겠습니다.
    (이 글은 기본적인 자바 웹 개발에 경험이 있는 분을 기준으로 작성합니다.)

    1. Source Structure


    사용자 삽입 이미지

    튜토리얼치고는 소스와 설정파일이 많은 편입니다. 이는 Spring 자체가 MVC를 지원하는 프레임워크이고 설정 파일도 논리적으로 분리하는 것을 권장하기 때문입니다. 튜토리얼에서도 이 개념은 유지하고 있습니다. 이는 차차 설명하기로 합니다.

    1) 소스 구조
     최상위 폴더는 아래와 같습니다.
    ┌ bin - 컴파일된 class 저장
    ├ db - DB와 관련된 스크립트
    ├ src - 말 그대로 소스들
    └ war - WAS에 deploy될 것들
    을 가리킵니다.

    각각의 폴더에 대해 조금 더 자세히 알아보도록 하겠습니다. 조금 생소한 부분만 설명합니다. 너무 쉬운 부분은 넘어가도록 하겠습니다.

    □ /src
    springapp은 최상위 package이고 여기에 domain, repository, service, web이라는 하위 package가 존재합니다. 아래의 기준으로 클래스 모듈을 위치 시킵니다.
    . domain - data 모델링 객체
    . repository - DAO 과 관련된 객체
    . service - 비지니스 로직
    . web - web 프리젠테이션 관련 객체

    □ /war
    . /WEB-INF/tld/spring-form.tld
    spring에서 사용할 수 있는 tag library의 정의입니다. tag library는 기존 jsp에서 사용하던 tag를 spring에서 재정의 한 것으로 좀 더 사용이 편합니다.
    . /WEB-INF/*.xml
    설정 파일들입니다. 실질적으로 spring을 사용함에 있어 가장 먼저 이해하여야 할 부분입니다. 뒤에서 자세히 설명하도록 하겠습니다.




















    2. Configuration 관련
    1) web.xml
      □ org.springframework.web.context.ContextLoaderListener
         . 계층별로 나눈 xml 설정파일을 web.xml에서 load되도록 등록할 때 사용.
         . 기본값으로 applicationContext.xml을 사용함
       예시)
       <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
       </listener>
       <context-param>
             <param-name>contextConfigLocation</param-name>
             <param-value>
                  /WEB-INF/mars-ibatis.xml
                  /WEB-INF/mars-service.xml
             </param-value>
       </context-param>

      □ <taglib>
         . spring tag library의 위치 설정

    2) applicationContext.xml
      □ ContextLoaderListener(또는 ContextLoaderServlet)가 ApplicationContext를 만들 때 사용

      □ ApplicationContext란, 웹에 독립적인 애플리케이션의 모든 영역(dao, service, manager, 기타 등등) 에 대한 정의를 말합니다.

      □ connection pool에 대한 정의
         . org.apache.commons.dbcp.BasicDataSource
            : datasource를 정의, db driver, 보안 정보등을 알림
         . org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
            : 읽어들일 property 파일에 대한 정보를 알림

      □ transaction에 대한 지원을 위한 한 방법
      이 부분은 나를 비롯한 spring 초보자의 입장에서 아직 이해하기 어려운 부분이기 때문에 아래의 코드가 AOP 기법을 활용하여 transaction을 지원하고 있다는 것만 알아두기로 하자.

    transaction 지원 코드 보기

    펼쳐두기..


    3) springapp-servlet.xml
      □ DispatcherServlet이 WebApplicationContext를 만들 때 사용

      □ WebApplicationContext이란, 특정 웹(DispatcherServlet)에 종속되는 영역(Controller 및 각종 웹관련 설정 bean들)에 대한 정의를 말합니다. (ApplicationContext과의 차이를 확인하세요.)

      □ 주로 웹 페이지 리소스에 대한 핸들러 클래스 빈의 정의가 존재합니다.

      □ org.springframework.context.support.ResourceBundleMessageSource
         . message.properties 파일에정의된 속성 값을 사용할 수 있도록 함
         . 국제화에 응용되면 아주 좋아 보입니다~

      □ org.springframework.web.servlet.view.InternalResourceViewResolver
         . prefix와 suffix 값의 정의에 따라 리소스 표현을 축약할 수 있도록 함.
         예시)
        <bean id="viewResolver"
         class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="viewClass"
              value="org.springframework.web.servlet.view.JstlView"></property>
            <property name="prefix" value="/WEB-INF/jsp/"></property>
            <property name="suffix" value=".jsp"></property>       
        </bean>
         라고 선언하면 /WEB-INF/jsp/testfile.jsp은 비지니스 로직 내부에서 testfile로 간단하게 표현 가능 함



    3. Inside Code
    spring web service는 아래의 흐름을 가집니다.
    ①(사용자) jsp 호출
    ②(웹서버) 설정 검색
    ③(웹서버) 요청 처리 클래스 호출
    ④(웹서버) 비지니스 로직 수행
    ⑤(웹서버) view로 결과 반환
    ⑥(사용자) jsp 화면


    1) Controller
      □ 사용자에게 jsp가 호출되면 xxxxx-servlet.xml에 정의된 클래스 빈을 호출합니다.

      □ 예제에는 DB에 있는 정보를 가져다가 저장하는 페이지와 원하는 값을 저장하는 페이지 두가지가 있습니다. hello.jsp가 전자에 해당하고 priceincrease.jsp는 후자에 해당합니다. 각 페이지를 확인하시고 각 페이지의 핸들러 클래스 빈을 springapp-servlet.xml에서 추적해 보시길 바랍니다.

      □ 일반적인 controller의 사용 예 : springapp.web.InventoryController.java
         . 일단 기본적인 Controller의 예시로 Controller interface를 구현합니다. 대부분의 경우에 사용하면 되고, request에 대한 처리를 위해 handleRequest()로 진입합니다.
         . 여기서 비지니스 로직을 처리한 후, ModelAndView 객체를 반환합니다. 이 때 넘기고자 하는 데이터를 파라미터에 담아서 보낼 수 있습니다.

        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            String now = (new java.util.Date()).toString();
            logger.info("returning hello view with " + now);
            Map<String, Object> myModel = new HashMap<String, Object>();
            myModel.put("now", now);
            myModel.put("products", this.productManager.getProducts());
            return new ModelAndView("hello", "model", myModel);
        }

      □ 폼을 처리하는 controller의 예 : springapp.web.PriceIncreaseFormController.java
         .  jsp가 폼을 다루고 있을 경우에는 SimpleFormController Class를 상속하여 확장합니다.
         . onSubmit()은 jsp에서 버튼이 눌려진 경우 콜백 지점을 가리키도 formBacjingObject는 폼에 기본값을 저장하고자 할 때 사용합니다.
         . formBackngObject는 domain에 존재하는 모델링 객체를 이용하고 있음에 주의합니다.

        public ModelAndView onSubmit(Object command)
                throws ServletException {
            int increase = ((PriceIncrease) command).getPercentage();
            logger.info("Increasing prices by " + increase + "%.");
            productManager.increasePrice(increase);
            logger.info("returning from PriceIncreaseForm view to " + getSuccessView());
            return new ModelAndView(new RedirectView(getSuccessView()));
        }
        protected Object formBackingObject(HttpServletRequest request) throws ServletException {
            PriceIncrease priceIncrease = new PriceIncrease();
            priceIncrease.setPercentage(20);
            return priceIncrease;
        }

    2) Model
      □ 비지니스 로직을 수행 중에 데이터를 읽고 쓰고 수정하고 삭제하기 위해 데이터 베이스에 대한 접근을 수행합니다. 해당 예제는 HSQLDB를 사용하고 있으며 이를 JDBC로 연결하고 있습니다.

      □ JDBC와 관련하여 몇개의 클래스를 추가 지원하고 있지만 미미합니다. 실제로는 iBatis, Hibernate를 지원하고 있다는 것이 더 큰 특징이라 하겠습니다.

      □ 간단한 예제를 확인하시길 바라며, 이 부분은 설명하지 않겠습니다.

    3) View
      □ 예제에서는 JSTL(Java Server page Standard Tag Library)이라는 것을 사용하고 있기에 약간의 공부가 필요합니다.
         . 예를 들면, <input type='text' /> 이렇게 사용하던 것을 <form:input path="percentage"/> 이런식으로 사용합니다.
         . iteration, print 같은 주요 제어문과 폼이 tag로 정리되어 있습니다.
         . 더 자세한 내용은 http://java.sun.com/products/jsp/jstl/ 참조 바랍니다.



    4. Ending
    이 문서의 목적은 spring을 자세하게 설명하는 것이 아니라, 튜토리얼데로 spring MVC를 간단하게 한번 만들어 보는 것에 그 의의를 두고 있습니다. 튜토리얼과의 차별화를 위해 Top-down 방식을 취했으며 전체의 구성을 설명하고자 했습니다.
    저도 약 2주전부터 틈틈히 공부하면서 알게된 것을 정리한 것이라 부족한 면이 많습니다. 시간이 나는데로 잘못된 부분은 수정하고 필요한 내용은 추가하겠습니다.
    긍적적이고 발전적인 커멘트를 기다립니다. 감사합니다.

    AOP 개요


    본 기사는 Aspect Oriented Programming에 대해 간략한 소개글이다. 아직까지는 생소한 분야일 수 있겠지만, 점점 더 많이 듣게 되리라 생각된다. AOP를 설명하는데 있어서 자주 등장하는 네 개의 용어들(Aspect, Cross-cutting concern, Point-cut, Advice)를 설명함으로서 AOP가 왜 등장하게 되었으며, AOP가 제시하는 해결책에 대해 살펴볼 것이다. 먼저 "Aspect", "Oriented", "Programming"에서 생소한 단어는 단연 "Aspect"일 것이다. 야후 사전의 정의에 따르면, "Aspect"은 "사물의 면, 국면, 관점"으로 정의되어 있다. 소프트웨어 시스템은 여러가지 관점에서 바라볼 수 있다, 또는 여러 가지 단면을 가지고 있고 있다. 예를 들어, 자금 이체를 하는 프로그램을 작성한다고 생각해 보자. 출금계좌와 입금계좌, 그리고 이체금액을 입력받아 SQL 문장 또는 함수 한 번 돌리는 것으로 끝나는가? 절대 아니다. 먼저, 해킹을 방지하기 위해 사용자가 적절한 보안 프로그램을 설치했는지 점검하는 코드도 있어야 하고, 사용자가 인증되었는지 점검하는 코드도 써야 하고, 상대방 은행에서 적절하게 처리되었는지도 점점해야 하고, 혹시 사용자가 이체버튼을 두 번 누른 것은 아닌가 체크해야 하고, 시스템 로그도 남겨야 한다. 즉, 구현하려고 하는 기능 뿐 아니라 보안, 인증, 로그, 성능와 같은 다른 기능들도 녹아 있어야 한다. 어쩌면 이체를 위한 코드보다 잡다한 다른 측면의 문제들을 다루는 코드가 더 길어질 수 있다. 이런 코드들은 입금이나 출금 같은 다른 곳에서 들어가야 한다. 구현하려고 하는 비즈니스 기능들을 Primary(Core) Concern, 보안, 로그, 인증과 같이 시스템 전반적으로 산재된 기능들을 Cross-cutting concern이라고 부른다. AOP는 Cross-cutting concern를 어떻게 다룰 것인가에 대한 새로운 패러다임이라고 할 수 있다.

    AOP는 구조적 방법론에서 객체지향 방법론으로 전환처럼 시스템 개발에 관한 전체적인 변화는 아니다. Object-Oriented Programming이 Aspect-Oriented Programming으로 대체되는 일은 없을 것이다. AOP는 구조적 방법론에도 적용될 수 있고, 다른 방법론에도 다 적용될 수 있지만, 주로 객체지향방법론이 가지는 단점을 보완하는 것으로 묘사되고 있다. 그러면 객체지향 프로그래밍이 또는 다른 이전의 프로그래밍 기법들이 Cross-cutting Concern를 어떻게 다루는지 알아보자. 매우 간단하다. Primary Concern를 구현한 프로그램에 함께 포함시켰다. 그것이 단 한 줄의 메소드 호출이라 하더라도. 많은 프로그래머들은 거의 모든 프로그램에 산재된 로그하는 단 한 줄의 코드를 찾아서 바꾸어 본 경험이 있을 것이다. 또는 간단하게 생각하고 프로그램을 수정하려고 했는데, 도데체 어디를 수정해야 되는지 모르게 코드가 길고, 알 수 없는 코드들이 자리를 차지하고 있을 때의 난감함. Primary concern, Cross-cutting concern이 하나의 프로그램 안에 들어가게 되면, 프로그램을 이해하기가 힘들고, Cross-cutting concern 코드가 여기저기에 산재되어 수정하기 힘들게 된다. 당연히 생산성 떨어지고, 품질 떨어지고, 유지보수 비용 많이 들게 된다.

    그럼 AOP는 Cross-cutting concern를 어떻게 처리하는가? 이것도 매우 간단하다. 새로운 아이디어라고 할 수도 없다. Primary Concern 구현하는 코드 따로, Cross-cutting concern 구현하는 코드 따로 쓰고, 나중에 두 개 조합하게 완벽한 어플리케이션 만들겠다는 것이다. 기술 용어로 쓰면, Advice(Cross-cutting concern 구현한 코드)와 Primary concern 구현한 코드를 Point-cut 정보를 이용해서 Weaving(조합)하는 것이 AOP가 이 문제를 다루는 방법이다.

    사용자 삽입 이미지


    기술적 용어로서의 "Aspect"은 "Advice"와 "Point-cut"을 함께 지칭하는 단어이다. Point-cut은 어떤 Advice를 Code 어느 위치에 둘 것인가 하는 것이다. 예를 들면, 로그 기능을 구현한 Advice는 Code 속에 있는 모든 public 메소드가 수행되고 나면, 그 마지막에 실행되어라 라고 지정한 것이라 할 수 있다.

    이전까지의 객체지향 프로그래밍은 Cross-cutting concern을 정적으로 어플리케이션에 결합시킨 반면 AOP는 동적으로 Cross-cutting concern를 다룬다고 표현하기도 합니다. 용어에서도 알 수 있듯이 AOP는 소프트웨어 엔지니어링 원칙 중에 하나인 "Separation of concern"를 구현하려고 하고 있습니다. 이러한 문제들을 다루고 있는 분야 중에 하나는 디자인 패턴할 수 있고, 예를 들어, Visitor 패턴은 정적인 구조를 동적으로 바꾸려고 합니다. AOP가 현재까지 나온 방법들 중에서 Cross-cutting concern를 다루는 가장 좋은 방법인가 하는 질문엔 아직 답하긴 힘들 것 같습니다. 그럼에도 분명 언제가는 책상 위에 관련 서적 한 권 있어야 할 것 같은 분야가 될 것 같습니다.

    출처 : http://network.hanb.co.kr/view.php?bi_id=968