레이블이 JAVA인 게시물을 표시합니다. 모든 게시물 표시
레이블이 JAVA인 게시물을 표시합니다. 모든 게시물 표시

[확장 예제] 스트럿츠를 위한 모델2 개념잡기 by VINS - Build in Concept MVC2(Model2) for Struts

앞서 스트럿츠를 위한 mvc2 개념 잡기에 있어서 MessageProcess서블릿에 고정값이 었던 부분을


값을 받아오고 처리하고 또 그 값을 request객체에 담아 화면으로 출력하는 확장 예제에 대한 확장 소스 공개 부분입니다.


 


1. 생략


2. Command.properties (한줄 더 추가)


-----------------------------------------------------------------------------------------------------


/message.do=mvc2.controller.MessageProcess
/parameter.do=mvc2.controller.MessageProcessEx


------------------------------------------------------------------------------------------------------


이제 호출은  http://localhost/mvc2/parameter.do?message=vins 으로 하자


 



3. Contoller 서블릿 구현 (이하 소스, 이놈에 네이버 에디터가 깨먹지 않아야 하는데 ㅋㅋ)



------------------------------------------------------------------------------------------------------


앞선 강의에서와 소스 동일


------------------------------------------------------------------------------------------------------




설명도 생략


 


4. CommandProcess.java 구현 (이 놈도 동일)


------------------------------------------------------------------------------------------------------


 


------------------------------------------------------------------------------------------------------



 


5. MessageProcess.java -> 이 놈과 같은 역활의 새로운 파일 생성


5. MessageProcessEx.java  (EX 확장이르는 의미로 붙였음)- 소스 역시 새로운 소스임


------------------------------------------------------------------------------------------------------


package mvc2.controller;


import javax.servlet.http.*;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;


public class MessageProcessEx implements CommandProcess {
    public String requestPro(HttpServletRequest request,HttpServletResponse response) throws Throwable {
  String message = request.getParameter("message"); //파라미터 확인
  
  Object result = null;
  if (message == null) {
   result="저는 아무개 입니다";
  } else if (message.equals("vins")) {
   result="저는 by Vins 입니다";
  } else {
   result="이런 파라미터는 무시";
  }
  
  request.setAttribute("result", result);
  request.setAttribute("myname", message);
    return "/view/messageview.jsp";
 }
}


------------------------------------------------------------------------------------------------------


리턴 되는 디스플레이 JSP하나 새로 생성


 



6. 디스플레이


------------------------------------------------------------------------------------------------------


<%@ page contentType="text/html;charset=euc-kr" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
 <title>간단한 컨트롤러(Controller)의 사용 예제</title>
</head>
<body>
 <c:set var="result" value="${requestScope.result}"/>
 <c:set var="myname" value="${requestScope.myname}"/>
 
 결과 : <c:out value="${result}"/><br/>
 이름 : <c:out value="${myname}"/>
</body>
</html>


------------------------------------------------------------------------------------------------------


 


화면 출력


 http://localhost/mvc2/parameter.do?message=vins


------------------------------------------------------------------------------------------------------


결과 : 저는 by Vins 입니다
이름 : vins


------------------------------------------------------------------------------------------------------


나머지 값들은 적절히 바꿔 보세요..


 


[1편 강의를 보시려면]


http://mcpicdtl.blogspot.com/2009/02/2-by-vins-build-in-concept-mvc2model2.html

스트럿츠를 위한 모델2 개념잡기 by VINS - Build in Concept MVC2(Model2) for Struts

모델1, 모델2 또는 MVC1, MVC2라고 표현하기도 한다.

최근 스트럿츠, 스프링 등 프레임워크가 주류를 이루고 있으며 빠르게 스트럿츠를 학습하기 위한

접근법으로 급히 스트럿츠를 익히다 보니 스트럿츠를 쓰면 모델1, 2에 비해서 어떤 이점들이 있는지

모델2의 재조명으로 바라보고자 한다

모델1,2 이런것은 자바에만 국한된 것이 아니다.

자바로 인해 유명해진 모델링 기법이긴 하지만 어떤 언어를 막론하고 모델2의 구현을 위한 불가능한 언어는 없다

다만 자바 & JSP로 구현하기가 좀 쉽다

WAS서버(여기선 톰캣)가 이를 위한 준비를 이미 마친 상태고 개발자는 web.xml 정보만 컨트롤 함으로서

쉽게 구현할 수 있게 되어 있는 것이마

NT계열의 웹 서버인 IIS에선 ISAPI 필터의 구현으로 이 기능을 구현할 수 있다,

ASP, PHP WITH WINDOW 경우 어떤 언어로 구현하더라도 우선 요청을 받아 해당하는 파일을 호출하도록

되어진 부분이 IIS이기 때문이다.

LAMP를 사용하는 경우 아파치만으로 구현을 하는 것은 조금 암울하게 보일지 모르나

아파치의 설정등 어느정도의 전체 요청의 컨트롤이 가능한 요소를 구현할 수가 있다.

하지만 위 두가지 경우 전부 "구현을 해야 한다는 것이다".

그리고 "검증되지 않았다는 것이다".

가장 부담스러운 것은 모든 요청에 대한 필터를 가해야 한다는 것이다.

해서 JSP, JAVA 만큼 활성화 되고 있진 않은 듯 하다.

물론 닷넷 진행은 2.0 이후 점차, 모델 개념을 도입하고 있는 모습이 보이기도 한다.

실제 2003년까지만 해도 모델2등의 굉장히 생소하게만 느껴졌다

이미 자기가 경험해 본 패턴임에도 용어부터가 낮설게 느껴졌었다 .

그때까지 개발의 대부분은 당시 유행하던 3-TIRE, 코바, 프록시 객체 기법등 다양한 방법으로

1.디자인-2.비지니스로직-3.데이터로직에 이르는 3-tire를 구현하기 위해 노력은 했지만

결국 1.디자인이라는 이 부분에 대한 극복이 불가능했었다

이때 이 1.디자인 이라고 하는 부분은 asp, php, jsp를 뜻한다. 요청을 받고 해당하는 비지니스 객체를 통해

데이터를 처리한 후 최요 요청 받았던 해당 파일로 output이 일어난다는 얘기다

요청과 출력 그리고 이동에 대한 모든 부분을 해당 jsp파일(나머지 언어 생략)이 담당했다는 얘기다.

뭐가 불편한지 우리는 알지 못한다.

익숙하고 간편하다고 생각했기 때문이다. 그리고 무엇보다 배우기 쉽다.

하지만 그 가장 프론트에 위치한 그 파일을 디자인 개편, 로직 변경, 유지보수를 위해 HTML코딩에 CSS까지

덕지덕지 붙은 페이지를 열어 바꾸어 주어야 하는 부분을 수정하고 재배로를 하곤 했다.

자 어떤 것들을 들어낼 수 있는지 보자

우선 프론트쪽에 위치한 jsp파일의 역활중 2가지를 들어내어 디자인 소스과 구분 할 수 있다.

1. 디자인(프론트) 역활 (<- 기존의 모델1)

1) 요청 처리

2) 프로세스 처리(페이지 이동)

3) 디스플레이 처리(DB에서 가져온 내용을 출력한 다는 등의..)

2. 모델 2 적용

1) 요청 처리를 위한 컨트롤러 (서블릿 형태의..)

2) 프로세스 처리를 위한 디스페쳐 컨트롤러 (비지니스 로직에 포함 가능)

3) 디스플레이 (디자인 페이지)

자 위를 보면 3가지 역활은 그대로 있고 하나의 통합적인 페이지에서 더이상 이루어 지지 않는다

전체 요청을 처리하는 컨트롤러를 하나 생성하고 web.xml의 설정과 연동 되도록 하기 위해

init 또는 요청 처리 메소드에서 적절한 구현을 한 컨트롤러가 이제 부터 모든 요청을 관장한다.

이전 모델) 클라이언트 -> JSP <-> 비지니스로직 <-> 데이터로직

적용 모델) 클라이언트 -> 컨트롤러(서블릿) <->비지니스로직(프로세스처리) <-> 데이터로직

디스플레이

자 컨트롤러가 요청을 처리 한 후 비지니스로직 결과와 함께 디스플레이로 포워드 시켜 버린다.

그럼 데이터로직을 처리하고 받아온 데이터 값에 대한 공유는 하고 궁금해 하실텐데

이는 Request객체에 저장을 하여 공유해야할 범위내에서 공유를 하게 된다.

이 범위라는 것이 page, request, session, application 범위가 있는데 아주 간단히만 설명하면

request에 저장을 하면 페이지가 이어지는 요청 정보를 가져갈 경우 그 Request는 계속 공유된다.

예를 들어 로그인 화면에서 아이디/패스를 입력 후 로그인 과정을 거지는 전 페이지 구성에서 그 데이터를

공유하게 된다는 것이다. page객체에 저장하면 그 페이지를 벗어남과 동시에 값은 사라지고 만다.

이해가 되었을 것이다.

그럼 디스플레이는 결국 jsp코딩이 남게 되나요?

결과 부터 말하자면 그렇다.

하지만 <%...%>로 시작되는 부분이 아닌 EL, JSTL등의 기법으로 디자이너가 코드자체에 대한 컨트롤은 불가능 하도록

다양한 기법들이 제공 되고 있다.

즉 게시판 리스트 화면을 출력하기 위해 for 또는 while문 등이 HTML소스 위를 기어다는 일이 없도록

커스텀 태그도 제공하고 있어 실제 디자인과 개발소스간의 경함(?)이 많이 줄어들었다 할수 있겠다.

그럼 구체적인 부분에 대해서 언급 하겠다. (프로젝트명 mvc2 빌드패스 WEB-INF/classes)

1. web.xml 컨트롤러 및 요청에 대한 필터링

WEB-INF 바로 아래 위치하며 아래 부분이 추가 되어 있어야 함

------------------------------------------------------------------------------------------------------

생략...

<servlet>
<servlet-name>Controller</servlet-name>
<servlet-class>mvc2.controller.Controller</servlet-class>
<init-param>
<param-name>propertyConfig</param-name>
<param-value>C:/websource/jakarta-tomcat-5.5.7/webapps/mvc2/WEB-INF/Command.properties</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>Controller</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>

생략...

------------------------------------------------------------------------------------------------------

해석하면 서블릿 이름은 Controller이머 mvc2.controller라는 패키지에 속한다.

컨트롤러에서 사용할 명령파라미터의 세팅 정보는 WEB-INF바로 아래 Command.properties 파일에 저정 (메모장으로 열고 저장하면 됨)

위와 같이 서블릿 정보에 대한 매칭을 시키지 위해서 페어로 존재하는

servlet-mapping의 설정은 모든 .do형식의 요청은 서블릿 Controller를 호출하도록 매칭 시킴.

(참고 여기서 do는 하다라는 의미의 실행하라는 명령으로 생각하면 됨 관용적임, 네이버 경우는 NHN을 사용하는 것 같음)

2. Command.properties (어떤 이름이라도 관계 없음, web.xml의 해당 부분과 일치만 한다면)

여기엔 단순하게 하나의 명령만 실행되도록 해 보겠다.

물론 게시판 간은 경우 list.do, write.do, modify.do 등등 처리할 파일들이 많겠지만.

여기선 아주 단순하게 파라미터로 전달 되어온 메세지하나 처리하도록 구현할 예정이므로 아래 한줄만 넣어주기 바란다.

------------------------------------------------------------------------------------------------------

/message.do=mvc2.controller.MessageProcess

------------------------------------------------------------------------------------------------------

프로젝트 메인 루트에서 message.do형식의 URL패턴이면 mvc2.controller.MessageProcess 서블릿을 호출하라는 뜻이다.

예를 들면 http://localhost/mvc2/message.do .....

3. Contoller 서블릿 구현 (이하 소스, 이놈에 네이버 에디터가 깨먹지 않아야 하는데 ㅋㅋ)

------------------------------------------------------------------------------------------------------

package mvc2.controller;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.Properties;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Controller extends HttpServlet
{
private java.util.Map commandMap = new java.util.HashMap();

public void init(ServletConfig config) throws ServletException {

String propertyConfig = config.getInitParameter("propertyConfig");
Properties prop = new Properties();

FileInputStream fs = null;

try
{
fs = new FileInputStream(propertyConfig);
prop.load(fs);
}
catch (IOException e)
{
throw new ServletException(e);
} finally {
if(fs!=null) try{fs.close();} catch (IOException ex){}
}

Iterator keyIter = prop.keySet().iterator();

while(keyIter.hasNext()) {
String command = (String)keyIter.next();
String className = prop.getProperty(command);

try
{
Class commandClass = Class.forName(className);
Object commandInstance = commandClass.newInstance();
commandMap.put(command, commandInstance);
}
catch (ClassNotFoundException e)
{
throw new ServletException(e);
}
catch (InstantiationException e)
{
throw new ServletException(e);
}
catch (IllegalAccessException e)
{
throw new ServletException(e);
}
}
}

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
requestPro(request, response);
}

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
requestPro(request, response);
}

private void requestPro(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
CommandProcess comp = null;
String view = null;
try
{
String command = request.getRequestURI();

if(command.indexOf(request.getContextPath())==0) {
command = command.substring(request.getContextPath().length());
}
comp = (CommandProcess)commandMap.get(command);
view = comp.requestPro(request, response);
}
catch (Throwable e)
{
throw new ServletException(e);
}

RequestDispatcher disp = request.getRequestDispatcher(view);
disp.forward(request,response);
}
}

------------------------------------------------------------------------------------------------------

초기화 메소드 init()에서 web.xml에서 필요한 정보를 읽어와 해쉬맵(HashMap)객체에 저장하는 것을 볼수 있다.

