2009년 10월 9일 금요일

eclipse plug-in (Aptana for javascript)

  1. Go to Help -> Install New Software...
  2. In the "Work with:" text box, enter: http://update.aptana.com/install/studio 
  3. Click Add...
  4. Specify the Name of the Update site (i.e. Aptana Studio Plugin)
  5. Click OK
  6. Select the "Aptana Studio" checkbox
  7. Click the Next Button
  8. Confirm the items you are installing
  9. Click the Next Button once more
  10. Accept the terms and conditions
  11. Click the Finish Button
  12.  

2009년 9월 21일 월요일

==, .equals(), compareTo(), and compare()

Equality comparison: One way for primitives, Four ways for objects

Comparison Primitives Objects
a == b, a != b Equal values Compares references, not values. The use of == with object references is generally limited to the following:
  • Comparing to see if a reference is null.
  • Comparing two enum values. This works because there is only one object for each enum constant.
  • You want to know if two references are to the same object
a.equals(b) N/A Compares values for equality. Because this method is defined in the Object class, from which all other classes are derived, it's automatically defined for every class. However, it doesn't perform an intelligent comparison for most classes unless the class overrides it. It has been defined in a meaningful way for most Java core classes. If it's not defined for a (user) class, it behaves the same as ==.

It turns out that defining equals() isn't trivial; in fact it's moderately hard to get it right, especially in the case of subclasses. The best treatment of the issues is in Horstmann's Core Java Vol 1. [TODO: Add explanation and example]

a.compareTo(b) N/A Comparable interface. Compares values and returns an int which tells if the values compare less than, equal, or greater than. If your class objects have a natural order, implement the Comparable<T> interface and define this method. All Java classes that have a natural ordering implement this (String, Double, BigInteger, ...).
compare(a, b) N/A Comparator interface. Compares values of two objects. This is implemented as part of the Comparator<T> interface, and the typical use is to define one or more small utility classes that implement this, to pass to methods such as sort() or for use by sorting data structures such as TreeMap and TreeSet. You might want to create a Comparator object for the following.
  • Multiple comparisions. To provide several different ways to sort somthing. For example, you might want to sort a Person class by name, ID, age, height, ... You would define a Comparator for each of these to pass to the sort() method.
  • System class. To provide comparison methods for classes that you have no control over. For example, you could define a Comparator for Strings that compared them by length.
  • Strategy pattern. To implement a Strategey pattern, which is a situation where you want to represent an algorithm as an object that you can pass as a parameter, save in a data structure, etc.

If your class objects have one natural sorting order, you may not need this.

Comparing Object references with the == and != Operators

The two operators that can be used with object references are comparing for equality (==) and inequality (!=). These operators compare two values to see if they refer to the same object. Although this comparison is very fast, it is often not what you want.

Usually you want to know if the objects have the same value, and not whether two objects are a reference to the same object. For example,

if (name == "Mickey Mouse")   // Legal, but ALMOST SURELY WRONG

This is true only if name is a reference to the same object that "Mickey Mouse" refers to. This will be false if the String in name was read from input or computed (by putting strings together or taking the substring), even though name really does have exactly those characters in it.

Many classes (eg, String) define the equals() method to compare the values of objects.

Comparing Object values with the equals() Method

Use the equals() method to compare object values. The equals() method returns a boolean value. The previous example can be fixed by writing:

if (name.equals("Mickey Mouse"))  // Compares values, not refererences.

Because the equals() method makes a == test first, it can be fairly fast when the objects are identical. It only compares the values if the two references are not identical.

Other comparisons - Comparable<T> interface

The equals method and == and != operators test for equality/inequality, but do not provide a way to test for relative values. Some classes (eg, String and other classes with a natural ordering) implement the Comparable<T> interface, which defines a compareTo method. You will want to implement Comparable<T> in your class if you want to use it with Collections.sort() or Arrays.sort() methods.

Defining a Comparator object

As described in the table above on compare(), you can create Comparators to sort any arbitrary way for any class. For example, the String class defines the CASE_INSENSITIVE_ORDER comparator.

If you override equals, you should also override hashCode()

Overriding hashCode(). The hashCode() method of a class is used for hashing in library data structures such as HashSet and HashMap. If you override equals(), you should override hashCode() or your class will not work correctly in these (and some other) data structures.

Shouldn't .equals and .compareTo produce same result?

