[Java] 직렬화


직렬화


컴퓨터에 저장했다가 다음에 다시 꺼내 쓸 수는 없을지 또는 네트웍을 통해 컴퓨터 간에 서로 객체를 주고 받을 수는 없을까라고 고민해 본 적이 있는가? 과연 이러한 일들이 가능할까?

가능하다. 이러한 것을 직렬화가 처리해준다.


1. 직렬화란


직렬화(스트림으로)란 객체를 데이터 스트림으로 만드는 것을 뜻한다. 즉, 객체에 저장된 데이터를 스트림에 쓰기 위해 연속적인 데이터로 변환하는 것을 말한다.

반대로 스트림으로부터 데이터를 읽어서 객체를 만드는 것을 역직렬화(객체로)라고 한다.

객체 스트림은 프로그램 메모리상에 존재하는 객체를 직접 입출력해 줄 수 있는 스트림으로 현재 상태를 보존하기 위한 영속성을 지원할 수 있다.

자바에서 객체 안에 저장되어 있는 내용을 파일로 저장하거나 네트워크를 통하여 다른 곳으로 전송하려면 객체를 바이트 형태로 일일이 분해해야 한다. 

이를 위하여 객체를 직접 입출력 할 수 있도록 해주는 객체 스트림이다.


직렬화(Serialization)

 - 객체를 데이터스트림(스트림에 쓰기(write)위한 연속적인(serial) 데이터)으로 만드는 것.

 - 예) 객체를 컴퓨터에 저장했다가 꺼내 쓰기. 네트워크를 통한 컴퓨터 간의 객체 전송.  


역직렬화(Deserialization)

 - 스트림으로부터 데이터를 읽어서 객체를 만드는 것. 



사실 객체를 저장하거나 전송하려면 당연히 직렬화를 거칠수 밖에 없다.

객체를 저장한다는 것이 무엇을 의미하는지 상기시켜야 한다.

객체는 클래스에 정의된 인스턴스변수의 집합이다. 객체에는 클래스변수나 메서드가 포함되지 않는다. 객체는 오직 인스턴스변수들로만 구성되어 있다.

인스턴스변수는 인스턴스마다 다른 값을 가질 수 있어야하기 때문에 별도의 메모리공간이 필요하지만 메서드는 변하는 것이 아니라서 메모리를 낭비해 가면서 인스턴스마다 같은 내용의 코드를 포함시킬 이유가 없다.



위의 그림은 6장에 나오는 Tv클래스의 객체가 생성되었을 때 사용한 그림인데, 왼쪽 그림은 이해를 돕기 위해 인스턴스에 메서드를 포함시켜서 그렸지만, 실제로는 오른쪽 그림과 같이 인스턴스에 메서드가 포함되지 않는것이 더 정확한 그림이다. 그래서 객체를 저장한다는 것은 바로 객체의 모든 인스턴스변수의 값을 저장한다는 것과 같은 의미이다. 어떤 객체를 저장하고자 한다면, 현재 객체의 모든 인스턴스변수의 값을 저장하기만 하면된다. 그리고 저장했던 객체를 다시 생성하려면, 객체를 생성한 후에 저장했던 값을 읽어서 생성한 객체의 인스턴스변수에 저장하면 되는 것이다.

클래스에 정의된 인스턴스변수가 단순히 기본형일 때는 인스턴스변수의 값을 저장하는 일이 간단하지만. 인스턴스변수의 타입이 참조형일 때는 그리 간단하지 않다. 예를 들어 인스턴스변수의 타입이 배열이라면 배열에 저장된 값들도 모두 저장되어야 할 것이다. 그러나 우리는 객체를 어떻게 직렬화해야 하는지 전혀 고민하지 않아도 된다. 다만 객체를 직렬화/역직렬화할 수 있는 ObjectInputStream과 ObjectOutputStream을 사용하는 방법만 알면 된다.

--> 두 객체가 동일한지 판단하는 기준이 두 객체의 인스턴스변수 값들이 같고 다름이라는 것을 기억하자.


2. ObjectInputStream(직렬화) / ObjectOutputStream(역직렬화)

직렬화(스트림에 객체를 출력)에는 ObjectInputStream을 사용하고 역직렬화(스트림으로부터 객체를 입력)에는 ObjectOutputStream을 사용한다.

ObjectInputStream과 ObjectOutputStream은 각각 InputStream / OutputStream을 직접 상속받지만 기반스트림을 필요로하는 보조스트림이다. 그래서 객체를 생성할 때 입출력(직렬화/역직렬화)할 스트림을 지정해주어야 한다.


만일 파일에 객체를 저장(직렬화)하고 싶다면 다음과 같이 해야 한다.


※ UserInfo 객체를 직렬화하여 저장

FileOutputStream fos = new FileOutputStream("objectfile.ser");

ObjectOutputStream out = new ObjectOutputStream(fos);

out.writeObject(new UserInfo());

--> objectfile.ser이라는 파일에 UserInfo객체를 직렬화하여 저장한다. 출력할 스트림(FileOutputStream)을 생성해서 이를 기반 스트림으로 하는 ObjectOutputStream을 생성한다.

ObjectOutputStream의 writeObject(Object obj)를 사용해서 객체를 출력하면, 객체가 파일에 직렬화되어 저장된다. 


※ UserInfo 객체를 역직렬화

FileInputStream fis = new FileInputStream("objectfile.ser");

ObjectInputStream in = new ObjectInputStream(fis);

UserInfo Info = (UserInfo)in.readObject();

--> 직렬화할 때와는 달리 입력스트림을 사용하고 writeObject(Object obj)대신 readObject()를 사용하여 저장된 데이터를 읽기만 하면 객체로 역직렬화된다.

다만, readObject()의 반환타입이 Object이기 때문에 객체  원래의 타입으로 형변환 해주어야 한다.


(1) 객체 전송의 단계

객체를 분해하여 전송하기 위해서는 직렬화(Serialization) 되어야 한다.

객체를 전송하기 위해서는 3가지 단계를 거친다.

1) 직렬화된 객체를 바이트 단위로 분해한다. (marshalling)

2) 직렬화 되어 분해된 데이터를 순서에 따라 전송한다.

3) 전송 받은 데이터를 원래대로 복구한다. (unmarshalling)


(2) 마샬링 (marshalling)

마샬링(marshalling)은 데이터를 바이트의 덩어리로 만들어 스트림에 보낼 수 있는 형태로 바꾸는 변환 작업을 뜻한다.

자바에서 마샬링을 적용할 수 있는 데이터는 원시 자료형(boolean, char, byte, short, int, long, float, double)와 객체 중에서 Serializable 인터페이스를 구현한 클래스로 만들어진 객체이다. 객체는 원시 자료형과 달리 일정한 크기를 가지지 않고 객체 내부의 멤버 변수가 다르기 때문에 크기가 천차만별로 달라진다. 이런 문제점을 처리할 수 있는게 ObjectOutputStream 클래스이다.


(3) 직렬화 (Serializable)

마샬링으로 바이트로 분해된 객체는 스트림을 통해서 나갈 수 있는 준비가 되었다. 앞에서 언급한대로 객체를 마샬링하기 위해서는 Serializable 인터페이스를 구현한 클래스로 만들어진 객체에 한해서만 마샬링이 진행될 수 있다. Serializable 인터페이스는 아무런 메소드가 없고 단순히 자바 버추얼 머신에게 정보를 전달하는 의미만을 가진다.


* 직렬화가 가능한 객체의 조건

1) 기본형 타입(boolean, char, byte, short, int, long, float, double)은 직렬화가 가능

2) Serializable 인터페이스를 구현한 객체여야 한다. (Vector 클래스는 Serializable 인터페이스구현)

3) 해당 객체의 멤버들 중에 Serializable 인터페이스가 구현되지 않은게 존재하면 안된다.

4) transient 가 사용된 멤버는 전송되지 않는다. (보안 변수 : null 전송)


(4) 언마샬링 (unmarshalling)

언마샬링은 객체 스트림을 통해서 전달된 바이트 덩어리를 원래의 객체로 복구하는 작업이다. 이 작업을 제대로 수행하기 위해서는 반드시 어떤 객체 형태로 복구할지 형 변환을 정확하게 해주어야 한다.

Vector v = (Vector)ois.readObject(); 

// OutputInputStream의 객체를 읽어서 Vector 형으로 형변환 한다.

이때 ObjectInputStream을 사용하여 데이터를 복구한다.



import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;


public class ObjectStream {

public static void main(String[] args){

// ObjectOutputStream 을 이용한 객체 파일 저장

FileOutputStream fos = null;

ObjectOutputStream oos = null;

// UserClass 에 이름과 나이를 입력하여 객체를 3개 생성한다.

UserClass us1 = new UserClass("하이언", 30);

UserClass us2 = new UserClass("스티브", 33);

UserClass us3 = new UserClass("제이슨", 27);

try{

// object.dat 파일의 객체 아웃풋스트림을 생성한다.

fos = new FileOutputStream("object.dat");

oos = new ObjectOutputStream(fos);

// 해당 파일에 3개의 객체를 순차적으로 쓴다

oos.writeObject(us1);

oos.writeObject(us2);

oos.writeObject(us3);

// object.dat 파일에 3개의 객체 쓰기 완료.

System.out.println("객체를 저장했습니다.");

}catch(Exception e){

e.printStackTrace();

}finally{

// 스트림을 닫아준다.

if(fos != null) try{fos.close();}catch(IOException e){}

if(oos != null) try{oos.close();}catch(IOException e){}

}

// 파일로 부터 객체 데이터 읽어온다.

FileInputStream fis = null;

ObjectInputStream ois = null;

try{

// object.dat 파일로 부터 객체를 읽어오는 스트림을 생성한다.

fis = new FileInputStream("object.dat");

ois = new ObjectInputStream(fis);

// ObjectInputStream으로 부터 객체 하나씩 읽어서 출력한다.

// (UserClass) 로 형변환을 작성해야 한다.

// System.out.println 으로 객체의 구현된 toString() 함수를 호출한다.

System.out.println( (UserClass)ois.readObject());

System.out.println( (UserClass)ois.readObject());

System.out.println( (UserClass)ois.readObject());



}catch(Exception e){

e.printStackTrace();

}finally{

// 스트림을 닫아준다.

if(fis != null) try{fis.close();}catch(IOException e){}

if(ois != null) try{ois.close();}catch(IOException e){}

}

}


}

실행결과)

- UserClass 객체를 생성하여 ObjectOutputStream을 통해 object.dat 에 순차적으로 객체를 쓴다.

- UserClass 객체를 Stream에 쓰기위해서는 Serializable 인터페이스를 사용해야 직렬화 할 수 있다.

- Serializable 구현하지 않으면 NotSerializableException이 발생한다.

- ObjectInputStream을 통해 object.dat에 저장되어 있는 객체를 읽어온다.

- ObjectInputStream에서 readObject()로 읽을때는 정확한 형변환을 해주어야 정확하게 언마샬링을 할 수 있다.


3. Serializable 과 transient


(1) Serializable

데이터를 파일에 쓰거나, 네트워크를 타고 다른 곳에 전송할 때는 데이터를 바이트 단위로 분해하여 순차적으로 보내야 한다. 이것을 직렬화(Serialization)라고 한다.

기본 자료형(boolean, char, byte, short, int ,long, float, double)은 정해진 바이트의 변수이기 때문에 바이트 단위로 분해하여 전송한 후 다시 조립하는데 문제가 없다.

하지만 객체의 크기는 가변적이며, 객체를 구성하는 자료형들의 종류와 수에 따라 객체의 크기는 다양하게 바뀔 수 있다. 이런 객체를 직렬화 하기 위해서 Serializable 인터페이스를 구현하게 된다.


* 직렬화가 가능한 객체의 조건

① 기본형 타입(boolean, char, byte, short, int, long, float, double)은 직렬화가 가능

② Serializable 인터페이스를 구현한 객체여야 한다. (Vector 클래스는 Serializable 인터페이스구현)

③ 해당 객체의 멤버들 중에 Serializable 인터페이스가 구현되지 않은게 존재하면 안된다.

④ transient 가 사용된 멤버는 전송되지 않는다. (보안 변수 : null 전송)

객체 직렬화는 객체에 implements Serializable 만 선언해 주면 된다.


(2) transient