doGet, doPost는 requestPro(request, response)형식의 메소드를 내부적으로 호출하게 되어 있다.

doGet, doPost에 아무것도 없는 것은 간략하게 하기 위합니다.

필요한 보안적 처리 및 필터는 보완을 해가면 사용하길 바란다.

requestPro 메소드를 보면 첫줄에 CommandProcess comp = null; 형식을 선언하고

그 보다 몇줄 더 아해 부분에 comp = (CommandProcess)commandMap.get(command); 이 부분에서 암시적 형변환을 한다.

이 부분은 web.xml 파일에서 propertyConfig라는 파라미터의 값의 Command.properties 파일 내용일 읽고

그 서블릿의 키 값과 객체의 인스턴스 형태를 해쉬맵에 저장한다.

그럼 키에 /message.do 가 남고 값에는 mvc2.controller.MessageProcess의 인스턴스가 지정된다.

이때 주목할 것은 난데 없는 CommandProcess 와 MessageProcess 두 오브젝트이다.

어찌됐든 디스페쳐가 포워드 해야 할 경로 정보를 받아 request값과 함께 디스플레이 화면으로 보낸다 (자세한 설명은 뒤..)

4. CommandProcess.java 구현 (인터페이스)

------------------------------------------------------------------------------------------------------

package mvc2.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

//요청 파라미터로 명령어를 전달하는 방식의 슈퍼 인터페이스
public interface CommandProcess {
public String requestPro(HttpServletRequest request, HttpServletResponse response)
throws Throwable;
}

------------------------------------------------------------------------------------------------------

인터페이스를 사용해 타입을 통일한다. (OOP상속의 단점을 피하고 횡적설계 인터페이스의 확장성을 고려한...)

사실 이 부분은 형변화 하지 않고 쓰면 없어도 그만이다.

뭐 전체적으로 컨트롤러의 해당 부분을 없에 버리면 그만이지만. 이 예제에서는 CommandProcess 서블릿(인터페이스)을

단 하나의 서블릿이 구현하고 있어 의미를 모를수 있으나 같은 형식의 구현체들이 여럿 있을 경우

if else / switch를 해 가며 해당 형마다 호출해 줄수는 없는 일이다. 지금 이해가 가지 않더라도 우선 따라가자 .

5. MessageProcess.java (구현체)

------------------------------------------------------------------------------------------------------

package mvc2.controller;

import javax.servlet.http.*;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class MessageProcess implements CommandProcess {
public String requestPro(HttpServletRequest request,HttpServletResponse response) throws Throwable {
request.setAttribute("message", "지금은 고정값으로 정합니다.");
return "/view/process.jsp";
}
}

------------------------------------------------------------------------------------------------------

request객체에 값을 세팅하고 난 후 3번 Contoller서블릿에서 전달해야 할 디스플레이 소스에 값을 전달하도록 리턴

그럼 컨트롤러의 requestPro메소드의 마지막 2줄이었던 RequestDispatcher가 값view를 받아 request, resposne함께

프로세스를 전힝시킨다.

RequestDispatcher disp = request.getRequestDispatcher(view);
disp.forward(request,response);

6. 디스플레이

------------------------------------------------------------------------------------------------------

<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>요청 파라미터를 명령어로 전달하는 예제</title>
</head>
<body>
처리결과 : <c:set var="message" value="${requestScope.message}"/>
<c:out value="${message}"/>
</body>
</html>

------------------------------------------------------------------------------------------------------

<c:out value="${message}"/> 여기서 ${message}은 5번 MessageProcess서블릿에서

세팅했던 값이다.

자 간단하게 MVC2에 대한 개념 및 구성에 대해 알아보았다.

자바를 잘 모르고 스트럿츠로 가자라고 하면 곧 포기하게 될 것이다.

자바를 안다고 하더라도 모델을 개념 없이 스트럿츠로 가더라도 힘든 길을 가게 될 것이다.

모델2까지 이해하고 가면 돌아가는 것 같지만 결국 스트럿츠에 모델2가 어느정도 개념이 녹아 있기 때문에

결국 돌아가는 길이 빨리 학습할 수 있는 길이라는 것을 명심했으면 한다.

"계단은 한번에 한계단씩 오르듯이...." 쩝

이건 거짓말이다.

"계단은 한번에 여러 계단을 뛰어 오를수 있지만 나이는 한번에 여러살 못 먹듯이 ..."

단계를 거치는 것이 곧 지름길이다.



[확장 예제를 보고 싶은 경우]
http://mcpicdtl.blogspot.com/2009/02/2-by-vins-build-in-concept-mvc2model2_03.html

Cannot create JDBC driver of class '' for connect URL 'null' 에러가 발생할 경우

Cannot create JDBC driver of class '' for connect URL 'null'
톰캣에서 JNDI Datasource 설정을 하여 DB를 사용할 때 다음과 같은 에러가 나는 경우가 있습니다.

Cannot create JDBC driver of class '' for connect URL 'null'...

제 경우에는 Resource설정을 GlobalNamingResources 섹션에다 한 서버에서 에러가 났는데 해결방법은 아래와 같이 DB를 사용할 Host 섹션의 Context에다 ResourceLink를 추가하는 것입니다.





---

2006년 7월 28일 추가

tomcat5에서는 위 설정을 server.xml에 추가하는 것이 아니라 개별파일로 셋팅하여야 한다. 파일의 위치는 $CATALINA_HOME/conf/[enginename]/[hostname]/ 이다.

자바 메일 보내기

 

이 내용은 오른쪽 위에 첨부된 자바메일보내기.zip파일을 토대로 한 것입니다.


다운받아서 압축풀어보시면 자바메일에 대해 자세히 설명해 놓은 PDF파일과 샘플소스가 있습니다.


참고하실분 받아보세요..^^


 


 


우선 해야할 것은 자바메일 API와 JAF를 설정하는 것입니다.


 


1. 자바메일 API 설정하기


다운로드 : http://java.sun.com/products/javamail/ 에 가셔서 다운받으세요


다운로드후 압축을 푸시면 mail.jar이라는 파일이 있는데 이녀석을 톰켓의 common\lib폴더 아래에 복사하여 주십시오.


 


2. JAF 설정하기


다운로드 : http://java.sun.com/products/javabeans/jaf/downloads/index.html 에 가셔서 다운 받으신 후 압축을 푸시면 activation.jar이라는 파일이 있는데 이녀석을 아까처럼 톰켓의 common\lib폴더 아래에 복사하여 주십시오.


 


그러면 모든 설정이 종료됩니다.


 


다음으로 할 일은 메일 보내기 구현입니다..


 


1. 첨부파일과 함께 보내기


 


package mailtest;


import java.util.Date;
import java.util.Properties;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;


public class MailTransfer {


 private String from;
 private String to;
 private String filename;
 
 public MailTransfer(String from, String to, String filename) {
  this.from = from;
  this.to = to;
  this.filename = filename;
 }
 
 public void mailSend(){
  if(from == null || to == null || filename == null) {
   System.out.println("usage : java <from> <to> <filename>");
   System.exit(0);
  }
  
  try{   
   // 시스템 속성 객체 생성
   Properties props = System.getProperties();


   // POP3 메일 서버 속성 설정
   props.put("mail.smtp.host", "127.0.0.1");


   // 메일 서버 세션 생성
   Session session = Session.getInstance(props, null);


   // 메세지 정의
   Message message = new MimeMessage(session);
   message.setFrom(new InternetAddress(from));
   message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
   message.setSubject("안녕하세요. 자바 메일 첨부 파일입니다.");


   // 메세지 몸체 생성
   BodyPart messageBodyPart = new MimeBodyPart();


   // 메세지 텍스트 내용 설정
   messageBodyPart.setText("파일 첨부 메일입니다.");
   
   // 다양한 종류의 데이터 추가를 위한 객체 생성
   Multipart multipart = new MimeMultipart();
   
   // 첫번째 메세지 몸체 추가
   multipart.addBodyPart(messageBodyPart);
   
   // 새로운 몸체 생성
   messageBodyPart = new MimeBodyPart();
   
   // 파일 객체 생성
   DataSource source = new FileDataSource(filename);
   
   // 메세지 몸체에 파일 객체 첨부
   messageBodyPart.setDataHandler(new DataHandler(source));
   
   // 파일 이름 설정
   messageBodyPart.setFileName(filename);
   
   // 두번째 메세지 몸체 추가
   multipart.addBodyPart(messageBodyPart);
   
   // Multipart 객체를  Message 객체에 추가
   message.setContent(multipart);


   // 보낸 날짜 설정..ㅡ0ㅡ;; 이거빠지니깐 에러나는데..ㅠㅠ
   message.setSentDate(new Date());
   
   // 생성된 메세지 전송
   Transport.send(message);
  }
  catch(javax.mail.MessagingException ex) {
   ex.printStackTrace();
  }
 }
}


 


 


 


2. html로 보내기


package mailtest;


import java.util.Date;
import java.util.Properties;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;


public class HtmlTransfer {


 private String from;
 private String to;
 private String filename;
 
 public HtmlTransfer(String from, String to, String filename) {
  this.from = from;
  this.to = to;
  this.filename = filename;
 }
 
 public void mailSend(){
  if(from == null || to == null || filename == null) {
   System.out.println("usage : java <from> <to> <filename>");
   System.exit(0);
  }
  
  try{   
   // 시스템 속성 객체 생성
   Properties props = System.getProperties();


   // POP3 메일 서버 속성 설정
   props.put("mail.smtp.host", "127.0.0.1");


   // 메일 서버 세션 생성
   Session session = Session.getInstance(props, null);


   // 메세지 정의
   Message message = new MimeMessage(session);


   // 메일 헤더 설정
   message.setSubject("저의 가족입니다.");
   message.setFrom(new InternetAddress(from));
   message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));


   // 메세지 몸체 생성
   BodyPart messageBodyPart = new MimeBodyPart();


   // HTML 데이터 생성
   String htmlText = "<H3>안녕 우리 가족이야<H3>" + "<img src = \"cid:family\">";


   // 메세지 데이터 MIME 형식 설정


   // 단순히 텍스트로만 발송할경우에는 text/html 대신에 text/plain으로 바꾸시면 됩니다.
   messageBodyPart.setContent(htmlText, "text/html; charset=euc-kr");
   
   // 다양한 종류의 데이터 추가를 위한 객체 생성
   MimeMultipart multipart = new MimeMultipart("related");
   
   // 메세지 몸체를 Multipart 객체에 추가
   multipart.addBodyPart(messageBodyPart);
   
   // 이미지 메세지 몸체 생성
   messageBodyPart = new MimeBodyPart();
   
   // 메세지 몸체에 이미지 첨부
   DataSource fds = new FileDataSource(filename);
   
   // 메세지 몸체에 파일 객체 첨부
   messageBodyPart.setDataHandler(new DataHandler(fds));
   
   // 파일 이름 설정
   messageBodyPart.setFileName(filename);
   
   // 메일 헤더 설정
   messageBodyPart.setHeader("Content-ID", "family");


   // 메세지 몸체를 Multipart 객체에 추가
   multipart.addBodyPart(messageBodyPart);
   
   // Multipart 객체를  Message 객체에 추가
   message.setContent(multipart);


   // 보낸 날짜 설정..ㅡ0ㅡ;; 이거빠지니깐 에러나는데..ㅠㅠ
   message.setSentDate(new Date());
   
   // 생성된 메세지 전송
   Transport.send(message);
  }
  catch(javax.mail.MessagingException ex) {
   ex.printStackTrace();
  }
 }
}


 


 


 


3. 참고(메일보내기샘플파일)


이건 위의 파일내용들을 바탕으로 제가 짜본 메일 보내기 입니다.


대충 허접하게 만들어서 정식소스로 쓸려면 좀 수정, 보안이 필요할것 같긴하군요..


메일 작성하는 html 부분이라던지..


흠..파일 받는 멀티파트 부분이라던지...


그냥 이런 흐름으로 짰다는 정도만..참고하고 싶으시면 참고해보세요..


본 목적은 제 참고용이라..흐흐;;;

Spring Quartz 혹은 Timer 를 사용한 스케쥴링

Quartz 혹은 Timer 를 사용한 스케쥴링





19.1. 소개