The general advice is that if a.equals(b) is true, then a.compareTo(b) == 0 should also be true. Curiously, BigDecimal violates this. Look at the Java API documentation for an explanation of the difference. This seems wrong, although their implementation has some plausibiliby.

Other comparison methods

String has the specialized equalsIgnoreCase() and compareToIgnoreCase(). String also supplies the constant String.CASE_INSENSITIVE_ORDER Comparator.

The === operator (Doesn't exist - yet?)

Comparing objects is somewhat awkward, so a === operator has been proposed. One proposal is that
a === b would be the same as ((a == b) || ((a != null) && a.equals(b)))

Common Errors

Using == instead of equals() with Objects
When you want to compare objects, you need to know whether you should use == to see if they are the same object, or equals() to see if they may be a different object, but have the same value. This kind of error can be very hard to find.
-----------------------------------------------------------------------------------------
primitive 타입
int, char, 이런것들이네... String은 오브젝트구나..여태 코딩하면서 그런것도 모르다니..멍충이 같다..ㅡㅡ;

2009년 9월 9일 수요일

iBATIS 매핑


드디어 iBatis의 매핑 구문을 한 번 알아보도록 하자...

iBatis의 매핑 구문에는 select, insert, update, delete, statement, procedure, sql, include가 있는데...

그 중에 select 구문을 알아보도록 하자..

사실 저거 하나만 알면 나머지 구문은 자연스럽게 알 수가 있다..

간단하게 게시판을 조회하는 쿼리로 알아보도록 하자..

 

 <select id="getBoardListByName1"
         parameterClass="java.util.HashMap"
         resultClass="java.util.HashMap">
  <![CDATA[
   select t.*
     from  (select b.*, (select count(*)
                           from board_comment c
                          where b.id = c.board) as 'comment'
              from board_master b
             where b.board = #board:VARCHAR#
          order by b.group desc, b.order) t
    limit #startpage:INTEGER#, #count:INTEGER#
  ]]>
 </select>

 

실제 사용하는 코드는 다음과 같다...

Map parameter = new HashMap();
parameter.put("board", "freeboard");
parameter.put("startpage", new Integer(0));
parameter.put("count", new Integer(10));
  
List list = client.queryForList("getBoardListByName1", parameter);

 

일단 <select>에는 id, parameterMap, parameterClass, resultMap, resultClass 등의 속성이 존재한다...

위의 예제는 그 중에 parameterClass와 resultClass를 사용하는 방법을 보여주고 있다...

즉... select문을 수행하는 getBoardListByName이란 명령이 있고..

매개변수로는 HashMap을 받고 결과값을 HashMap으로 돌려준다는 얘기이다..

queryForList 메소드를 사용하면 결과 HashMap이 List에 저장된 형태로 리턴될 것이고..

queryForMap 메소드를 사용하면 결과 HashMap이 HashMap에 key에 따라 저장된 형태로 리턴될 것이다..

 

그럼 전달받은 매개변수는 어떻게 사용하면 되는가...

인라인 매개변수 매핑과 외부 매개변수 매핑 방식이 있는데 위에서 볼 수 있듯이 #을 사용하는 것이 인라인 매핑이라고 한다...

: 뒤에는 전달받은 매개변수의 데이터 타입을 명시해주는 것인데 성능향상에 도움이 된다고 한다...

java.sql.Types에 있는 데이터 타입을 사용하면 된다...

또한 인라인 매핑을 위해 $를 사용할 수도 있는데 SQL 주입 공격에 취약하므로 사용시 주의한다...

 

그렇다면 리턴값은 어떻게 설정해 주는가 보자..

리턴값은 자동 결과 매핑 방식과 명시적인 결과 매핑 방법이 있는데 위의 방법은 자동 결과 매핑 방식이다...

한마디로 아무것도 안 해줘도 알아서 매핑해준다는 것이다..

물론 자동으로 하다 보니 성능이 조금 떨어지고 유지보수가 어렵다는 단점이 존재하기도 한다..

 

그렇다면 외부 매개변수 매핑과 명시적인 결과 매핑은 어떻게 할까..

아래 2개의 예제를 보면 간단하게 알 수 있다...


<sqlMap>

 <parameterMap id="BoardListByNameParameter" class="java.util.HashMap">
  <parameter property="board" jdbcType="VARCHAR"/>
  <parameter property="startpage" jdbcType="INTEGER"/>
  <parameter property="count" jdbcType="INTEGER"/>
 </parameterMap>
 
 <resultMap id="BoardListByNameResult" class="java.util.HashMap">
  <result property="id" column="id"/>
  <result property="board" column="board"/>
  <result property="title" column="title"/>
  <result property="writer" column="writer"/>
  <result property="content" column="content"/>
  <result property="open" column="open"/>
  <result property="group" column="group"/>
  <result property="order" column="order"/>
  <result property="depth" column="depth"/>
  <result property="saveday" column="saveday"/>
  <result property="saveid" column="saveid"/>
  <result property="modifyday" column="modifyday"/>
  <result property="modifyid" column="modifyid"/>
  <result property="comment" column="comment"/>
 </resultMap>

 <select id="getBoardListByName2"
         parameterMap="BoardListByNameParameter"
         resultClass="java.util.HashMap">
  <![CDATA[
   select t.*
     from  (select b.*, (select count(*)
                           from board_comment c
                          where b.id = c.board) as 'comment'
              from board_master b
             where b.board = ?
          order by b.group desc, b.order) t
    limit ?, ?
  ]]>
 </select>
 
 <select id="getBoardListByName3"
         parameterMap="BoardListByNameParameter"
         resultMap="BoardListByNameResult">
  <![CDATA[
   select t.*
     from  (select b.*, (select count(*)
                           from board_comment c
                          where b.id = c.board) as 'comment'
              from board_master b
             where b.board = ?
          order by b.group desc, b.order) t
    limit ?, ?
  ]]>
 </select>

 

parameterMap과 resultMap을 사용하는 것을 볼 수 있다..

둘 다 class에는 어떤 타입의 자료형을 사용할 것인지 지정하는 것이고..

id 다른 태그에서 사용하던 것과 같은 의미이다...

그리구 둘 다 property 속성(parameterProperty, resultProperty)을 갖는데..

parameterProperty같은 경우는 전달받은 변수의 프로퍼티명을 지정해주면 되고...

resultProperty에는 결과로 전달할 변수의 프로퍼티명과 연결할 컬럼명을 지정해주면 된다...

물론 명시적으로 jdbcType이나 javaType을 지정해주는 것도 좋은 방법이다..

그리고 sql문에는 PreparedStatement에서 사용하는 것과 비슷하게 매개변수 지정을 ?로 바꿔준다..

?에 들어가는 순서는 차례대로 들어가게 되므로 잘 맞춰서 parameterMap을 작성한다..

getBoardListByName2는 parameterMap만 사용하고 있고..

getBoardListByName3는 둘 다 사용하는 모습을 볼 수 있다...

 

iBatis 책에서는 명시적인 방법을 추천하고 있다...

성능상에도 이점이 있고 무엇보다 유지보수가 편하다는 점을 들고 있다..

그리고 계속 자바빈즈를 사용하고 있는데 난 아무래도 자바빈즈보단 HashMap이 편하다...

다음에는 동적 쿼리에 대해 알아보도록 하자...

사실 이 책을 산 이유 중의 하나가 동적쿼리 때문이었다는... -_-;;

iBATIS Setting


이번엔 iBatis를 이용해보자...

일단 iBatis를 사용하기 위한 준비를 해보자...

 

http://ibatis.apache.org/javadownloads.cgi

 

위 사이트에서 2.3버전을 다운받은 후 압축을 풀도록 한다...

압축을 푼 후 lib폴더에 보면 ibatis-2.3.0.677.jar 파일이 있는데..

라이브러리에 추가하도록 하자..

이전 버전에서는 파일이 여러개로 나뉘어져 있었는데..

2.3버전으로 오면서 하나로 통합되었다...

 

그리고 자신이 사용할 DB에 맞는 JDBC 라이브러리를 다운받도록 한다..

나같은 경우는 MySQL을 사용하므로 MySQL JDBC 커넥터를 받았다..

아래 사이트에서 다운 받도록 하자...

 

http://dev.mysql.com/downloads/connector/j/5.1.html

 

준비는 모두 끝났다.. 본격적으로 시작해보자..

일단 iBatis 설정 파일을 만들어 보자..

보통 SqlMapConfig.xml 로 만든다..

이 파일에는 JDBC 연결 정보 및 환경 설정 등을 할 수 있다...

 

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

 

 <properties resource="/org/cuvic/sql/db.properties"/>
 
 <settings useStatementNamespaces="false"
     cacheModelsEnabled="true"
     enhancementEnabled="true"
     lazyLoadingEnabled="true"
     maxRequests="512"
     maxSessions="128"
     maxTransactions="32"/>
 
 <transactionManager type="JDBC">
  <dataSource type="SIMPLE">
   <property name="JDBC.Driver" value="${driver}"/>
   <property name="JDBC.ConnectionURL" value="${url}"/>
   <property name="JDBC.Username" value="${user}"/>
   <property name="JDBC.Password" value="${pword}"/>
  </dataSource>
 </transactionManager>
 
 <sqlMap resource="/org/cuvic/sql/map/SqlMap.xml"/>
 
</sqlMapConfig>

 

<property> 태그는 properties 파일을 읽어오기 위한 태그이다...

properties 파일에 있는 속성을 ${프로퍼티이름} 이런 식으로 사용할 수 있다..

db.propertieis 파일은 아래와 같이 작성하면 된다...

 

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/cuvicdb
user=user
pword=pword

 

<transactionManager> 태그에 보면 ${driver} 이런 식으로 사용하는 것을 볼 수 있다..

 

<settings> 태그를 살펴보면 옵션이 많이 있는데...

useStatementNamespace는 이름공간을 사용할 수 있도록 하고 디폴트는 false이다..

보통의 이름 공간이 가지는 장점들을 사용할 수 있다...

cacheModelsEnabled는 캐싱을 사용할지 지정하는 옵션으로 디폴트는 true이다...

lazyLoadingEnabled는 적재지연 기술을 사용할지 지정하는 옵션이고 디폴트는 true이다...

적재지연은 데이터를 읽어올 때 실제 요청이 있을 경우에만 읽어오는 기술이다..

enhancementEnabled는 적재지연의 성능을 향상시킬 때 사용하고 디폴트는 true이다....

실행시간에 코드를 변경할 수 있는 CGLIB를 사용하므로 라이브러리가 없다면 자동 비활성화 된다...

기타 max 시리즈의 옵션들은 위에 나온 값이 디폴트 값이다..

위 옵션들은 작성을 권장하지 않으므로 그대로 사용하는 것이 좋을 듯 하다..

만약 수정한다면 값이 maxRequest > maxSessions > maxTransactions 이렇게 되어야 한다...

 

이제 제일 중요한 <transactionManager> 태그를 살펴보자...

type에는 JDBC, JTA, EXTERNAL 3가지 종류가 있다..

 

iBatis에서는 트랜잭션을 자동, 로컬, 글로벌, 사용자 정의 4가지로 구분하게 된다..

자동 트랜잭션은 아래에 나오는 쿼리 실행 메소드를 호출하는 것으로 지원된다...

즉.. 하나의 쿼리 실행 메소드는 하나의 트랜잭션이라는 것이다..

그리고 로컬 트랜잭션은 자동 트랜잭션과 다를게 없다..

단지 자동이 아니고 개발자가 직접 제어할 수 있다는 것이 자동과 로컬의 차이일 뿐이다...

또한 분산 DB가 아닌 하나의 DB에만 적용할 수 있다...

여기까지가 바로 JDBC type을 사용할 경우 사용할 수 있는 트랜잭션이다...

 

글로벌 트랜잭션... 즉 분산 DB를 적용하고 싶은 경우에 JTA와 EXTERNAL type을 사용한다..

능동 혹은 수동으로 글로벌 트랜잭션을 사용할 수 있는데...

iBatis가 직접 트랜잭션을 관리하고자 한다면 JTA를...

외부에 트랜잭션을 맡기고자 한다면 EXTERNAL을 사용한다..

 

<dataSource> 태그에 실제 DB에 접속할 정보를 설정해준다...

type에는 역시나 SIMPLE, DBCP, JNDI 3가지 종류가 있다..

보통 SIMPLE을 사용해서 DB 접속 정보를 직접 넣어주면 된다...

혹시나 Jakarta Commons Database Connection Pool을 사용하고자 한다면 DBCP를 쓰면 되고...

JNDI 데이터 소스를 사용하고자 한다면 JNDI를 사용하면 된다...

 

아무튼 이것으로 iBatis 설정 파일은 끝이다...

실제 SQL을 모아놓은 파일은 <sqlMap> 태그 경로에 있는 SqlMap.xml 파일이다...

물론 하나만 할 수도 있고 여러 개를 만들 수도 있다...

SqlMap.xml은 다음에 알아보도록 하고...

이제 iBatis를 어떻게 사용하는지 알아보도록 하자...

 

import com.ibatis.sqlmap.client.*;
import com.ibatis.common.resources.*;

 

public class Test
{

     public static void main(String args[]) throws Exception

     {

          Reader reader = Resources.getResourceAsReader("/org/cuvic/sql/SqlMapConfig.xml");
          SqlMapClient client = SqlMapClientBuilder.buildSqlMapClient(reader);

 

          // 아래와 같은 메소드들을 사용할 수 있다.

          client.queryForObject(id, parameter);

          client.queryForList(id, parameter);

          client.queryForMap(id, parameter, key);

 

          client.insert(id, parameter);

          client.update(id, parameter);

          client.delete(id, parameter);

     }

}

 

일단 앞서 설정한 SqlMapConfig.xml 파일을 읽어온다...

원래는 WEB-INF에 넣어놓으면 경로를 안 써도 된다고 하는데 난 안되서 저렇게 해주었다...

그 후에 SqlMapClient 객체를 생성해주면 된다...

이 객체가 바로 모든 SQL 명령을 내릴 수 있는 객체이다...

일단 기본적으로 select문 insert문, update문, delete문을 위한 메소드가 있다..

 

참고로 iBatis는 SQL 결과를 자바빈즈로 만드는 것을 추천하고 있다..

근데 난 자바빈즈 만드는게 귀찮아서 HashMap을 주로 이용한다..

물론 각각 장단점이 있지만 난 저게 편하다...

 

아무튼 먼저 select문에 사용되는 메소드는 다음과 같이 3가지가 있다...

 

Object queryForObject(String id, Object parameter) 메소드

List queryForList(String id, Object parameter) 메소드

Map queryForMap(String id, Object parameter, String key) 메소드

 

물론 메소드가 오버로드 되서 2개씩 존재하지만 내가 사용해 본 것만 적어놨다...

 

일단 queryForObject는 레코드가 1개만 존재할 때 사용할 수 있다...

1개 이상 리턴될 경우 에러가 발생한다...

그리고 리턴되는 객체에 디폴트 생성자가 없어도 에러가 발생하므로 주의하자...

id에는 SqlMap에 정의된 SQL id를 넘겨주고 파라미터가 필요하면 parameter에 넘겨주면 된다..

파라미터가 1개 밖에 존재하지 않는데 파라미터가 2개 이상이면 어쩌나 고민할지도 모르겠다..

2개 이상인 경우는 자바빈즈를 만들어서 전달하던가 HashMap 등을 만들어서 넘기면 된다...

이것은 insert나 update나 delete나 전부 해당하는 내용이다...

나 같은 경우는 당연히 HashMap으로 만들어서 전달하는 방식을 사용한다...

 

queryForList는 1개 이상의 레코드를 가져올 때 사용한다...

위의 예에서 볼 수 있듯이 결과값이 List 형태로 넘어오게 된다..

만약 자바빈즈를 결과 값으로 갖도록 SqlMap을 만들었다면...

List에 자바빈즈가 들어간 상태로 리턴된다...

 

queryForMap도 1개 이상의 레코드를 가져올 때 사용하는데..

Map의 특성상 key를 지정해주는 매개변수가 하나 더 추가되어 있다...

해당 key값에 결과를 저장해서 리턴해주게 된다..

 

이번에는 나머지 메소드들을 알아보도록 하자...

 

Object insert(String id, Object parameter)

int update(String id, Object parameter)

int delete(String id, Object paramter)

 

3개 다 사용법은 queryForObject와 같은 방식으로 사용하면 된다...

여기서 주목할 것은 insert의 반환값이 Object라는 사실이다...

<selectKey> 태그를 사용해 반환값을 지정해 줄 수 있는데...

이를 이용해서 자동 생성 키(오라클의 경우 sequence)를 사용할 수 있다고 한다..

update나 delete 같은 경우는 반환값이 int라서 수정되거나 삭제된 개수만 리턴하게 된다...

 

여기까지 기본적으로 iBatis를 어떻게 사용하는지까지 알아보았다..

아직 SqlMap에 대한 파악이 끝나지 않은 관계로 여기까지만 정리하도록 하자...

다시 한 번 말하지만 iBatis는 자바빈즈를 추천하고 있지만...

개인적으로 HashMap이 편해서 앞으로 전부다 HashMap을 이용할 계획이다..

[출처] iBatis 사용하기...|작성자 따굴찬

JAVA Beans, DTO, DAO, Manager


# 1. Beans(강남콩)의 이해
      - JSP Page 상에 나열되는 자바 처리 로직은 디자인 코드와 함께 매우 복잡한 코드를 구성합니다.
        이로 인해 디자인 변경시 자바코드가 영향을 받아 오류가 자주 발생되며, 코드 수정시 코드를 알아볼 수
        없어 유지보수가 매우 힘이 듭니다.
        이러한 반복되는 자바 코드들을 JAVA 파일안에 저장하여 사용하는 형태를 빈즈라고 합니다.
      - 확장자는 *.java입니다. 컴파일하여 .class 형태로 배포합니다.
      - 메모리에 생성된 Beans(DTO)는 다른 자바 클래스(DAO, Business Logic)에 의하여 사용됩니다.
      - Beans는 dll과 같은 원리를 가지고 있습니다.
      - Beans는 sun에서 제시한 작성 규칙이 존재합니다.

# 2. DTO(Data Transfer Object : 데이터 전송 객체, value Object) Beans
      - 폼에서 입력된 데이터들은 하나의 DTO 객체로 변환 될 수 있습니다.
      - 하나의 데이터베이스 레코드를 저장하며 레코드와 같은 구조를 가지고 있습니다.
      - 하나의 레코드는 Beans 클래스 객체 하나로 매핑됩니다.

   1) 필드 선언 (멤버 변수, 인스턴트 변수)
       - 하나의 컬럼 값을 저장
       - 보안성 및 캡슐화, 데이터 은닉의 목적으로 private를 선언합니다.
       ex) private String name;

   2) setter
       - 필드에 값을 저장하는 기능을 합니다.
       - HTML 폼의 INPUT 태그의 값을 빈에 저장하는 역할을 합니다.
       - 메소드 명은 set + input 태그의 이름 중 첫자를 반드시 대문자로 사용하는 규칙을 적용해 태그명을 지정
          합니다. 따랏 HTML에서 input 태그의 이름은 영문 소문자로 사용하여 태그의 이름에 신중을 기해야
          합니다,

          예1)
          <input type="text" name="id" size="15" value='user1'>
          <input type="text" name="addr" size="15" value='user1'>

           public void setId(String id) {
               this.id = id;
           }

           public void setAddr(String addr) {
               this.addr = addr;
           }

   3) getter
       - 인스턴스 변수의 값을 가져오는 기능을 합니다.

          예1) public String getName() {
                    return name;
                 }