하지만, 객체의 데이터 중 일부의 데이터는(패스워드와 같은 보안) 여러가지 이유로 전송을 하고 싶지 않을 수 있다. 이러한 변수는 직렬화에서 제외해야 되며, 이를 위해서 변수에 transient를 선언한다.

또한, 직렬화 조건 중 객체의 멤버들 중에 Serializable 인터페이스 구현되지 않은 객체가 있으면, 직렬화 할 수 없다.(NonSerializableException) 직렬화 해야 되는 객체 안의 객체 중 Serializable 인터페이스가 구현되지 않으면서 전송하지 않아도 되는 객체 앞에는 transient 를 선언해준다. 그러면 직렬화 대상에서 제외되므로 해당 객체는 직렬화가 가능해진다.


public class UserInfo implements Serializable{

    String name;

    String password;

    int age;

     

    Object ob = new Object();   

    // 모든 클래스의 최고조상인 Object는 Serializable을

    // 구현하지 않았기 때문에 직렬화가 불가능하다.

     

    Object obj = new String("abc"); // String은 직렬화될 수 있다.

     

    // 직렬화 제외

    transient String weight;    

    transient Object obe = new Object();

}


cp.) 직렬화하고자 하는 객체의 클래스에 직렬화가 안 되는 객체에 대한 참조를 포함하고 있다면, 제어자 transient를 붙여서 직렬화 대상에서 제외되도록 할 수 있다.

또는 password와 같이 보안상 직렬화되면 안되는 값에 대해서 transient를 사용할 수 있다.

다르게 표현하면 transien가 붙은 인스턴스변수의 값은 그 타입의 기본값으로 직렬화된다고 볼 수 있다.

즉, UserInfo객체를 역직렬화하면 참조변수인 obj와 password의 값은 null이 된다.


※ Serializable 과 transient 사용 예제

UserClass.java

import java.io.Serializable;


// 직렬화 한다.

public class UserClass implements Serializable{

private static final long serialVersionUID = 4220461820168818967L;

String name;

// age 비 전송

transient int age;

// NonSerializable 클래스

NonSerializableClass nonSerializable;

public UserClass() {

}

public UserClass(String name, int age){

this.name = name;

this.age = age;

this.nonSerializable = new NonSerializableClass(false);

}


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;

}


public NonSerializableClass getNonSerializable() {

return nonSerializable;

}


public void setNonSerializable(NonSerializableClass nonSerializable) {

this.nonSerializable = nonSerializable;

}


@Override

public String toString() {

return "UserClass [name=" + name + ", age=" + age

+ ", nonSerializable=" + nonSerializable + "]";

}

}


public class NonSerializableClass {

boolean serializable;

public NonSerializableClass(){

this.serializable = false;

}

public NonSerializableClass(boolean serializable){

this.serializable = serializable;

}

}


import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;


public class ObjectStream {

public static void main(String[] args){

// ObjectOutputStream 을 이용한 객체 파일 저장

FileOutputStream fos = null;

ObjectOutputStream oos = null;

// UserClass 에 이름과 나이를 입력하여 객체를 3개 생성한다.

UserClass us1 = new UserClass("하이언", 30);

UserClass us2 = new UserClass("스티브", 33);

UserClass us3 = new UserClass("제이슨", 27);

try{

// object.dat 파일의 객체 아웃풋스트림을 생성한다.

fos = new FileOutputStream("object.dat");

oos = new ObjectOutputStream(fos);

// 해당 파일에 3개의 객체를 순차적으로 쓴다

oos.writeObject(us1);

oos.writeObject(us2);

oos.writeObject(us3);

// object.dat 파일에 3개의 객체 쓰기 완료.

System.out.println("객체를 저장했습니다.");

}catch(Exception e){

e.printStackTrace();

}finally{

// 스트림을 닫아준다.

if(fos != null) try{fos.close();}catch(IOException e){}

if(oos != null) try{oos.close();}catch(IOException e){}

}

// 파일로 부터 객체 데이터 읽어온다.

FileInputStream fis = null;

ObjectInputStream ois = null;

try{

// object.dat 파일로 부터 객체를 읽어오는 스트림을 생성한다.

fis = new FileInputStream("object.dat");

ois = new ObjectInputStream(fis);

// ObjectInputStream으로 부터 객체 하나씩 읽어서 출력한다.

// (UserClass) 로 형변환을 작성해야 한다.

// System.out.println 으로 객체의 구현된 toString() 함수를 호출한다.

System.out.println( (UserClass)ois.readObject());

System.out.println( (UserClass)ois.readObject());

System.out.println( (UserClass)ois.readObject());



}catch(Exception e){

e.printStackTrace();

}finally{

// 스트림을 닫아준다.

if(fis != null) try{fis.close();}catch(IOException e){}

if(ois != null) try{ois.close();}catch(IOException e){}

}

}

}


UserClass.java 의 변수를 보면 transient int age; 로 age 변수는 직렬화에서 제외했다.

- NonSerializableClass 객체는 Serializable 인터페이스를 구현하지 않은 클래스이다.

- 따라서 UserClass.java 로 직렬화를 시도하면, 위와 같이 NonSerializableClass Exception이 발생한다.

- 위의 문제를 해결하기 위해서는 NonSerializableClass.java 에 Serializable 인터페이스를 구현하여 직렬화를 할 수 있게 하는 방법과

- NonSerializableClass 를 전송하지 않아도 되면, 또는 않아야 한다면 transient 를 앞에 붙여주는 것이다.

- 그러면 NonSerializableClass 객체는 직렬화 대상에서 제외되면서 UserClass 가 정상적으로 직렬화되어 처리될 것이다.


※ NonSerializableClass 객체 선언 앞에 transient 선언 결과

import java.io.Serializable;


// 직렬화 한다.

public class UserClass implements Serializable{

private static final long serialVersionUID = 4220461820168818967L;

String name;

// age 비 전송

transient int age;

// NonSerializable 클래스

transient NonSerializableClass nonSerializable;

public UserClass() {

}

public UserClass(String name, int age){

this.name = name;

this.age = age;

this.nonSerializable = new NonSerializableClass(false);

}


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;

}

public NonSerializableClass getNonSerializable() {

return nonSerializable;

}


public void setNonSerializable(NonSerializableClass nonSerializable) {

this.nonSerializable = nonSerializable;

}


@Override

public String toString() {

return "UserClass [name=" + name + ", age=" + age

+ ", nonSerializable=" + nonSerializable + "]";

}

}


- 객체가 정상적으로 직렬화되어 전송되고, 가져와 출력되는 것을 볼 수 있다.

- 당연히 transient가 붙은 age 변수와 nonSerializable 은 직렬화 되지 않기에 데이터가 없다.


4. 직렬화가능한 클래스의 버전관리
직렬화된 객체를 역직렬화할 때 서로 같은 클래스를 사용해야 하는데, 클래스의 이름이 같더라도 클래스의 내용이 변경된 경우 역직렬화가 실패하며 에러가 발생한다.
static 변수나 상수 또는 trasient가 붙은 인스턴스변수의 경우 직렬화에 영향을 미치지 않는다.

 java.io.InvalidClassException: UserInfo; local class incompatible: stream classdesc
 serialVersionUID = 6953673583338942489, local class serialVersion UID = -6256164443556992367 ...
 
해결책) serialVersionUID를 정의한다.
class MyData implements java.io.Serializable{
    static final long serialVersionUID = 3518731767529258119L;
    // 이렇게 추가해주면 클래스의 내용이 바뀌어도 클래스의 버전이 고정된다.
     
    int value;
}
 
serialVersionUID 얻기 (아무 정수를 써도 상관없지만 중복될 가능성때문에 사용하는 편이 좋다.)

cp.) erialVersionUID 이란? Warning 해결하기


객체를 파일에 쓰거나 전송하기 위해서는 직렬화를 해야 하는데 그러기 위해 객체 클래스에 Serializable 인터페이스를 implements 하게 된다.
하지만 Serializable 인터페이스를 implements 하게 되면 노란색 Warning이 발생한다.
The serializable class *** does not declare a static final serialVersionUID field of type long
저렇게 Warning이 발생하지만 동작하는데는 문제가 없다.
그래도 계속 저렇게 Warning이 떠있는데 왜 생기는 것이며 serialVersionUID 는 무엇이길래 없다고 그러는 건가?
serialVersionUID 는 직렬화에 사용되는 고유 아이디인데, 선언하지 않으면 JVM에서 디폴트로 자동 생성된다.
따라서 선언하지 않아도 동작하는데 문제는 없지만, 불안하기 때문에 JAVA에서는 명시적으로 serialVersionUID를 선언할 것을 적극 권장하고 있다.

* JVM에 의한 디폴트 serialVersionUID 계산은 클래스의 세부 사항을 매우 민감하게 반영하기 때문에 컴파일러 구현체에 따라서 달라질 수 있어 deserialization 과정에서 예상하지 못한 InvalidClassException을 유발할 수 있다.

serialVersionUID는 private static final 로 선언하면 된다.

그럼 serialVersionUID는 어떻게 생성하면 될까?
이클립스에서는 serialVersionUID를 자동으로 선언해주는 플러그인 있다.
위의 파일을 다운받고 압축을 풀어서 eclipse\plugin 폴더에 넣어 놓고 이클립스를 재시작 한다.

http://hyeonstorage.tistory.com/attachment/cfile26.uf@25748E385325AEA31EC4FD.zip


serialVersionUID 를 생성하고자 하는 (Serializable을 implements 한) 클래스에 마우스 오른쪽 버튼을 누르면 
아래 그림과 같이 Add SerialVersionUID 가 있다.


Add SerialVersionUID를 클릭하면 serialVersionUID가 생성된다.

앞에 private를 붙여서 private static final long 형태가 되도록 하자.


이제 노란 Warning이 없어지는 것을 볼 수 있다.
Warning을 없애는 방법은 SerialVersionUID 선언 외에 다른 방법이 있다.
클래스 위에 @SuppressWarnings("serial") 이라고 어노테이션 처리를 해주면 없어진다.
하지만 SerialVersionUID를 선언해주는 것이 권장되는 방법이다.


'Programing > Java' 카테고리의 다른 글

[Java] 쓰레드의 우선순위  (0) 2014.12.21
[Java] 쓰레드 기본  (0) 2014.12.21
[Java] File 클래스  (0) 2014.12.16
[Java] 문자 기반 스트림  (0) 2014.12.16
[Java] 바이트 기반의 스트림  (0) 2014.12.16

[Spring] 자바 코드 기반 설정


자바 코드 기반 설정


Spring JavaConfig 프로젝트는 XML이 아닌 자바 코드를 이용해서 컨테이너를 설정할 수 있는 기능을 제공하는 프로젝트로서, 이를 사용하면 XML이 아닌 자바 코드를 이용해서 생성할 빈 객체와

각 빈 간의 연관 등을 처리하게 된다.


1. @Configuration 어노테이션과 @Bean 어노테이션을 이용한 코드 기반 설정

org.springframework.context.annoatation 패키지의 @Configuration 어노테이션과 @Bean 어노테이션을 이용해서 스프링 컨테이너에 새로운 빈 객체를 제공할 수 있다. 


다음은 자바 코드를 이용해서 새로운 빈 객체를 스프링 컨테이너에 제공하는 클래스의 예를 보여주고 있다.

import org.springframework.context.annoatation.Bean;

import org.springframework.context.annoatation.Configuration;


@Configuration

public class SpringConfig{

}

@Bean

public AlarmDevice alarmDevice(){// alarmDevice()를 빈의 식별값으로 사용

return new SmsAlarmDevice();

}

}


@Bean 어노테이션은 새로운 빈 객체를 제공할 때 사용되며, @Bean이 적용된 메서드의 이름을 빈의 식별값으로 사용한다. 따라서 위 코드는 다음의 스프링 XML 설정과 동일한 빈을 정의한다.

<bean id="alarmDevice" class="mad.spring.ch4.homecontrol.SmsAlarmDevice"/>


메서드의 이름이 아닌 다른 이름을 빈 객체의 이름으로 사용하고 싶다면 @Bean 어노테이션의 name 속성을 사용하면 된다.

@Bean(name="smsAlarmDevice")//alarmDevice이 아닌 smsAlarmDevice을 사용하고 싶은 경우

public AlarmDevice alarmDevice(){

return new SmsAlarmDevice();

}


(1) @Bean 객체 간의 의존 설정