Spring은 스케쥴링을 지원하는 통합 클래스들을 제공한다. 현재적으로, Spring은 1.3 이후버전 JDK의 일부분인 Timer와 Quartz 스케쥴러 (http://www.quartzscheduler.org)를 지원하고 있다. 이 두개의 스케쥴러들은 각각 Timer 혹은 Triggers에 대한 선택적 참조를 가지는 FactoryBean을 사용하여 세팅된다. 게다가 당신이 타겟 object의 메써드를 편리하게 호출할 수 있도록 도와주는 Quartz 스케쥴러와 Timer에 대한 편의 클래스를 제공한다.(이것은 일반적인 MethodInvokingFactoryBeans와 비슷하다.)






19.2. OpenSymphony Quartz 스케쥴러 사용하기



Quartz는 Triggers, Jobs 그리고 모든 종류의 jobs를 인식하고 있는 JobDetail를 사용한다. Quartz에 깔려 있는 기본적인 개념을 알고 싶다면, http://www.opensymphony.com/quartz를 찾아보길 바란다. 편리한 사용을 위해서, Spring은 Spring 기반 어플리케이션 내에서 Quartz의 사용을 손쉽게 만들어주는 두 개의 클래스들을 제공한다.






19.2.1. JobDetailBean 사용하기



JobDetail 객체는 job을 실행하기 위해 필요한 모든 정보를 가지고 있다. Spring은 소위 JobDetailBean이라고 불리는 클래스를 제공하는데, 이것은 JobDetail을 합리적인 디폴트값을 가진 실질적인 JavaBean 객체로 만들어준다. 다음의 예제를 보도록 하자.

<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass">
<value>example.ExampleJob</value>
</property>
<property name="jobDataAsMap">
<map>
<entry key="timeout"><value>5</value></entry>
</map>
</property>
</bean>

위의 job detail bean은 job(ExampleJob)을 실행하기 위한 모든 정보를 가지고 있다. 타임아웃은 job data map으로 기술되었다. job data map은 (실행시 넘겨지는) JobExecutionContext를 통해 이용할 수 있지만, JobDetailBean 역시 job data map으로부터 실질적인 job의 프라퍼티들을 매핑할 수 있다. 때문에 이러한 경우, 만약 ExampleJob이 timeout이라는 프라퍼티를 가지고 있다면, JobDetailBean은 그것을 자동으로 적용할 것이다.

package example;

public class ExampleJob extends QuartzJobBean {

private int timeout;

/**
* Setter called after the ExampleJob is instantiated
* with the value from the JobDetailBean (5)
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}

protected void executeInternal(JobExecutionContext ctx)
throws JobExecutionException {
// do the actual work
}
}

당신은 job detail bean의 모든 부가적인 세팅들 역시 마찬가지로 이용할 수 있다.


주의: namegroup 프라퍼티를 사용함으로써, 당신은 job의 name과 group을 변경할 수 있다. default로 job의 이름은 job detail bean의 이름과 동일하다.(위의 예에서는 exampleJob이 된다.)






19.2.2. MethodInvokingJobDetailFactoryBean 사용하기



종종 당신은 특정한 객체의 메써드를 호출할 필요가 있을 것이다. 당신은 MethodInvokingJobDetailFactoryBean을 사용하여 다음과 같이 할 수 있다.

<bean id="methodInvokingJobDetail" 
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject"><ref bean="exampleBusinessObject"/></property>
<property name="targetMethod"><value>doIt</value></property>
</bean>

위의 예는 (아래에 있는) exampleBusinessObject의 doIt을 호출하는 것을 의미한다.

public class BusinessObject {

// properties and collaborators

public void doIt() {
// do the actual work
}
}

<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>

MethodInvokingJobDetailFactoryBean을 사용할 때, 메써드를 호출할 한줄짜리 jobs를 생성할 필요가 없으며, 당신은 단지 실질적인 비지니스 객체를 생성해서 그것을 묶기만 하면된다.


default로는 Quartz Jobs는 비상태이며, 상호 작용하는 jobs의 가능성을 가진다. 만약 당신이 동일한 JobDetail에 대해 두 개의 triggers를 명시한다면, 첫번째 job이 끝나기 이전에 두번째가 시작할지도 모른다. 만약 JobDetail 객체가 상태 인터페이스를 구현한다면, 이런 일은 발생하지 않을 것이다. 두번째 job은 첫번째가 끝나기 전에는 시작하지 않을 것이다. MethodInvokingJobDetailFactoryBean를 사용한 jobs가 동시작용하지 않도록 만들기 위해서는, concurrent 플래그를 false로 세팅해주어야 한다.

<bean id="methodInvokingJobDetail"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject"><ref bean="exampleBusinessObject"/></property>
<property name="targetMethod"><value>doIt</value></property>
<property name="concurrent"><value>false</value></property>
</bean>

주의: 기본적으로 jobs는 concurrent 옵션에 따라 실행될 것이다.






19.2.3. triggers 와 SchedulerFactoryBean을 사용하여 jobs를 묶기



우리는 job details과 jobs를 생성했고, 당신이 특정 객체의 메써드를 호출할 수 있도록 하는 편의클래스 bean을 살펴보았다. 물론, 우리는 여전히 jobs를 그자체로 스케쥴할 필요가 있다. 이것은 triggers와 SchedulerFactoryBean을 사용하여 이루어진다. 여러가지 triggers는 Quartz 내에서 이용할 수 있다. Spring은 편의를 위해 2개의 상속받은 triggers를 기본적으로 제공한다.:CronTriggerBeanSimpleTriggerBean이 그것이다.


Triggers는 스케쥴될 필요가 있다. Spring은 triggers를 세팅하기 위한 프라퍼티들을 드러내는 SchedulerFactoryBean을 제공하고 있다. SchedulerFactoryBean은 그 triggers와 함께 실질적인 jobs를 스케쥴한다.


다음 두가지 예를 보자.

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<property name="jobDetail">
<!-- see the example of method invoking job above -->
<ref bean="methodInvokingJobDetail"/>
</property>
<property name="startDelay">
<!-- 10 seconds -->
<value>10000</value>
</property>
<property name="repeatInterval">
<!-- repeat every 50 seconds -->
<value>50000</value>
</property>
</bean>

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail">
<ref bean="exampleJob"/>
</property>
<property name="cronExpression">
<!-- run every morning at 6 AM -->
<value>0 0 6 * * ?</value>
</property>
</bean>

OK, 이제 우리는 두 개의 triggers를 세팅했다. 하나는 10초 늦게 실행해서 매 50초마다 실행될 것이고, 다른 하나는 매일 아침 6시에 실행될 것이다. 모든 것을 완료하기 위해서, 우리는 SchedulerFactoryBean을 세팅해야 한다.

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref local="cronTrigger"/>
<ref local="simpleTrigger"/>
</list>
</property>
</bean>

당신이 세팅할 수 있는 더욱 많은 속성들이 SchedulerFactoryBean에 있다. 이를테면, job details에 의해 사용되는 calendars라던가, Quartz를 커스터마이징할 수 있게 하는 프라퍼티같은 것들이 말이다. 더 많은 정보를 위해서는 JavaDoc(http://www.springframework.org/docs/api/org/springframework/scheduling/quartz/SchedulerFactoryBean.html)을 참조하도록 해라.






19.3. JDK Timer support 사용하기



Spring에서 스케쥴링 업무를 처리하는 또다른 방법은 JDK Timer 객체들을 사용하는 것이다. Timers 자체에 대한 더 많은 정보는 http://java.sun.com/docs/books/tutorial/essential/threads/timer.html에서 찾아볼 수 있다. 위에서 살펴 본 기본개념들은 Timer support에도 마찬가지로 적용된다. 당신은 임의의 timers를 생성하고 메써드들을 호출하기 위해 timer를 사용한다. TimerFactoryBean을 사용하여 timers를 묶는다.






19.3.1. 임의의 timers 생성하기



당신은 TimerTask를 사용하여 임의의 timer tasks를 생성할 수 있다. 이것은 Quartz jobs와 유사하다

public class CheckEmailAddresses extends TimerTask {

private List emailAddresses;

public void setEmailAddresses(List emailAddresses) {
this.emailAddresses = emailAddresses;
}

public void run() {
// iterate over all email addresses and archive them
}
}

이것을 묶는 것 역시 간단하다:

<bean id="checkEmail" class="examples.CheckEmailAddress">
<property name="emailAddresses">
<list>
<value>test@springframework.org</value>
<value>foo@bar.com</value>
<value>john@doe.net</value>
</list>
</property>
</bean>

<bean id="scheduledTask" class="org.springframework.scheduling.timer.ScheduledTimerTask">
<!-- wait 10 seconds before starting repeated execution -->
<property name="delay">
<value>10000</value>
</property>
<!-- run every 50 seconds -->
<property name="period">
<value>50000</value>
</property>
<property name="timerTask">
<ref local="checkEmail"/>
</property>
</bean>


task를 단지 한번만 실행하고자 한다면, period 속성을 -1(혹은 다른 음수값으)로 바꿔주면 된다.






19.3.2. MethodInvokingTimerTaskFactoryBean 사용하기



Quartz support와 비슷하게, Timer 역시 당신이 주기적으로 메써드를 호출할 수 있도록 하는 요소들을 기술한다.

<bean id="methodInvokingTask" 
class="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean">
<property name="targetObject"><ref bean="exampleBusinessObject"/></property>
<property name="targetMethod"><value>doIt</value></property>
</bean>

위의 예제는 (아래와 같은) exampleBusinessObject에서 호출되는 doIt에서 끝날 것이다

public class BusinessObject {

// properties and collaborators

public void doIt() {
// do the actual work
}
}

ScheduledTimerTask가 언급된 위의 예제의 참조값을 methodInvokingTask로 변경하면 이 task가 실행될 것이다.






19.3.3. 감싸기 : TimerFactoryBean을 사용하여 tasks를 세팅하기



TimerFactoryBean은 실질적인 스케쥴링을 세팅한다는 같은 목적을 제공한다는 점에서 Quartz의 SchedulerFactoryBean과 비슷하다. TimerFactoryBean는 실질적인 Timer를 세팅하고 그것이 참조하고 있는 tasks를 스케쥴한다. 당신은 대몬 쓰레드를 사용할 것인지 말것인지를 기술할 수 있다.

<bean id="timerFactory" class="org.springframework.scheduling.timer.TimerFactoryBean">
<property name="scheduledTimerTasks">
<list>
<!-- see the example above -->
<ref local="scheduledTask"/>
</list>
</property>
</bean>

Spring을 사용한 원격(Remoting)및 웹서비스

Spring을 사용한 원격(Remoting)및 웹서비스





17.1. 소개



원격 지원을 위한 Spring통합 클래스는 다양한 기술을 사용한다. 원격 지원은 당신의 (Spring) POJO에 의해 구현되는 원격-가능 서비스의 개발을 쉽게 한다. 현재 Spring은 4가지 원격 기술을 지원한다.





  • 원격 메소드 호출 (RMI). RmiProxyFactoryBeanRmiServiceExporter의 사용을 통해 Spring은 전통적인 RMI(java.rmi.Remote 인터페이스와 java.rmi.RemoteException을 가지는)와 RMI 호출자(어떠한 자바 인터페이스를 가진)를 통한 투명한 원격 모두 지원합니다.



  • Spring의 HTTP 호출자. Spring은 어떤 자바 인터페이스(RMI 호출자와 같은)를 지원하는 HTTP를 통한 자바 직렬화를 허용하는 특별한 원격 전략을 제공한다. 관련 지원 클래스는 HttpInvokerProxyFactoryBeanHttpInvokerServiceExporter이다.



  • Hessian. HessianProxyFactoryBeanHessianServiceExporter를 사용하여 Caucho에 의해 제공되는 가벼운 바이너리 HTTP기반 프로토콜을 사용하는 당신의 서비스를 투명하게 드러낼수 있다.



  • Burlap. Burlap은 Hessian을 위한 Caucho의 XML기반의 대안이다. Spring은 BurlapProxyFactoryBeanBurlapServiceExporter 같은 지원 클래스를 제공한다.



  • JAX RPC. Spring은 JAX-RPC를 통한 웹서비스를 위한 원격 지원을 제공한다.



  • JMS (TODO).



Spring의 원격 기능에 대해 언급하는 동안 우리는 다음의 도메인 모델과 관련 서비스를 사용할것이다.

// Account domain object
public class Account implements Serializable{
private String name;

public String getName();
public void setName(String name) {
this.name = name;
}
}

// Account service
public interface AccountService {

public void insertAccount(Account acc);

public List getAccounts(String name);
}

// Remote Account service
public interface RemoteAccountService extends Remote {

public void insertAccount(Account acc) throws RemoteException;

public List getAccounts(String name) throws RemoteException;
}

// ... and corresponding implement doing nothing at the moment
public class AccountServiceImpl implements AccountService {

public void insertAccount(Account acc) {
// do something
}

public List getAccounts(String name) {
// do something
}
}


우리는 RMI를 사용하여 원격 클라이언트에 서비스를 드러내길 시작하고 RMI를 사용한 장애에 대해 조금 이야기할것이다. 우리는 Hessian을 위한 예제를 보여줄 것이다.






17.2. RMI를 사용한 서비스 드러내기



RMI를 위한 Spring의 지원을 사용하여, 당신은 RMI내부구조를 통해 당신의 서비스를 투명하게 드러낼수 있다. 이 셋업 후 당신은 보안 컨텍스트 위임이나 원격 트랜잭션 위임을 위한 표준적인 지원이 없다는 사실을 제외하고 기본적으로 remote EJB와 유사한 설정을 가진다. Spring은 RMI호출자를 사용할때 추가적인 호출 컨텍스트와 같은 것을 위한 고리(hooks)를 제공한다. 그래서 당신은 예를 들어 보안 프레임워크나 사용자정의 보안 증명에 붙일수 있다.






17.2.1. RmiServiceExporter를 사용하여 서비스 내보내기



RmiServiceExporter를 사용하여, 우리는 RMI객체처럼 AccountService객체의 인터페이스를 드러낼수 있다. 인터페이스는 RmiProxyFactoryBean을 사용하거나 전통적인 RMI서비스의 경우 일반적인 RMI를 통해 접근될수 있다. RmiServiceExporter는 RMI호출자를 통해 RMI가 아닌 서비스의 발생을 명시적으로 지원한다.


우리는 먼저 Spring BeanFactory내 우리의 서비스를 셋업한다.

<bean id="accountService" class="example.AccountServiceImpl">
<!-- any additional properties, maybe a DAO? -->
</bean>


그리고 나서 우리는 RmiServiceExporter를 사용하여 우리의 서비스를 드러낼것이다.

<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<!-- does not necessarily have to be the same name as the bean to be exported -->
<property name="serviceName"><value>AccountService</value></property>
<property name="service"><ref bean="accountService"/></property>
<property name="serviceInterface"><value>example.AccountService</value></property>
<!-- defaults to 1099 -->
<property name="registryPort"><value>1199</value></property>
</bean>

당신이 볼수 있는 것처럼, 우리는 RMI등록(registry)을 위한 포트를 오버라이딩한다. 종종, 당신의 애플리케이션 서버는 RMI등록을 유지하고 그것을 방해하지 않는것이 현명하다. 게다가 서비스 이름은 서비스를 바인드 하기 위해 사용된다. 서비스는 rmi://HOST:1199/AccountService에 비인드될것이고 우리는 클라이언트측에서 서비스로 링크하기 위해 이 URL을 사용할것이다.


노트 : 우리는 하나의 프라퍼티를 남겨두었다. 이를테면 servicePort이고 디폴트로에 의해 0이된다. 이것은 서비스와 통신하기 위해 사용될 익명 포트를 의미한다. 당신은 다른 포트를 명시할수 있다.






17.2.2. 클라이언트에서 서비스 링크하기



우리의 클라이언트는 계좌(accounts)를 관리하기 위한 AccountService을 사용하는 간단한 객체이다.

public class SimpleObject {
private AccountService accountService;
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
}


클라이언트에서 서비스에 링크하기 위해, 우리는 간단한 객체와 서비스 링크 설정을 포함하는 분리된 bean factory를 생성할 것이다.

<bean class="example.SimpleObject">
<property name="accountService"><ref bean="accountService"/></property>
</bean>

<bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl"><value>rmi://HOST:1199/AccountService</value></property>
<property name="serviceInterface"><value>example.AccountService</value></property>
</bean>

그것은 우리가 클라이언트에서 원격 계좌(account)서비스를 지원하기 위해 해야 할 필요가 있는 모든것이다. Spring은 호출자를 투명하게 생성하고 RmiServiceExporter를 통해 계좌 서비스를 원격적으로 가능하게 한다. 클라이언트에서 우리는 RmiProxyFactoryBean를 사용하여 이것을 링크한다.






17.3. HTTP를 통해 서비스를 원격으로 호출하기 위한 Hessian 이나 Burlap을 사용하기.



Hessian은 바이너리 HTTP-기반 원격 프로토콜을 제공한다. 이것은 Caucho에 의해 생성되었고 Hessian자체에 대한 좀더 상세한 정보는 http://www.caucho.com에서 찾을수 있다.






17.3.1. Hessian을 위해 DispatcherServlet을 묶기.



Hessian은 HTTP를 통해 통신하고 사용자정의 서블릿을 사용해서도 그렇게 한다. Spring의 DispatcherServlet 원리를 사용하여, 당신의 서비스를 드러내는 서블릿을 쉽게 묶을수 있다. 먼저 우리는 애플리케이션내 새로운 서블릿을 생성해야만 한다. (이것은 web.xml으로 부터 인용한다.)

<servlet>
<servlet-name>remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>/remoting/*</url-pattern>
</servlet-mapping>


당신은 아마도 Spring의 DispatcherServlet 원리에 익숙하고 만약 그렇다면 당신은 WEB-INF 디렉토리내 (당신의 서블릿 이름 뒤) remoting-servlet.xml라는 이름의 애플리케이션 컨텍스트를 생성할것이다. 애플리케이션 컨텍스트는 다음 부분에서 사용될것이다.






17.3.2. HessianServiceExporter를 사용하여 bean을 드러내기



remoting-servlet.xml 라고 불리는 새롭게 생성된 애플리케이션 컨텍스트내에서 우리는 당신의 서비스를 내보내는 HessianServiceExporter를 생성할것이다.

<bean id="accountService" class="example.AccountServiceImpl">
<!-- any additional properties, maybe a DAO? -->
</bean>

<bean name="/AccountService" class="org.springframework.remoting.caucho.HessianServiceExporter">
<property name="service"><ref bean="accountService"/></property>
<property name="serviceInterface">
<value>example.AccountService</value>
</property>
</bean>

지금 우리는 클라이언트에서 서비스를 링크할 준비가 되어있다. 명시된 핸들러 맵핑은 없고 서비스를 향한 요청 URL을 맵핑한다. 그래서 BeanNameUrlHandlerMapping은 사용될것이다. 나아가 서비스는 bean이름(http://HOST:8080/remoting/AccountService)을 통해 표시되는 URL에 내보내어질것이다.






17.3.3. 클라이언트의 서비스로 링크하기



HessianProxyFactoryBean을 사용하여 우리는 클라이언트에서 서비스로 링크할수 있다. 같은 원리는 RMI예제처럼 적용한다. 우리는 분리된 bean factory나 애플리케이션 컨텍스트를 생성하고 SimpleObject가 계좌를 관리하기 위해 AccountService를 사용하는 다음 bean을 따른다.

<bean class="example.SimpleObject">
<property name="accountService"><ref bean="accountService"/></property>
</bean>

<bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<property name="serviceUrl"><value>http://remotehost:8080/AccountService</value></property>
<property name="ServiceInterface"><value>example.AccountService</value></property>
</bean>

That's all there is to it.






17.3.4. Burlap 사용하기



우리는 Hessian의 XML기반의 대응물인 Burlap을 여기서 상세하게 언급하지 않을것이다. Hessian처럼 정확하게 같은 방법으로 설정되고 셋업된 후 변형물은 위에서 설명된다. Hessian 이라는 단어를 Burlap으로 교체하고 당신은 모든것을 셋팅한다.






17.3.5. Hessian 이나 Burlap을 통해 드러나는 서비스를 위한 HTTP 기본 인증 적용하기



Hessian 과 Burlap 장점중 하나는 두가지 프로토콜이 모두 HTTP-기반이기 때문에 우리가 HTTP 기본 인증을 쉽게 적용할수 있다는 것이다. 당신의 일반적인 HTTP서버의 보안 기법은 web.xml 보안 기능을 사용하여 쉽게 적용될수 있다. 대개 당신은 여기서 사용자별 보안 증명을 사용하지 않지만 공유된 증명은 Hessian/BurlapProxyFactoryBean 레벨(JDBC 데이터소스와 유사한)에서 정의된다.


<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="authorizationInterceptor"/>
</list>
</property>
</bean>

<bean id="authorizationInterceptor"
class="org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor">
<property name="authorizedRoles">
<list>
<value>administrator</value>
<value>operator</value>
</list>
</property>
</bean>


이것은 우리가 BeanNameUrlHandlerMapping을 명시적으로 언급하고 오직 관리자만을 허용하는 인터셉터를 셋팅하며 이 애플리케이션 컨텍스트내에서 언급된 bean을 호출하는 작업을 수행하는 예제이다.


노트 : 물론, 이 예제는 보안 내부구조의 유연한 종류를 보여주지 않는다. 보안이 관련된 만큼 좀더 많은 옵션을 위해서 Spring을 위한 Acegi 보안 시스템을 보라. 그것은 http://acegisecurity.sourceforge.net에서 찾을수 있다.






17.4. HTTP호출자를 사용하여 서비스를 드러내기



그들 자체의 얼마 안되는 직렬화 기법을 사용하는 가벼운 프로토콜인 Burlap 과 Hessian이 상반돤것처럼 Spring HTTP호출자는 HTTP를 통해 서비스를 드러내기 위한 표준적인 자바 직렬화 기법을 사용한다. 이것은 당신의 인자와 반환 타입이 Hessian 과 Burlap이 사용하는 직렬화 기법을 사용하여 직렬화될수 없는 복합(complex)타입이라면 커다란 장점을 가진다. (원격 기술을 선택할때 좀더 많은 숙고사항을 위해서 다음 부분을 참조하라.)


Spring은 HTTP호출을 수행하거나 Commons HttpClient를 위해 J2SE에 의해 제공되는 표준적인 기능을 사용한다. 만약 당신이 좀더 향상되었거나 사용하기 쉬운 기능이 필요하다면 후자를 사용하라. 좀더 많은 정보를 위해서는 jakarta.apache.org/commons/httpclient을 참조하라.






17.4.1. 서비스 객체를 드러내기



Hessian 이나 Burlap을 사용하는 방법과 매우 유사한 서비스 객체를 위한 HTTP 호출자 내부구조를 셋업하라. Hessian지원이 HessianServiceExporter를 제공하는 것처럼 Spring Http호출자 지원은 org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter라고 불리는 것을 제공한다. (위에서 언급된) AccountService을 드러내기 위해 다음 설정이 대체될 필요가 있다.

    <bean name="/AccountService" class="org.sprfr.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service"><ref bean="accountService"/></property>
<property name="serviceInterface">
<value>example.AccountService</value>
</property>
</bean>






17.4.2. 클라이언트에서 서비스 링크하기



다시 클라이언트로부터 서비스를 링크하는것은 Hessian 이나 Burlap을 사용할때 이것을 하는 방법과 매우 유사하다. 프록시를 사용하여 Spring은 내보내어진 서비스를 위한 URL을 위해 당신의 호출을 HTTP POST요청으로 번역할수 있을것이다.

 
<bean id="httpInvokerProxy" class="org.sprfr.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl">
<value>http://remotehost:8080/AccountService</value>
</property>
<property name="serviceInterface">
<value>example.AccountService</value>
</property>
</bean>


전에 언급된것처럼, 당신은 사용하고자 하는 HTTP클라이언트를 선택할수 있다. 디폴트에 의하면 HttpInvokerProxy가 J2SE HTTP기능을 사용한다. 하지만 당신은 httpInvokerRequestExecutor 프라퍼티를 셋팅하여 Commons HttpClient를 사용할수 있다.

<property name="httpInvokerRequestExecutor">
<bean class="org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor"/>
</property>






17.5. 웹 서비스



Spring은 다음을 위한 지원을 가진다.




  • JAX-RPC를 사용하여 서비스를 드러내기
  • 웹 서비스에 접근하기


위의 지원 목록 다음으로, 당신은 XFire xfire.codehaus.org를 사용하여 웹 서비스를 드러낼수 있다. XFire는 Codehaus에서 현재 개발중인 가벼운 SOAP라이브러리이다.






17.5.1. JAX-RPC를 사용하여 서비스를 드러내기



Spring은 JAX-RPC 서블릿 endpoint구현물(ServletEndpointSupport)을 위한 편리한 base클래스이다. 우리의 AccountService를 드러내기 위해 우리는 Spring의 ServletEndpointSupport클래스를 확장하고 여기서 언제나 비지니스 레이어로 호출을 위임하는 비지니스 로직을 구현한다.

/**
* JAX-RPC compliant RemoteAccountService implementation that simply delegates
* to the AccountService implementation in the root web application context.
*
* This wrapper class is necessary because JAX-RPC requires working with
* RMI interfaces. If an existing service needs to be exported, a wrapper that
* extends ServletEndpointSupport for simple application context access is
* the simplest JAX-RPC compliant way.
*
* This is the class registered with the server-side JAX-RPC implementation.
* In the case of Axis, this happens in "server-config.wsdd" respectively via
* deployment calls. The Web Service tool manages the life-cycle of instances
* of this class: A Spring application context can just be accessed here.
*/
public class AccountServiceEndpoint extends ServletEndpointSupport implements RemoteAccountService {

private AccountService biz;

protected void onInit() {
this.biz = (AccountService) getWebApplicationContext().getBean("accountService");
}

public void insertAccount(Account acc) throws RemoteException {
biz.insertAccount(acc);
}

public Account[] getAccounts(String name) throws RemoteException {
return biz.getAccounts(name);
}

}

우리의 AccountServletEndpoint는 Spring의 기능에 접근하기 위해 허용하는 Spring컨텍스트처럼 같은 웹 애플리케이션내에서 수행할 필요가 있다. Axis의 경우, AxisServlet정의를 web.xml로 복사하고 "server-config.wsdd" 내 endpoint를 셋업한다.(또는 배치툴을 사용한다.) Axis를 사용한 웹 서비스처럼 드러나는 OrderService가 있는 샘플 애플리케이션 JPetStore를 보라.






17.5.2. 웹 서비스에 접근하기



Spring은 웹 서비스 프록시인 LocalJaxRpcServiceFactoryBeanJaxRpcPortProxyFactoryBean을 생성하기 위한 두가지 factory bean을 가진다. 전자는 JAX-RPC서비스 클래스만을 반환할수 있다. 후자는 우리의 비지니스 서비스 인터페이스를 구현하는 프록시를 반환할수 있는 완전한 버전이다. 이 예제에서 우리는 이전 단락내 나오는 AccountService Endpoint를 위한 프록시를 생성하기 위해 나중에 사용된다. 당신은 Spring이 조금의 코딩을 요구하는 웹 서비스를 위한 대단한 지원을 가진다는 것을 볼것이다. 대부분의 마법은 대개 Spring설정 파일내에서 이루어진다.

    <bean id="accountWebService" class="org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean">
<property name="serviceInterface">
<value>example.RemoteAccountService</value>
</property>
<property name="wsdlDocumentUrl">
<value>http://localhost:8080/account/services/accountService?WSDL</value>
</property>
<property name="namespaceUri">
<value>http://localhost:8080/account/services/accountService</value>
</property>
<property name="serviceName">
<value>AccountService</value>
</property>
<property name="portName">
<value>AccountPort</value>
</property>
</bean>

serviceInterface는 클라이언트가 사용할 원격 비지니스 인터페이스이다. wsdlDocumentUrl은 WSDL파일을 위한 URL이다. Spring은 JAX-RPC서비스를 생성하기 위해 시작시 이것이 필요하다. namespaceUri는 .wsdl파일내 targetNamespace에 대응한다. serviceName은 .wsdl파일내 서비스 이름에 대응한다. portName은 .wsdl파일내 포트명에 대응한다.


웹 서비스에 접근하는 것은 우리가 RemoteAccountService인터페이스처럼 이것을 드러내는 bean factory를 가지는것처럼 매우 쉽다. 우리는 Spring내 이것을 묶을수 있다.

    <bean id="client" class="example.AccountClientImpl">
...
<property name="service">
<ref bean="accountWebService"/>
</property>
</bean>

그리고 클라이언트 코드로 부터 우리는 이것이 RemoteException을 던지는것을 제외하고 일반 클래스인것처럼 웹 서비스에 접근할수 있다.

public class AccountClientImpl {

private RemoteAccountService service;

public void setService(RemoteAccountService service) {
this.service = service;
}

public void foo() {
try {
service.insertAccount(...);
} catch (RemoteException e) {
// ouch
...
}
}

}


우리는 Spring이 관련된 체크되지 않은 RemoteAccessException으로의 자동변환을 지원하기 때문에 체크된 RemoteException을 제거할수 있다. 이것은 우리가 비-RMI인터페이스 또한 제공하는것을 요구한다. 우리의 설정은 다음과 같다.

    <bean id="accountWebService" class="org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean">
<property name="serviceInterface">
<value>example.AccountService</value>
</property>
<property name="portInterface">
<value>example.RemoteAccountService</value>
</property>
...
</bean>

serviceInterface는 비-RMI 인터페이스를 위해 변경된다. 우리의 RMI 인터페이스는 portInterface 프라퍼티를 사용하여 정의된다. 우리의 클라이언트 코드는 java.rmi.RemoteException을 피할수 있다.

public class AccountClientImpl {

private AccountService service;

public void setService(AccountService service) {
this.service = service;
}

public void foo() {
service.insertAccount(...);
}

}






17.5.3. Register Bean 맵핑



Account와 같은 정보를 넘어 복합 객체를 이동시키기 위해 우리는 클라이언트 측에서 bean맵핑을 등록해야만 한다.









[Note]Note

서버측에서 Axis를 사용하여 등록된 bean맵핑은 server-config.wsdd에서 언제나 수행된다.


우리는 클라이언트 측에서 bean맵핑을 등록하기 위해 Axis를 사용할것이다. 이것을 하기 위해 우리는 Spring Bean factory의 하위클래스를 만들 필요가 있고 프로그램에 따라 bean맵핑을 등록한다.

public class AxisPortProxyFactoryBean extends JaxRpcPortProxyFactoryBean {

protected void postProcessJaxRpcService(Service service) {
TypeMappingRegistry registry = service.getTypeMappingRegistry();
TypeMapping mapping = registry.createTypeMapping();
registerBeanMapping(mapping, Account.class, "Account");
registry.register("http://schemas.xmlsoap.org/soap/encoding/", mapping);
}

protected void registerBeanMapping(TypeMapping mapping, Class type, String name) {
QName qName = new QName("http://localhost:8080/account/services/accountService", name);
mapping.register(type, qName,
new BeanSerializerFactory(type, qName),
new BeanDeserializerFactory(type, qName));
}

}






17.5.4. 자체적인 핸들러 등록하기



이 장에서 우리는 SOAP메시지를 정보를 통해 보내기 전에 코드를 사용자정의 할수 있는 웹 서비스 프록시를 위한 javax.rpc.xml.handler.Handler를 등록할 것이다. javax.rpc.xml.handler.Handler는 콜백 인터페이스이다. jaxrpc.jar내 제공되는 편리한 base클래스인 javax.rpc.xml.handler.GenericHandler가 있다.

public class AccountHandler extends GenericHandler {

public QName[] getHeaders() {
return null;
}

public boolean handleRequest(MessageContext context) {
SOAPMessageContext smc = (SOAPMessageContext) context;
SOAPMessage msg = smc.getMessage();

try {
SOAPEnvelope envelope = msg.getSOAPPart().getEnvelope();
SOAPHeader header = envelope.getHeader();
...

} catch (SOAPException e) {
throw new JAXRPCException(e);
}

return true;
}

}

우리의 AccountHandler를 JAX-RPC 서비스에 등록하는 것이 필요하다. 그래서 메시지가 정보를 통해 전달되기 전에 handleRequest을 호출할것이다. Spring은 이 시점에 핸들러를 등록하기 위한 선언적인 지원을 가지지 않는다. 그래서 우리는 프로그램마다 다른 접근법을 사용해야만 한다. 어쨌든 Spring은 우리가 이 bean factory를 확장하고 postProcessJaxRpcService 메소드를 오버라이드 할수 있는 것처럼 이것을 쉽게 만든다.

public class AccountHandlerJaxRpcPortProxyFactoryBean extends JaxRpcPortProxyFactoryBean {

protected void postProcessJaxRpcService(Service service) {
QName port = new QName(this.getNamespaceUri(), this.getPortName());
List list = service.getHandlerRegistry().getHandlerChain(port);
list.add(new HandlerInfo(AccountHandler.class, null, null));

logger.info("Registered JAX-RPC Handler [" + AccountHandler.class.getName() + "] on port " + port);
}

}

그리고 마지막으로 우리는 factory bean을 사용하기 위해 Spring설정을 변경하는 것을 기억해야만 한다.

    <bean id="accountWebService" class="example.AccountHandlerJaxRpcPortProxyFactoryBean">
...
</bean>






17.5.5. XFire를 사용하여 웹 서비스를 드러내기



XFire는 Codehaus에서 호스팅되는 가벼운 SOAP라이브러리이다. 현 시점(2005년 3월)에, XFire는 여전히 개발중이다. 비록 Spring지원이 안정적이라고 하더라도 대부분의 기능은 나중에 추가될것이다. XFire를 드러내는 것은 당신이 WebApplicationContext에 추가할 RemoteExporter-스타일의 bean으로 조합된 XFire를 가진 XFire 컨텍스트를 사용하는 것이다.


당신이 서비스를 드러내는 것을 허용하는 모든 메소드처럼 당신은 드러낼 서비스를 포함하는 관련된 WebApplicationContext를 가진 DispatcherServlet을 생성해야 한다.

<servlet>
<servlet-name>xfire</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
</servlet>


당신은 XFire설정을 링크해야만 한다. 이것은 ContextLoaderListener(또는 서블릿)가 가지는 contextConfigLocations 컨텍스트 파라미터에 컨텍스트 파일을 추가하는것이다. 설정 파일은 XFire jar파일내 위치하고 물론 애플리케이션의 클래스패스에 위치할수도 있다.

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:org/codehaus/xfire/spring/xfire.xml
</param-value>
</context-param>

<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>


당신이 서블릿 맵핑(위에서 선언된 XFire서블릿을 위한 /* 맵핑)을 추가한 후에 당신은 오직 XFire를 사용하는 서비스를 드러내기 위한 추가적인 bean을 추가해야만 한다. 예를 들어 당신은 xfire-servlet.xml을 다음에 추가하라.

<beans>
<bean name="/Echo" class="org.codehaus.xfire.spring.XFireExporter">
<property name="service">
<ref bean="echo"/>
</property>
<property name="serviceInterface">
<value>org.codehaus.xfire.spring.Echo</value>
</property>
<property name="serviceBuilder">
<ref bean="xfire.serviceBuilder"/>
</property>
<!-- the XFire bean is wired up in the xfire.xml file you've linked in earlier
<property name="xfire">
<ref bean="xfire"/>
</property>
</bean>

<bean id="echo" class="org.codehaus.xfire.spring.EchoImpl"/>
</beans>


XFire는 나머지를 다룬다. 이것은 당신의 서비스 인터페이스를 분석하고 이것으로 부터 WSDL을 생성한다. 이 문서의 일부는 XFire사이트로부터 가져왔다. XFire와 Spring통합에 대한 좀더 상세한 정보는 docs.codehaus.org/display/XFIRE/Spring를 보라.






17.6. 자동-탐지(Auto-detection)는 원격 인터페이스를 위해 구현되지 않는다.



구현된 인터페이스의 자동-탐지가 원격 인터페이스에는 발생하지 않는 가장 중요한 이유는 원격 호출자를 위해 너무 많은 문이 열리는 것을 피하는 것이다. 대상 객체는 호출자에게 드러내는 것을 원하지 않는 InitializingBean 이나 DisposableBean처럼 내부 콜백 인터페이스를 구현해야만 한다.


대상에 의해 구현된 모든 인터페이스를 가진 프록시를 제공하는 것은 로컬의 경우 언제나 문제가 되지 않는다. 하지만 원격 서비스를 내보낼때 당신은 원격 사용의 경향이 있는 특정 작업을 가진 특정 서비스 인터페이스를 보여야만 한다. 내부 콜백 인터페이스외에도 대상은 원격 노출의 경향이 있는 것중 하나를 가진 다중 비지니스 인터페이스를 구현해야만 한다. 이러한 이유로 우리는 명시되는 서비스 인터페이스를 요구한다.


이것은 설정의 편리함과 내부 메소드의 뜻하지 않는 노출의 위험사이의 거래이다. 서비스 인터페이스를 명시하는 것은 많은 노력이 필요하지 않고 당신을 특정 메소드의 제어된 노출에 관련된 안전적인 쪽에 두게된다.






17.7. 기술을 선택할때 고려사항.



여기에 표시된 각각 그리고 모든 기술은 결점을 가진다. 당신이 기술을 선택할때 당신이 드러내는 서비스와 당신이 정보를 통해 보낼 객체중 필요한 것을 주의깊게 검토해야만 한다.


RMI를 사용할때, 당신이 RMI 소통을 관통(tunneling)하지 않는 한 HTTP프로토콜을 통해 객체에 접근하는 것은 불가능하다. RMI는 정보를 통해 직렬화가 필요한 복합 데이터 모델을 사용할때 중요한 완전한 객체 직렬화를 지원하는 상당히 무거운 프로토콜이다. 어쨌든 RMI-JRMP는 자바 클라이언트에 묶인다. 이것은 자바-대-자바 원격 솔루션이다.


Spring의 HTTP호출자는 만약 당신이 HTTP-기반 원격이 필요하지만 자바 직렬화에 의존한다면 좋은 선택이다. 이것은 수송기처럼 HTTP를 사용하는 RMI호출자를 가진 기본 내부구조를 공유한다. HTTP호출자는 자바-대-자바 원격에 제한을 가지지 않을뿐 아니라 클라이언트측과 서버측 모두 제한을 가하지 않는다. (후자는 비-RMI인터페이스를 위해 Spring의 RMI호출자에 적용한다.)


Hessian 그리고/또는 Burlap은 명시적으로 비-자바 클라이언트를 허용하기 때문에 이종 환경내에서 작동할때 명백한 값을 제공한다. 어쨌든 비-자바 지원은 여전히 제한된다. 알려진 문제는 늦게 초기화하는 collection으로 조합된 Hibernate객체의 직렬화를 포함한다. 만약 당신이 그러한 데이타 모델을 가진다면 Hessian대신에 RMI나 HTTP호출자를 사용하는 것을 검토하라.


JMS는 서비스의 클러스터(clusters)를 제공하기 위해 유용할수 있고 로드 밸런싱, 발견(discovery) 그리고 자동 대체(failover)를 다루기 위해 JMS 브로커(broker)를 허용한다. 디폴트에 의해 자바 직렬화는 JMS원격을 사용하지만 JMS제공자가 서버가 다른 기술로 구현되는것을 허용하는 XStream과 같은 포맷팅을 묶기 위한 다른 기법을 사용할수 있을때 사용된다.


EJB는 표준적인 권한(role)-기반 인증과 인증, 그리고 원격 트랜잭션 위임을 지원하는 면에서 RMI를 능가하는 장점을 가진다. 이것은 비록 핵심 Spring에 의해 제공되지는 않지만 보안 컨텍스트 위임을 지원하는 RMI호출자나 HTTP호출자를 얻는것은 가능하다. 써드 파티나 사용자정의 솔루션내 플러그인하기 위한 선호하는 고리(hooks)가 있다. 


Spring EJB에 접근하고 구현하기

EJB에 접근하기





16.1.1. 개념



local또는 remote 비상태유지(stateless) 세션빈의 메소드를 호출하기 위해, 클라이언트 코드는 (local또는 remote)EJB Home객체를 얻기위해 대개 JNDI룩업을 수행해야만 한다. 그 다음 실질적인 (local또는 remote)EJB객체를 얻기 위해 객체의 'create' 메소드 호출을 사용한다. 하나 이상의 메소드는 EJB에서 호출된다.


반복적인 하위레벨 코드를 파하기 위해 많은 EJB애플리케이션은 서비스 위치자(Locator)와 비지니스 위임 패턴을 사용한다. 클라이언트 코드 도처에 JDNI룩업을 하는것보다 더 좋다. 하지만 그들의 일반적인 구현물은 명백한 단점을 가진다. 예를 들면





  • EJB를 사용한 전형적인 코드는 서비스 위치자나 비지니스 위임 패턴에 의존한다. 하지만 이것은 테스트를 좀더 어렵게 한다.



  • 비지니스 위임이 없이 사용되는 서비스 위치자 패턴의 경우, 애플리케이션 코드는 여전히 EJB Home의 create()메소드를 호출하는것으로 끝이 나고 결과 예외를 다룬다. 게다가 이것은 EJB API와 EJB프로그래밍 모델의 복잡함에 묶인다.



  • 비지니스 위임 패턴을 구현하는 것은 일반적으로 우리가 EJB의 같은 메소드를 간단히 호출하는 다양한 메소드를 써야만 하는 위치의 명백한 코드 중복의 결과를 낳는다.


Spring접근법은 대개 코드가 없는 비지니스 위임처럼 작동하는 Spring ApplicationContext나 BeanFactory내 설정되는 프록시 객체의 생성과 사용을 허용한다. 당신은 실제값을 추가하지 않는다면 다른 서비스 위치자, 다른 JNDI 룩업 또는 손으로 작성된 비지니스 위임내 중복 메소드를 사용할 필요가 없다.






16.1.2. local SLSBs에 접근하기



우리가 local EJB를 사용할 필요가 있는 웹 컨트롤러를 가지고 있다고 가정하자. 우리는 최상의 상황을 따를것이고 EJB 비지니스 메소드 인터페이스 패턴을 사용할것이다. 그래서 EJB의 local 인터페이스는 EJB 성격이 아닌 비지니스 메소드 인터페이스를 확장한다. 이 비지니스 메소드 인터페이스를 MyComponent라고 부르자.

public interface MyComponent {
...
}

(비지니스 메소드 인터페이스 패턴을 위한 중요한 이유중 하나는 local 인터페이스내 메소드 시그너처와 bean구현 클래스사이 동기화가 자동적이라는 것을 확인하는 것이다. 다른 이유는 이것이 나중에 우리는 위해 그렇게 하도록 만들때 서비스의 POJO로 교체하는것을 좀더 쉽게 만든다는 것이다. ) 물론 우리는 local home인터페이스를 구현할 필요가 필요가 있을것이다. 그리고 SessionBean과 MyComponent비지니스 메소드 인터페이스를 구현하는 bean구현 클래스를 제공한다. 지금 우리의 웹 티어 컨트롤러를 EJB구현물로 연결하는 필요가 있는 자바코드만이 컨트롤러의 MyComponent타입의 setter메소드를 드러낸다. 이것은 컨트롤러내 인스턴스 변수처럼 참조를 저장할것이다.

private MyComponent myComponent;

public void setMyComponent(MyComponent myComponent) {
this.myComponent = myComponent;
}

우리는 컨트롤러내 어떠한 비지니스 메소드내 인스턴스 변수를 순차적으로 사용할수 있다. 지금 우리가 Spring ApplicationContext 나 BeanFactory밖에서 컨트롤러 객체를 얻는다고 가정하자. 우리는 EJB프록시 객체가 될 LocalStatelessSessionProxyFactoryBean를 설정하는 같은 컨텍스트를 사용할수 있다. 프록시의 설정은 그리고 컨트롤러의 myComponent 프라퍼티의 셋팅은 다음처럼 설정 항목으로 한다.

<bean id="myComponent"
class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
<property name="jndiName"><value>myComponent</value></property>
<property name="businessInterface"><value>com.mycom.MyComponent</value></property>
</bean>

<bean id="myController" class = "com.mycom.myController">
<property name="myComponent"><ref bean="myComponent"/></property>
</bean>

Spring AOP프레임워크의 도움으로 비록 당신이 이러한 결과를 즐기기 위해 AOP개념으로 작업을 강제로 하지 않더라도 이 상황뒤에는 마법같은 일이 많다. myComponent bean정의는 비지니스 메소드 인터페이스를 구현하는 EJB를 위한 프록시를 생성한다. EJBlocal home은 시작시 캐시된다. 그래서 하나의 JNDI룩업만이 있다. 각각의 EJB가 호출되는 시점에 프록시는 local EJB의 create()메소드를 호출하고 EJB의 관련된 비지니스 메소드를 호출한다.


myController bean정의는 프록시를 위한 컨트롤러의 myController 프라퍼티를 셋팅한다.


EJB접근 기법은 애플리케이션 코드의 굉장한 단순화를 초래한다. 웹 티어 코드(또는 다른 EJB클라이언트 코드)는 EJB사용의 의존성을 가지지 않는다. 만약 우리가 POJO나 모의(mock)객체 또는 다른 테스트 스텁(stub)을 가진 EJB참조를 교체하기를 원한다면 우리는 자바코드의 한줄의 변경도 없이 myComponent bean정의를 간단하게 변경할수 있다. 추가적으로 우리는 JNDI룩업을 한줄도 쓰지 않거나 우리의 애플리케이션의 일부처럼 다른 EJB 관련 코드를 쓰지 않는다.


실제 애플리케이션에서 벤치마크와 경험은 이 접근법(대상 EJB 반영적인 호출의 포함하는)의 의 성능 오버헤드가 죄소이고 일반적인 사용에서 측정불가능이라는 것을 표시힌다. 애플리케이션 서버내 EJB구조와 관련된 비용때문에 우리는 EJB를 위한 잘 정의된 호출을 만드는것을 원하지 않는다는것을 기억하라.


JNDI룩업에 관련되는 한가지 주의사항이 있다. bean컨테이너에서 이 클래스는 대개 싱글톤처럼(이것을 프로토타입으로 만들기 위한 이유는 없다) 사용되는것이 가장 좋다. 어쩄든 bean컨테이너가 싱글톤(XML ApplicationContext 변형을 하는것처럼) 을 먼저 인스턴스화 한다면 당신은 EJB컨테이너가 대상 EJB를 로드하기 전에 bean컨테이너가 로드된다면 문제를 가지게 된다. 그것은 JNDI룩업이 이 클래스의 init메소드내 수행될것이고 캐시되지만 EJB는 대상 위치에 여전히 바운드되지 않을것이기 때문이다. 해결법은 이 factory객체를 미리 인스턴스화하지 않지만 첫번째 사용시 이것이 생성되는것을 허용한다. XML컨테이너에서 이것은 lazy-init 속성을 통해 컨트롤된다.


비록 이것이 Spring사용자에게 중요 관심사가 되지는 않을것이지만 EJB를 사용한 프로그램에 따른 AOP작업을 하는것은 LocalSlsbInvokerInterceptor를 찾는것을 원할것이다.






16.1.3. remote SLSB에 접근하기



remote EJB에 접근하는것은 local EJB에 접근하는것과 비교해서 SimpleRemoteStatelessSessionProxyFactoryBean를 사용하는것을 제외하고 기본적으로 같다. 물론 Spring을 사용하든 사용하지 않든 remote호출은 의미적으로 적용한다. 다른 컴퓨터내 다른 VM안에서 객체의 메소드를 호출하는것은 때때로 사용 시나리오와 실패(failure)핸들링의 개념으로 다르게 처리된다.


Spring의 EJB 클라이언트 지원은 Spring을 사용하지 않는 접근법에 비해 하나 이상의 장점을 추가한다. 대개 EJB를 local이나 remote로 호출하는 것들간에는 쉽게 진행하고 되돌리기 위한 EJB클라이언트 코드를 위해서는 문제가 있다. 이것은 local인터페이스 메소드가 호출되지 않는동안 remote인터페이스 메소드가 RemoteException를 던지는 것을 명시해야하고 클라이언트 코드는 이것을 다루어야 하기 때문이다. remote EJB로 옮겨질 필요가 있는 local EJB를 위해 쓰여진 클라이언트 코드는 일반적으로 remote 예외를 위한 핸들링을 추가하기 위해 변경되고 local EJB로 옮겨질 필요가 있는 remote EJB를 위해 쓰여진 클라이언트 코드는 remote 예외의 많은 필요없는 핸들링이 수행되지만 같은코드로 그대로 유지될수 있거나 그 코드를 제거하기 위해 변경될 필요가 있다. Spring remote EJB프록시를 사용하여 당신은 당신의 비지니스 메소드 인터페이스내 던져지는 RemoteException을 선언하는것을 대신할수 있고 EJB코드를 구현한다. RemoteException를 던지는 것을 제외하면 동일한 remote 인터페이스를 가지고 그들이 같다면 두개의 인터페이스를 자동적으로 처리하기 위한 프록시에 의존한다. 클라이언트 코드는 체크된 RemoteException을 다루지 않는다. 어떠한 실질적인 RemoteException은 EJB호출이 RuntimeException의 하위클래스인 체크되지 않은 RemoteAccessException 클래스처럼 다시 던져질것이다. 대상 서비스는 그 다음 클라이언트 코드의 인식및 처리가 없이 local EJB나 remote EJB(또는 POJO) 구현물 사이에서 교체될것이다. 물론 이것은 옵션적이다. 당신의 비지니스 인터페이스내 선언된 RemoteExceptions으로 부터 당신을 정지시키는것은 아무것도 없다.






16.2. Spring의 편리한 EJB구현물 클래스를 사용하기.



Spring은 또한 당신이 EJB를 구현하도록 도와주는 편리한 클래스를 제공한다. EJB가 트랜잭션 설정과 원격작업을 책임지도록 놔둔체 POJO내 EJB뒤에 비지니스 로직을 두는 좋은 상황을 만들기 위해 디자인되었다.


비상태유지(stateless) 또는 상태유지(stateful) 세션빈, 또는 메시지빈을 구현하기 위해, 당신은 AbstractStatelessSessionBean, AbstractStatefulSessionBean, 그리고 AbstractMessageDrivenBean/AbstractJmsMessageDrivenBean으로 부터 반복적으로 당신의 구현 클래스를 끌어낸다.


구현물을 명확한 자바 서비스 객체로 실질적으로 위임하는 비상태유지(stateless) 세션빈을 시험하는것을 검토하라. 우리는 비지니스 인터페이스를 가진다.

public interface MyComponent {
public void myMethod(...);
...
}

We have the plain java implementation object:

public class MyComponentImpl implements MyComponent {
public String myMethod(...) {
...
}
...
}

그리고 마지막으로 비상태유지(stateless) 세션빈 자체:

public class MyComponentEJB extends AbstractStatelessSessionBean
implements MyComponent {

MyComponent _myComp;

/**
* Obtain our POJO service object from the BeanFactory/ApplicationContext
* @see org.springframework.ejb.support.AbstractStatelessSessionBean#onEjbCreate()
*/
protected void onEjbCreate() throws CreateException {
_myComp = (MyComponent) getBeanFactory().getBean(
ServicesConstants.CONTEXT_MYCOMP_ID);
}

// for business method, delegate to POJO service impl.
public String myMethod(...) {
return _myComp.myMethod(...);
}
...
}

Spring EJB지원 기초 클래스는 그들의 생명주기처럼 BeanFactory(또는 이 경우 ApplicationContext의 하위클래스)를 디폴트로 생성하고 로드함으로써 이루어진다. 그 다음 EJB(예를 들면 POJO서비스 객체를 얻기 위한 위의 코드내에서 사용된것처럼)에 사용가능하게 된다. 로딩은 BeanFactoryLocator의 하위클래스인 전략(strategy)객체를 통해 이루어진다. BeanFactoryLocator의 실질적인 구현은 디폴트인 JNDI환경 변수(EJB의 경우, java:comp/env/ejb/BeanFactoryPath)처럼 명시된 자원 위치로부터 ApplicationContext을 생성하는 ContextJndiBeanFactoryLocator에 의해 사용된다. 만약 BeanFactory/ApplicationContext 로딩 전략을 변경할 필요가 없다면 디폴트 BeanFactoryLocator구현물은 setBeanFactoryLocator()메소드를 호출하거나 setSessionContext()내, 또는 EJB의 실질적인 생성자내에서 오버라이드되어 사용된다. 좀더 다양한 정보를 위해서는 JavaDoc를 보라.


JavaDoc에서 언급된것처럼 상태유지(stateful) 세션빈은 그들의 생명주기의 일부처럼 수동적이고 재활성화되기 위해 기대되고 EJB컨테이너에 의해 저장되지 않을수 있기 때문에 수동적이고 활성화된 BeanFactory를 로드하지않고 다시 로드하기 위한 ejbPassivateejbActivate로 부터 unloadBeanFactory()loadBeanFactory를 수동으로 호출할 non-serializable한 BeanFactory/ApplicationContext 인스턴스를 사용한다.


EJB의 사용을 위해 ApplicationContext를 로드하기 위한 ContextJndiBeanFactoryLocator의 디폴트 사용법은 몇몇 상황을 위해 적절하다. 어쟀든 모든 EJB가 자신이 복사본을 가진후 ApplicationContext가 많은 수의 bean을 로드하거나 그러한 bean의 초기화가 시간을 소비하거나 메모리 집중적일때 문제가 있다. 이 경우 사용자는 디폴트 ContextJndiBeanFactoryLocator사용법을 오버라이드하길 원할것이고 다중 EJB또는 다른 클라이언트에 의해 사용되기 위한 공유 BeanFactory나 ApplicationContext를 로드하고 사용할수 있는 ContextSingletonBeanFactoryLocatore와 같은 다른 BeanFactoryLocator을 사용한다. 이것을 하는것은 EJB를 위해 유사한 코드를 추가함으로써 비교적 간단하다.

   /**
* Override default BeanFactoryLocator implementation
*
* @see javax.ejb.SessionBean#setSessionContext(javax.ejb.SessionContext)
*/
public void setSessionContext(SessionContext sessionContext) {
super.setSessionContext(sessionContext);
setBeanFactoryLocator(ContextSingletonBeanFactoryLocator.getInstance());
setBeanFactoryLocatorKey(ServicesConstants.PRIMARY_CONTEXT_ID);
}

그것들의 사용법에 대한 좀더 다양한 정보를 위해서 BeanFactoryLocatorContextSingletonBeanFactoryLocatore를 위한 관련 JavaDoc를 보라.



Spring JDBC를 사용한 데이터 접근

JDBC를 사용한 데이터 접근





10.1. 소개



JDBC추상 프레임워크는 Spring에 의해 제공되는 4개( core , datasource , object , 그리고 support )의 패키지로 구성된다.


org.springframework.jdbc.core 패키지는 JdbcTemplate를 포함하고 이것의 다양한 callback인터페이스, 거기다가 다양한 관련 클래스를 포함한다.


org.springframework.jdbc.datasource 패키지는 쉬운 데이터소스 접근을 위한 유틸리티 클래스를 포함하고 J2EE컨테이너밖에서 변경이 되지 않은 JDBC코드를 테스트하고 실행하기 위해 사용될수 있는 여러가지 간단한 DataSource구현을 포함한다. 유틸리티클래스는 필요하다면 JNDI로 부터 Connection을 얻고 Connection을 닫는 정적 메소드를 제공한다. 이것은 DataSourceTransactionManager를 사용하는 것처럼 쓰레드범위의 연결을 지원한다.


그 다음 org.springframework.jdbc.object 패키지는 쓰레드에 안전하고 재사용가능한 객체처럼 RDBMS 쿼리, update 그리고 저장 프로시저를 표현하는 클래스를 포함한다. 이 접근법은 JDO에 의해 형상화 되었다. 쿼리에 의해 반환된 객체는 데이터베이스로 부터 “disconnected” 된다. JDBC추상화의 높은 레벨은 org.springframework.jdbc.core 패키지내에서 하위 레벨에 의존한다.


마지막으로 org.springframework.jdbc.support 패키지는 SQLException 번역 기능과 몇개의 유틸리티 클래스를 찾을수 있는 곳이다.


JDBC처리중에 던져진 예외는 org.springframework.dao 패키지내에서 정의된 예외로 번역이 된다. 이것은 Spring JDBC추상 레이어를 사용하는 코드가 JDBC또는 RDBMS특성 에러 처리를 구현할 필요가 없다는 것을 의미한다. 모든 번역된 예외는 호출자에게 전파되기 위한 다른 예외를 허락하는 동안 당신이 복구할수 있는 예외를 잡는 옵션을 제공하고 체크되지 않는다.






10.2. 기본적인 JDBC처리와 에러 처리를 위한 JDBC Core클래스 사용하기







10.2.1. JdbcTemplate



이것은 JDBC Core패키지에서 핵심 클래스이다. 이것은 자원을 생성하고 해재함으로써 JDBC의 사용을 단순화시킨다. 이것은 연결을 닫는것을 잊어버리는것처럼 공통적으로 발생할수 있는 에러를 피하도록 도와준다. 이것은 statement생성및 수행, SQL을 생성하고 결과물을 반환하고 애플리케이션 코드를 벗어나는 핵심적인 JDBC절차를 수행한다. 이 클래스는 SQL쿼리, update문 또는 저장 프로시저 호출, ResultSets를 넘어서 순환을 모방하고 반환된 인자값을 보여주는 작업을 수행한다. 이것은 또한 JDBC예외를 잡고 일반적인 것으로 그것들을 번역하고 좀더 다양한 정보를 제공하도록 하고 org.springframework.dao 패키지내에 정의된 예외 구조제공한다.


이 클래스를 사용하는 코드는 단지 명백하게 정의된 규칙을 제공하는 callback인터페이스만 구현할 필요가 있다. PreparedStatementCreator callback인터페이스는 SQL과 필요한 인자를 제공하는 클래스에 의해 제공되는 Connection으로 prepared statement를 생성한다. 호출 가능한 statement를 생성하는 것은 CallableStatementCreateor 인터페이스이다. RowCallbackHandler 인터페이스는 ResultSet으로 부터 각각의 row에서 값을 뽑아낸다.


이 클래스는 데이터소스 참조또는 애플리케이션 컨텍스트내에서 준비되고 빈(bean)참조처럼 서비스하기 위해 직접적인 초기화를 통해 서비스구현내에서 사용될수 있다. 주의: 데이터소스는 애플리케이션 컨텍스트내에서 언제나 빈처럼 설정되어야 한다. 이 클래스는 callback인터페이스와 SQLExceptionTranslator인터페이스에 의해 인자화 되기 때문에 이것을 하위클래스화 할 필요가 없다. 이 클래스에 의해 발생된 모든 SQL은 로그화된다.






10.2.2. DataSource



데이터베이스로부터 데이터작업을 수행하기 위해서 우리는 데이터베이스로 부터 Connection을 얻을 필요가 있다. Spring은 DataSource 을 통해서 이것을 수행한다. DataSource 는 JDBC스펙의 일부이고 생성된 connection공장처럼 볼수 있다. 이것은 컨테이너또는 프레임워크에게 높은 성능의 Connection pooling와 애플리케이션 코드로 부터 트랜잭션 관리 부분을 숨길수 있도록 한다. 개발자의 입장에서 당신은 데이터베이스에 연결하는 상세내역을 알 필요가 없다. 이것은 데이터베이스를 셋팅하는 관리자의 책임이다. 당신은 당신의 코드를 개발하고 테스트하는 동안 두가지 책임을 모두 수행해야 할지도 모르지만 어떻게 데이터소스가 설정이 되는지에 대해서 알필요는 없다.


Spring의 JDBC레이어를 사용할때 당신은 JNDI로 부터 데이터소스를 얻거나 Spring배포내에서 제공되어 있는 구현물로 자신만의 설정을 할수도 있다. 후자의 경우 웹 컨테이너밖에서 단위테스팅을 능숙하게 할수 있게 한다. 우리는 나중에 다루어질 여러개의 추가적인 구현물이 있지만 이 섹션에서 DriverManagerDataSource 을 사용할것이다. DriverManagerDataSource 는 당신이 JDBC Connection을 얻었을때 작업하기 위해서 사용되어진 것과 같은 방식으로 작동한다. 당신은 DriverManager 가 드라이버클래스를 로드할수 있도록 JDBC드라이버의 패키지를 포함한 전체이름을 명시해야 한다. 그 다음 당신은 JDBC드라이버사이에 변경이 되는 url을 제공해야만 한다. 여기서 정확한 값을 위해서 당신 드라이버의 문서를 찾아보아야 한다. 마지막으로 당신은 데이터베이스 연결에 사용되는 사용자명과 비밀번호를 제공해야만 한다. 이것은 DriverManagerDataSource :을 설정하기 위한 방법을 보여주는 예제이다.

DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName( "org.hsqldb.jdbcDriver");
dataSource.setUrl( "jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername( "sa");
dataSource.setPassword( "");





10.2.3. SQLExceptionTranslator



SQLExceptionTranslator 은 SQLExceptions과 우리의 데이터접근 전략에 얽매이지 않는 org.springframework.dao.DataAccessException 사이에 해석할수 있는 클래스에 의해 구현될수 있는 인터페이스이다.


구현은 좀더 정확성을 위해서 일반적(예를 들면, JDBC를 위해 SQLState코드를 사용하는)이거나 소유(예를 들면, Oracle에러코드를 사용하는)될수있다.


SQLErrorCodeSQLExceptionTranslator 는 초기설정에 의해서 사용이 되는 SQLExceptionTranslator의 구현이다. 이 구현은 업체코드를 명시하는데 사용한다. SQLState 구현보다 좀더 정확하지만 업체에 종속적이다. 에러코드해석은 SQLErrorCodes 이라고 불리는 자바빈 타입의 코드에 기초를 둔다. 이 클래스는 "sql-error-codes.xml"라는 이름의 설정파일의 내용에 기초를 두는 SQLErrorCodes 를 생성하기 위한 공장같은 이름의 SQLErrorCodesFactory 에 의해서 생성되고 활성화된다. 이 파일은 업체코드에 의해 활성화되고 DatabaseMetaData로 부터 얻어진 DatabaseProductName에 기초를 둔다.


SQLErrorCodeSQLExceptionTranslator 는 다음의 일치규칙(matching rules)을 적용한다.





  • 어느 하위 클래스에 의해서 구현되는 사용자지정해석(custom translation). 이 클래스는 이 규칙을 적용하지 않는 경우에 견고해지고 스스로 사용되는 것에 주의하라.



  • 에러코드일치를 적용하라. 에러코드는 초기설정에 의해서 SQLErrorCodesFactory으로 부터 얻어진다. 이것은 클래스패스로부터 에러코드를 찾고 데이터베이스 메타데이터로부터 데이터베이스 이름으로 부터 키를 입력한다.



  • fallback해석자를 사용하라. SQLStateSQLExceptionTranslator는 초기설정의 fallback해석자이다.



SQLErrorCodeSQLExceptionTranslator 는 다음과 같은 방법으로 확장할수 있다.

public class MySQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator {
protected DataAccessException customTranslate(String task, String sql, SQLException sqlex) {
if (sqlex.getErrorCode() == -12345)
return new DeadlockLoserDataAccessException(task, sqlex);
return null;
}
}

이 예제에서 에러코드 '-12345'는 해석되었거나 초기설정 해석자구현에 의해서 해석되기 위해서 남겨진 다른 에러코드이다. 이 사용자지정 해석자(custom translator)를 사용하기 위해서 setExceptionTranslator 메소드를 사용하고 이 해석자가 필요한 데이터 접근 처리를 위해 JdbcTemplate 를 사용하기 위한 JdbcTemplate 으로 값을 넘길필요가 있다. 여기에 사용자지정 해석자가 어떻게 사용되는지에 대한 예제가 있다.

// create a JdbcTemplate and set data source 
JdbcTemplate jt = new JdbcTemplate();
jt.setDataSource(dataSource);
// create a custom translator and set the datasource for the default translation lookup
MySQLErrorCodesTransalator tr = new MySQLErrorCodesTransalator();
tr.setDataSource(dataSource);
jt.setExceptionTranslator(tr);
// use the JdbcTemplate for this SqlUpdate
SqlUpdate su = new SqlUpdate();
su.setJdbcTemplate(jt);
su.setSql("update orders set shipping_charge = shipping_charge * 1.05");
su.compile();
su.update();

이 사용자지정 해석자는 sql-error-codes.xml 내의 에러코드를 찾기위해 디폴트 해석자를 원하기 때문에 데이터소스를 전달했다.






10.2.4. Statements 실행하기



SQL문을 실행하기 위해 필요한 작은 코드가 있다. 당신이 필요한 모든것은 DataSourceJdbcTemplate 이다. 당신이 그것을 가졌을때 당신은 JdbcTemplate 과 함께 제공되는 많은 편리한 메소드를 사용할수 있다. 여기에 작지만 새로운 테이블을 생성하는 모든 기능적인 클래스를 위해 필요한 짧은 예제를 보여준다.

import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class ExecuteAStatement {
private JdbcTemplate jt;
private DataSource dataSource;

public void doExecute() {
jt = new JdbcTemplate(dataSource);
jt.execute("create table mytable (id integer, name varchar(100))");
}

public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}






10.2.5. 쿼리문 실행하기



메소드를 수행하는 것에 추가적으로 여기엔 많은 수의 쿼리 메소드가 있다. 그 메소드의 몇몇은 하나의 값을 반환하는 쿼리를 위해 사용되는 경향이 있다. 아마도 당신은 하나의 레코드로 부터 카운트나 특정값을 가져오길 원할지도 모른다. 만약 그 경우라면 당신은 queryForInt , queryForLong 또는 queryForObject 를 사용할수 있다. 후자는 반환된 JDBC타입을 인자처럼 전달된 자바 클래스로 변환할 것이다. 만약 타입변환이 유효하지 않다면 InvalidDataAccessApiUsageException 를 던질것이다. 여기에 int 를 위한것과 String 을 위한 두개의 쿼리 메소드를 포함하는 예제가 있다.

import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class RunAQuery {
private JdbcTemplate jt;
private DataSource dataSource;

public int getCount() {
jt = new JdbcTemplate(dataSource);
int count = jt.queryForInt("select count(*) from mytable");
return count;
}

public String getName() {
jt = new JdbcTemplate(dataSource);
String name = (String) jt.queryForObject("select name from mytable", java.lang.String.class);
return name;
}

public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}

하나의 결과물을 위한 쿼리 메소드에 추가적으로 쿼리가 반환하는 각각의 레코드를 가지는 List를 반환하는 다양한 메소드가 있다. 가장 일반적인 하나는 각각의 레코드를 위한 칼럼값을 표현하는 Map 형태의 List 를 반환하는 queryForList 이다. 만약 우리가 모든 레코드의 리스트를 가져오는 메소드를 추가한다면 다음과 같을것이다.

    public List getList() {
jt = new JdbcTemplate(dataSource);
List rows = jt.queryForList("select * from mytable");
return rows;
}

반환되는 리스트는 이것저것 보일것이다. [{name=Bob, id=1}, {name=Mary, id=2}].






10.2.6. 데이터베이스 수정하기



당신이 사용할수 있는 많은 update메소드가 있다. 나는 어떠한 기본키를 위한 칼럼을 수정하는 예제를 보여줄것이다. 이 예제에서 나는 레코드 파라미터를 위한 위치자(place holders)를 가진 SQL문을 사용한다. 대부분의 쿼리및 update메소드는 이 기능을 가진다. 파라미터 값은 객체의 배열내에 전달된다.

import javax.sql.DataSource;

import org.springframework.jdbc.core.JdbcTemplate;

public class ExecuteAnUpdate {
private JdbcTemplate jt;
private DataSource dataSource;

public void setName(int id, String name) {
jt = new JdbcTemplate(dataSource);
jt.update("update mytable set name = ? where id = ?", new Object[] {name, new Integer(id)});
}

public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}






10.3. 데이터베이스에 연결하는 방법을 제어하기







10.3.1. DataSourceUtils



헬퍼 클래스는 필요하다면 JNDI로 부터 connection을 얻거나 connection을 닫기 위한 정적 메소드를 제공하고 쓰레드 범위의 connection을 위한 지원 예를 들면 DataSourceTransactionManager를 사용한다.


주의 : getDataSourceFromJndi메소드는 BeanFactory를 사용하지 않는 애플리페이션을 목표로 한다. 이것은 factory내 당신의 빈즈나 JdbcTemplate 인스턴스를 먼저 설정하는것을 선호한다. JndiObjectFactoryBean 는 JNDI로 부터 DataSource 를 꺼내고 다른 빈즈에게 DataSource 빈참조를 주는데 사용될수 있다. 다른 DataSource 로의 교체는 설정의 문제이다. 당신은 non-JNDI의 DataSource 를 가진 FactoryBean 의 정의를 교체할수 있다.






10.3.2. SmartDataSource



클래스에 의해 구현되기 위한 인터페이스는 관계 데이터베이스로의 connection을 제공할수 있다. javax.sql.DataSource 인터페이스를 확장하는것은 클래스가 주어진 작업후에 닫혀야만하는 connection이거나 그렇지 않더라도 쿼리하기 위해 사용하도록 허락한다. 이것은 때때로 우리가 connection을 재사용하길 원한다는 것을 안다면 효율을 위해 유용할수 있다.






10.3.3. AbstractDataSource



Spring의 DataSource 구현을 위한 추상 기본 클래스는 "시시함(uninteresting)"을 처리한다. 이것은 당신이 자신의 DataSource 구현을 쓴다면 확장할 클래스이다.






10.3.4. SingleConnectionDataSource



하나의 connection을 포장한 SmartDataSource 의 구현은 사용후에 닫히질 않는다. 분명히 이것은 다중 쓰레드 성능은 아니다.


만약 퍼시스턴스 툴들을 사용할때 suppressClose 을 true로 설정한 것처럼 클라이언트코드가 풀링된 connection의 소비내 close를 호출할 것이다. 이것은 물리적 connection대신에 close-억제 프록시를 반환할것이다. 당신은 이것을 고유의 Oracle connection이나 다른 어떠한 것처럼 형변환할수 없다는것을 알라.


이것은 기본적으로 테스트 클래스이다. 예를 들면 이것은 간단한 JNDI환경과 함께 연결되어 애플리케이션 서버밖의 코드를 쉽게 테스팅 가능하도록 한다. DriverManagerDataSource 와는 대조적으로 이것은 언제나 물리적 connection생성 초과를 피하고 같은 connection을 재사용한다.






10.3.5. DriverManagerDataSource



SmartDataSource 의 구현은 빈 프라퍼티를 통해 일반적인 예전 JDBC드라이버를 설정하고 모든 시점에 새로운 connection을 반환한다.


각각의 ApplicationContext내 DataSource 빈 이나 간단한 JNDI환경과의 연결내에서 어느쪽이건 테스트나 J2EE컨테이너 밖의 단독 환경을 위해 유용하다. 풀 성격의 Connection.close() 호출은 간단하게 connection을 닫을 것이다. 그래서 어떠한 DataSource-인식 퍼시스턴스코드도 작동할것이다.






10.3.6. DataSourceTransactionManager



하나의 JDBC데이터 소스를 위한 PlatformTransactionManager구현은 특정 데이터 소스로 부터 쓰레드에 JDBC connection을 바인드한다. 잠재적으로 데이터 소스당 하나의 쓰레드 connection을 허락한다.


애플리케이션 코드는 J2EE의 표준적인 DataSource.getConnection 대신에 DataSourceUtils.getConnection(DataSource) 을 통해 JDBC connection을 가져와야만 한다. 이것은 어쨌든 추천된다. 그리고 이것은 체크된 SQLException 대신에 체크되지 않은 org.springframework.dao 예외를 던진다. JdbcTemplate 와 같은 모든 프레임워크 클래스는 절대적으로 이 전략을 사용한다. 만약 이 트랜잭션 관리자를 사용하지 않는다면 룩업 전략은 공통된 것과 같이 정확하게 작동한다.


사용자 지정 격리 레벨을 지원하고 선호하는 JDBC statement쿼리 중단처럼 적용된 것을 중단한다. 후자를 지원하기 위해 애플리케이션 코드는 JdbcTemplate 나 각각의 생성된 statement를 위한 DataSourceUtils.applyTransactionTimeout 메소드 호출을 사용해야만 한다.


이 구현은 하나의 자원일 경우 JTA를 지원하기 위해 컨테이너를 요구하지 않는것처럼 JtaTransactionManager 대신에 사용될수 있다. 두가지 사항 사이의 전환은 설정상의 문제이다. 만약 당신이 요구되는 connection룩업 패턴을 고집한다면 JTA는 사용자 지정 격리 레벨을 지원하지 않는다는 것에 주의하라.!






10.4. 자바 객체처럼 JDBC작업을 모델링 하기.



org.springframework.jdbc.object 패키지는 좀더 객체 지향적인 방법으로 데이터베이스에 접근하는 것을 허락하는 클래스를 포함한다. 당신은 쿼리를 수행하고 관계적인 칼럼 데이터를 비지니스 객체의 프라퍼티로 맵핑하는 비지니스 객체를 포함하는 리스트처럼 결과를 얻을수 있다. 당신은 또한 저장 프로시저와 update, delete그리고 insert 구문을 실행할수 있다.






10.4.1. SqlQuery



SQL궈리를 표현하기 위한 쓰레드에 안전한 객체를 재사용가능하다. 하위 클래스는 ResultSet를 반복하는 동안 결과를 저장할수 있는 객체를 제공하기 위해 newResultReader()메소드를 구현해야만 한다. 이 클래스는 드물게 직접적으로 사용된다. 이 클래스를 확장하는 MappingSqlQuery 가 레코드를 자바 플래스로 맵핑하기 위한 좀더 편리한 구현을 제공한다. SqlQuery 을 확장하는 다른 구현은 MappingSqlQueryWithParametersUpdatableSqlQuery 이다.






10.4.2. MappingSqlQuery



MappingSqlQuery 는 명확한 하위 클래스가 JDBC ResultSet 의 각각의 레코드를 객체로 변환하기 위한 추상메소드인 mapRow(ResultSet, int) 를 구현함으로써 재사용가능한 쿼리이다.


모든 SqlQuery 구현으로 이것은 매우 종종 사용되고 이것은 사용하기 가장 쉬운 것중 하나이다.


여기에 사용자 지정 테이블로부터 데이터를 Customer라고 불리는 자바 객체로 맵핑하는 사용자 지정 쿼리의 예제를 간단히 설명하는것이 있다.

  private class CustomerMappingQuery extends MappingSqlQuery {
public CustomerMappingQuery(DataSource ds) {
super(ds, "SELECT id, name FROM customer WHERE id = ?");
super.declareParameter(new SqlParameter("id", Types.INTEGER));
compile();
}
public Object mapRow(ResultSet rs, int rowNumber) throws SQLException {
Customer cust = new Customer();
cust.setId((Integer) rs.getObject("id"));
cust.setName(rs.getString("name"));
return cust;
}
}

우리는 오직 파라미터로 DataSource 를 가지는 사용자지정 쿼리를 위한 생성자를 제공한다. 이 생성자에서 우리는 DataSource 와 이 쿼리를 위해 레코드를 가져오기 위해 수행되어야 하는 SQL을 가진 수퍼클래스의 생성자를 호출한다. 이 SQL은 수행되는 동안 전달될 어떠한 파라미터를 위한 위치자를 포함한 PreparedStatement 를 생성하기 위해 사용될 것이다. 각각의 파라미터는 SqlParameter 내에 전달될 declareParameter 메소드를 사용해서 선언되어야 한다. SqlParameter 는 이름과 java.sql.Types 내에 정의될 JDBC타입을 가진다. 모든 파라미터가 compile 메소드를 호출해서 정의된 후에 statement는 준비되고 나중에 수행된다.


이 사용자 정의 쿼리가 초기화되고 수행되는 코드를 보자.

    public Customer getCustomer(Integer id) {
CustomerMappingQuery custQry = new CustomerMappingQuery(dataSource);
Object[] parms = new Object[1];
parms[0] = id;
List customers = custQry.execute(parms);
if (customers.size() > 0)
return (Customer) customers.get(0);
else
return null;
}

예제내 메소드는 오직 파마리터로써 전달되는 id와 함께 customer를 가져온다. CustomerMappingQuery 클래스의 인스턴스를 생성한 후에 우리는 전달될 모든 파라미터를 포함할 객체의 배열을 생성한다. 이 경우에 오직 한개의 파라미터가 있고 이것은 Integer 로 전달된다. 지금 우리는 파라미터의 배열을 사용해서 쿼리를 수행할 준비가 되었고 우리의 쿼리를 통해 반환되는 각각의 레코드를 위한 Customer 객체를 포함하는 List 를 얻게된다. 이 경우에 적합한 경우 하나의 항목이 될것이다.






10.4.3. SqlUpdate



RdbmsOperation 하위 클래스는 SQL update를 상징한다. 쿼리처럼 update객체는 재사용가능하다. 모든 RdbmsOperation객체처럼 update는 파라미터를 가지고 SQL내 정의된다.


이 클래스는 쿼리 객체의 execute()메소드를 위한 많고 유사한 update()메소드를 제공한다.


이 클래스는 명확하다. 비록 이것이 하위 클래스(예를 들면 사용자 정의 update메소드를 추가하는)가 될수 있지만 이것은 SQL을 셋팅하고 파라미터를 선언함으로써 쉽게 파라미터화 될수 있다.

import java.sql.Types;

import javax.sql.DataSource;

import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.SqlUpdate;

public class UpdateCreditRating extends SqlUpdate {
public UpdateCreditRating(DataSource ds) {
setDataSource(ds);
setSql("update customer set credit_rating = ? where id = ?");
declareParameter(new SqlParameter(Types.NUMERIC));
declareParameter(new SqlParameter(Types.NUMERIC));
compile();
}

/**
* @param id for the Customer to be updated
* @param rating the new value for credit rating
* @return number of rows updated
*/
public int run(int id, int rating) {
Object[] params =
new Object[] {
new Integer(rating),
new Integer(id)};
return update(params);
}
}





10.4.4. StoredProcedure



RDBMS 저장 프로시저의 객체 추상화를 위한 수퍼클래스이다. 이 클래스는 추상적이고 execute메소드들은 protected상태이다. 좀더 단단하게 타이핑된 하위 클래스를 통해 다른것보다 좀더 사용이 제한적이다.


상속된 sql프라퍼티는 RDBMS에 저장된 저장프로시저의 이름이다. JDBC 3.0은 명명된 파라미터를 소개한다. 비록 이 클래스에 의해 제공되는 다른 특징이지만 여전히 JDBC 3.0에서 필요하다.


이것은 Oracle데이터베이스에서 사용되는 sysdate()함수를 호출하는 프로그램 예제이다. 저장 프로시저 기능을 사용하기 위해 당신은 StoredProcedure 를 확장한 클래스를 생성해야만 한다. 여기엔 입력 파라미터가 없지만 SqlOutParameter 클래스를 사용한 date처럼 선언된 출력 파라미터는 있다. execute() 메소드는 key로써 파라미터이름을 사용한 각각의 선언된 출력 파라미터를 위한 항목을 가지는 map을 반환한다.

import java.sql.Types;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.datasource.*;
import org.springframework.jdbc.object.StoredProcedure;

public class TestStoredProcedure {

public static void main(String[] args) {
TestStoredProcedure t = new TestStoredProcedure();
t.test();
System.out.println("Done!");
}

void test() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("oracle.jdbc.driver.OracleDriver");
ds.setUrl("jdbc:oracle:thin:@localhost:1521:mydb");
ds.setUsername("scott");
ds.setPassword("tiger");

MyStoredProcedure sproc = new MyStoredProcedure(ds);
Map res = sproc.execute();
printMap(res);
}

private class MyStoredProcedure extends StoredProcedure {
public static final String SQL = "sysdate";

public MyStoredProcedure(DataSource ds) {
setDataSource(ds);
setFunction(true);
setSql(SQL);
declareParameter(new SqlOutParameter("date", Types.DATE));
compile();
}

public Map execute() {
Map out = execute(new HashMap());
return out;
}

}

private static void printMap(Map r) {
Iterator i = r.entrySet().iterator();
while (i.hasNext()) {
System.out.println((String) i.next().toString());
}
}
}





10.4.5. SqlFunction



결과의 하나의 레코드를 반환하는 쿼리를 위한 SQL "함수" 랩퍼. 디폴트 행위는 int를 반환하는 것이지만 추가적인 반환 타입 파라미터를 가진 메소드를 사용해서 오버라이드 할수 있다. 이것은 JdbcTemplatequeryForXxx 메소드를 사용하는것이 유사하다. SqlFunction 이 가진 장점은 JdbcTemplate 을 생성할 필요가 없다는 것이다. 이것은 상태(scenes)뒤에서 행해진다.


이 클래스는"select user()" 나 "select sysdate from dual" 와 같은 쿼리를 사용해서 하나의 결과를 반환하는 SQL함수들을 호출하는 것을 사용하는 경향이 있다. 이것은 좀더 복잡한 저장 프로시저를 호출하거나 저장 프로시저나 저장 함수를 호출하기 위한 CallableStatement 를 사용하는 경향은 아니다. 이러한 타입의 처리를 위해 StoredProcedureSqlCall 을 사용하라.


이것은 하위 클래스에 알반적으로 필요하지 않은 명확한 클래스이다. 이 패키지를 사용하는 코드는 이 타입의 객체를 생성하고 SQL과 파라미터를 선언하고 함수를 수행하기 위해 반복적으로 선호하는 run메소드를 호출 할수 있다. 이것은 테이블로 부터 레코드의 카운트를 가져오는 예제이다.


    public int countRows() {
SqlFunction sf = new SqlFunction(dataSource, "select count(*) from mytable");
sf.compile();
return sf.run();
}