# 3. DAO(Data Access Object : 데이터 접근 객체t) Beans
      - DTO 객체를 만들어 편집 및 조작을 합니다.
      - DTO를 데이터베이스 서버에 저장하기도 하고, 데이터 베이스 서버로부터 레코드를 Select해 DTO 객체로
         변경해 가져오기도 합니다.
      - Insert, delete, upadate, select 등 데이터 처리를 주 목적으로 ㅎ바니다.

# 4. Manager Class(관리 클래스, MGR)
      - DTO와 DAO 사이에서 연결 및 처리 역할을 합니다.
      DreamWeaver                      Eclipse            DbEdit
      (*.html, *.jsp)                       (*.java)          (*.sql)              
      ----------- ------------------------  -------------     
      JSP <-----> Manager Class <-----> DAO <-----> Oracle
       ↑                                ↑
       │                                │
       └--------------------------------┘   
                       DTO 
             계층간 데이터 전송 객체

# 5. Beans의 사용 Scope(범위)
      - page : 기본값, page를 벗어나면 자동으로 소멸합니다.(중요)
     
      - request : forward, include에서 사용 가능, 약간 사용됩니다..

      - Session : 사용자가 로그인해 있는 동안 계속적으로 살아 있습니다.
                       메모리 소모가 심함으로 필요한 곳에 적절히 사용해야 됩니다.
                       쇼핑카드 구현등 객체를 지속적으로 유지해야하는 경우에 사용됩니다.
                       사용자가 브라우저를 닫으면 고나련 JSP session Bean은 소멸됩니다.(중요)

      - Application : 웹 사이트 전체, 모든 사용자에게 영향을 미치는 빈
                           메모리 소모가 심함으로 많이 사용하지 않고, 서버가 운영되는 동안 객체가 살아있습니다.
                           모든 사용자가 변수와 객체를 공유하게 됩니다.
                           서버를 재 시작해야 변수들이 재 설정됩니다.

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.종료
-->

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