의존 설정은 매우 간단하다. 다음과 같이 의존할 빈 객체에 대한 메서드를 호출하는 것으로 의존관계를 설정할 수 있다.


@Configuration

public class SpringConfig{


@Bean

public Camera camera1(){

return new camera();

}


@Bean

public infraredRaySensor windowSensor(){

return new infraredRaySensor("창 센서");

}


@Bean

public Viewer viewer(){

MonitorViewer viewer = new MonitorViewer();

viewer.setDisplayStrategy(displayStrategy());

return viewer;

}

@Bean

public DisplayStrategy displayStrategy(){

return new DefaultDisplayStrategy();

}


@Bean

public HomeController homeController(){

HomeContoller homeController = new HomeController();

List<infraredRaySensor> sensors = new ArrayList<infraredRaySensor>();

sensors.add(windowSensor());

sensors.add(doorSensor());

homeController.setSensors(sensors);

...

homeController.setCamera1(camera1());

homeController.setDisplayStrategy(displayStrategy());

return homeController;

}

}

위 코드에서 각 빈 객체들은 의존할 빈에 해당하는 메서드를 호출함으로써 의존 관계를 설정하고 있다.

여기서 눈여겨 볼 부분은 displayStrategy() 메서드가 viewer() 메서드와 homeController() 메서드에서 각각 한 번씩 호출된다는 점이다.

즉, displayStrategy() 메서드가 두 번 호출된다. displayStrategy() 메서드는 호출 횟수에 상관없이 매번 동일한 객체를 리턴한다.


스프링은 CGLIB를 이용해서 @Configuration이 적용된 클래스의 프록시 객체를 생성한다. 이 프록시 객체는 @Bean 어노테이션이 적용된 메서드가 호출될 때 생성할 빈 객체의 범위에 

따라서 알맞은 객체를 제공한다. 예를 들어, 설정한 빈의 범위가 singleton이면 메서드가 리턴하는 객체는 매번 동일한 객체가 되고, 빈의 범위가 prototype이면 메서드가 메번 새로운 빈 객체를 

리턴하게 된다.


(2) @Bean 어노테이션의 autowire 속성을 이용한 연관 자동 설정

<bean id="alarmDevice" class="mad.spring.ch4.homecontrol.SmsAlarmDevice" autowire="byName"/>


@Bean(autowire=autowire.BY_NAME)

Autowire.BY_NAME: 이름을 이용해서 자동 연관을 처리한다.

Autowire.BY_TYPE: 타입을 이용해서 자동 연관을 처리한다.

Autowire.NO: 자동 연관 처리를 하지 않는다.


2. @Configuration 어노테이션 기반 설정 정보 사용

클래스에 @Configuration 어노테이션을 적용한다고 해서 스프링 컨테이너가 해당 클래스로부터 빈 정보를 구할 수 있는 것은 아니다.

@Configuration 어노테이션이 적용된 클래스를 이용해서 스프링 빈을 생성하는 방법 2가지를 살펴보자


(1) AnnotationConfigApplicationContext를 이용한 @Configuration 클래스 사용

@Configuration 어노테이션이 적용된 클래스를 설정 정보로 이용하는 첫 번째 방법은 AnnotationConfigApplicationContext를 이용하는 것이다.


다음과 같이 @Configuration 어노테이션 클래스를 전달해 주기만 하면 된다.

import org.springframework.context.ApplicationContext;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import org.springframework.context.support.AbstractApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;


public class MainConfig {


public static void main(String[] args) {

ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);


HomeController homeControl = context.getBean(:homeController", HomeController.class);

....

}

}


cf.) 한 개 이상의 @Configuration 어노테이션 적용 클래스로부터 ApplicationContext를 생성하고 싶다면 다음과 같이 @Configuration 어노테이션 클래스의 목록을 지정하면 된다.

ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class, ArticleRepositoryConfig.class);


cf.) AnnotationConfigApplicationContext클래스는 <context:annotation-config> 태그를 사용했을 때와 마찬가지로 각 빈 클래스에서 사용된 @Autowired 어노테이션과 @Resource 어노테이션 등 

앞서 '어노테이션 기반 설정' 절에서 살펴봤던 어노테이션들이 기본을 적용된다.


(2) XML 설정 파일에서 @Configuration 어노테이션 클래스 사용하기

XML 설정 파일에서 @Configuration 클래스를 사용하려면 ConfigurationClassPostProcessor 클래스와 @Configuration 어노테이션이 적용된 클래스를 스프링 설정 파일에 빈 객체로 등록해 주면 된다.

<bean class="org.springframework.context.annotation.ConfigurationClassPostProcessor"/>

<bean class="mad.spring.ch4.config.SpringConfig"/>

ConfigurationClassPostProcessor 클래스는 @Configuration 어노테이션이 적용된 빈 객체에서 @Bean 어노테이션이 적용된 메서드로부터 빈 객체를 가져와 스프링 컨테이너에 등록한다.


@Configuration 어노테이션이 적용된 클래스는 @Component 어노테이션이 적용된 클래스와 마찬가지로 컴포넌트 스캔 대상이다. 따라서, 아래와 같이 컴포넌트 스캔 기능을 이용해서 

@Configuration 어노테이션이 적용된 클래스를 자동으로 빈으로 등록할 수 있다.

<?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:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans   

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.0.xsd">


<context:component-scan base-package="mad.spring.ch4.config"/>

...

</beans>



(3) @Importresource를 통해 @Configuration 설정 클래스에서 XML 사용하기

@Configuration 클래스에서 XML 설정 정보를 사용할 수도 있다.

@Configuration 클래스에서 XML 설정 정보를 함께 사용하고 싶다면 @ImportResource 어노테이션을 사용하면 된다.

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.ImportResource;


@Configuration

@ImportResource({"classpath:/article-repository.xml"})

public class ArticleConfigWithImportResource {


@Autowired

private ArticleRepository articleRepository;

@Bean

public ArticleService articleService() {

return new ArticleServiceImpl(articleRepository);

}

}


3. 서로 다른 @Configuration 어노테이션 클래스 간의 의존 설정
@Configuration 어노테이션 클래스를 작성하다보면, 다른 @Configuration 어노테이션 클래스에서 설정한 빈 객체를 의존하는 경우가 있다. 이런 경우는 주로 레이어 별로 설정 정보를 구분할 때 발생한다.예를 들어, 아래 코드와 같이 서비스 레이어와 리포지토리 레이어에 대한 설정을 별도 클래스로 제공한다고 해보자.
위 코드에서 ArticleServiceConfig에 설정된 articleService 빈은 ArticleRepositoryConfig에 설정된 articleRepository 빈에 의존하고 있다.
이렇게 서로 다른 설정 클래스에 존재하는 빈 객체 간의 의존을 처리할 때에는 @Autowired 어노테이션이나 @Resource 어노테이션을 이용해서 의존에 필요한 빈 객체를 전달 받을 수 있다.

위 코드에서 ArticleServiceConfig 클래스의 articleRepository 필드에 @Autowired 어노테이션이 적용되었기 때문에 articleRepository 필드에는 ArticleServiceConfig 클래스에 정의된
articleRepository() 메서드가 생성한 빈 객체가 할당된다. 따라서, ArticleServiceConfig.srticleService() 메서드에서 ArticleServiceImpl 객체를 생성할 때 사용되는 articleRepository 객체는
ArticleRepositoryConfig 클래스에서 생성한 ArticleRepositoryImpl 객체가 된다.

@Configuration
public class ArticleServiceConfig{
  @Autowired
private ArticleRepositoryConfig repositoryConfig;

@Bean
public ArticleService articleService(){
return new ArticleServiceImpl(repositoryConfig.articleRepository());
}
}
의존 객체를 참조하는 또다른 방법은 @Configuration 클래스를 @Autowired로 전달받는 것이다. 위 코드는 @Configuration 클래스를 전달받아 의존을 처리하는 경우의 예를 보여주고 있다.

cp.) @Import를 이용한 @Configuration 어노테이션 적용 클래스의 조합

@Import 어노테이션을 이용하면 하나의 @Configuration 클래스에서 다수의 @Configuration 클래스를 묶을 수 있다. 아래와 같이 @import 어노테이션에 설정 정보를 가져올 @Configuration 클래스 목록을 지정해주면 된다.

import org.springframework.context.annotaion.Cofiguration;

import org.springframework.context.annotation.Import;


@Configuration

@Import({ArticleServiceConfig.class, ArticleRepositoryConfig.class})

public class ArticleConfigWithImport{

...

}

@Import 어노테이션을 이용할 경우의 장점은 개발자가 모든 @Configuration 클래스 목록을 기억할 필요 없이 @import 어노테이션이 적용된 클래스만 기억하면 손쉽게 설정 정보 추적이 가능하다는 점이다.


[Spring] 어노테이션 기반 설정


어노테이션 기반 설정


1. XML에서의 <context:annotation-config/> 설정

<?xml version="1.0" encoding="UTF-8"?>

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

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

xsi:schemaLocation="http://www.springframework.org/schema/beans",

http://www.springframework.org/schema/context"   

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

http://www.springframework.org/schema/beans/spring-context-3.0.xsd">

<context:annotation-config/>

  <!-- <context:component-scan> 태그를 이용해서 스프링이 클래스를 검색할 패키지를 지정하면 된다. <context:component-scan> 태그를 추가하면 스프링은 지정한 패키지에서 @Component 어노테이션 등이 적용된 클래스를 검색하여 빈으로 등록하게 된다.

<context:component-scan base-package="mad.spring.ch4.HomeController"/>-->

...

</beans>


<context:annotation-config/> 태그는 어노테이션과 관련해서 다음의 BeanPostProcessor를 등록해 준다.

- RequredAnnotationBeanPostProcessor: @Required 어노테이션 처리

- AutowiredAnnotationBeanPostProcessor:@Autiwired 어노테이션 처리

- CommonAnnotationBeanPostProcessor: @Ressource, @PostConstruct, @PreDestroy 어노테이션 처리

- ConfigurationClassProcessor: @Configuration 어노테이션 처리


2. @Autowired 어노테이션을 이용한 자동 설정

org.springframework.beans.factory.annotation 패키지에 위치한 @Autowired 어노테이션은 의존관계를 자동으로 설정할 때 사용된다.

@Autowired 어노테이션은 스프링 2.5에 추가된 기능으로 타입을 이용하여 의존하는 객체를 삽입해준다.

@Autowired 어노테이션은 타입을 이용한 프로퍼티 자동 설정 기능을 제공한다.


(1) 프로퍼티 설정 메서드에 적용한 예

import org.springframework.beans.factory.annotation.Autowired;


public class MonitorViewer implements Viewer{

private DisplayStrategy displayStrategy;

@Autowired

public void setDisplayStrategy(DisplayStrategy displayStrategy){

this.displayStrategy = displayStrategy;

}

}

위 코드는 displayStrategy 프로퍼티에 DisplayStrategy 타입의 빈 객체를 전달한다.


(2) 일반 메서드에 적용한 예

메서드 이름이 setXXX() 형식이 아니라도 @Autowired 어노테이션을 적용할 수 있다. 아래 코드는 prepare() 메서드에 @Autowired 어노테이션을 적용할 수 있는데,

이 경우 스프링은 HomeController 객체를 생성할 때 prepare() 메서드에 AlarmDevice 타입의 빈 객체와 Viewer 타입의 빈 객체를 전달한다.

import org.springframework.beans.factory.annotation.Autowired;


public class HomeController{

private AlarmDevice alarmDevice;

private Viewer viewer;


@Autowired

public void prepare(AlramDevice alarmDevice, Viewer viewer){

this.alarmDevice = alarmDevice;

this.viewer viewer;

}

...

}


(3) 멤버 필드에 적용한 예

멤버 필드에 직접 @Autowired 어노테이션을 적용해도 된다.

public class MonitorViewer implements Viewer{

@Autowired

private DisplayStrategy displayStrategy;

...

}


(4) 배열에 적용한 예

@Autowired 어노테이션을 배열에 적용하게 되면, 해당 타입의 모든 빈 객체를 배열로 전달 받는다. 예를 들어, 아래 코드와 같이 @Autowired 어노테이션을 적용하면 InfraredRaySensor 타입의 빈 객체가 배열로

setSensor() 메서드에 전달된다. 메서드 뿐만 아니라 배열 타입의 멤버 필드에 @Autowired 어노테이션을 적용해도 동일하게 동작한다.

public class HomeController{

...

private InfraredRaySensor[ ] sensors;

@Autowired

public void setSensors(InfraredRaySensor[ ] sensors){

this.sensors = sensors;

}

...

}


(5) 제네릭이 적용된 컬렉션 타입을 적용한 예

제네릭이 적용된 컬렉션 타입을 사용하는 경우 List 타입이나 Set 타입을 이용해서 특정 타입의 빈 객체 목록을 전달 받을 수 있다.

public class HomeController{

private List<InfraredRaySensor> sensor;


@Autowired

public void setSensors(List<InfraredRaySensor> sensors){

this.sensors=sensors;

}

}


(6) @Autowired 어노테이션 적용 프로퍼티의 필수 여부 지정
@Autowired 어노테이션은 타입을 이용해서 자동적으로 프로퍼티 값을 설정하기 때문에, 해당 타입의 빈 객체가 존재하지 않거나 또는 빈 객체가 두 개 이상 존재할 경우
스프링은 @Autowired 어노테이션이 적용된 빈 객체를 생성할 때 예외를 발생시킨다.

@Autowired 어노테이션을 적용한 프로퍼티를 반드시 설정할 필요가 없는 경우도 있는데, 이런 경우에는 @Autowired 어노테이션에 required 속성의 값을 false로 지해주면 된다.
public class HomeController{
...
@Autowired(requred=false)
public void setSensors(List<InfraredRaySensor> sensors){
this.sensors = sensors;
}
...
}
required 속성의 값을 false로 지정할 경우, 해당 타입의 빈 객체가 존재하지 않더라도 스프링은 예외를 발생하지 않는다.
required 속성의 기본 값은 true이다.


3. @Resource 어노테이션을 이용한 프로퍼티 설정

@Resource 어노테이션은 어플리케이션에서 필요로 하는 자원을 자동 연결할 때 사용된다.

스프링에서는 의존하는 빈 객체를 전달할 때 사용된다.

import javax,annotation.Resource;


public class HomeController{

@Resource(name="camera1")

private Camera camera1;

private Camera camera4;

@Resource(name="camera4);

public void setcamera4(Camera camera4){

this.camera4 = camera4;

}

...

}

스프링에서 @Resource 어노테이션을 사용하려면 name 속성에 자동으로 연결할 빈 객체의 이름을 입력하면 된다.


<bean class="org.springframework.beans.factory.annotation.RequredAnnotationBeanPostProcessor"/>

<context:annotation-config/>를 사용해도 좋다.


'Programing > Spring' 카테고리의 다른 글

[Spring] HTML 폼과 자바빈 객체  (0) 2014.12.18
[Spring] 자바 코드 기반 설정  (0) 2014.12.17
[Spring] 외부 설정 프로퍼티  (0) 2014.12.17
[Spring] iBatis 2  (0) 2014.12.16
[Spring] iBatis 1  (0) 2014.12.16

[Spring] 외부 설정 프로퍼티


외부 설정 프로퍼티


1. 외부 설정 프로퍼티

PropertyPlaceholderConfigurer 클래스를 빈으로 등록하면 외부의 프로퍼티에 저장된 정보를 스프링 설정 파일에서 사용할 수 있다.

예를 들어, 다음과 같은 프로퍼티 파일을 작성했다고 하자.

jdbc.driver=com.mysql.jdbc.Driver

jdbc.url=jdbc:mysql://dbserver:3306/springbook

jdbc.username=springbook

jdbc.password=springbook

위 프로퍼티의 파일의 정보를 스프링 설정 파일에서 사용하고 싶다면, 다음과 같이 PropertyPlaceholderConfigurer 클래스를 빈으로 등록한 뒤, 프로퍼티의 이름을 파일에서 사용하면 된다.


<bean class="org.springframwork.beans.factory.config.PropertyPlaceholderConfigurer">

<property name="locations">

<!-- 프로퍼티 파일 경로 지정 -->

<value>classpath:config/jdbc.properties</value>

</property>

</bean>


<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destory-method="close">

<property name="driverClassName" value="${jdbc.driver}"/>

<property name="url" value="${jdbc.url}">

<property name="username" value="${jdbc.username}/>

<property name="password" value="${jdbc.password}/>

</bean>

locations 프로퍼티의 값에는 콤마나 공백으로 구분된 프로퍼티 파일 목록이 오며, 프로퍼티 파일에 포함된 프로퍼티의 값은 

'${프로퍼티의 이름}' 형식으로 사용할 수 있다. 즉, 위 코드에서 ${jdbc.driver}는 프로퍼티 파일의 jdbc.driver 프로퍼티의 값으로 대체된다.


 한 개 이상의 프로퍼티 파일을 지정하면 <list> 태그를 이용하여 프로퍼티 목록을 지정해주면 된다.

<bean class="org.springframwork.beans.factory.config.PropertyPlaceholderConfigurer">

<property name="locations">

<list>

<value>classpath:config/jdbc.properties</value>

<value>classpath:config/monitor.properties</value>

</list>

</property>

</bean>

외부 환경에 따라 설정 정보가 변경되는 경우에 프로퍼티 파일을 유용하게 사용할 수 있다.

예를 들어, 로컬에서의 테스트와 테스트 서버에서의 통합 테스트, 그리고 실제 운영 환경에서 사용하는 데이터베이스가 다르다고 하자.

이 경우 각 환경에 맞게 스프링 설정 파일을 변경하는 것 보다는 환경에 맞는 프로퍼티 설정 파일을 작성하고, 

환경에 따라 알맞은 프로퍼티 파일을 사용하도록 배치 스크립트를 작성하는 것이 유지 및 보수에 더 편리하다.


2. <context:property-placeholder> 태그를 사용한 외부 설정 프로퍼티 사용

<context:property-placeholder> 태그를 사용하여 외부 프로퍼티 파일을 로딩하도록 설정할 수 있다.

<?xml version="1.0" encoding="UTF-8"?>

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

xmlns:context="http://www.springframework.org/schema/context"

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

xsi:schemaLocation="http://www.springframework.org/schema/beans   

      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

  http://www.springframework.org/schema/context 

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

  <!--두 개이상 프로퍼티 사용할 수 있다.(콤마로 구분하여)-->

<context-property-placeholder location="classpath:config/jdbc.properties, classpath:config/monitor.properties"/>


<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destory-method="close">

<property name="driverCalssName" value="${jdbc.driver}">/>

</bean>

....


</beans>

<context:property-placeholder> 태그를 사용하려면 위 코드와 같이 context 접두어에 대한 네임스페이스를 http://www.springframework.org/schema/context로 지정하고 네임스페이스와 관련된 XML 스키마 경로를 

spring-context-3.0.xsd로 지정해 주어야 한다.


3. PropertyPlaceholderConfigurer 사용시 주의 사항

PropertyPlaceholderConfigurer를 사용할 때 주의할 점은 두 개 이상의 PropertyPlaceholderConfigurer 빈을 설정하면 안 된다는 점이다.

이 경우, 첫 번째 PropertyPlaceholderConfigurer의 설정이 적용되며, 두 번째 설정 내용은 적용되지 않는다.

--> 각각의 PropertyPlaceholderConfigurer 빈은 서로 프로퍼티 정보를 공유하지 않기 때문이다.

스프링을 이용하다보면 두 개 이상의 설정 파일을 사용하는 경우가 빈번한데, 이때 서로 다른 파일에 PropertyPlaceholderConfigurer 빈이 설정되어 있을 경우 프로퍼티 값이 올바르게

처리되지 않아서 올바르게 처리되지 않아서 예외가 발생할 수 있다.




'Programing > Spring' 카테고리의 다른 글

[Spring] 자바 코드 기반 설정  (0) 2014.12.17
[Spring] 어노테이션 기반 설정  (0) 2014.12.17
[Spring] iBatis 2  (0) 2014.12.16
[Spring] iBatis 1  (0) 2014.12.16
[Spring] web.xml 기본 설정  (1) 2014.12.13

[Java] File 클래스



File 클래스


자바에서는 File 클래스를 통해서 파일과 디렉터리를 다룰 수 있도록 하고 있다. 그래서 File 인스턴스는 파일 일 수도 있고 디렉터리 일 수도 있다.


1. File 클래스 API


- 경로와 관련된 File의 멤버변수

 멤버변수

 설명

 static String pathSeparator

 OS에서 사용하는 경로 구분자. 윈도우";", 유닉스":"

 static char pathSeparatorChar

 OS에서 사용하는 경로 구분자. 윈도우';', 유닉스':'

 static String separator

 OS에서 사용하는 이름 구분자. 윈도우"\", 유닉스"/"

 static char separatorChar

 OS에서 사용하는 이름 구분자. 윈도우'\', 유닉스'/'


import java.io.*;


class FileEx1

{

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

{

File f = new File("c:\\jdk1.5\\work\\ch14\\FileEx1.java");

String fileName = f.getName();

int pos = fileName.lastIndexOf(".");


System.out.println("경로를 제외한 파일이름 - " + f.getName());

System.out.println("확장자를 제외한 파일이름 - " + fileName.substring(0,pos));

System.out.println("확장자 - " + fileName.substring(pos+1));


System.out.println("경로를 포함한 파일이름 - " + f.getPath());

System.out.println("파일의 절대경로        - " + f.getAbsolutePath());

System.out.println("파일이 속해 있는 디렉토리 - " + f.getParent());

System.out.println();

System.out.println("File.pathSeparator - " + File.pathSeparator); // 파일 전체 path 구분자. ;

System.out.println("File.pathSeparatorChar - " + File.pathSeparatorChar);

System.out.println("File.separator - " + File.separator); // 디렉터리 구분다. /

System.out.println("File.separatorChar - " + File.separatorChar);

System.out.println();

System.out.println("user.dir="+System.getProperty("user.dir"));

System.out.println("sun.boot.class.path=" + System.getProperty("sun.boot.class.path"));

}

}

실행결과)

경로를 제외한 파일이름 - FileEx1.java

확장자를 제외한 파일이름 - FileEx1

확장자 - java

경로를 포함한 파일이름 - c:\jdk1.5\work\ch14\FileEx1.java

파일의 절대경로        - c:\jdk1.5\work\ch14\FileEx1.java

파일이 속해 있는 디렉토리 - c:\jdk1.5\work\ch14


File.pathSeparator - ;

File.pathSeparatorChar - ;

File.separator - \

File.separatorChar - \


user.dir=E:\Eclipse_Source\JAVA\My_Lib

sun.boot.class.path=C:\Program Files (x86)\Java\jre6\lib\resources.jar;C:\Program Files (x86)\Java\jre6\lib\rt.jar;C:\Program Files (x86)\Java\jre6\lib\sunrsasign.jar;C:\Program Files (x86)\Java\jre6\lib\jsse.jar;C:\Program Files (x86)\Java\jre6\lib\jce.jar;C:\Program Files (x86)\Java\jre6\lib\charsets.jar;C:\Program Files (x86)\Java\jre6\lib\modules\jdk.boot.jar;C:\Program Files (x86)\Java\jre6\classes


File 인스턴스를 생성하고 메서드를 이용해서 파일의 경로와 구분자 등의 정보를 출력하는 예제이다.

cp.) OS의 시스템 변수로 설정하는 classpath외에 sun.boot.class.path라는 시스템속성에 기본적인 classpath가 있어서 기본적인 경로들은 이미 설정되어 있다. 

그리고 한가지 더 알아 둘 것은 File 인스턴스를 생성했다고 해서 파일이나 디렉터리가 생성되는 것은 아니라는 것이다.

파일명이나 디렉터리명으로 지정된 문자열이 유효하지 않더라도 컴파일 에러나 예외를 발생시키지 않는다.

새로운 파일을 생성하기 위해서는 File 인스턴스를 생성한 다음, 출력스트림을 생성하거나 createNewFile()을 호출해야 한다.


1 이미 존재하는 파일을 참조할 때

File f = new File("c:\\jdk\\work\\ch14", "File1.java");


2. 기존에는 없는 파일을 새로 생성할 때

File f = new File("c:\\jdk\\work\\ch14", "File2.java");

f. createNewFile(); //새로운 파일이 생성된다.


(1) 예제1

1) File 클래스

File(.txt)가 자바 프로그래밍에서 사용되기 위해서는 객체로 되어있어야 사용할수 있다.

--> File클래스가 파일을 자바 프로그램의 객체로 변환시켜준다.

 Exam_01.java 파일 자바에서 사용하기 위한 객체화 작업

import java.io.File;


public class Exam_02 {

public static void main(String[] ar) {

File f = new File("Exam_01.java");// 존재하든 관계없이 파일 객체 생성 가능

File f1 = new File("Exam_03.java");// 존재 안하든 관계없이 파일 객체 생성 가능

File f2 = new File("C:" + File.separator + "test" + File.separator + "lecture\\Exam_02.java");

File dir = new File("E:\\Eclipse_Source\\JAVA\\Java_KSH\\io");

File f3 = new File(dir, "Exam_04.java");

File f4 = new File("E:\\Eclipse_Source\\JAVA\\Java_KSH\\io", "Exam_05.java");

}

}


2) 예제2

import java.io.File;

import java.util.Date;


public class Exam_04 {

public static void main(String[] ar) {

File f = new File("aaa");

// 폴더 만들기

f.mkdir();

File ff = new File("bbb/ccc/ddd/eee");

// 복수의 폴더 만들기

ff.mkdirs();

//File fff = new File("Exam_01");

// 수정일자 변경

f.setLastModified(new Date().getTime());

f.setReadOnly();

//ff.setLastModified(new Date().getTime());

}

}


3) 예제3

import java.io.File;

import java.io.FileDescriptor;

import java.io.FileOutputStream;

import java.io.IOException;


public class Exam_05 {

public static void main(String[] ar) throws IOException {

// 출력 객체 만들기, FileDescriptor.out: 콘솔에 출력하기

FileOutputStream fos1 = new FileOutputStream(FileDescriptor.out);// 파일 객체, network 객체

File f = new File("C:\\test\\aaa.txt");

// 두번째 매개변수에 true면 덮어쓰기 하지 않고 내용이 추가된다.

FileOutputStream fos2 = new FileOutputStream(f, true);

byte[] by = new byte[]{'H', 'e', 'l', 'l', 'o', ' ', 'J', 'a', 'v', 'a'};


fos1.write(by, 6,4); 

// index[6]에서 부터 4번째 문자열 출력

fos2.write(by);

fos1.write(65);

fos1.close();

fos2.close();

}

}


4) 예제4

import java.io.BufferedInputStream;

import java.io.DataInputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;


public class Exam_08 {

public static void main(String[] ar) throws IOException {

File f = new File("C://test/bbb.txt");

FileInputStream fis = new FileInputStream(f);

BufferedInputStream bis = new BufferedInputStream(fis, 1024);

DataInputStream dis = new DataInputStream(bis);

while(true) {

int x = dis.read();

if(x == -1) break;

System.out.print(x);

}

dis.close();

}

}


5) 예제5
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class Exam_09 {
public static void main(String[] ar) throws IOException {
//System.out.println("test 안녕하세요...");
//FileOutputStream fos = new FileOutputStream(FileDescriptor.out);
// 출력 대상은 콘솔뿐만 아니라 웹, 서블릿, jsp에서 출력할 수 있는 객체가 OutputStreamWriter이다.
OutputStreamWriter osw = new OutputStreamWriter(System.out);
BufferedWriter bw = new BufferedWriter(osw, 1024);
// write 뿐만 아니라 다양한 메서드 사용할 수 있다.
PrintWriter pw = new PrintWriter(bw);
File f = new File("C://test/ccc.txt");
// 출력대상이 파일이다, 출력은 파일이 자동 생성되어 출력이 된다.
FileWriter fw = new FileWriter(f);
BufferedWriter bw1 = new BufferedWriter(fw, 1024);
PrintWriter pw1 = new PrintWriter(bw1);
pw.println(10);
pw.println("test 안녕하세요!");
pw1.println(10);
pw1.println("test 안녕하세요!");
pw.close();
pw1.close();
}
}


6) ★ 예제6★ 

import java.io.BufferedReader;

import java.io.File;

import java.io.FileReader;

import java.io.IOException;

import java.io.InputStreamReader;


public class Exam_10 {

public static void main(String[] ar) throws IOException {

InputStreamReader isr = new InputStreamReader(System.in);

BufferedReader br = new BufferedReader(isr);

// 한줄로 요약

//BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

File f = new File("C://test/ccc.txt");

FileReader fr = new FileReader(f);

BufferedReader br1 = new BufferedReader(fr);

System.out.print("문자열 = ");

String str = br.readLine();

System.out.println("str = " + str);

System.out.print("숫자 = ");

int x = Integer.parseInt(br.readLine());

System.out.println("x = " + x);

System.out.println("==================================");

while(true) {

// 개행하며 한 줄씩 읽어들이기

String s = br1.readLine();

if(s == null) break;

System.out.println("s = " + s);

}

br.close();

br1.close();

}

}



'Programing > Java' 카테고리의 다른 글

[Java] 쓰레드 기본  (0) 2014.12.21
[Java] 직렬화  (0) 2014.12.17
[Java] 문자 기반 스트림  (0) 2014.12.16
[Java] 바이트 기반의 스트림  (0) 2014.12.16
[Java] 파일I/O 개요  (0) 2014.12.16

[Java] 문자 기반 스트림


문자 기반 스트림


1. 문자 기반 스트림

문자데이터를 다루는 데 사용된다는 것을 제외하고는 바이트기반 스트림과 문자기반 스트림의 사용방법은 거의 같다.

문자기반 스트림이라는 것이 단순히 2byte로 스트림을 처리하는 것만을 의미하지 않는다는 것이다. 


(1) FileReader와 FileWriter

 FileReader와 FileWriter는 파일로부터 텍스트 데이터를 읽고, 파일에 쓰는데 사용된다. 사용방법은 FileInputStream과 FileOutputStream과 다르지 않다.

import java.io.*;


class FileReaderEx1 {

public static void main(String args[]) {

try {

String fileName = "test.txt";

FileInputStream fis = new FileInputStream(fileName);

FileReader fr = new FileReader(fileName);


int data =0;

// FileInputStream을 이용해서 파일내용을 읽어 화면에 출력한다.

while((data=fis.read())!=-1) {

System.out.print((char)data);

}

System.out.println();

fis.close();


// FileReader를 이용해서 파일내용을 읽어 화면에 출력한다.

while((data=fr.read())!=-1) {

System.out.print((char)data);

}

System.out.println();

fr.close();


} catch (IOException e) {

e.printStackTrace();

}

} // main

}


2. 문자기반 보조 스트림

(1) BuffredReader와 BufferedWriter

BuffredReader와 BufferedWriter는 버퍼를 이용해서 입출력의 효율을 높일 수 있도록 해주는 역할을 한다. 버퍼를 이용하면 입출력의 효율이 비교할 수 없을 정도로 좋아지기 때문에 사용하는 것이 좋다.

BuffredReader의 readLine()을 사용하면 데이터를 라인단위로 읽어올 수 있다는 장점이 있다. 그리고 BufferedWriter는 newLine()라는 줄바꿈 해주는 메서드를 가지고 있다.


(2) InputStreamReader와 OutputStreamWriter

바이트 기반 스트림을 문자 기반 스트림으로 연결시켜주는 역할을 한다. 그리고 바이트기반 스트림의 데이터를 지정된 인코드의 문자 데이터로 변환하는 작업을 수행한다.

import java.io.*;


class InputStreamReaderEx 

{

public static void main(String[] args) 

{

String line = "";


try {

InputStreamReader isr = new InputStreamReader(System.in);

BufferedReader br = new BufferedReader(isr);


System.out.println("사용중인 OS의 인코딩 :" + isr.getEncoding());


do {

System.out.print("문장을 입력하세요. 마치시려면 q를 입력하세요. ");

line = br.readLine();

System.out.println("입력하신 문장 : "+line);

} while(!line.equalsIgnoreCase("q"));


// br.close();   // System.in과 같은 표준입출력은 닫지 않아도 된다.

System.out.println("프로그램을 종료합니다.");

} catch(IOException e) {}

} // main

}



'Programing > Java' 카테고리의 다른 글

[Java] 직렬화  (0) 2014.12.17
[Java] File 클래스  (0) 2014.12.16
[Java] 바이트 기반의 스트림  (0) 2014.12.16
[Java] 파일I/O 개요  (0) 2014.12.16
[Java] 제네릭  (3) 2014.12.14

[Java] 바이트 기반의 스트림


바이트 기반의 스트림


1. 바이트 기반 스트림

(1) InputStream과 OutputStream

InputStream과 OutputStream은 모든 바이트기반의 스트림의 조상이며 같은 메서드가 선언되어 있다.

cp.)

void close(): 스트림을 닫음으로써 사용하고 있던 자원을 반환한다.

abstract int read(): 1byte를 읽어온다.(0~255 사이의 값), 더 이상 읽어 올 데이터가 없으면 -1을 반환한다. abstract메서드라서 InputStream의 자손들은 자신의 상황에 알맞게 구현해야 한다.

int read(byte[] b): 배열 b의 크기만큼 읽어서 배열을 채우고 읽어 온 데이터의 수를 반환한다. 반환하는 값은 항상 배열의 크기보다 작거나 같다.

int read(byte[] b, int off, int len): 최대 len개의 byte를 읽어서, 배열 b의 지정된 위치(off)부터 저장한다. 실제로 읽어올 수 있는 데이터가 len개보다 적을 수 있다.

flush() 버퍼가 있는 출력 스트림의 경우에만 의미가 있으며, OutputSteam에 정의된 flush()는 아무런 일도 하지 않는다.

프로그램이 종료될 때, 사용하고 닫지 않은 스트림을 JVM이 자동적으로 닫아 주기는 하지만, 스트림을 사용해서 모든 작업을 마치고 난 후에는 close()를 호출해서 반드시 닫아 주어야 한다.

cf.) ByteArrayInputStream과 같이 메모리를 사용하는 스트림과 System.in, System.out과 같은 표준입출력은 닫아 주지 않아도 된다.


(2) FileInputStream과 FileOutputStream
FileInputStream과 FileOutputStreamdms 파일에 입출력을 하기 위한 스트림이다.


cp.) FileInputStream(File fileObj): 파일의 이름이 String이 아닌 File 인스턴스로 지정해주어야 하는 점을 제외하고 FileInputStream(String filePath)와 같다.


import java.io.*;


class FileViewer {
public static void main(String args[]) throws IOException{
FileInputStream fis = new FileInputStream(args[0]);
int data =0;
while((data=fis.read())!=-1) {
char c = (char)data;
System.out.print(c);
}
}
}

FileInputStream과 FileOutputStream을 사용해서 FileCopy.java파일의 내용을 그대로 복사하는 내용의 예제이다.
import java.io.*;

class FileCopy {
public static void main(String args[]) {
try {
FileInputStream fis = new FileInputStream(args[0]);
FileOutputStream fos = new FileOutputStream(args[1]);

int data =0;

while((data=fis.read())!=-1) {
fos.write(data); // void write(int b)
}

fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}


import java.io.*;

class FileViewer {
public static void main(String args[]) throws IOException{
FileInputStream fis = new FileInputStream(args[0]);
int data =0;
while((data=fis.read())!=-1) {
char c = (char)data;
System.out.print(c);
}
}
}
read()의 반환값이 int형(4byte)이긴 하지만, 더 이상 입력값이 없음을 알리는 -1을 제외하고는 0~255(1byte)의 범위의 정수값이기 때문에, char형(2byte)으로 변환한다해도 손실되는 값이 없다.
read()가 한 번에 1byte씩 파일로부터 데이터를 읽어 들이긴 하지만, 데이터의 범위가 십진수로 0~255(16진수로는 0x00~0xff)범위의 정수값이고, 또 읽을 수 있는 입력값이 더 이상 없음을 알릴 수 있는 값도 필요하다.
그래서 다소 크긴 하지만 정수형 중에서는 연산이 가장 효율적이고 빠른 int형 값을 반환하도록 한 것이다.

2. 바이트기반의 보조스트림
(1) FilterInputStream과 FilterOutputStream

FilterInputStream과 FilterOutputStream은 InputStream/OutputStream의 자손이면서 모든 보조 스트림의 조상이다. 
보조 스트림은 자체적으로 입출력을 수행할 수 없기 때문에 기반 스트림을 필요로 한다.

- FilterInputStream과 FilterOutputStream의 생성자이다.
Protected FilterInputStream(InputStream in)
public FilterOutputStream(OutputStream out)
FilterInputStream과 FilterOutputStream의 모든 메서드는 단순히 기반 스트림의 메서드를 그대로 호출할 뿐이다. FilterInputStream과 FilterOutputStream 자체로는 아무런 일도 하지 않음을 의미한다.
FilterInputStream과 FilterOutputStream 자체로는 아무런 일도 하지않고 상속을  통해 원하는 작업을 수행하도록 읽고 쓰는 메서드를 오버라이딩 해야 한다.

(2) BufferedInputStream과 BufferedOutputStream
BufferedInputStream과 BufferedOutputStream은 스트림의 입출력 효율을 높이기 위해 버퍼를 사용하는 보조 스트림이다. 한 바이트씩 입출력하는 것 보다 버퍼(바이트배열)를 사용해서 한 번에 여러 바이트를 입출력하는 것이 빠르기 때문에 대부분의 입출력 작업에 사용된다.

BufferedInputStream과 BufferedOutputStream의 버퍼크기는 입력소스로부터 한 번에 가져올 수 있는 데이터의 크기로 지정하면 좋다. 보통 입력소스가 파일인 경우 보통 작게는 1024부터 2048 또는 4096 정도의 크기로 하는 것이 보통이며, 버퍼의 크기를 변경해가면서 테스트하면 최적의 버퍼 크기를 알아낼 수 있다.

프로그램에서 입력 소스로부터 데이터를 읽기 위해 처음으로 read 메서드를 호출하면, BufferedInputStream은 입력 소스로 부터 버퍼 크기만큼의 데이터를 읽어다 자신의 내부 버퍼에 저장한다. 이제 프로그램에서는 
BufferedInputStream의 버퍼에 저장된 데이터를 읽으면 되는 것이다. 외부의 입력 소스로 부터 읽는 것보다 내부의 버퍼로 읽는 것이 훨씬 빠르기 때문에 그만큼 작업 효율이 높아진다.
프로그램에서 버퍼에 저장된 모든 데이터를 다 읽고 그 다음 데이터를 읽기 위해 read 메서드가 호출되면, BufferedInputStream은 입력 소스로부터 다시 버퍼 크기만큼의 데이터를 읽어다 버퍼에 저장해 놓는다.


BufferedOutputStream 역시 버퍼를 이용해서 작업을 하게 되는데, 입력 소스로부터 데이터를 읽을 때와는 반대로, 프로그램에서 write 메서드를 이용한 출력이 BufferedOutputStream의 버퍼에 저장된다. 
버퍼가 가득 차면, 그 때 버퍼의 모든 내용을 출력 소스에 출력한다.
버퍼가 가득 찼을때만 출력소스에 출력을 하기 때문에, 마지막 출력 부분이 출력소스에 쓰여지지 못하고, BufferedOutputStream의 버퍼에 남아있는 채로 프로그램이 종료될 수 있다는 점을 주의해야 한다.
그래서 프로그램에서 모든 출력 작업을 마친 후 BufferedOutputStream에 close()나 flush()를 호출해서 마지막에 버퍼에 있는 모든 내용이 출력소스에 출력되도록 해야 한다.

import java.io.*;

class BufferedOutputStreamEx1 {
public static void main(String args[]) {
try {
    FileOutputStream fos = new FileOutputStream("123.txt");      
             BufferedOutputStream bos = new BufferedOutputStream(fos, 5);
     // BufferedOutputStream의 버퍼 크기를 5로 한다.
   
    for(int i='1'; i <= '9'; i++) {   
    bos.write(i);
     // 파일 123.txt에  1 부터 9까지 출력한다.
    }
    fos.close();
} catch (IOException e) {
    e.printStackTrace();
}
}
}
크기가 5인 BufferedOutputStream을 이용해서 파일 123.txt에 1부터 9까지 출력하는 예제인데 결과를 보면 5까지만 출력된 것을 알 수 있다. 그 이유는 버퍼에 남아있는 데이터가 출력되지 못한 상태로 프로그램이 종료되었기 때문이다.
이 예제에서 fos.close()를 호출해서 스트림을 닫아주기는 했지만, 이렇게 해서는 BufferedOutputStream의 버퍼에 있는 내용이 출력되지 않는다. bos.close();와 같이 해서 BufferedOutputStream의 close()를 호출해 주어야 버퍼에 남아있던 모든 내용이 출력된다. BufferedOutputStream의 close()는 기반 스트림인 FileOutputStream의 close()를 호출하기 때문에 FileOutputStream의 close()는 따로 호출해주지 않아도 된다.
--> 보조스크림을 사용한 경우에는 기반스트림의 close()나 flush()를 호출할 필요없이 단순히 보조스트림의 close()를 호출하기만 하면 된다.

(3) PrintStream
PrintStream은 데이터를 기반스트림에 다양한 형태로 출력할 수 있는 print, println, printf와 같은 메서드를 오버로딩하여 제공한다.
PrintStream은 데이터를 적절한 문자로 출력하는 것이기 때문에 문자기반 스트림의 역할을 수행한다.
cf.) PrintStream은 우라기 지금까지 알게 모르게 많이 사용해 왔다. System 클래스의 static 멤버인 out과 err, 다시 말하자면 System.out, System.err이 PrintStream이다.


import java.util.Date;

class PrintStreamEx1 {
public static void main(String[] args) {
int i = 65;
float f = 1234.56789f;

Date d = new Date();

System.out.printf("문자 %c의 코드는 %d\n", i, i);
System.out.printf("%d는 8진수로 %o, 16진수로 %x\n", i ,i, i);
System.out.printf("%3d%3d%3d\n", 100, 90, 80);
System.out.println();
System.out.printf("123456789012345678901234567890\n");
System.out.printf("%s%-5s%5s\n", "123", "123", "123");
System.out.println();
System.out.printf("%-8.1f%8.1f %e\n",f,f,f);
System.out.println();
System.out.printf("오늘은 %tY년 %tm월 %td일 입니다.\n", d,d,d,d );
System.out.printf("지금은 %tH시 %tM분 %tS초 입니다.\n", d,d,d,d );
System.out.printf("지금은 %1$tH시 %1$tM분 %1$tS초 입니다.\n", d );
}
}
실행결과)
문자 A의 코드는 65
65는 8진수로 101, 16진수로 41
100 90 80

123456789012345678901234567890
123123    123

1234.6    1234.6 1.234568e+03

오늘은 2014년 04월 23일 입니다.
지금은 01시 50분 29초 입니다.
지금은 01시 50분 29초 입니다.



'Programing > Java' 카테고리의 다른 글

[Java] File 클래스  (0) 2014.12.16
[Java] 문자 기반 스트림  (0) 2014.12.16
[Java] 파일I/O 개요  (0) 2014.12.16
[Java] 제네릭  (3) 2014.12.14
[Java] 컬렉션 프레임워크  (0) 2014.12.14

[Java] 파일I/O 개요


파일I/O 개요


1. 입출력이란?

입출력이란 컴퓨터 내부 또는 외부의 장치와 프로그램간의 데이터를 주고받는 것을 말한다.

예를 들면 키보드로부터 데이터를 입력받는다든가 System.out.println()을 이용해서 화면에 출력한다던가 하는 것이 가장 기본적인 입출력의 예이다.


2. 스트림

자바에서 입출력을 수행하려면, 즉 어느 한쪽에서 다른 쪽으로 데이터를 전달하려면, 두 대상을 연결하고 데이터를 전송할 수 있는 무언가가 필요한데 이것을 스트림이라 한다.

스트림은 단방향 통신만 가능하기 때문에 하나의 스트림으로 입력과 출력을 동시에 처리할 수 없다.

스트림은 먼저 보낸 데이터를 먼저 받게 되어 있으며 중간에 건너뜀 없이 연속적으로 데이터를 주고 받는다. 큐와 같은 FIFO(First In First Out)구조로 되어 있다고 생각하면 쉬울 것이다.

cf.) 스트림은 TV와 VCR을 연결하는 입력선과 출력선과 같은 역할을 한다.

스트림이란 데이터를 운반하는데 사용된느 연결통로이다.


3. 바이트기반 스트림 - InputStream, OutputStream

스트림은 바이트단위로 데이터를 전송하며 입출력 대상에 따라 다음과 같은 입출력 스트림이 있다.

스트림의 종류가 달라도 읽고 쓰는 방법은 동일하다.



입력스트림 

출력스트림 

입출력 대상의 종류 

 FileInputStream

 FileOutputStream

 파일 

 ByteArrayInputStream

 ByteArrayOutputStream 

 메모리(byte 배열) 

 PipedOnputStream

 PipedOutputStream 

 프로세스(프로세스간의 통신) 

 AudioInputStream

 AudioOutputStream 

 오디오 장치 


어떠한 대상에 대해서 작업을 할 것인지 그리고 입력을 할 것인지 출력을 할 것인지에 따라서 해당 스트림을 선택해서 사용하면 된다.

예를 들어, 어떤 파일의 내용을 읽고자 하는 경우 FileInputStream을 사용하면 될 것이다.

이들은 모두 InputStream 또는 OutputStream의 자손들이며, 각각 읽고 쓰는데 필요한 추상메서드를 자신에 맞게 구현해 놓았다.


※ InputStream과 OutputStream에 정의된 읽기와 쓰기를 수행하는 메서드

 InputStream

 OutputStream

 abstract int read() 

 abstract void write(int c

 int read(byte cbuf[]) 

 void write(byte cbuf[]) 

 int read(byte cbuf[], int offset, int length) 

 void write(byte cbuf[], int offset, int length) 

InputStream의 read()와 OutputStream의 write(int b)는 입출력의 대상에 따라 읽고 쓰는 방법이 다를 것이기 때문에 각 상황에 알맞게 구현하라는 의미에서 추상메소드로 정의되어 있다.


cf.) read()의 반환타입이 byte가 아니라 int인 이유는 read()의 반환값의 범위가 0~255와 -1이기 때문이다.


public abstract class InputStream{

...

abstract int read();

// 입력스트림으로부터 1byte를 읽어서 반환한다. 읽을 수 없으면 -1을 반환한다.

int read(byte[] b, int off, int len){

  // 입력스트림으로부터 ien개의 byte를 읽어서 byte배열 b의 off위치부터 저장한다.

...

for(int i-off, i < off+len; i++){

b[i]=(byte)read();

// read()를 호출해서 데이터를 읽어서 배열을 채운다.

}

...

}

int read(byte[] b){

  // 입력스트림으로부터 byte배열 b의 크기만큼 데이터를 읽어서 배열 b에 저장한다.

return read(b, 0, b.length);

}

...


4. 보조 스트림

스트림의 기능을 보완하기 위한 보조 스트림이 제공된다. 보조 스트림은 실제 데이터를 주고받는 스트림이 아니기 때문에 데이터를 입출력할 수 있는 기능을 없지만. 스트림의 기능을 향상시키거나 새로운 기능을 추가할 수 있다.그래서 보조 스트림만으로는 입출력 처리를 할 수 없고, 스트림을 먼저 생성한 다음에 이를 이용해서 보조 스트림을 생성해야 한다.

예를 들어 test.txt라는 파일을 읽기 위해 FileInputStream을 사용할 때, 입력 성능을 향상시키기 위해 버퍼를 사용하는 보조 스트림인 BufferedInputStream을 사용하는 코드는 다음과 같다.


// 먼저 기반스트림을 생성한다.FileInputStream은 InputStream의 자손 클래스이다.

FileInputStream fis = new FileInputStream("test.txt");

// 기반스트림을 이용해서 보조스트림을 생성한다.

BufferedInputStream bis = new BufferedInputStream(fis);

// 보조 스트림인 BufferedInputStream으로 부터 데이터를 읽는다.

bis.read();

코드 상으로는 보조스트림인 BufferedinputStream이 입력기능을 수행하는 것처럼 보이지만, 실제 입력기능은 BuffterdinputStream과 연결된 FileInputStream이 수행하고, 

보조스트림인 BufferedinputStream은 버퍼만을 제공한다.


모든 보조 스트림은 InputStream과 OutputStream의 자손들이므로 입출력 방법이 같다.


5. 문자기반 스트림

바이트 기반이라 함은 입출력의 단위가 1byte라는 뜻이다. 문자 데이터를 입출력 할 때는 바이트기반 스트림 대신 문자기반 스트림을 사용하자.

Java에서는 한 문자를 의미하는 char형이 1byte가 아니라 2 byte이기 때문에 바이트기반의 스트림으로 2byte인 문자를 처리하는데 어려움이 있다.

이 점을 보완하기 위해서 문자기반의 스트림이 제공된다. 문자데이터를 입출력할 때는 바이트기반 스트림 대신 문자기반 스트림을 사용하자.




'Programing > Java' 카테고리의 다른 글

[Java] 문자 기반 스트림  (0) 2014.12.16
[Java] 바이트 기반의 스트림  (0) 2014.12.16
[Java] 제네릭  (3) 2014.12.14
[Java] 컬렉션 프레임워크  (0) 2014.12.14
[Java] static  (0) 2014.12.14

[Spring] iBatis 2


iBatis 2


1. sqlMapConfig.xml: 설정파일

iBATIS 의 메인 설정 파일인 SQL Map XML Configuration 파일(이하 sqlMapConfig.xml 설정 파일) 작성과 상세한 옵션 설정에 대해 알아본다.


(1) sqlMapConfig.xml

SqlMapClient 설정관련 상세 내역을 제어할 수 있는 메인 설정 파일로 주로 transaction 관리 관련 설정 및 다양한 옵션 설정, Sql Mapping 파일들에 대한 path 설정 등을 포함한다.

<?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="META-INF/spring/jdbc.properties" />

 

<settings cacheModelsEnabled="true" enhancementEnabled="true"

lazyLoadingEnabled="true" maxRequests="128" maxSessions="10"

maxTransactions="5" useStatementNamespaces="false"

defaultStatementTimeout="1" />

 

<typeHandler javaType="java.util.Calendar" jdbcType="TIMESTAMP"

callback="egovframework.rte.psl.dataaccess.typehandler.CalendarTypeHandler" />

 

<transactionManager type="JDBC">

<dataSource type="DBCP">

<property name="driverClassName" value="${driver}" />

<property name="url" value="${dburl}" />

<property name="username" value="${username}" />

<property name="password" value="${password}" />

<!-- OPTIONAL PROPERTIES BELOW -->

<property name="maxActive" value="10" />

<property name="maxIdle" value="5" />

<property name="maxWait" value="60000" />

<!-- validation query -->

<!--<property name="validationQuery" value="select * from DUAL" />-->

<property name="logAbandoned" value="false" />

<property name="removeAbandoned" value="false" />

<property name="removeAbandonedTimeout" value="50000" />

<property name="Driver.DriverSpecificProperty" value="SomeValue" />

</dataSource>

</transactionManager>

<sqlMap resource="META-INF/sqlmap/mappings/testcase-basic.xml" />

<sqlMap ../>

..

</sqlMapConfig>

- properties : 표준 java properties (key=value 형태)파일에 대한 연결을 지원하며 설정 파일내에서 ${key} 와 같은 형태로 properties 형태로 외부화 해놓은 실제의 값(여기서는 DB 접속 관련 driver, url, id/pw)을 참조할 수 있다. resource 속성으로 classpath, url 속성으로 유효한 URL 상에 있는 자원을 지정 가능하다.


- settings : 이 설정 파일을 통해 생성된 SqlMapClient instance 에 대하여 다양한 옵션 설정을 통해 최적화할 수 있도록 지원한다. 모든 속성은 선택사항(optional) 이다.

 속 성

설 명 

example, default 

 maxRequests

 같은 시간대에 SQL문을 실행한 수 있는 thread 의 최대 갯수 지정.

 maxRequests=“256”, 512

 maxSessions

 주어진 시간에 활성화될 수 있는 session(또는 client) 수 지정.

 maxSessions=“64”, 128

 maxTransactions

 같은 시간대에 SqlMapClient.startTransaction() 에 들어갈 수 있는 최대 갯수    지정.

 maxTransactions=“16”, 32

 cacheModelsEnabled

 SqlMapClient 에 대한 모든 cacheModel 에 대한 사용 여부를 global 하게 지  정.

 cacheModelsEnabled=“true”, true (enabled) 

 lazyLoadingEnabled

 SqlMapClient 에 대한 모든 lazy loading 에 대한 사용 여부를 global 하게 지정

 lazyLoadingEnabled=“true”, true (enabled)

 enhancementEnabled

 runtime bytecode enhancement 기술 사용 여부 지정.

 enhancementEnabled=“true”, false (disabled)

 useStatementNamespaces

 mapped statements 에 대한 참조 시 namespace 조합 사용 여부 지정. true  인 경우  queryForObject(“sqlMapName.statementName”); 과 같이 사용함.

 useStatementNamespaces=“false”, false (disabled)

 defaultStatementTimeout

 모든 JDBC 쿼리에 대한 timeout 시간(초) 지정, 각 statement 의 설정으로  override 가능함. 모든 driver가  이 설정을 지원하는 것은 아님에 유의할 것.

  정하지 않는 경우 timeout 없음(cf. 각 statement 설정에 따라)

 classInfoCacheEnabled

 introspected(java 의 reflection API에 의해 내부 참조된) class의 캐쉬를 유지  할지에 대한 설정

 classInfoCacheEnabled=“true”, true (enabled)

 statementCachingEnabled

 prepared statement 의 local cache 를 유지할지에 대한 설정

 tatementCachingEnabled=“true”, true (enabled)


- typeHandler : javaType ↔ jdbcType 간의 변환(prepared statement 의 파라메터 세팅/resultSet 의 값 얻기)을 처리하는 typeHandler 구현체를 등록할 수 있다.


- transactionManager : 트랜잭션 관리 서비스를 설정할 수 있다. type 속성으로 어떤 트랜잭션 관리자를 사용할지 지시할 수 있는데, JDBC, JTA, EXTERNAL 의 세가지 트랜잭션 관리자가 프레임워크에 포함되어 있다. 위에서는 일반적인 Connection commit()/rollback() 메서드를 통해 트랜잭션을 관리하는 JDBC 타입으로 설정하였다.


- dataSource : transactionManager 설정의 일부 영역으로 DataSource 에 대한 설정이다. type 속성으로 어떤 DataSourceFactory 를 사용할지 지시할 수 있는데, SIMPLE, DBCP, JNDI 의 세가지 설정이 가능하다. 위에서는 Apache Commons DBCP(Database Connection Pool) 를 사용하는 DBCP 타입으로 설정하였다. iBATIS 는 DBCP 속성에 대한 설정을 직접 명시할 수 있도록 지원하고 있다. iBATIS 2 버전 이후로는 단일 dataSource 만 지원한다.


- sqlMap : 명시적으로 각 SQL Map XML 파일을 포함하도록 설정한다. classpath (resource 속성으로 지정) 나 url(url 속성으로 지정) 상의 자원을 stream 형태로 로딩하게 된다. 위에서는 classpath 상에 존재하는 sql 매핑 파일을 지정하였다.


이 외에도 typeAlias(global 한 type 별명-풀패키지명에 비해 간략히), resultObjectFactory (SQL 문의 실행에 의한 결과 객체의 생성을 iBATIS 의 ResultObjectFactory 인터페이스를 구현한 factory 클래스를 통해 처리할 수 있도록 지원) 에 대한 설정이 가능하다. DTD 상 sqlMap 설정은 하나 이상이 필요하고 다른 설정은 선택사항이다.


2. SQL Map XML 파일: 매핑파일

sql 매핑 파일은 iBATIS 의 mapped statement 형태로 처리될 수 있도록 SQL Map 문서 구조에 따라 다양한 옵션 설정 및 매핑 정의, sql 문을 외부화하여 저장하는 파일이다.


(1) sqlMap.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="Dept">

<typeAlias alias="deptVO" type="egovframework.DeptVO" />

 

<resultMap id="deptResult" class="deptVO">

<result property="deptNo" column="DEPT_NO" />

<result property="deptName" column="DEPT_NAME" />

<result property="loc" column="LOC" />

</resultMap>

 

<insert id="insertDept" parameterClass="deptVO">

insert into DEPT

          (DEPT_NO,

           DEPT_NAME,

           LOC)

values     (#deptNo#,

           #deptName#,

           #loc#)

</insert>

 

<select id="selectDept" parameterClass="deptVO" resultMap="deptResult">

<![CDATA[

select DEPT_NO,

      DEPT_NAME,

      LOC

from   DEPT

where  DEPT_NO = #deptNo#

]]>

</select>

</sqlMap>

- typeAlias : 현재 매핑 파일내에서 객체에 대한 간략한 alias 명을 지정함. (cf. 매우 자주 쓰이는 class 의 경우 sqlMapConfig.xml 에 global 하게 등록하는 것이 좋음)


- resultMap : DB 칼럼명(select 문의 칼럼 alias) 과 결과 객체의 attribute 에 대한 매핑 및 추가 옵션을 정의함.


- insert, select : 각 statement 타입에 따른 mapped statement 정의 요소 예시. 유형에 따라 insert/update/delete/select/procedure/statement 요소 사용 가능

이 외에도 parameterMap, resultMap 에 대한 상세 정의, cacheModel 설정, sql 문 재사용을 위한 sql 요소 설정이 나타날 수 있다. 각각에 대한 상세 사항은 관련 가이드를 참고한다.


3. sqlMapclientFactoryBean 작성

SqlMapClientFactoryBean 은 iBATIS 의 SqlMapClient 를 생성하는 FactoryBean 구현체로, Spring 의 context 에 iBATIS 의 SqlMapClient 를 셋업하는 일반적인 방법으로 사용된다. 여기서 얻어진 SqlMapClient 는 iBATIS 기반 DAO 에 dependency injection 을 통해 넘겨지게 된다.

<!-- dataSource 설정 -->

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

<property name="driverClassName" value="${driver}"/>

<property name="url" value="${dburl}"/>

<property name="username" value="${username}"/>

<property name="password" value="${password}"/>

<property name="defaultAutoCommit" value="false"/>

<property name="poolPreparedStatements" value="true"/>

</bean>

 

<!-- SqlMap setup for iBATIS Database Layer -->

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">

<property name="configLocation" value="classpath:/META-INF/sqlmap/sqlMapConfig.xml"/>

<property name="dataSource" ref="dataSource"/>

</bean>

- dataSource : 데이터베이스 연결 추상화를 제공하는 DataSource 설정. 위에서는 Apache Commons DBCP 를 사용하였으며 DB 접속과 관련된 설정은 property-placeholder 를 사용하여 외부화 하였다.

- sqlMapClient : Spring의 iBATIS 연동을 위한 SqlMapClientFactoryBean 설정으로 configLocation 속성을 통해 지정한 iBATIS 메인 설정 파일인 sqlMapConfig.xml에 대해 iBATIS 의 SqlMapClient instance 를 생성하여 Spring 환경에서 사용 가능토록 한다. 

Spring 의 dataSource 를 iBATIS 에 넘길 수 있도록 injection 을 지시하고 있으며 이로 인해 iBATIS 설정 파일 자체에서는 dataSource 및 transaction 설정 필요없이(Spring 환경에서는 iBATIS 기반 DAO 호출 이전에 서비스 단에서 선언적인 트랜잭션 처리를 해주었을 것임) Spring 이 제공하는 유연한 dataSource 및 트랜잭션 처리를 사용하게 된다.


Spring 현재 버전에서는 configLocations 속성을 추가하여 sql-map-config.xml 에 대한 패턴 표현식이나 복수 연동(런타임에 하나의 통합 설정으로 merge 됨)도 지원하고 있다. useTransactionAwareDataSource 속성으로 SqlMapClient 에 대해 Spring 이 관리하는 transaction timeout 을 함께 적용할 수 있는 transaction-aware DataSource 를 사용하도록 설정 가능하며(default), lobHandler 속성을 통해 Spring 의 lobHandler 를 설정할 수도 있다.


cp.) mappingLocations 지원

또한 iBATIS 사용 환경에서의 중요한 개선 사항으로 mappingLocations 속성을 통해 기존에 iBATIS 메인 설정 파일 내에서 sqlMap 태그로 일일이 지정하여야만 했던 sql 매핑 파일에 대해 Spring 의 SqlMapClientFactoryBean 빈 설정파일에서 Spring 의 유연한 리소스 추상화를 적용하여 리소스 패턴 형태로 일괄 지정이 가능하다. 이 경우 sql 매핑 파일들의 위치는 sqlMapConfig 설정 파일과 런타임에 merge 되도록 세팅된다. 이 방법은 Spring 2.5.5 이상, iBATIS 2.3.2 이상에서만 지원됨에 유의한다.

<!-- SqlMap setup for iBATIS Database Layer -->

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">

<property name="configLocation" value="classpath:/META-INF/sqlmap/sqlMapConfig.xml" />

<property name="mappingLocations" value="classpath:/META-INF/sqlmap/mappings/testcase-*.xml" />

<property name="dataSource" ref="dataSource" />

</bean>

단 위와 같이 일괄 sql 매핑 파일 지정을 Spring 설정 파일에 지시하였더라도 iBATIS 의 sqlMapConfig.xml 의 DTD(http://www.ibatis.com/dtd/sql-map-config-2.dtd) 에 sqlMap 태그가 최소 1개 이상이 나타나야 하도록 지정되어 있으므로 아래와 같이 dummy sql 매핑 파일 하나를 지정하는 sqlMapConfig.xml 로 작성하면 편할 것이다.


4. SQLMapClient를 구현하는 DAO 작성

사원정보의 SELECT, INSERT 작업을 담당하는 DAO를 작성한다.


(1) EmpDAO class 작성

- SqlMapClient를 이용하여 사원정보의 DB 작업을 담당하는 DAO를 작성한다.

- com.spring.mvc.emp.dao 패키지를 생성한다.

- SqlMapClientDaoSupport를 상속받아 getSqlMapClientTemplate() 메소드를 이용하여 등록 및 조회작업을 한다.

cf.) 

@Repository 어노테이션을 EmpDAO의 class선언부에 추가한다.

@Repository 어노테이션을 지정하면 SqlMapClient 클래스에 대한 객체가 자동생성 된다.


Spring 의 SqlMapClientDaoSupport 클래스는 iBATIS 의 SqlMapClient data access object 를 위한 편리한 수퍼 클래스로 이를 extends 하는 서브 클래스에 SqlMapClientTemplate 를 제공한다. 

SqlMapClientTemplate 는 iBATIS 를 통한 data access 를 단순화하는 헬퍼 클래스로 SQLException 을 Spring dao 의 exception Hierarchy 에 맞게 unchecked DataAccessException 으로 변환해 주며 Spring 의 JdbcTemplate 과 동일한 처리 구조의 SQLExceptionTranslator 를 사용할 수 있게 해준다. 또한 iBATIS 의 SqlMapExecutor 의 실행 메서드에 대한 편리한 mirror 메서드를 다양하게 제공하므로 일반적인 쿼리나 insert/update/delete 처리에 대해 편리하게 사용할 수 있도록 권고된다. 

그러나 batch update 와 같은 복잡한 수행에 대해서는 Spring 의 SqlMapClientCallback 에 대한 명시적인 구현(보통 anonymous inner class 로 작성)이 필요하다.

com.spring.mvc.emp.dao.EmpDAO.java

package com.spring.mvc.emp.dao;


import java.util.List;

import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;

import com.spring.mvc.emp.model.Emp;


public class EmpDAO extends SqlMapClientDaoSupport {

    private static final String NAMESPACE = "emp.";


    @SuppressWarnings("unchecked")

    public List<Emp> selectEmpList() {

        return (List<Emp>) getSqlMapClientTemplate().queryForList(NAMESPACE + "selectEmpList");

    }


    public void insertEmp(Emp emp) {

        getSqlMapClientTemplate().insert(NAMESPACE + "insertEmp", emp);

    }

}


(2) plicationContext-dao.xml 설정

applicationContext-dao.xml에 EmpDAO 빈등록 작업을 한다.

iBATIS 연동 DAO 에 sqlMapClient 빈을 주입(의존성 주입)해 주어야 한다.

/srping/applicationContext-dao.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.xsd 

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

      

    <bean id="empDAO" class="com.spring.mvc.emp.dao.EmpDAO" >

        <property name="sqlMapClient" ref="sqlMapClient"/>           

    </bean>   

</beans>

iBATIS 연동 DAO 는 SqlMapClientDaoSupport 를 extends 하고 있으며, getSqlMapClientTemplate() 를 통해 SqlMapClientTemplate 를 얻어 iBATIS 의 data access 처리를 래핑하여 실행토록 처리하고 있다.


'Programing > Spring' 카테고리의 다른 글

[Spring] 어노테이션 기반 설정  (0) 2014.12.17
[Spring] 외부 설정 프로퍼티  (0) 2014.12.17
[Spring] iBatis 1  (0) 2014.12.16
[Spring] web.xml 기본 설정  (1) 2014.12.13
[spring] 스프링 MVC 인터페이스 구현 클래스  (0) 2014.12.13

[Spring] iBatis 1


iBatis 1


iBatis는 자바오브젝트와 SQL문 사이의 자동 매핑 기능을 지원하는 ORM 프레임워크이다.

iBatis는 코드 내에서 자바오브젝트만을 이용해 데이터 로직을 작성할 수 있게 해주고, SQL을 별도의 파일로 분리해서 관리하게 해주며, 오브젝트-SQL 사이의 파라미터 매핑 작업을 자동으로 해주기 때문에 많은 인기를 얻고 있는 기술이다.

→ JDBC 코드 작성의 불편함을 제거해주고, 도메인 오브젝트나 DTO를 중심으로 개발이 가능하다는 장점이 있다. iBatis의 가장 큰 특징은 SQL을 자바 코드에서 분리해서 별도의 XML 파일 안에 작성하고 관리할 수 있다는 점이다.

XML에 담긴 SQL과 자바오브젝트 사이의 매핑은 이름 치환자와 빈 프로퍼티 사이의 매핑을 이용한다.

스프링 DataSource 빈 사용, 스프링 트랜잭션 적용, 예외 자동변환, 템플릿/콜백 스타일의 템플릿, SqlMapClient 팩토리 빈 등을 지원한다.


1. Spring과 iBatis의 연동 


(1) Dependency 설정

ibatis와 spring-orm의 dependency 설정을 pom.xml 에서 확인한다.

ibatis 버전이 2.3.4.726 인지 확인한다.

pom.xml

<dependency>

   <oupId>org.apache.ibatis</groupId>

   <artifactId>ibatis-sqlmap</artifactId>

   <version>2.3.4.726</version>

</dependency>

<dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-orm</artifactId>

    <version>${spring-core-version}</version>

</dependency>


(2) Spring datasource 설정

/spring/applicationContext-datasource.xml

<!-- Apache Commons DBCP DataSource -->

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

    <property name="driverClassName" value="${JDBC.Driver}"/>

    <property name="url" value="${JDBC.ConnectionURL}"/>

    <property name="username" value="${JDBC.Username}"/>

    <property name="password" value="${JDBC.Password}"/>

    <property name="maxActive" value="15" />

    <property name="initialSize" value="15" />

    <property name="maxIdle" value="15" />

    <property name="minIdle" value="15" />

    <property name="testOnBorrow" value="false" />

    <property name="validationQuery" value="select 1" />

    <property name="timeBetweenEvictionRunsMillis" value="10000" />

    <property name="testWhileIdle" value="true" />  

    <property name="numTestsPerEvictionRun" value="3" />        

    <property name="minEvictableIdleTimeMillis" value="-1" />

</bean>


(3) sqlMapConfig, sqlMap XML 파일 설정

sqlMapConfig, sqlMap XML 파일을 생성한다.


1) sqlMapConfig.xml

cp.) 설정파일 역할: 설정파일에는 데이터소스, 트랜잭션 매니저, 매핑 리소스 파일 목록, 프로퍼티, 타입 별칭과 핸들러, 오브젝트 팩토리와 설정 프로퍼티 값을 넣을 수 있다.

resources/datasource/sqlMapConfig.xml 파일로 생성한 예이다.

/datasource/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>

<settings

cacheModelsEnabled="true"

enhancementEnabled="true"

lazyLoadingEnabled="true"

useStatementNamespaces="true"

/>

// 매핑정보를 담은 파일의 클래스패스를 지정한다. 매핑파일마다 <sqlMap> 태그를 추가하면된다.

<sqlMap resource="sqlmap/Emp.xml"/>

</sqlMapConfig>


2) sqlMap.xml

cp.) 매핑파일 역할: 매핑의 목적--> DB의 데이터인 SQL문(DB의 테이블)을 자바의 객체(클래스)로 전환하는 것이 주목적이다.

SQL-오브젝트 사이의 매핑정보는 XML 파일로 만들어두면 된다. 매핑정보에는 사용할 SQL문과 SQL 파라미터, 실행 결과를 어떻게 자바오브젝트로 변환하는지가 담겨 있다.

sqlMap.xml 파일에 아래와 같이 SELECT, INSERT 문장을 작성한다.

/src/main/resources/sqlmap/sqlMap.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">

<sqlMap namespace="emp">

  <typeAlias alias="emp" type="com.spring.mvc.emp.model.Emp"/>  

  <select id="selectEmpList" resultClass="emp" >

SELECT empno, ename, job, sal, NVL(mgr,0) mgr, hiredate

FROM  emp

  </select>     

  <insert id="insertEmp"  parameterClass="emp"  >

     INSERT INTO emp (empno, ename, job, sal, mgr,  hiredate, deptno)

        VALUES (#empno#, #ename#, #job#, #sal#, 0, #hiredate#, #deptno#)

    </insert> 

</sqlMap>

테이블의 컬럼 이름과 매핑할 오브젝트의 프로퍼티 이름이 일치하면 특별한 매핑정보 없이도 자동으로 파라미터와 결과의 전환이 가능하다. 이름이 일치하지 않은 경우라면 컬럼-프로퍼티 사이의 매핑정보를 추가해주면 된다.


(4) sqlMapClient 설정

applicationContext-datasource.xml 파일에 sqlMapClient 아이디로 SqlMapClientFactoryBean 빈을 등록한다.

sqlMapClient 등록시 dataSource와 sqlMapConfig XML 파일의 의존성주입을 추가한다.

CLOB 데이터 타입을 사용하기 위해서는 DefaultLobHandler의 의존성 주입을 추가해야 한다.


cp.) iBatis의 핵심 API는 SqlMapClient 인터페이스에 담겨 있다. JDBC가 Connection, statement, ResultSet을 생성해 사용하듯이 iBatis를 이용하려면 이 SqlMapCilent를 구현한 오브젝트가 필요하다. 

SqlMapClient는 SqlMapCilentBuilder를 이용해 코드에서 생성할 수 있다. 

하지만 스프링에서는 SqlMapClient를 빈으로 등록해주는 팩토리 빈의 도움이 필요하다. 스프링이 제공하는 SqlMapClient용 팩토리 빈은 SqlMapClientFactoryBean이다. 

이 빈을 이용해서 DAO에서 사용할 SqlMapClient를 빈으로 등록해줘야 한다.


cp.) DAO가 iBatis 기능을 사용하려면 SqlMapClient가 필요하다. JDBC의 Connection처럼 모든 데이터 액세스 작업에서 필요로 하는 오브젝트이다. 스프링에서는 SqlMapClient를 싱글톤으로 등록해서 DAO에서 DI 받아 사용할 수 있다. SqlMapClient는 멀티스레드에서 공유해서 사용해도 안전한 오브젝트이다. SqlMapClient의 구현 클래스를 직접 빈으로 등록하는 대신 다음과 같이 SqlMapClientFactoryBean을 이용해 팩토리 빈이 생성해줘야 한다. 필요한 프로퍼티는 

Datasource와 앞에서 만들어둔 설정파일의 위치다.

/spring/applicationContext-datasource.xml

<!-- SqlMap setup for iBATIS Database Layer -->

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">

    <property name="configLocation" value="classpath:datasource/sql-map-config.xml"/>

    <property name="dataSource" ref="defaultDataSource" />

    <property name="lobHandler" ref="lobHandler"/>

</bean>


/filter/build-local.filter

JDBC.Driver=oracle.jdbc.driver.OracleDriver

JDBC.ConnectionURL=jdbc:oracle:thin:@127.0.0.1:1521:ORCL

JDBC.Username=scott

JDBC.Password=tiger



'Programing > Spring' 카테고리의 다른 글

[Spring] 외부 설정 프로퍼티  (0) 2014.12.17
[Spring] iBatis 2  (0) 2014.12.16
[Spring] web.xml 기본 설정  (1) 2014.12.13
[spring] 스프링 MVC 인터페이스 구현 클래스  (0) 2014.12.13
[Spring] 스프링 MVC 패턴 개요  (0) 2014.12.13