'분류 전체보기'에 해당되는 글 38건

  1. 2018.09.29 업비트 거래소 자동 거래 코인봇24를 소개합니다. 1
  2. 2015.06.22 목표
  3. 2013.01.07 SKT 갤럭시 노트 LTE (SHV-E160S) 순정 펌웨어 - UL13 1
  4. 2010.07.09 ellipsis 로 문자열 자르기
  5. 2010.07.09 자식창에서 opner 창 dom 컨트롤 하기
  6. 2010.07.08 구글 안드로이드 2.2에 대한 모든 것
  7. 2010.07.08 안드로이드 활용 완전정복 ‘팁&테크 40선’
  8. 2010.07.08 자바스크립트로 새창 열기
  9. 2010.07.04 jCoverflow를 이용하여 앨범 목록만들기
  10. 2010.07.04 HTML 특수문자 코드표 2
  11. 2010.07.04 iframe 자동 height 수정 (Auto Height Resize)
  12. 2009.12.16 자바 메소드 작성시 알아야 할 것
  13. 2009.12.14 자바 프로그래밍 일반 1
  14. 2009.12.14 Ubuntu 9.10 설치 후 Eclipse 에서 OK 나 Finish 버튼 클릭 후 무반응
  15. 2009.12.14 자바 클래스를 싱글 패턴으로 구현하기
  16. 2009.12.14 struts2에서 세션(session) 사용하기
  17. 2009.12.13 간결한 데이터 매퍼의 대명사, iBATIS SQLMaps 활용법
  18. 2009.12.13 iBatis 에서 페이징하는 방법
  19. 2009.12.13 Eclibatis: iBATIS Eclipse Plugin 2
  20. 2009.12.13 Java2Html - Eclipse Plugin
  21. 2009.12.13 iBatis 참고 사이트
  22. 2009.12.13 iBatis SqlMapClient API 1
  23. 2009.12.13 iBatis Framework의 구성
  24. 2009.12.13 iBatis Framework
  25. 2009.12.13 Spring 참고 사이트
  26. 2009.12.13 Spring에서 iBatis 연동하기 7
  27. 2009.12.13 AOP : Aspect Oriented Programming
  28. 2009.12.13 빈 라이프 사이클
  29. 2009.12.13 제어의 역행 / 의존성 주입(Inversion of Control / Dependency Injection)
  30. 2009.12.13 Spring Framework의 개요

업비트 거래소를 대상으로 자동으로 거래해 주는 봇 서비스가 있어서 소개합니다. 저는 지인 소개로 알게되었습니다. 처음에는 정말로 봇을 돌리면 수익이 생길까 의문이 생겼는데, 약 2주 정도 사용해보니 정말로 수익이 발생하였습니다. 그래서 지금은 확신이 생겼습니다.


코인봇24는 별도의 회원가입없이 카카오톡 로그인으로 바로 사용가능합니다. 회원가입이 없이 바로 사용할 수 있어서 정말 편합니다.




로그인하고 나면 아래와 같은 화면을 볼 수 있습니다. 지금은 이벤트 중이라서 가입하면 1000포인트를 줍니다. 이 포인트는 봇 거래시 차감됩니다. 그리고 "봇1개 더 받기 이벤트"도 진행중이에요. 이벤트 끝나기 전에 얼른 가입하세요~^^ 그리고 저는 아직 등급이 낮아서 수수료가 0.1%이지만, 거래를 많이 해서 등급이 올라가면 수수료가 점점 낮아집니다.




저는 지금 봇을 4개 운영중입니다. 봇은 추가로 구매 가능합니다. 그리고 봇 목록 페이지에서 추가매수 횟수, 봇 상태(on/off) 등을 체크할 수 있습니다.



아래 화면은 코인별 거래 기록 통계입니다. 이 통계를 참고하여 봇을 만들면 높은 수익률을 낼 수 있습니다. 저도 이 자료를 참고해서 봇을 생성하였습니다.




혹시 봇이 정지되거나 오작동 하는 경우를 대비해서 텔레그램 알람서비스도 제공하고 있습니다. 봇이 정지되면 텔레그램 알람으로 바로 통지가 날라옵니다.ㅎㅎ


마지막으로 코인봇24 서비스는 업비트 API를 사용해서 거래를 하기 때문에 업비트 거래소에서 발급해주는 개인API Key 를 직접 등록해야 합니다. 업비트 API Key 등록 방법은  "업비트 API Key 발급 방법 보기"를 클릭하면 자세한 설명을 볼 수 있습니다.


일단 가입하면 기본으로 봇을 제공해줍니다. 그 봇을 사용하여 작은 금액이라도 돌려보면 수익이 발생하는 것을 확인할 수 있을 것입니다. 모두 코인봇24 서비스에 가입해서 돈 많이 버시기 바랍니다. 


화이팅입니다~!!

'기본 포스트' 카테고리의 다른 글

목표  (0) 2015.06.22
Posted by 피곤키오
,

목표

기본 포스트 2015. 6. 22. 10:12
안녕하세요~! 

제 블로그에 오신 것을 환영합니다.


"리드십(Leadship)"을 가져라~!!!

뜻을 같이하는 열정적인 사람 3명 이상이 모이면 리드십을 발휘 할 수 있다.


다음은 2009년 당시 미국에서 사스쿼치 뮤직 페스티벌에서 벌어진 일입니다.

동영상을 보시면 한 청년이 혼자서 우수꽝스러운 춤을 추기 시작합니다. 그러다가 지나가던 사람들이 한명씩 반응을 보이며 동참하여 청년과 같이 추기 시작합니다. 이렇게 한 두명씩 계속 동참하다 보니 나중에는 거의 모든 사람들이 같이 춤을 추며 즐기기 시작합니다.

 


혁신적인 조직은 이러게 시작되는게 아닐까요??



저는 창의성과 열정을 가진 사람들로 구성되어진 조직을 만들어보고 싶습니다~!



"100%" 성공율을 가진 일에만 도전하고 싶지 않습니다.


많은 경험과 참여, 도전이 "99%"를 만들어 내는 것입니다.



감사합니다.


Posted by 피곤키오
,

HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.md5

7zip(http://www.7-zip.org/) 으로 분할 압축하였습니다.


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.001


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.002


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.003


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.004


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.005


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.006


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.007


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.008


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.009


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.010


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.011


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.012


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.013


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.014


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.015


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.016


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.017


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.018


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.019


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.020


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.021


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.022


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.023


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.024


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.025


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.026


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.027


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.028


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.029


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.030


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.031


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.032


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.033


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.034


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.035


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.036


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.037


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.038


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.039


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.040


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.041


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.042


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.043


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.044


HOME_E160S.UL13.1625_CL966809_REV02_user_low_ship.tar.7z.045


Posted by 피곤키오
,


제목의 문자열이 긴 경우 이제 더이상 substring으로 자르지 맙시다.
다음 css 를 사용하면 브라우저 상관없이 영문, 한글 상관없이 일정하게 잘립니다.

스타일 ellipsis.css
@CHARSET"UTF-8"; 
@charset"utf-8"; 

.ellipsis
{
  line-height: 1.2em;
  height: 1.2em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  width: 100%;
  -o-text-overflow: ellipsis;
  -moz-binding: url(moz_fix2.xml#ellipsis);
}

.moz-ellipsis > DIV:first-child
{
  float: left;
  margin-right: -26px;
}
.moz-ellipsis > DIV + DIV
{
  float: right;
  margin-top: -1.2em;
  /*
  background: url(ellipsis.png) repeat-y;
  optimization for 
  */
  background: url("https://t1.daumcdn.net/cfile/tistory/2767E73F56E6E8EA32") repeat-y;
  padding-left: 26px;
}
.moz-ellipsis > DIV + DIV::after
{
  background-color: white;
  content: '...';
}
moz_fix2.xml

이 xml 파일은 파이오폭스에서도 ellipsis 가 동작하도록 하기 위한 파일입니다.

<?xml version="1.0"?>
<bindings xmlns="http://www.mozilla.org/xbl" xmlns:html="http://www.w3.org/1999/xhtml">

<binding id="ellipsis" applyauthorstyles="false">
	<implementation>
		<constructor><![CDATA[
			(function(block){
				setTimeout(function(){
					block.style.mozBinding = '';
  					var t = document.createElement('DIV');
					block.appendChild(t);
					block.appendChild(document.createElement('DIV'));
					while (block.firstChild != t)
				  		t.appendChild(block.firstChild);
					block.className = block.className + ' moz-ellipsis';
				}, 0);
			})(this);
		]]></constructor>
	</implementation>
</binding>

</bindings>
Posted by 피곤키오
,
부모창
<select name="show_in" id="show_data">
	<option value="aaa">aaa</option>
</select>
<input onclick="open_show()" value="click" type="button"/>

<script type="text/javascript">
function open_show(url) {
	window.open("jasu_input_show.html", "win", "");
}
function test(data){		
	var objSelect = document.getElementById("show_data"); 
	var dataLength = data.length;
	for(var i=0; i<dataLength; i++) {
		var objOption = document.createElement("OPTION");
		objOption.appendChild(document.createTextNode(data[i].text)); //텍스트 노드를 생성해서 추가한다.
		objOption.value = data[i].value;       
		objSelect.appendChild(objOption);      //자식 노드를 추가한다.
	}
}
</script>
팝업창
<script type="text/javascript">
	var data = [ {text:"tt1", value:"tt1"}, {text:"tt2", value:"tt2"}];	//json 형태의 데이터
</script>
<input onclick="opener.test(data)" value="click" type="button">
Posted by 피곤키오
,
Posted by 피곤키오
,
Posted by 피곤키오
,

새창 열기 기몬 유형

자바스크립트에서 새창을 열때에는 다음과 같이 사용합니다.
window.open('문서경로', 'window', 'location=no, directories=no, resizable=no, status=no, toolbar=no, menubar=no, width=300, height=400, left=0, top=0, scrollbars=yes')
//location : 주소표시줄 //directoryies : 연결 //resizable : 크기조절 //status : 상태표시줄 //toolbar : 표시단추 //memubar : 메뉴 //width : 새창 너비 // height : 새창 높이 //left : 새창 왼쪽 위치 //top : 새창 위쪽 위치

* yes와 no대신 1과 0을 사용해도 됩니다.

 

새창 열기 사용 방법

a 링크 태그에서 스크립트를 사용하여 페이지를 새창으로 여는 경우에는 href에 새창으로 열 페이지 주소를 적고 자바스크립트에는 this.href를 넘겨줍니다.
웹접근성과 관련하여 자바스크립트가 동작하지 않는 브라우저의 경우에도 동일하게 동작하게 하기 위해서 입니다.



클릭

 

팝업창 자동으로 리사이즈 하기


Posted by 피곤키오
,

JavaScript CoverFlow 2.0 (jCoverFlow)

라이브러리는 이곳에서 다운로드 받습니다.
http://jcoverflow.sourceforge.net/

jCoverFlow_2.0.rar 파일을 다운로드해서 압축풀면, coverflow.js와 coverflow.css이 있습니다.

이런 형식의 html을 원하는 위치에 넣고,

<div id="coverflowHolder"> 
    <div id="__cvfl-coverflow-holder" style="display:none"> 
        <div id="__cvfl-coverflow"> 
            <div id="__cvfl-coverflow-wrapper"></div> 
            <div id="smallerPreview"></div> 
            <div id="__cvfl-coverflow-label"></div> 
        </div> 
    </div> 
</div>

아래와 같이 자바스크립트에서 호출해주면 됩니다.

Coverflow.init([리스트배열], {createLabel, onSelectCenter, refill});

리스트배열에는 [{src, labelObject}] 형식으로 src는 이미지 주소를 넣고, labelObject는 선택되었을 때 넣을 데이터를 넣으면 나중에 보여줄 때 편리하게 하는 것 같습니다. 그리고, 함수정의로 createLabel은 현재위치의 coverflow의 정보를 보여주기 위한 html을 리턴하는 함수입니다. onSelectCenter는 선택된 것 클릭했을 때 액션이고, refill은 잘 모르겠습니다.

주의사항은 자바스크립트가 HTML보다 나중에 와야됩니다.

실제 예제에서 보면 다음과 같이 사용합니다.

<SCRIPT type="text/javascript">
	if (canvas_compatible) 
	{	
		window.onload = function()
		{	
			Coverflow.init([
				{src: 'CoverFlows_files/covers/cover1med.jpg',
				  label: {album: 'Abbey Road', artist: 'The Beatles', url:'http://www.beatles.com/', high:'CoverFlows_files/covers/cover1large.jpg'}},
				{src: 'CoverFlows_files/covers/cover2med.jpg',
				   label: {album: 'Graduation', artist: 'Kanye West', url:'http://www.kanyewest.com', high:'CoverFlows_files/covers/cover2large.jpg'}},
				{src: 'CoverFlows_files/covers/cover3med.jpg',
				   label: {album: 'Circus', artist: 'Britney Spears', url:'http://www.britneyspears.com', high:'CoverFlows_files/covers/cover3large.jpg'}},			   
				{src: 'CoverFlows_files/covers/cover13med.jpg',
				   label: {album: 'The Fame', artist: 'Lady Gaga', url:'http://www.ladygaga.com', high:'CoverFlows_files/covers/cover13large.jpg'}},			   
				{src: 'CoverFlows_files/covers/cover14med.jpg',
				   label: {album: 'The Fray', artist: 'The Fray', url:'http://www.thefray.com', high:'CoverFlows_files/covers/cover14large.jpg'}},			   
				{src: 'CoverFlows_files/covers/cover12med.jpg',
				   label: {album: 'Funhouse', artist: 'Pink', url:'http://www.pinkspage.com', high:'CoverFlows_files/covers/cover12large.jpg'}},
				{src: 'CoverFlows_files/covers/cover4med.jpg',
				   label: {album: '21st Century Breakdown', artist: 'Greenday', url:'http://www.greenday.com', high:'CoverFlows_files/covers/cover4large.jpg'}},
				{src: 'CoverFlows_files/covers/cover5med.jpg',
				   label: {album: 'Carter III', artist: 'Lil Wayne', url:'http://www.lilwayne.com', high:'CoverFlows_files/covers/cover5large.jpg'}},
				{src: 'CoverFlows_files/covers/cover6med.jpg',
				   label: {album: 'Clumsy', artist: 'Fergie', url:'http://www.fergie.com', high:'CoverFlows_files/covers/cover6large.jpg'}},
				{src: 'CoverFlows_files/covers/cover7med.jpg',
				   label: {album: 'The Best Of', artist: 'Blur', url:'http://ww.blur.com', high:'CoverFlows_files/covers/cover7large.jpg'}},
				{src: 'CoverFlows_files/covers/cover8med.jpg',
				   label: {album: 'Rockferry', artist: 'Duffy', url:'http://www.duffy.com', high:'CoverFlows_files/covers/cover8large.jpg'}},
				{src: 'CoverFlows_files/covers/cover9med.jpg',
				   label: {album: 'Dig out your soul', artist: 'Oasis', url:'http://www.oasis.com', high:'CoverFlows_files/covers/cover9large.jpg'}},
				{src: 'CoverFlows_files/covers/cover10med.jpg',
				   label: {album: 'Loose', artist: 'Nelly Furtado', url:'http://www.nellyfurtado.com', high:'CoverFlows_files/covers/cover10large.jpg'}}		   
			],
			{	
				createLabel: function(item)
				{			
					return item.label.album +'<br><span id="artist">'+ item.label.artist + '</span><br><a href="' + item.label.url + '">'+item.label.url+'</a>';
				 
				},

				onSelectCenter: function(item,id) 
				{		
					var img = new Image();
					img.onload = function()
					{ 		
						Lightbox.show(this.src,id);	
			
					}; 		
					img.src = item.label.high;		
				},		

				refill: function(start)
				{
				new HTTPQuery("/ajax/cflow/0/?from="+start+"&l=1&cache=3998668924011356071",0,"updateCflow");
				}		
			});
		}
	}
	
	function updateCflow(oHttp) {
		cResponse = oHttp.responseText;
		if (cResponse.substr(0,1)=="!"){
			cResponse=cResponse.substr(1);
			eval(cResponse);
		}
	}
</SCRIPT>
Posted by 피곤키오
,
표현문자 숫자표현 문자표현 설명
- &#00;-&#08; - 사용하지 않음
space &#09; - 수평탭
space &#10; - 줄 삽입
- &#11;-&#31; - 사용하지 않음
space &#32; - 여백
! &#33; - 느낌표
" &#34; &quot; 따옴표
# &#35; - 숫자기호
$ &#36; - 달러
% &#37; - 백분율 기호
& &#38; &amp; Ampersand
' &#39; - 작은 따옴표
( &#40; - 왼쪽 괄호
) &#41; - 오른쪽 괄호
* &#42; - 아스트릭
+ &#43; - 더하기 기호
, &#44; - 쉼표
- &#45; - Hyphen
. &#46; - 마침표
/ &#47; - Solidus (slash)
0 - 9 &#48;-&#57; - 0부터 9까지
: &#58; - 콜론
; &#59; - 세미콜론
< &#60; &lt; 보다 작은
= &#61; - 등호
> &#62; &gt; 보다 큰
? &#63; - 물음표
@ &#64; - Commercial at
A - Z &#65;-&#90; - A부터 Z까지
[ &#91; - 왼쪽 대괄호
\ &#92; - 역슬래쉬
] &#93; - 오른쪽 대괄호
^ &#94; - 탈자부호
_ &#95; - 수평선
` &#96; - Acute accent
a - z &#97;-&#122; - a부터 z까지
{ &#123; - 왼쪽 중괄호
| &#124; - 수직선
} &#125; - 오른쪽 중괄호
~ &#126; - 꼬리표
- &#127;-&#159; - 사용하지 않음
&#160; &nbsp; Non-breaking space
¡ &#161; &iexcl; 거꾸로된 느낌표
&#162; &cent; 센트 기호
&#163; &pound; 파운드
¤ &#164; &curren; 현재 환율
&#165; &yen;
| &#166; &brvbar; 끊어진 수직선
§ &#167; &sect; 섹션 기호
¨ &#168; &uml; 움라우트
&#169; &copy; 저작권
ª &#170; &ordf; Feminine ordinal
&#171; &laquo; 왼쪽 꺾인 괄호
&#172; &not; 부정
­ &#173; &shy; Soft hyphen
? &#174; &reg; 등록상표
&hibar; &#175; &macr; Macron accent
° &#176; &deg; Degree sign
± &#177; &plusmn; Plus or minus
² &#178; &sup2; Superscript-x two
³ &#179; &sup3; Superscript-x three
´ &#180; &acute; Acute accent
μ &#181; &micro; Micro sign (Mu)
&#182; &para; 문단기호
· &#183; &middot; Middle dot
¸ &#184; &cedil; Cedilla
¹ &#185; &sup1; Superscript-x one
º &#186; &ordm; Masculine ordinal
&#187; &raquo; 오른쪽 꺾인 괄호
¼ &#188; &frac14; 4분의 1
½ &#189; &frac12; 2분의 1
¾ &#190; &frac34; 4분의 3
¿ &#191; &iquest; 거꾸로된 물음표
A &#192; &Agrave; Capital A, grave accent
A &#193; &Aacute; Capital A, acute accent
A &#194; &Acirc; Capital A, circumflex accent
A &#195; &Atilde; Capital A, tilde
A &#196; &Auml; Capital A, dieresis or umlaut mark
A &#197; &Aring; Capital A, ring (Angstrom)
Æ &#198; &AElig; Capital AE diphthong (ligature)
C &#199; &Ccedil; Capital C, cedilla
E &#200; &Egrave; Capital E, grave accent
E &#201; &Eacute; Capital E, acute accent
E &#202; &Ecirc; Capital E, circumflex accent
E &#203; &Euml; Capital E, dieresis or umlaut mark
I &#204; &Igrave; Capital I, grave accent
I &#205; &Iacute; Capital I, acute accent
I &#206; &Icirc; Capital I, circumflex accent
I &#207; &Iuml; Capital I, dieresis or umlaut mark
Ð &#208; &ETH; Capital Eth, Icelandic
N &#209; &Ntilde; Capital N, tilde
O &#210; &Ograve; Capital O, grave accent
O &#211; &Oacute; Capital O, acute accent
O &#212; &Ocirc; Capital O, circumflex accent
O &#213; &Otilde; Capital O, tilde
O &#214; &Ouml; Capital O, dieresis or umlaut mark
× &#215; &times; Multiply sign
Ø &#216; &Oslash; width="130"Capital O, slash
U &#217; &Ugrave; Capital U, grave accent
U &#218; &Uacute; Capital U, acute accent
U &#219; &Ucirc; Capital U, circumflex accent
U &#220; &Uuml; Capital U, dieresis or umlaut mark
Y &#221; &Yacute; Capital Y, acute accent
Þ &#222; &THORN; Capital Thorn, Icelandic
ß &#223; &szlig; Small sharp s, German (sz ligature)
a &#224; &agrave; Small a, grave accent
a &#225; &aacute; Small a, acute accent
a &#226; &acirc; Small a, circumflex accent
a &#227; &atilde; Small a, tilde
a &#228; &auml; Small a, dieresis or umlaut mark
a &#229; &aring; Small a, ring
æ &#230; &aelig; Small ae diphthong (ligature)
c &#231; &ccedil; Small c, cedilla
e &#232; &egrave; Small e, grave accent
e &#233; &eacute; Small e, acute accent
e &#234; &ecirc; Small e, circumflex accent
e &#235; &euml; Small e, dieresis or umlaut mark
i &#236; &igrave; Small i, grave accent
i &#237; &iacute; Small i, acute accent
i &#238; &icirc; Small i, circumflex accent
i &#239; &iuml; Small i, dieresis or umlaut mark
ð &#240; &eth; Small eth, Icelandic
n &#241; &ntilde; Small n, tilde
o &#242; &ograve; Small o, grave accent
o &#243; &oacute; Small o, acute accent
o &#244; &ocirc; Small o, circumflex accent
o &#245; &otilde; Small o, tilde
o &#246; &ouml; Small o, dieresis or umlaut mark
÷ &#247; &divide; Division sign
ø &#248; &oslash; Small o, slash
u &#249; &ugrave; Small u, grave accent
u &#250; &uacute; Small u, acute accent
u &#251; &ucirc; Small u, circumflex accent
u &#252; &uuml; Small u, dieresis or umlaut mark
y &#253; &yacute; Small y, acute accent
þ &#254; &thorn; Small thorn, Icelandic
y &#255; &yuml; Small y, dieresis or umlaut mark
Posted by 피곤키오
,
첫번째 방법
<script type="text/javascript">
function autoResize(i) {
   var iframeHeight=
   (i).contentWindow.document.body.scrollHeight;
   (i).height=iframeHeight+20;
}
</script>

<iframe onload="autoResize(this)" id="tree"></iframe>

 

두번째 방법
<script type="text/javascript">
function autoIframe(frameId){
	try{
		frame = document.getElementById(frameId);
		innerDoc = (frame.contentDocument) ? frame.contentDocument : frame.contentWindow.document;
		objToResize = (frame.style) ? frame.style : frame;
		objToResize.height = innerDoc.body.scrollHeight + 10;
	}
	catch(err){
		window.status = err.message;
	}
}
</script>

<iframe id="tree" name="tree" src="tree.htm" onload="if (window.parent && window.parent.autoIframe) {window.parent.autoIframe('tree');}"></iframe> 
Posted by 피곤키오
,

인자의 유효성을 검사하라.

유효성 검사를 통해 인자가 유효하지 않을 때 적절한 예외를 발생시키면. 빠르고 깔끔하게 메소드를 종료할 수 있다.

Public 메소드의 경우 javadoc의 @throws 태그를 써서 인자값이 제약 조건을 어겼을 때 던지는 예외를 문서화 해야한다.

public class Big {
	/**
	* (this mod m)의 값을 가지는 BigInteger를 리턴한다.
	* 이 메소드는 항상 음수가 아닌 BigInteger를 리턴한다는 점이
	* remainder 메소드와 다르다
	* 
	* @param m 나누는 수, 반드시 양수이다.
	* @return this BigInteger를 m으로 나눈 몫. 항상 양수이다.
	* @throws ArithmeticException m <=0 일 때 발생한다.
	*/
	public BigInteger mod(BigInteger m) {
		if(m.signum() <= 0)
			throw new ArithmeticException("Modulus not positive");
		//...실제 계산 코드
		return null;
	}
}
 

필요한 경우 방어복사하라.

클래스의 모든 클라이언트는 항상 불변규칙을 깨뜨리기 위해 최선을 다한다고 가정하고, 프로그래밍할 때 항상 방어하는 자세를 가져야 한다.

다음 클래스는 두 날짜 사이의 기간을 표시한다.

//가짜 불변 클래스
public final class Period {
	private final Date start;
	private final Date end;
    
	/**
	* @param start        시작일.
	* @param end  종료일. 반드시 시작일 이후이다.
	* @throws IllegalArgumentException 시작일이 종료일보다 늦을 때 발생한다.
	* @throws NullPorinterException 시작일이나 종료일이 null일때 발생한다.
	*/
	public Period(Date start, Date end) {
		if(start.compareTo(end) > 0)
			throw new IllegalArgumentException(start + "after" + end);
               
		this.start = start;
		this.end = end;
	}

	public Date start() {
		return start;
	}

	public Date end() {
		return end;
	}

	//...이하 생략
	
}

Date 클래스가 가변 클래스이기 때문에 불변규칙은 쉽게 깨진다.

//Period 인스턴스의 내부를 공격하라.
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78);       //p를 수정한다.

이런 종류의 공격으로부터 Period 인스턴스 내부를 보호하려면, 생성자에 전달되는 변경가능 인자들을 방어복사(defensive copy)해야한다.

//인자를 방어복사한다.
public Period(Date start, Date end) {
	this.start = new Date(start.getTime());
	this.end = new Date(end.getTime());
               
	if(start.compareTo(end) > 0)
			throw new IllegalArgumentException(start + "after" + end);
               
	}

생성자를 이렇게 하면 앞에 나왔던 공격 패턴은 더 이상 먹히지 않는다. 인자의 유효성 검사를 하지 전에 먼저 복사하고 나서 원본이 아닌 복사본의 유효성을 검사한다는 것에 주목할 필요가 있다.

개선한 생성자가 모든 공격을 막아낼 수 있을 것 같지만, 아직도 Period 인스턴스를 공격할 수 있는 방법이 있다.

//Period 인스턴스에 대한 다른 공격
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
p.end().setYear(78);   //p를 수정한다.

접근자 메소드에서 가변 객체에 대한 참조를 방어복사하여 리턴하도록 고쳐야 이런 종류의 공격을 막아 낼 수 있다.

public Date start() {
	return (Date) start.clone();
}

public Date end() {
	return (Date) end.clone();
}

Period의 내부 Date 객체는 java.util.Date 클래스의 인스턴스라는 것이 확실하기 때문에 clone 메소드를 써도 아무 문제가 없다.

 

메소드를 중복정의할 때는 신중하라.

다음 예제는 컬렉션을 종류에 따라 분류할 목적으로 만든 것이다.

//틀린 구현 - 메소드 중복정의를 잘못 쓰고 있다.
public class CollectionClassifier {
	public static String classify(Set s) {
		return "Set";
	}
    
	public static String classify(List l) {
		return "List";
	}
      
	public static String classify(Collection c) {
		return "Uknow Collection";
	}
        
	public static void main(String[] args) {
		Collection[] tests = new Collection[] {
			new HashSet(),         //Set
			new ArrayList(),       //List
			new HashMap().values() //Set도 List도 아닌 것
		};
               
		for(int i=0; i<tests.length; i++)
			System.out.println(classify(tests[i]));
	}
}

아마 다음과 같은 출력을 예상했을 것이다.

Set
List
Unknown Collection

하지만, 실제로는 “Unknown Collection” 만 세 번 출력한다.

이 문제를 해결하려면 세개의 classify 메소드를 하나로 합치고 명시적으로 instanceof를 써서 타입을 검사해야 한다.

public static String classify(Collection c) {
	return (c instanceof Set ? "Set" : (c instanceof List ? "List" : "Unknow Collecion"));
}
 

널( null)이 아닌 길이가 0인 (zero-length)배열을 리턴하라.

다음과 같이 메소드를 만드는 경우를 많이 볼 수 있다.

private List cheeseInStack = …;
 
/**
 * 상점에 남아 있는 모든 치즈의 배열을 리턴하거나,
 * 팔 수 있는 치즈가 없으면 null을 리턴한다.
 */
public Cheese[] getCheeses() {
	if(cheeseInStack.size() == 0)
		return null;
 
	//...코드 생략
 
}

팔 수 있는 치즈가 남아 않으면 다음과 같이 처리 할 수 있다.

Cheese[] cheese = shop.getCheeses();

public void in() {
	if(Arrays.asList(shop.getCheeses()).contains(Cheese.STILTON))
		System.out.println("Jolly good, just the thing.");
}

길이가 0인 배열을 리턴할 수 있는 곳에서는 null을 처리하는 코드가 반복되어야 한다. 배열을 리턴하는 메소드에서 null을 리턴할 이유가 전혀 없다. Null을 리턴할 상황이라면 길이가 0인 배열을 리턴하면 된다.

다음 코드는 null대신에 길이가 0인 배열을 리턴한다.

Main.java
class Cheese {
	String name;
        
	Cheese(String name) {
		this.name = name;
	}
        
	public static final Cheese STILTON = new Cheese("Stilton");
	public static final Cheese CHEDDAR = new Cheese("Cheddar");  
        
}
 
class CheeseShop {
	private static Cheese[] ECA = new Cheese[0];
	private List cheesesInStock = Collections.singletonList(Cheese.STILTON);
        
	private final static Cheese[] NULL_CHEESE_ARRAY = new Cheese[0];
        
	/**
	* @return Cheese[] 팔 수 있는 모든 치즈의 배열을 리턴한다.
	*/
	public Cheese[] getCheeses() {
		return (Cheese[]) cheesesInStock.toArray(NULL_CHEESE_ARRAY);
	}
}
 
public class Main {
	static CheeseShop shop = new CheeseShop();
        
	public static void main(String[] args) {
		Cheese[] cheeses = shop.getCheeses();
		if (Arrays.asList(cheeses).contains(Cheese.STILTON))
			System.out.println("Jolly good, just the thing.");
		if (Arrays.asList(cheeses).contains(Cheese.CHEDDAR))
			System.out.println("Oops, too bad.");        
	}
}

'개발자 센터 > Java' 카테고리의 다른 글

자바 프로그래밍 일반  (1) 2009.12.14
자바 클래스를 싱글 패턴으로 구현하기  (0) 2009.12.14
Posted by 피곤키오
,

지역 변수의 유효범위를 최소화하라

  • 지역 변수의 유효범위를 최소화하는 가장 좋은 방법은 쓰기 바로 전에 선언하는 것이다.
  • 대부분 지역 변수는 선언과 함께 초기화해야 한다.
  • 만약 반복변수를 반복문 안에서만 사용한다면, while 반복문 보다는 for 반복문을 쓰는 것이 좋다.

다음은 컬렉션 전체를 순회할 때 주로 사용하는 구현패턴이다.

for( Iterator i = c.iterator(); i.hasNext(); ) {
        doSomething(i.next());
}

일부로 버그를 넣은 코드를 살펴보면서 왜 for 반복문이 while 반복문보다 나은지 알아보자.

Iterator i = c.iterator();
while ( i.hasNext() ) {
        doSomething(i.next());
}

//...코드 생략


Iterator i2 = c2.iterator();
while ( i.hasNext() ) {		//버그 발생!
        doSomething(i2.next());
}

두 번째 반복문에서 복사 붙여넣기 때문에 발생한 오류가 있다. 이 코드는 문제없이 컴파일 되고, 아무런 예외도 발생하지 않고 실행 될것이다. 하지만 분명히 오류가 있다. 이런 종류의 오류는 찾아내기 어렵다.

다음과 같이 for문을 사용했을 때에는 같은 오류를 범했다하더라도 컴파일에서 에러가 난다.

for(Iterator i = c.iterator(); i.hasNext(); ) {
        doSomething(i.next());
}

//컴파일 시점에서 오류 - the symbol i cannot be resolved
for(Iterator i2 = c2.iterator(); i.hasNext(); ) {
        doSomething(i2.next());
}

지역 변수의 유효범위를 최소화하면서 리스트를 순회하는 반복문의 또 다른 구현패턴을 살펴보자

//임의 접근(random access) 리스트를 순회하는 구현패턴, 성능 좋다.
for(int i = 0, n = list.size(); i < n; i++) {
        doSomething(list.get(i));
}

이 구현 패턴은 ArrayList, Vector 클래스와 같은 임의 접근 리스트를 순회할 때, 앞에서 설명한 구현패턴보다 더 빠르다. 이 구현 패턴은 반드시 임의 접근 리스트에만 사용해야 한다. 다른 종류의 리스트에 사용하면 성능은 저하된다.

이 구현 패턴은 사용하지 말자.

for(int i = 0; i < list.size(); i++) {
		//size()를 매번 호출한다.
        doSomething(list.get(i));
}

마지막으로 지역변수의 유효범위를 최소화하려면, 한 메소드는 한가지 작업만 처리하도록 가능하면 작게 만들어야 한다.

 

라이브러리를 배우고 익혀서 써라

많은 프로그래머들이 0이상의 int 타입 난수를 생성하기 위해 다음과 같은 메소드를 사용할 것이다.

static Random rnd = new Random();

//흔히 쓰는 방법이지만 문제가 있다.
static int random(int n) {
        return Math.abs(rnd.nextInt()) % n;
}

이 메소드는 세가지 결함이 있다.

  • 첫째, n이 2의 제곱수 중 작은 수라면, 이 메소드가 만들어 내는 난수열은 꽤 짧은 주기로 반복된다.
  • 둘째, n이 2의 제곱수가 아니라면, 어떤 수가 다른 것보다 더 자주 나타날 가능성이 크다. n이 클수록 이 결함은 더 커진다.
  • 셋째, 지정한 범위를 벗어나는 난수가 발생할 수도 있다.

하지만, 이 결함을 해결할 Random.nextInt(int)라는 메소드가 1.2 배포판부터 java.util 패키지에 들어있으니 걱정할 필요가 없다.

다음은 안전하게 0이상의 int 타입 난수를 만드는 예제이다.

Main.java

import java.util.Random;

public class Main {    

        static Random rnd = new Random();

        public static void main(String[] args) {
               int n = 2 * (Integer.MAX_VALUE/3);
               int low = 0;
               for (int i=0; i<1000000; i++)
                       if(rnd.nextInt(n) < n/2)
                              low++;

               System.out.println(low);
        }
}

 

정확한 계산에 fload이나 double 타입을 쓰지 마라

특히, 금전을 계산할 때 float이나 double 타입을 절대로 쓰지 말아야 한다. 이 타입들은 0.1, 0.01과 같은 10의 음의 지수값들을 정확하게 표현하지 못한다.

//틀린 구현 - 화폐 계산에 부동 소수점 타입을 사용
public static void main(String[] args) {
	   double funds = 1.00;
	   int itemsBought = 0;

	   for(double price = .10; funds >= price; price += .10) {
			   funds -= price;
			   itemsBought++;
	   }

	   System.out.println(itemsBought + " items bougnt.");
	   System.out.println("Change: $" + funds);
}
결과값:
3 items bougnt.
Change: $0.3999999999999999

사탕은 세개 밖에 사지 못했고 잔돈은 0.3999999999999999 달러 밖에 남지 않는다.

다음과 같이 BigDecimal, int, long과 같은 타입을 써야 정확한 소수점 계산을 할 수 있다.

public static void main(String[] args) {
	   final BigDecimal TEN_CENTS = new BigDecimal(".10");	   

	   BigDecimal funds = new BigDecimal("1.00");
	   int itemsBought = 0;

	   for(BigDecimal price = TEN_CENTS; 
			   funds.compareTo(price) >= 0 ; 
			   price = price.add(TEN_CENTS)) {
			   funds = funds.subtract(price);
			   itemsBought++;
	   }

	   System.out.println(itemsBought + " items bougnt.");
	   System.out.println("Money left over: $" + funds);
}
결과값:
4 items bougnt.
Money left over: $0.00

이제서야 사탕 네개를 살 수 있고, 잔돈도 남지 않는다. 하지만 BigDecimal은 기본타입을 쓰는 것보다 불편하고 느리다.

소수점 이하 자릿수를 직업 관리한다면 BigDecimal을 쓰지 않고 int나 long을 쓸수 있다. 예를 들어 달러 대신에 센트를 기본단위로 계산하면 위의 코드가 다음과 같이 바뀐다.

public static void main(String[] args) {  

	   int funds = 100;
	   int itemsBought = 0;

	   for(int price = 10; funds >= price; price += 10 ) {
			   funds -= price;
			   itemsBought++;
	   }

	   System.out.println(itemsBought + " items bougnt.");
	   System.out.println("Money left over: $" + funds);
}

다루어야 하는 숫자의 자릿수가 9자리를 넘지 않으면 int를 쓰고, 18자리를 넘지 않으면 long을 써라. 이것보다 더 커지면 BigDicimal을 써라.

 

적절한 타입 대신 문자열을 쓰지 마라

  • 문자열로 다른 값타입(value type)을 대신하지 마라.
  • 문자열로 열거타입을 대신하지 마라, 타입안전 열거를 쓰는 것이 가장 좋다.
  • 문자열로 집합타입(aggregate type)을 대신하지 마라.
  • 문자열로 어떤 기능(capability)을 대신하지 마라.
//틀린 구현 – 문자열로 기능을 대신하고 있다.
public class ThreadLocal {
        private ThreadLocal() {       }       //인스턴스를 만들수 없다.        

        //특정 이름을 가진 키를 써서 현재 스래드의 값을 저장한다.
        public static void set(String key, Object value) {

               //...코드 생략

        }        

        //특정 이름을 저장된 현재 스레드의 값을 리턴한다.
        public static Object get(String key) {

               //...코드 생략

               return null;
        }
}

이 방식의 문제점은 키로 쓴 문자열을 모든 스레드가 공유할 수 있다는 것이다. 즉, 보안상 문제가 생길 수 있다.

위조 불가능한 키를 써서 다음과 같이 수정할 수 있다.

public class ThreadLocal {
        private ThreadLocal() {       }       //인스턴스를 만들수 없다.        

        public static class Key {
               Key() { }
        }        

        //위조할 수 없는 유일한 키를 생성한다.
        public static Key getKey() {
               return new Key();
        }        

        public static void get(Key key, Object value) {

               //...코드 생략

        }

        public static Object get(Key key) {

               //...코드 생략

               return null;
        }      
}

 

성능을 떨어뜨리는 문자열 연결을 조심하라.

//잘못된 문자열 연결로 엄청 느리다

public String statement() {
	   String s = "";

	   for(int i=0; i < numItems(); i++)
			   s += lineForItem(i);   //문자열 연결

	   return s;
}

이 메소드는 항목이 많아지면 성능은 엄청 느려진다. String 클래스 대신에 StringBuffer 클래스를 써야 원하는 성능을 낼 수 있다.

public String statement() {
	   StringBuffer s = new StringBuffer(numItems() * LINE_WIDTH);

	   for(int i=0; i < numItems(); i++)
			   s.append(lineForItem(i));     //문자열 연결

	   return s.toString();
}

위의 두 메소드의 성능은 90배 정도 차이난다. 새로 만든 메소드에서 StringBuffer를 주문서를 다 담을 수 있을 만큼 넉넉하게 메모리를 할당했다.

 

인터페이스 타입으로 객체를 참조하라.

  • 객체를 참조할 때에는 클래스 타입보다는 인터페이스 타입을 쓰는 것이 더 좋다.
  • 적절한 인터페이스 타입이 있다면 인자, 리턴값, 변수, 필드는 반드시 인터페이스 타입으로 선언해야 한다.
//Good - 인터페이스 타입을 쓴다.
List subscribers1 = new Vector();
List subscribers2 = new ArrayList();

이렇게 하면 안 된다.

//Bad - 클래스 타입을 쓴다.
Vector subscribers1 = new Vector();
ArrayList subscribers2 = new ArrayList();

만약, 적절한 인터페이스 타입이 있다면, 객체를 반드시 인터페이스 타입으로 참조해야 프로그램이 유연해진다. 그러나 인터페이스 타입이 없다면, 필요한 기능을 제공해 주는 최상위 클래스 타입을 사용하는 것이 좋다.

 

리플렉션보다 인터페이스를 써라

Java.lang.reflect 패키지는 이미 로딩한 클래스에 접근할 수 있는 리플렉션(reflection)기능을 제공한다. reflect 패키지가 제공하는 Constructor, Method, Field 인스턴스를 쓰면, 이것들의 근원이 되는 실제 생성자, 메소드, 필드를 직접 조작할 수 있다. 하지만, 여기에는 대가가 따른다.

  • 예외에 대한 검사를 포함하여, 모든 컴파일 시점의 타입 검사를 포기해야 한다.
  • 리플렉션을 쓰면 코드가 매우 번거로워지고 양도 많아진다.
  • 성능이 떨어진다.

응용프로그램 설계시점(design time)에만 리플렉션을 쓴다. 일반 응용프로그램에서 실행시점에 리플렉션을 써서 객체에 접근하면 안된다.

물론, 리플랙션을 꼭 써야 하는 복잡한 응용 프로그램이 있기는 하다. 클래스 브라우저(class brower), 객체 조사기(object inspector), 코드 분석기, 인터프리터 기반의 내장 시스템과 같은 응용프로그램에서는 리플렉션을 쓸 수 밖에 없다.

아주 제한된 형태로 리플렉션을 장점만을 활용할 수가 있다. 리플랙션으로 존재하진 않는 클래스의 인스턴스를 생성하고, 인터페이스나 상위클래스로 이 인스턴스에 접근하는 방식을 쓸 수 있다.

//리플렉션을 써서 인스턴스를 생성하고 
//인터페이스를 통해 이 인스턴스에 접근한다.
public static void main(String[] args) {
	   //클래스 이름을 클래스 객체로 바꾼다.
	   Class cl = null;
	   try {
			   cl = Class.forName(args[0]);
	   } catch(ClassNotFoundException e) {
			   System.err.println("Class not found");
			   System.exit(1);
	   }               

	   //인스턴스를 생성한다.
	   Set s = null;
	   try {
			   s = (Set) cl.newInstance();
	   } catch(IllegalAccessException e) {
			   System.err.println("Class not accessible.");
			   System.exit(1);
	   } catch(InstantiationException e) {
			   System.err.println("Class not instantiable.");
			   System.exit(1);
	   }             

	   //집합의 동작을 검사한다.
	   s.addAll(Arrays.asList(args).subList(1, args.length));
	   System.out.println(s);
}

 

EFFECTIVE JAVA
카테고리 컴퓨터/IT
지은이 Joshua Bloch (대웅, 2003년)
상세보기
Posted by 피곤키오
,
얼마 전 Ubuntu 9.10이 배포되었습니다.
9.04 버전에서 정상적으로 작동했던 Eclipse 가 9.10 으로 설치 이후에는 정상적인 반응을 보이지 않습니다.
Eclipse와 관련된 문제인가 생각을 하고 Eclipse 를 다시 다운로드 해서 실행해봤지만 똑같은 반응을 보이더군요. ^^;;
그래서 오늘은 구글에서 방법을 찾아보았습니다. 역시 구글신이구요!!
ubuntu 9.10 eclipse not finish
검색어를 넣어 검색을 확인 하던 중에 이클립스 버그(https://bugs.eclipse.org)에서 관련한 문제가 생긴다는 것을
확인하였습니다. ^^ 이클립스는 GTK+ 2.18 에서 정상적으로 작동을 하지 않는 것 같습니다. 그래서 이클립스에서
OK(Submit 버튼?)버튼을 클릭할 때 이클립스가 정상 작동을 하지 않는 것으로 보입니다. 이를 정상적으로 해결하는
방법은 아직 나오지는 않은 듯 합니다. ^^; 아직 완전한 해결책을 발견하지는 못했습니다.
https://bugs.eclipse.org/bugs/show_bug.cgi?id=291257
위의 사이트를 가게 되면 이와 관련된 이야기들을 나눌 수 있습니다. ^^;
간단하게 처리방법을 이야기해보자면, 아래와 같은 스크립트 실행파일을 하나 만들어주면 끝납니다.
#!/bin/sh
export GDK_NATIVE_WINDOWS=true
/home/honeymon/Programs/eclipse/eclipse
중요한 것은
export GDK_NATIVE_WINDOWS=true
입니다. ^^
이와 관련한 내용으로는 우분투 사용자모임에서도 비슷한 내용으로 이야기가 있었군요.
http://ubuntu.or.kr/viewtopic.php?f=22&t=8177

어쨌든 위의 스크립트 파일은 임사방편인 것이죠. 아직 우분투 9.10 에는 여러가지 버그가 있는 상황입니다. 아마도 9.04 에서 9.10 으로 업데이트가 되면서 내부적인 변화(소프트웨어 설치관리가 통합되고 Ubuntu One 라는 웹하드 라고 할 수 있는 서비가 추가되고 ext4 파일시스템이 기본으로 변하는 등에 변화가 동시에 진행이 되어서 그렇지 않을까? 라고 좋게 생각합니다. ㅎㅎ

예전에 윈도우즈를 사용할 때와는 다르게 조금 더 관대해진 느낌입니다. ^^; 그때는 내가 불법사용자(아시는 분은 알듯?)라는 사실에 떳떳하지 못했지만 우분투는 이렇게 사용하면서 내 사용경험을 우분투에서 제공하고 공유함으로써 '내가 뭔가를 하고 있구나'라는 느낌이 들기 때문에 느긋해지는 듯 합니다.

저는 위에서 작성한 스크립트파일을 eclipse.sh 라는 이름으로
chmod +x eclipse.sh
명령으로 실행속성을 부여한 후에 패널에 올려놓고 사용하고 있습니다. ^^;
이와 관련된 문제도 언젠가는 해결되겠지요. 괜찮아유~~ ㅎㅎㅎ

이건, 우분투에서 STS가 실행되지 않을 때도 동일한 방법으로 처리가 가능하다.

'글감보관함' 카테고리의 다른 글

HTML 특수문자 코드표  (2) 2010.07.04
Eclibatis: iBATIS Eclipse Plugin  (2) 2009.12.13
Java2Html - Eclipse Plugin  (0) 2009.12.13
Posted by 피곤키오
,

생성자 대신 스태틱 팩토리 메소드를 고려하라

스태틱 팩토리 메소드는 단순히 자신이 정의된 클래스의 인스턴스를 리턴하는 메소드로 public static으로 정의한다. 기본타입 boolean의 래퍼클래스인 boolean에 1.4 배포판부터 추가된 Boolean.valueOf(boolean b) 메소드는 스태틱 메소드의 좋은 예이다.

  • 스태틱 팩토리 메소드는 생성자와 달리 알맞은 이름을 줄 수 있다.
  • 스태틱 팩토리 메소드는 생성자와 달리 호출될 때마다 새로은 객체를 생성하지 않아도 된다.
  • 생성자는 자신이 정의된 클래스의 인스턴스만 리턴할 수 있지만, 스태틱 팩토리 메소드는 자신이 선언된 것과 같은 타입의 인스턴스는 모두 리턴할 수 있다.
  • 스태틱 팩토리 메소드의 가장 큰 단점은 이 메소드를 정의한 클래스가 public 이나 protected 생성자를 제공하지 않으면, 다른 클래스가 이 클래스를 상속 받을 수 없다는 것이다.
  • 스태틱 팩토리 메소드의 두번째 단점은 다른 스태틱 메소드와의 차이를 명시 할 수 없다는 것이다.

Foo.java 서비스 제공자 프레임워크 예제
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
 

//서비스 제공자 프레임워크 예제
public abstract class Foo {
        //문자열 키와 구현 클래스의 class 객체를 결합시킨 맵
        private static Map implementations = null;
        private static ResourceBundle classNames;
        
        //처음 호출되었을때 맵을 초기화 한다.
        private static synchronized void initMapIfNecessary() {
               if( implementations == null ) {
                       implementations = new HashMap();
               }
               
               //속성 파일에서 키와 구현 클래스 이름을 가져온다.
               //Class.forName을 써서 클래스 이름으로부터 Class 객체를
               //생성하고 Map에 저장한다.
               
               String firstFoo = getValue("firstFoo");
               String secondFoo = getValue("secondFoo");
               String thirdFoo = getValue("thirdFoo");
               
               try {
                       Class obj1 = Class.forName(firstFoo);
                       Class obj2 = Class.forName(secondFoo);
                       Class obj3 = Class.forName(thirdFoo);
                       implementations.put("firstFoo", obj1);
                       implementations.put("secondFoo", obj2);
                       implementations.put("thirdFoo", obj3);
                       
               } catch (ClassNotFoundException e) {
                       System.out.println("Class not found");
               }
        }
        
        private static String getValue(String s) {
               String value = classNames.getString(s);
               return value;
        }
        
        static {
               try {
                       classNames = ResourceBundle.getBundle("TableText");                  
               } catch (java.util.MissingResourceException e) {
                       System.out.println("Resource File not Found");
                       System.exit(1);
               }
        }
        
        public static Foo getInstance(String key) {
               initMapIfNecessary();
               Class c = (Class) implementations.get(key);
               System.out.println(c + " " + "returned");
               if (c == null)
                       return new DefalutFoo();
               try {
                       return (Foo)c.newInstance();
               } catch (Exception e) {
                       return new DefalutFoo();
               }
        }
}

 

Private 생성자를 써서 싱글톤을 유지하라

싱글톤(singleton)이란 정확히 하나의 인스턴스만 만들어지는 클래스로 원래부터 유일할 수밖에 없는 비디오 출력이나 파일 시스템과 같은 시스템 구성요소들을 주로 표현한다.

싱글톤은 두가지 방법으로 구현 할수 있다. 두 방법 모두 생성자를 private으로 정의하고 클라이언트가 이 클래스의 유일한 인스턴스에 접근 할 수 있는 public static 멤버를 제공한다.

public static final 필드로 구현한 싱글톤
//public static final 필드로 구현한 싱글톤
public class Elvis {
        public static final Elvis INSTANCE = new Elvis();
        
        private Elvis() {
               //... 코드 생략
        }
        
        //... 이하 생략
        
}

Elvis 클래스의 private 생서자는 public static final 멤버 필드인 Elvis.INSTANCE가 초기화 될 때 딱 한번만 호출된다. 싱글톤을 유지 하려면 public이나 protected 생성자를 두지 말아야 한다.

스태틱 팩토리 메소드로 구현한 싱글톤
//스태틱 팩토리 메소드로 구현한 싱글톤
public class Elvis {
        private static final Elvis INSTANCE = new Elvis();
        
        private Elvis() {
               //... 코드 생략
        }
        
        public static Elvis getInstance() {
               return INSTANCE;
        }
        
        //... 이하 생략
        
}

스태틱 팩토리 메소드인 Elvis.getInstance는 호출될 때마다 새로운 Elvis 인스턴스를 생성하지 않고 계속 같은 객체 참조를 리턴한다.

 

Priavet 생성자로 인스턴스를 만들지 못하게 하라

가끔 static 메소드와 static 필드로만 이루어진 클래스를 만들어야 할때가 있다. 이때에는 private 생성자 하나만 만들어 주면 된다.

//인스턴스를 만들 필요가 없는 유틸리티 클래스
public class UtilityClass {
        //private 생성자를 하나 만들어 컴파일러가 자동으로 기본 생성자를
        //추가 하지 못하게 한다.
        private UtilityClass() {
               //이 생성자는 호출되지 않는다.
        }
        
        //...이하 생략
        
}

Utility 클래스의 생성자는 이 클래스 밖에서 접근할 수 없다. 따라서 utility 클래스 내부에서 생성자를 호출하지 않는 다면 , 절대 인스턴스가 생성되는 일은 없다.

 

쓸데없는 객체를 중복 생성하지 마라

동등한 기능을 하는 객체는 필요할때마다 생성하는 것보다 한 객체를 재사용하는 것이 더 낫다. 다음과 같은 코드는 만들면 안된다.

String s = new String("바보"); //이렇게 만들지 말것!

다음은 가변 객체를 잘못 사용하고 있는 예제이다.

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
 
public class Person {
        private final Date birthDate;
        //다른 필드는 생략했다.
        
        public Person(Date birthDate) {
               this.birthDate = birthDate;
        }
        
        //절대 이렇게 하지 말 것!
        public boolean isBabyBoomer() {
               Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
               gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
               Date boomStart = gmtCal.getTime();
               gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
               Date boomEnd = gmtCal.getTime();
               return birthDate.compareTo(boomStart) >= 0 && birthDate.compareTo(boomEnd) < 0;
        }
}

isBabyBoomer 메소드를 호출할때마다 Calendar 인스턴스하나, TimeZone 인스턴스하나, Date 인스턴스 두개가 쓸데 없이 계속 생성된다. isBabyBoomer 메소드는 내용이 변하지 않기 때문에 static 초기화를 사용하여 다음과 같이 해결한다.

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
 
public class Person {
        private final Date birthDate;
        //다른 필드는 생략했다.
        
        public Person(Date birthDate) {
               this.birthDate = birthDate;
        }
        
        /**
         * 베이비 붐의 시작 및 종료 일자
         */
        private static final Date BOOM_START;
        private static final Date BOOM_END;
        
        static {
               Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
               gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
               BOOM_START = gmtCal.getTime();
               gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
               BOOM_END = gmtCal.getTime();  
        }
        
        public boolean isBabyBoomer() {
               return birthDate.compareTo(BOOM_START) >= 0 && birthDate.compareTo(BOOM_END) < 0;
        }
}

 

쓸모 없는 객체 참조는 제거하라

다음 예제를 살펴보자.

import java.util.EmptyStackException; 

//어디선가 메모리가 새고(memory leak) 있다.
public class Stack {
        private Object[] elements;
        private int size = 0;
        
        public Stack(int initialCapacity) {
               this.elements = new Object[initialCapacity];
        }
        
        public void push(Object e) {
               ensureCapacity();
               elements[size++] = e;
        }
        
        public Object pop() {
               if(size ==0)
                       throw new EmptyStackException();
               return elements[--size];
        }
        
        /**
         * 최소한 하나의 구성요소를 담을 수 있는 여유를 두어야 하며,
         * 여유가 없을 때 구성요소를 담는 배열의 크기를 두배로 늘린다.
         */
        public void ensureCapacity() {
               if(elements.length == size) {
                       Object[] oldElements = elements;
                       elements = new Object[2*elements.length+1];
                       System.arraycopy(oldElements, 0, elements, 0, size);
               }
        }
}

elements 배열의 “유효부분(active portion)”을 벗어난 배열 구성요소가 저장하고 있는 참조들은 쓸모 없는 참조들이다. 팝(pop)된 객체들은 다른 프로그램에서 더 이상 참조하지 않더라도 이 객체들은 가비지 컬렉션 대상이 되지 않는다. 가비지 컬렉터가 있는 언어에서 메모리 누수현상은 매우 위험한 것이다. 이런문제는 쓸모없는 참조에 null을 대입해 버리면 된다. 따라서 Stack 클래스의 pop메소드는 다음과 같이 수정되어야 한다.

public Object pop() {
       if(size ==0)
	       throw new EmptyStackException();
       Object result =  elements[--size];
       elements[size] = null; //쓸모없는 참조를 없앤다.
       return result;
}
* 가비지 컬렉션

자바 플랫폼의 모든 배열과 객체들은 힘(heap)이라는 메모리 공간에 저장된다. New 키워드를 쓸때마다 힙에 새로운 메모리가 객체마다 할당된다. 이때 할당된 메모리를 가비지 컬렉터가 알아서 반환해준다. 보통 프로그래머들은 메모리를 반환할 때, 대상 객체 참조에 null을 대입하거나 System.gc()를 호출하는 방법을 사용한다. 물른, System.gc()를 호출했다고 해서 가비지 컬렉션이 바로 실행된다는 보장은 없다.

* 종료자

Object 클래스에는 protected 메소드인 finalize라는 메소드가 있다. 자바의 모든 클래스는 이메소드를 재정의 할 수 있다. 특정 객체에 실제로 호출되는 finalize 메소드를 이 객체의 종료자(finalizer)라고 한다. 시스템은 가비지 컬렉터가 객체를 파괴하기 전에 반드시 종료자를 호출한다. 하지만, 가비지 컬렉터가 객체를 언제 파괴할지 알수 없기 때문에 종료자도 언제 호출될지 알수 없고, 아예 호출되지 않을수도 있다.

//종료가 가디언 구현패턴
public class Foo {
        //이 객체는 오직 자신을 포함하는 Foo객체의 종료처리를 수행한다.
        private final Object finalizerGuradin = new Object() {
               protected void finalize() throws Throwable {
                       System.out.println("Outer Foo object finalized");
               }
        };
        
        public static void main(String args[]) {
               Foo foo = new Foo();
               //강제 종료자 수행
               foo = null;
               System.gc();
        }
}

 

EFFECTIVE JAVA
카테고리 컴퓨터/IT
지은이 Joshua Bloch (대웅, 2003년)
상세보기

'개발자 센터 > Java' 카테고리의 다른 글

자바 메소드 작성시 알아야 할 것  (0) 2009.12.16
자바 프로그래밍 일반  (1) 2009.12.14
Posted by 피곤키오
,

struts2에서 세션(session) 사용하기

 

스트럿츠2에서 세션 받아오기

- 스트럿츠2에서는 세션 데이터을 맵으로 관리하기 때문에 받아올때에도 다음과 같이 맵객체에 담아준다.

ActionContext con = ActionContext.getContext();
Map session = con.getSession();  

 

세션에 내용을 입력할 경우

- map에 데이터를 넣는 것과 동일하다. 키값으로는 object를 사용해도 된다.

 session.put( "키값" , 내용 );  
 

 

현재 무슨 세션이 있는지 알고자 할 경우


1. 키 값을 알고 있는 경우

- key값을 알고 있을경우에는 key값으로 바로 가져오면 된다.

  Object value = session.get("키값");  

2. 키 값을 모르고 있는 경우

- key값을 모르는 경우에는 세션에 존재하는 모든 키값을 가져와서 해당 데이터를 몽땅 가져온다.

Set set = session.keySet();
Iterator ite = set.iterator();
while(ite.hasNext())
{
     String key = (String) ite.next();
     Object value = session.get(key);
}  

 

세션 삭제하기

- session에서 해당 키값에 해당하는 데이터를 제거합니다.

session.remove("키값");  

 

JSP 문서에서 세션 확인하기

- if문 사이에 세션을 확인하는 코드를 넣어줍니다.

<%@ taglib prefix="s" uri="/struts-tags" %>
<s:if test="#session">
<!--세션 확인 하는 코드-->
</s:if>  

'개발자 센터 > Struts2' 카테고리의 다른 글

Struts2 문자열 출력 예제  (0) 2009.12.11
Struts2 Action : 액션  (0) 2009.12.11
Struts2 Framework 아키텍처  (0) 2009.12.11
Struts2 Framework의 특징  (0) 2009.12.11
Struts2 Framework 환경설정 방법  (0) 2009.12.11
Posted by 피곤키오
,

iBATIS SQLMaps 활용법

목차

  1. ORM? 데이터 매퍼?
  2. 기능에 따른 사용형태
    1. 일반적인 CRUD작업
    2. 프로시저
    3. N+1 문제
    4. 동적 SQL
  3. 아이바티스 3.0 소식
  4. 정리

 

ORM? 데이터 매퍼?

iBATIS SQLMaps(이하 아이바티스)는 이미 국내외 많은 개발자들이 사용하고 있는 퍼시스턴스 계층의 프레임워크이다. 실제로 주요한 두 포털 사이트인 Naver와 Daum이 아이바티스를 기본적으로 사용하고 있고 다른 업체들도 아이바티스를 그대로 사용하거나 약간 변형시킨 형태로 사용하고 있는 것으로 알고 있다. 과거 ORM의 대표인 하이버네이트와 데이터 매퍼인 아이바티스간에 어느 프레임워크가 더 좋으냐? 하는 논쟁이 있었던 적도 있었을 만큼 많은 자바 개발자들이 데이터베이스에 관련된 ORM프레임워크에 대한 관심이 크다. 이런 논쟁 가운데 아이바티스는 ORM이 아니기 때문에 논쟁의 대상이 될 수 없다는 의견이 있었다. 사용 목적이라는 관점에서 본다면 하이버네이트와 아이바티스는 논쟁의 대상이 될 수 있으나 ORM이냐 아니냐 라는 관점에서 본다는 역시 아이바티스는 ORM이 아니라고 할 수 있다. 이 부분은 아이바티스의 핵심 개발자인 래리 메도스는 다음처럼 ORM과 데이터 매퍼를 구분한다.

ORM = 데이터베이스 객체를 자바 객체로 매핑
Data mapping = SQL "구문"을 자바 객체로 매핑

여기서 말하는 개념은 두 프레임워크의 성격을 제대로 이해할 수 있게 해준다. 이러한 개념적인 차이점은 결과적으로 ORM의 캐싱전략은 데이터베이스 객체에 매핑된 자바 객체가 캐시에 저장되는 게 기본이고 데이터매퍼는 객체가 아닌 SQL구문 자체가 캐시에 저장이 되는 것과 같은 차이점을 낳게 된다. 엄밀히 따지면 이렇겠지만 역시 사용목적으로 본다면 대체적으로 아이바티스도 ORM의 범주에 넣는 게 크게 무리가 없다고 보는 게 사실이다. ORM이 생겨난 원인을 무엇일까.? 가장 기본적인 것은 현재의 데이터베이스 프로그래밍에서 일반 JDBC형태의 개발 방법이 개발자에게 필요 이상의 많은 타이핑과 자원관리를 요구한다는 것이다. ORM을 사용해본 개발자는 이 부분에 대해 ORM이 어느 정도 효과를 보인다고 생각할 것이다. 물론 새로운 프레임워크에 대한 학습과 해당 프레임워크가 요구하는 설정작업, 그리고 개발방식은 분명히 개발자에게 또 다른 부담으로 작용하는 것 또한 사실이다. 하지만 개인적으로 개발자는 항상 새로운 어떤 것을 배우고 습득해야 한다고 생각한다. 그런 면에서 이런 프레임워크로 인한 부담을 느끼지 말고 자기 계발의 수단으로 생각했으면 좋겠다. 필자가 처음으로 아이바티스를 접했던 3년 전과는 달리 현재는 국내에 서적도 나와있고 많은 관련문서가 있어서 개발자들이 아이바티스를 학습하는데 전혀 무리가 없다고 봐진다. 그래서 이번에는 특정 부분에 집중하지 않고 아이바티스를 활용할 수 있는 부분들을 전체적으로 살펴보고 아이바티스 홈페이지를 통해 논의가 되고 있는 아이바티스 3.0이 나아가고자 하는 방향을 여기서 간단히 살펴보고자 한다.

 

기능에 따른 사용형태

필자가 테스트 한 환경은 다음과 같다.
eclipse-europa, iBATIS 2.3.0, MySQL 5.0.45, mysql-connector-java-5.0.3-bin.jar

필자는 기본적으로 아이바티스의 환경정보를 설정하는 파일을 SQLMaps파일,
SQL구문을 저장하는 파일을 SQLMap 파일이라고 명명했다.

아이바티스를 사용하는 개발에서는 크게 설정정보를 가지는 SQLMaps파일, SQL구문을 가지는 SQLMap파일 그리고 아이바티스 API를 사용하는 자바 코드로 구성이 된다고 볼 수 있다. 먼저 설정정보를 가지는 SQLMaps 파일을 보자. SQLMaps 파일 설정은 이미 많이 알려져 있기 때문에 첨부된 예제소스를 참조하도록 한다[아래 설정파일 제거] 샘플용 소스이기 때문에 typeAlias와 SQLMap 파일의 개수가 적다. 하지만 실제 프로젝트에 아이바티스를 적용할 때는 사전에 typeAlias에 대한 명명규칙을 정해서 중복이 되지 않도록 하거나 각각의 SQLMap파일에 정의해서 StatementNamespaces 를 사용해서 각각을 구분하도록 작업을 해주어야 설정파일의 증가로 복잡해질 때 혼동의 여지가 줄어들 것이다. 즉 설정정보의 증가로 인해 발생할 수 있는 혼동을 사전 정의를 통해 충분히 보완이 되어 있어야 할 것이다.

<sqlMapConfig>
  <properties resource="jdbc.properties" />

  <settings 
    cacheModelsEnabled="true" 
    enhancementEnabled="true"
    lazyLoadingEnabled="true" 
    maxRequests="32" 
    maxSessions="10"
    maxTransactions="5" 
    useStatementNamespaces="false" />
  
  <typeAlias alias="Account" type="kr.or.openframework.domain.Account"/>
  <typeAlias alias="Family" type="kr.or.openframework.domain.AccountFamily"/>

  <transactionManager type="JDBC">
    <dataSource type="DBCP">
      <property name="JDBC.Driver"
        value="${mysql.jdbc.driverClassName}" />
      <property name="JDBC.ConnectionURL"
        value="${mysql.jdbc.url}" />
      <property name="JDBC.Username"
        value="${mysql.jdbc.username}" />
      <property name="JDBC.Password"
        value="${mysql.jdbc.password}" />
      <property name="JDBC.DefaultAutoCommit" value="true" />
    </dataSource>
  </transactionManager>

  <sqlMap resource="kr/or/openframework/dao/ibatis/MySQLAccount.xml" />
</sqlMapConfig>  

전역 셋팅에 해당되는 설정과 typeAlias, 그리고 트랜잭션 관리자 및 SQLMap파일에 대한 위치 정보를 가지고 있다. 전역 셋팅에 해당되는 설정은 이외에도 더 있으며 상세한 설명은 이 글의 범위를 벗어나는 듯하다. 각각의 설정 값에 대한 상세한 정보는 kldp.net에서 호스팅 중인 아이바티스 개발자 가이드 한글문서나 공식 영문문서를 보면 알 수 있다. typeAlias는 말 그대로 타입에 대한 별칭이다. 자바의 패키지 구조로 인해 전체 클래스의 이름이 길 경우 아무래도 사용시 어렵기 때문에 여기서 별칭 형태로 정의해서 차후 불필요한 중복 타이핑을 줄이고자 하는 목적을 지닌다. 트랜잭션 관리자 부분은 간단히 볼 때 데이터베이스 셋팅이다. 간혹 커뮤니티를 통해 아이바티스에서 데이터소스를 2개 이상 설정할 수 없냐는 질문이 나오는데 1.x 버전에서는 가능했으나 2.x 부터는 혼란의 소지가 있어 불가능하도록 만들었다. 꼭 필요한 경우라면 이 설정파일을 데이터소스 개수만큼 생성해서 동적으로 읽어 들이는 수밖에 없을 듯하다. 마지막으로 SQL구문을 가지게 되는 SQLMap파일이다. 여기서는 클래스 패스 기준으로 설정되었다. 아이바티스 사용자 사이에 항상 고민이 되는 WAS 재 시작 없이 SQL구문이 갱신되는 기능은 현재 아이바티스 단독으로만 사용할 때는 SQLMap파일을 파일 경로로 지정해주는 방법 외에는 없을 듯 하다. 물론 이 방법 또한 정상적으로 갱신이 안 되는 경우가 있다고 하니 필자로서는 Spring프레임워크를 사용하는 방법을 권장한다. Spring프레임워크를 사용해서 WAS의 재 시작 없이 SQL구문이 갱신되는 것은 AppFuse를 통해 간단히 체크해볼 수 있다.

다음 그림은 이제부터 살펴볼 기본적인 CRUD작업에 사용된 테이블의 구조이다. 계정(account) 정보와 그 계정의 가족정보(account_family)를 가지는 지극히 간단한 구조로 되어 있다. 하지만 여기서 살펴볼 아이바티스의 기능을 구현하기에는 더없이 적절한 구조라고 생각된다.

- ER 다이어그램


일반적인 CRUD작업

데이터 매퍼인 아이바티스는 SQL "구문"을 자바 객체로 매핑하기 때문에 기본적으로 정적인 CRUD작업에 가장 최적화가 되어 있다고 할 수 있다. 물론 데이터베이스 함수 및 프로시저, 동적 SQL 처리에도 사용은 가능하나 제약성을 일부 볼 수 있게 마련이다. 여기서는 SQL구문의 재사용을 위해 <sql> 요소를 사용했고 이를 <include> 요소를 사용해서 다른 구문에서 가져다가 사용하고 있다. 쿼리문을 일종의 특정 id값을 가지는 xml에 저장하고 그 id를 기준으로 호출해서 사용하는 게 아이바티스의 기본 사용법이다. 이 파일을 이해하는데 별무리가 없으리라 생각된다. 그러면 이 SQL구문들을 어떻게 호출해서 사용할까? 자바 코드를 보자.

SQLMap 파일

<sql id="selectAccount_frag">
select
  acc_id,
  acc_first_name,
  acc_last_name,
  acc_email
from account
</sql>

<select id="selectAccountById" parameterClass="int" resultClass="Account">
<include refid="selectAccount_frag"/>
where acc_id = #id#
</select>

<insert id="insertAccount" parameterClass="Account">
insert into account (
  acc_id,
  acc_first_name,
  acc_last_name,
  acc_email
) values (
  #id#, #firstName#, #lastName#, #emailAddress#
)
</insert>

<update id="updateAccount" parameterClass="Account">
update account set
  acc_first_name = #firstName#,
  acc_last_name = #lastName#,
  acc_email = #emailAddress#
where acc_id = #id#
</update>

<delete id="deleteAccount" parameterClass="int">
delete from account 
where acc_id = #id#
</delete> 

자바 코드에서는 각각의 SQL구문을 호출하는 형태를 다음처럼 취하고 있으며 사전에 아이바티스 설정파일을 통해 관련 정보를 얻는 부분이 static 구문 안에 처리가 되어 있다. 이 소스는 아이바티스를 처리하는 결과를 보기 위해 log4j설정과 아이바티스 설정정보를 읽어오는 두 가지로 구성이 되어 있다. log4j.xml 파일과 아이바티스 설정파일의 위치를 클래스 패스 기준으로 가져오도록 했다. SqlMapClient가 제공하는 각각의 메소드에 SQLMap파일에 정의된 SQL구문의 id와 인자로 넣어줄 객체를 정의하는 형태를 취한다. 짐작하겠지만 SQL구문의 id와 일치하는 SQL구문에 해당 객체의 값이 인자로 전달되어서 내부적으로 java.sql의 PreparedStatement 객체 생성 후 일반 JDBC처럼 처리가 된다.

자바 코드

private static SqlMapClient sqlMapper;

static {
  try {
    // log4j setting
    Properties configProps = new Properties();
    ClassLoader classLoader = ClassLoader.getSystemClassLoader();
    InputStream inputStream = classLoader.getResourceAsStream("log4j.xml");
    configProps.load(inputStream);      
    PropertyConfigurator.configure(configProps);

    // iBATIS SQLMaps setting
    Reader reader = Resources.getResourceAsReader("kr/or/openframework/dao/ibatis/SqlMapConfig.xml");
    sqlMapper = SqlMapClientBuilder.buildSqlMapClient(reader);
    reader.close();
  } catch (IOException e) {
    throw new RuntimeException("Something bad happened while building the SqlMapClient instance." + e, e);
  }
}

public static Account selectAccountById(int id) throws SQLException {
  return (Account) sqlMapper.queryForObject("selectAccountById", id);
}
public static String insertAccount(Account account) throws SQLException {
  sqlMapper.insert("insertAccount", account);
  return "SUCCESS";
}
public static String updateAccount(Account account) throws SQLException {
  sqlMapper.update("updateAccount", account);
  return "SUCCESS";
}

public static String deleteAccount(int id) throws SQLException {
  sqlMapper.delete("deleteAccount", id);
  return "SUCCESS";
} 

프로시저

예제를 위해 필자는 다음처럼 간단한 프로시저를 생성했다. 여기서는 IN 타입의 파라미터만을 사용했지만 IN, OUT, INOUT 타입 모두 지원한다. 여기서 사용된 프로시저는 인자로 들어온 값을 id로 해서 샘플용 데이터를 생성하는 처리를 한다.

drop procedure if exists procedurein;

delimiter // ;

create procedure procedurein (in p_param int)
begin
    insert into account (
      acc_id,
      acc_first_name,
      acc_last_name,
      acc_email
    ) values (
      p_param, "procedure_test", "procedure_test", "test@test.com"
    );
end;
//

delimiter ; //

프로시저 호출은 일반적으로 콘솔 창에서 실행하는 것처럼 하고 { }로 감싸주면 된다. 처리 자체는 오히려 CRUD보다 간단하다고 할 수 있으나 직접 해보면 쉽지 않다는 것을 알 수 있다. 생각처럼 명확하게 처리가 되지 않는 경우도 있고 에러를 보기 쉽다.

SQLMap 파일

<procedure id="inProcedure" parameterClass="java.lang.Integer">
{ call procedurein(#val#) }
</procedure> 

자바 코드

public static String callInTypeProcedure(int id) throws SQLException {
  sqlMapper.update("inProcedure", new Integer(id));
  return "SUCCESS";
}  

프로시저의 처리는 아이바티스 홈페이지나 메일링 리스트를 통해 끊임없이 나오는 문제거리 중에 하나이다. 개발자와 데이터베이스마다 구현방식이 조금씩 다른 것도 있겠지만 여러 가지 눈에 띄는 어려움이 존재하기 때문에 아이바티스 문서를 보면 꼭 표준형태의 프로시저를 사용하도록 권장하고 있다. 필자의 경우에도 Oracle에서는 정상적으로 프로시저 형태의 샘플 코드를 테스트 했으나 MySQL로 작성하면서 이런저런 문제에 봉착했었다. Oracle기준으로 작성된 샘플은 참고문서의 URL을 통해 보길 권한다. 실제 프로젝트에서 아이바티스를 사용하여 프로시저를 처리할 때는 프로시저 사용패턴을 정의하고 사전에 정상적으로 처리가 되는데 테스트 해보길 권하고 싶다. 프로시저 처리시 사용할 메소드에 대한 권고사항이 있다. 살펴보면 다음과 같다.

케이스 메소드명
결과세트를 반환할 때 queryForList(), queryForObject()
한개 이상의 결과 객체를 반환할 때 queryForList()
한개의 결과 객체를 반환할 때 queryforObject()
결과세트를 반환하지 않거나 OUT 파라미터에 결과세트를 셋팅하지 않을 때 update()

위 예제는 IN 파리미터에 값을 전달하고 내부적으로 처리한 후 어떠한 데이터도 반환하지 않기 때문에 update()메소드가 사용되었다.

프로시저 처리시 결과세트를 반환하고 데이터를 업데이트 할 경우, <transactionManager> 요소의 commitRequired 속성값을 true롤 셋팅해줘야 한다.


N+1 문제

SQLMap 파일

<resultMap id="get_account_family_nplus1" class="Account">
  <result property="id" column="id" />
  <result property="firstName" column="firstName" />
  <result property="lastName" column="lastName" />
  <result property="emailAddress" column="emailAddress" />
</resultMap>

<resultMap id="get_family_nplus1" class="Family">
  <result property="acc_id" column="id" />
  <result property="fullName" column="fullName" />
  <result property="account" column="id" select="getFamiliesUsingNplus1" />
</resultMap>

<sql id="selectAccount_frag">
select
  acc_id as id,
  acc_first_name as firstName,
  acc_last_name as lastName,
  acc_email as emailAddress
from account
</sql>

<select id="selectAccountWithFamilyUsingNplus1" parameterClass="int" resultMap="get_family_nplus1">
select
  acc_id as id,
  family_fullname as fullName
from Account_family
where acc_id = #id#
</select>

<select id="getFamiliesUsingNplus1" parameterClass="int" resultMap="get_account_family_nplus1">
<include refid="selectAccount_frag"/>
where acc_id = #id#
</select> 

자바 코드

@SuppressWarnings("unchecked")
public static List selectAccountWithFamilyUsingNPlus1(int id) throws SQLException {
  List familys = (ArrayList)sqlMapper.queryForList("selectAccountWithFamilyUsingNplus1", new Integer(id));
  return familys;
}

log4j를 이용해 실제로 수행되는 SQL문을 찍어보면 다음처럼 나온다. 2(N)개의 데이터를 가져오기 위해 실제로는 SQL문을 한번 더(+1) 수행하여 총 3번(Account_family에 한번, Account에 두번)을 실행하게 되는 셈이다. 이것을 N+1 문제라고 하는데 이를 개선하기 위한 방법으로는 groupBy를 사용하는 방법이 있다. groupBy 를 사용하는 방법을 계속 이어서 보자.

Executing Statement:	select acc_id as id family_fullname as fullName from Account_family where acc_id = ?   
Executing Statement:	select acc_id, acc_first_name, acc_last_name, acc_email from account  where acc_id = ?   
Executing Statement:	selec acc_id, acc_first_name, acc_last_name, acc_email from account where acc_id = ?
 

SQLMap 파일

<resultMap id="get_account_family_avoidnplus1" class="Account" groupBy="id">
  <result property="id" column="id" />
  <result property="firstName" column="firstName" />
  <result property="lastName" column="lastName" />
  <result property="emailAddress" column="emailAddress" />
  <result property="families" resultMap="Account.get_family_avoidnplus1"/>
</resultMap>

<resultMap id="get_family_avoidnplus1" class="Family">
  <result property="acc_id" column="id" />
  <result property="fullName" column="fullName" />
</resultMap>

<select id="selectAccountWithFamilyAvoidNplus1" parameterClass="int" resultMap="get_account_family_avoidnplus1">
select 
  a.acc_id as id, 
  a.acc_first_name as firstName,
  a.acc_last_name as lastName,
  a.acc_email as emailAddress, 
  f.family_fullname fullName
from account a left join account_family f
on a.acc_id=f.acc_id
where a.acc_id=#id#
</select> 

자바 코드

@SuppressWarnings("unchecked")
public static List selectAccountWithFamilyAvoidNPlus1(int id) throws SQLException {
  List familys = (ArrayList)sqlMapper.queryForList("selectAccountWithFamilyAvoidNplus1", new Integer(id));
  return familys;
}  

조인구문과 groupBy를 사용하면 다음처럼 일반 join문을 사용하여 처리를 하기 때문에 N+1과 같은 문제가 발생하지 않는다. 현재는 이 방법이 가장 추천된다고 할 수 있다. 하지만 방법적으로 하위 결과맵을 사용하는 것이 추천되는 방법이라고 보기는 어려울 듯 하다. 아이바티스 3.0에서는 좀더 개선된 방법이 제공될 것으로 짐작된다. 그 방법에 대해서는 뒷 부분에서 다시 살펴볼 것이다.

Executing Statement:   select    a.acc_id,    a.acc_first_name,   a.acc_last_name,   a.acc_email,    f.family_fullname  from account a left join account_family f  on a.acc_id=f.acc_id  where a.acc_id=?
 

동적 SQL

SQLMap파일

<select id="selectAccountDynamic" parameterClass="Account" resultClass="Account">
  <include refid="selectAccount_frag" />
  <dynamic prepend="WHERE">
    <isGreaterEqual prepend="AND" property="id" compareValue="2">
      acc_id = 1
    </isGreaterEqual>
  </dynamic>
</select>

자바코드

@SuppressWarnings("unchecked")
public static List selectAccountDynamic(Account account) throws SQLException {
  List accounts = (List)sqlMapper.queryForList("selectAccountDynamic", account);
  return accounts;
}  

필자의 경우 인자로 넣어준 id의 값이 3이었기 때문에 다음처럼 WHERE acc_id =1 가 추가되어 처리되었다. 동적 SQL의 경우 자바 코드가 아닌 XML의 태그로 동적 SQL을 생성하기 때문에 손에 익지 않아 불편하기도 한다. 이미 아이바티스 개발팀은 이 동적 SQL부분에 대한 개선작업을 진행하고 있다. 그 결과물로 3.0에서 도입될 방식에 대해 잠시 뒤에 살펴보도록 하겠다.

생성된 SQL문

Executing Statement:       select       acc_id as id,       acc_first_name as firstName,       acc_last_name as lastName,       acc_email as emailAddress     from account       WHERE           acc_id = 1  

 

아이바티스 3.0 소식

아이바티스 3.0 개발은 다음과 같은 방향으로 진행이 된다.

  • 테스트 주도 개발
  • 성능보다는 코드의 간결성
  • 복잡한 디자인 보다는 간결한 디자인
  • 하나의 JAR파일
  • 다른 라이브러리의 의존성을 없앰
  • 더 나은 플러그인 지원
  • 추가적인 플러그인을 위한 프로젝트(http://sourceforge.net/projects/ibatiscontrib/)

역시나 기본적으로 계속 간결한 프레임워크를 유지하면서 많은 사용자들이 원하는 기능은 플러그인 형태로 제공하는 것이 기본적인 개발 방향임을 짐작할 수 있다. 역시 아이바티스는 간단함 내지 간결함으로 표현할 수 있는 프레임워크이다. 그럼 저 기본 방향을 염두해두고 현재 다소 형상화되어 보이는 3.0 기능에 대해서 한번 훓어보자.

인터페이스 바인딩

기존의 코딩 방식은 다음의 코드를 보자. 일단 이러한 방식의 경우 문제점은 다음과 같다. 매핑 구문명이 문자열 기반이라 매핑 구문명의 철자가 잘못되어 있을 경우 에러가 발생하게 되고 컴파일시가 아닌 런타임 시 에러가 발견할 수 있게 된다. 더군다나 문자열 기반이라 SQLMap파일의 증가는 곧 사용할 구문에 대한 체크에 일부 시간을 소요할 수 밖에 없게 만드는 계기가 된다. 그리고 반환 타입은 형변환을 통해 명시화되지만 실제로 반환타입의 모호로 인해 형변환시 ClassCastException은 아이바티스 사용자가 한번씩은 겪게 마련인 에러가 된다. 즉 애매한 형변화과 문자열 기반의 매핑 구문은 결과적으로 대부분의 에러가 런타임 시 발생하여 개발을 힘들게 만드는 요인이 되고 있다. 이런 점을 해결하기 위해 나온 것인 이 인터페이스 바인딩이다.

Employee employee = (Employee)sqlMapper.queryForList("getEmployee", 5);
//...그리고...
List employees = sqlMapper.queryForList("listAllEmployees");


public interface EmployeeMapper {
  Employee getEmployee (int employeeId);
  List listAllEmployees();
}  

기존에 XML에 타입을 선언하고 문자열 기반으로 구문을 호출하는 것 대신에 좀더 서술적이고 타입에 안전한 인터페이스의 사용으로 앞서 언급된 문제점을 많이 보완하고자 하고 있다. 아래의 코드는 기존 방식의 호출을 새로운 인터페이스 바인딩 방법으로 호출한 것이다. 애매한 모호한 형변환 작업이 없고 문자열 기반의 매핑 구문을 사용하지 않아 기본적으로 컴파일 시 많은 에러를 찾아낼 수 있다. 요즘처럼 IDE를 기본적으로 사용하는 환경이라면 얼마나 많은 도움이 될지는 읙심할 여지가 없다.

Employee emp = empMapper.getEmployee(5);
//...그리고...
List employees = empMapper.listAllEmployees();  

결과적으로 인터페이스 바인딩을 통해 다음의 값을 자동적으로 알 수 있게 되는 셈이다.

  • 매핑 구문명
  • 파라미터 타입
  • 결과 타입
 

다중 레벨 설정

아이바티스의 오랜 설정방식인 XML은 3.0에서도 역시 좋은 방법이 되겠지만 3.0에서 디폴트 설정방법이 되지는 않는다. 3.0에서는 다음과 같은 다중 레벨 설정이 가능하게 된다.

  • 관례(Convention)
  • 어노테이션(말 그대로 하면 주석) : 앞의 관례보다 우선시 된다.
  • XML : 앞의 관계, 어노테이션보다 우선시 된다.
  • 자바 API : 앞의 관례, 어노테이션, XML보다 우선시 된다.

관례는 메소드 시그너처로 정해진 규칙이라고 보면 된다.

Employee getEmployee (int id);  

라는 메소드 시그너처는 다음과 같은 SQL과 동일시 된다고 볼 수 있다.

SELECT id, firstName, lastName FROM Employee WHERE id = ?  

여기서 메소드 반환 타입의 이름이 테이블 명이 되고 메소드의 인자로 주어진 값이 자동으로 WHERE 조건문을 구성한다고 볼 수 있다. 간단한 조건문이나 들어가는 형식의 전체 데이터를 뽑는 기능이라면 이 메소드 선언으로 추가작업 없이 해당 기능이 구현된다.

어노테이션으로 설정하기

XML설정을 넘어서 어노테이션으로 메타데이터 정보를 설정하는 기능이 많이 도입되었다. 이에 아이바티스도 이러한 기능을 추가적으로 지원한다. 현재 아이바티스의 XML파일은 다음과 같은 정보를 가진다.

  • 설정
  • 메타 정보
  • 코드

설정은 환경적인 정보이다. 메타정보는 결과맵, 파라미터맵 그리고 캐시모델들을 나타내고 코드는 SQL과 동적 SQL요소를 포함한다.

설정정보는 properties파일이나 XML에 담겨야 하고 코드는 자바 코드나 XML에 담겨야 한다. 단 메타정보만이 어노테이션에서 처리가 가능하도록 될 것이다. 어노테이션은 예제는 다음과 같다. 앞의 관례에 의한 방식에서 칼럼 별 타입을 정의하고 조건문을 구체화하기 위해 어노테이션이 사용된 것을 볼 수 있다.

@Select({"SELECT #id(EMP_ID:NUMERIC), #firstName(FIRST_NAME:VARCHAR), #lastName(LAST_NAME:VARCHAR) ",
"FROM EMPLOYEE",
"WHERE EMP_ID = @id"})
Employee selectEmployee(int id);

@Insert({"INSERT INTO EMPLOYEE (EMP_ID, FIRST_NAME, LAST_NAME)",
"VALUES (@id, @firstName, @lastName)"})
void insertEmployee(Employee emp);

@Update({"UPDATE EMPLOYEE SET",
"EMP_ID=@id(NUMERIC:IN),
FIRST_NAME=@firstName(VARCHAR:IN),
LAST_NAME=@lastName(VARCHAR:IN)"})
void updateEmployee(Employee emp);

@Delete("DELETE EMPLOYEE WHERE EMP_ID = @id")
void deleteEmployee(int id);  

다음과 같은 형식의 어노테이션도 사용 가능하게 된다. 여기서는 ResultClass와 PropertyResult 를 추가적으로 어노테이션을 통해 정의한 것이다.

@ResultClass (Department.class)
@PropertyResults({
      @Result(property="id", column="DEPT_ID"),
      @Result(property="name", column="NAME"),
      @Result(property="employees",
      nestedQuery=@QueryMethod(type=CompanyMapper.class, methodName="getEmployeesForDeparment", parameters="id"))
      })
@Select("SELECT #id, #name FROM DEPARTMENT WHERE COMP_ID = @id ")
List getDepartmentsForCompany(int id);  

다음과 같이 캐싱설정또한 가능하게 된다.

@CacheContext("Employee")
public class EmployeeMapper {
  void insertEmployee(Employee emp);
  void updateEmployee(Employee emp);
  void deleteEmployee(Employee emp);
  Employee getEmployee(int id);
  List findEmployeesLike(Employee emp);
}  

기존 설정이라면 다음과 같은 의미를 가진다.

<Mapper cacheContext="Employee">
  <Insert id="insertEmployee" ...> ... </Insert>
  <Update id="updateEmployee" ...> ... </Update>
  <Delete id="deleteEmployee" ...> ... </Delete>
  <Select id="getEmployee" ...> ... </Select>
  <Select id="findEmployeesLike" ...> ... </Select>
</Mapper>  
@flushCache
  List updateAndGetSpecialEmployees();  

는 다음과 같은 의미를 가진다.

<Select id="findEmployeesLike" flushCache="true"...>  ... </Select>  

예제를 통해 보면 현재의 설정을 일일히 XML파일에 적어야 할 사항을 어노테이션을 통해 설정을 함으로써 일단 XML파일내용의 갱신문제에 좀더 자유로워 질 수 있으리라 짐작이 되고 개발자가 초기 XML설정외 대부분의 작업을 자바소스에서 제어함으로써 한결 수월할 작업이 이루어질 수 있으리라 생각할 수 있다. 기본적으로 어노테이션으로 많은 설정이 가능하고 관례라는 방법에 대한 보완적인 성격으로도 사용이 가능하게 된다.

XML 설정 향상

  1. 필드, 생성자 파라미터 그리고 자바빈즈 프라퍼티에 결과 및 파라미터를 맵핑된다.
  2. N+1 문제점을 해결하는 조인 매핑과 groupBy는 사용이 더 쉬워진다. 아래 예제의 요소를 사용해서 하위 결과맵을 생성하는 작업이 필요 없도록 한다.
  3. 결과맵과 파라미터맵은 기본적으로 "자동 매핑"이 되고 이름이 일치하지 않는 프라퍼티만을 명시한다.
  4. 기능적으로 좀더 뛰어나면서 간단한 타입핸들러 구현체와 데이터 타입 변환 필터를 제공할 것으로 보인다.
  5. 가장 큰 변화는 XML파일이 Mapper.class의 복사본과 함께 처리한다. 이를테면 EmployeeMapper.xml은 클래스패스에 존재하는 EmployeeMapper.class를 위해 로드된다. 해당 매퍼 클래스의 객체를 생성하면 자동으로 같은 이름의 매퍼 XML파일이 로드되는 형식이라고 짐작이 된다. 객체 생성에 따라 매퍼 XML파일이 로드되는 형식이라면 SQL구문의 갱신이 3.0에서는 어느 정도 해결되는게 아닌가 싶다.
< Mapper>
  <ResultMap id="selectACompanyWithJoin" resultClass="Company">
    <Constructor column="C.COMP_ID"/>
    <Constructor column="C.NAME"/>

    <Property name="departments.id" column="D.DEPT_ID"/>
    <Property name="departments.name" column="D.NAME"/>

    <Collection type="Department.class" property="departments" groupBy="id"/>
    <Collection type="Employee.class" property="departments.employees" groupBy="departments.id"/>
  </ResultMap>

  <Select id="selectACompanyWithJoin" parameters="id:int,order:string">
      SELECT
        #{id},
        #{name},
        #{departments.id},
        #{departments.name},
      FROM COMPANY C 
        INNER JOIN DEPARTMENT D ON C.COMP_ID = D.COMP_ID
        INNER JOIN EMPLOYEE E ON D.DEPT_ID = E.DEPT_ID
      WHERE
        C.COMP_ID = @{id}
      ORDER BY ${order}
  </Select>
</Mapper>  

동적 SQL

현재의 <dynamic> 요소를 사용하는 방법외에 3.0에서는 다음과 같은 방법이 가능하게 된다. 아무래도 자바 개발자에게는 XML 에서 문자열을 조작하는 것보다는 자바 로직으로 처리하는게 편할 듯 하다.

public class GetAccountListSQL extends SQLSource {
  public String getSQL(Object param) {
    Account acct = (Account) param;
    append("select * from ACCOUNT");
    prepend("WHERE"); // 다음의 append 앞에 기본적으로 추가한다. 
    if (exists(acct.getEmailAddress())) {
      append("AND", "ACC_EMAIL like #EmailAddress#"); // 필요하다면 첫번째 인자를 붙이겠지만 그렇지 않다면 첫번째 인자는 무시된다. 
    }
    if (greaterThan(0,acct.getID())) {
      append("AND", "ACC_ID = #ID#");
    }
    prepend(); // 앞의 prepend 뒤에 아무 코드가 없다면 앞의 prepend를 지운다.
    append ("order by ACCT_LAST_NAME");
  }
}  

위 소스는 다음처럼 사용가능하다.

@SQLSource(GetAccountListSQL.class)
List getAccountList(Account acct);  

또는 다음처럼도 가능하다.

<Select id="getAccountList" source="org.apache.GetAccountListSQL" ...> ... </Select>

그 외 테이블 관계를 나타내기 위한 간단한 기능이 추가되고 SQL생성을 지원하는 몇 가지 옵션이 추가될 것으로 보인다.

 

정리

개인적으로 원했던 기능 중에 3.0에서 추가되도록 활발히 진행중인 것 위주로 살펴보았다. 하지만 곰곰이 생각해보면 아이바티스가 추구했던 간결함이라는 방향에 반하는 기능이 추가되고 있는 것이 아닌가 라는 생각이 들기도 한다. 예를 들어 SQLSource를 사용한 동적 SQL생성 부분은 XML에서 어렵게 SQL문을 조작하는 방법에서 자바 개발자에게 친숙한 자바코드로 SQL문을 생성하게 되는 점에서 환영 받을만하나 기존에 XML에서 모두 관리하던 SQL문이 다소 분산되어서 관리에 어려움이 될 수도 있다고 본다. 즉 다양한 방법의 제공은 관리의 어려움을 양산하기도 한다는 것이다. 물론 이 부분은 철저히 동적 SQL문만을 SQLSource를 사용하여 자동 생성하도록 관리 체계를 수립하면 추가되는 관리상의 어려움이 크지 않을 듯 하다. 그리고 여기서 간단히 살펴본 3.0의 모습은 실제로 구현이 될 수도 있고 다른 형태로 변형이 되어서 구현이 될 수도 있다. 관심 있는 분은 아이바티스 3.0 화이트보드 페이지에 방문해서 원하는 형태의 덧글을 남겨도 좋을 듯 하다. 이 글은 비교되는 다른 ORM프레임워크와 함께 비교가 되는 글이다. 프레임워크마다 저마다의 장점과 단점을 가지게 마련인데 아이바티스의 장점은 간결함이다. 다시 말하면 사용하기가 쉽다는 것이다. JDBC에 익숙한 개발자라면 몇 시간의 교육만으로도 실제 프로젝트에 적용하기가 용이할 것이라고 생각한다. 단점이라고 하면 매핑 작업이 번거롭다는 것이다. 파라미터맵과 결과맵에 매핑해주는 작업이 JDBC를 그대로 사용하는 것보다 그렇게 쉬운 작업이 아니다. 데이터 복잡해질수록 매핑해 주는 작업이 어렵게 느껴지기도 한다. 그럼에도 불구하고 아이바티스를 많이 사용하게 되는데는 충분히 사용할만한 장점이 있다는 것이다. 앞으로도 발전하는 아이바티스의 모습을 기대한다.

참고문서

'개발자 센터 > iBatis' 카테고리의 다른 글

iBatis 에서 페이징하는 방법  (0) 2009.12.13
iBatis 참고 사이트  (0) 2009.12.13
iBatis SqlMapClient API  (1) 2009.12.13
iBatis Framework의 구성  (0) 2009.12.13
iBatis Framework  (0) 2009.12.13
Posted by 피곤키오
,

한 페이지에 10개의 게시글을 불러오는 메서드 작성

sqlMap.queryForList(String args0, Object args1)
public static List getBoardList(long rowsPerPage, long currentPage) {
  List result = new ArrayList(); 
  SqlMapClient sqlMap = MyAppSqlConfig.getSqlMapInstance();
  
  //페이지 정보를 저장한 객체생성
  BoardPage bp = new BoardPage();
  bp.setRowsPerPage(rowsPerPage);
  bp.setCurrentPage(currentPage);
  
  try {
   
   //페이지정보를 저장한 객체를 인자로 넘겨서 리스트를 구함
   result = sqlMap.queryForList("getBoardList", bp);   
   
  } catch(Exception e) {
   e.printStackTrace();
  }
  return result;
 }

 

Board.xml에서 매핑되는 코드 작성

Board.xml
<!-- select된 Board객체를 Mapping시킴 -->
 <resultMap id="boardResultMap" class="board.model.Board">
  <result property="bno" column="bno" />
  <result property="subject" column="subject" />
  <result property="writer" column="writer" />
  <result property="writedate" column="writedate" />
  <result property="hitcount" column="hitcount" />
 </resultMap>
 
  <!-- 페이지에 맞춰 게시글 검색  -->
  <select id="getBoardList" parameterClass="board.model.BoardPage" resultMap="boardResultMap" resultClass="board.model.Board">
  <![CDATA[
  select *
  from ( select rownum rnum, bno, subject, writer, writedate, hitcount
   from ( select bno, subject, writer, writedate, hitcount
             from board
       order by bno desc )
   where rownum <= ( #rowsPerPage# * #currentPage# )
   )
  where rnum >= ( #rowsPerPage# * ( #currentPage# - 1 ) + 1 )
  ]]>
 

'개발자 센터 > iBatis' 카테고리의 다른 글

간결한 데이터 매퍼의 대명사, iBATIS SQLMaps 활용법  (0) 2009.12.13
iBatis 참고 사이트  (0) 2009.12.13
iBatis SqlMapClient API  (1) 2009.12.13
iBatis Framework의 구성  (0) 2009.12.13
iBatis Framework  (0) 2009.12.13
Posted by 피곤키오
,

Related posts in this website for your search

Eclibatis helps developers in their daily work with iBATIS mapping and configuration files by providing code completion and linking between mapping and source files.

image

If you use iBATIS and Eclipse, then you should install this plugin.

Posted by 피곤키오
,

Version 1.5.0, January 5th, 2006:

download java2html_eclipse_150.zip 325kB

(previous version: Java2Html Eclipse plugin V1.4.2)

Download package includes binaries and source code.

System Requirements

Eclipse IDE installed (tested with 3.0 and greater).

Installation

  • Download and unpack the ZIP-file to the eclipse directory (for example "c:\java\eclipse\").
  • Restart Eclipse

Usage

Convert files:
Select items in the Package Explorer, Navigator or other views and right click to open the context menu. There is a Java2Html menu item that opens the conversion dialog.
Convert text from a text editor:
Right click on an editor and choose the Java2Html context menu item to convert the current selected text.
Adjust preferences:
Colors, tab size, etc. for conversion can be adjusted on the preference pages (Menu "Window" - "Preferences" - "Java" - "Java2Html").

Troubleshooting

  • If the installation did not seem to work, look into the Eclipse installation folder and make sure that there is a folder ECLIPSE/plugins/de.java2html_${version}/ containing the plugin binaries. If there is none, reinstall the plugin following the instructions above.
Posted by 피곤키오
,

reference

 

참고문서

 

관련툴

 

 샘플코드

  • queryForMap() 사용예제 - iBATIS사용시 대개의 개발자는 queryForObject queryForList 사용한다. 그래서 queryForMap 대한 이해나 예제가 부족한 실정이다. 그래서 이해를 돕고자 예제를 제공한다.
  • Procedure 사용예제 - Procedure사용예제를 다룬다. IN, OUT, INOUT타입을 다룬다.
  • iBATIS Error Message - iBATIS 사용하면서 겪게 되는 에러 메시지.

'개발자 센터 > iBatis' 카테고리의 다른 글

간결한 데이터 매퍼의 대명사, iBATIS SQLMaps 활용법  (0) 2009.12.13
iBatis 에서 페이징하는 방법  (0) 2009.12.13
iBatis SqlMapClient API  (1) 2009.12.13
iBatis Framework의 구성  (0) 2009.12.13
iBatis Framework  (0) 2009.12.13
Posted by 피곤키오
,

SqlMapClient API – 데이터 조회

 

queryForObject()

데이터베이스로부터 한 개의 레코드를 가져다가 자바 객체에 저장

Object queryForObject(String id, Object parameter) throws SQLException;

-       디폴트 생성자를 가진 객체를 생성(보편적 방법)

-       디폴트 생성자가 없으면 throws "런타임 예외"

 

queryForList()

한 개 이상의 레코드를 가져와서 자바 객체의 List를 만드는 데 사용

List queryForList(String id, Object parameter) throws SQLException;

-       매핑 구문이 반환하는 모든 객체를 반환

 

queryForMap()

데이터베이스로부터 한 개 혹은 그 이상의 레코드를 가져올 때 자바 객체의 Map을 반환

Map queryForMap(String id, Object parameter, String key) throws SQLException;

-       퀴리 실행후 Map 객체를 생성하여 반환

-       key : 결과 객체를 가리키는 키 - > 지정된 프로퍼티의 값

Map queryForMap(String id, Object parameter, String key, String value) throws SQLException;

-       key : 결과 객체를 가리키는 키 - > 지정된 프로퍼티의 값

-       value : 결과 객체 - > 지정된 프로퍼티의 값

 

 

SqlMapClient API – 데이터 갱신

 

insert 메소드

Object insert(String id, Object parameterObject) throws SQLException;

-       parameterObject : 파라미터 객체(데이터베이스에 데이터 삽입하는 데 사용)

-       반환 : 객체

 

update 메소드

int update(String id, Object parameterObject) throws SQLException;

-       parameterObject : 값을 제공하는 데 사용할 파라미터 객체

-       반환: update 구문에 의해 영향을 받은 레코드의 개수

 

 

delete 메소드

int delete(String id, Object parameterObject) throws SQLException;

-       parameterObject : 값을 제공하는 데 사용할 파라미터 객체

-       반환: 삭제된 레코드의 개수

 

 

트랜잭션(Transaction) 관리

SqlMapClient인터페이스는 트랜잭션경계를 지정하기 위해 메소드를 가진다. 트랜잭션이 시작되고 SqlMapClient인터페이스의 다음과 같은 메소드를 사용함으로써 commit되거나 rollback된다.

-       public void startTransaction () throws SQLException

-       public void commitTransaction () throws SQLException

-       public void endTransaction () throws SQLException

 

private Reader reader =

             new Resources.getResourceAsReader ("com/ibatis/example/sqlMap-config.xml");

private SqlMapClient sqlMap = XmlSqlMapBuilder.buildSqlMap(reader);

            

public updateItemDescription (String itemId, String newDescription)

             throws SQLException {

             try {

                           sqlMap.startTransaction ();

                           Item item = (Item) sqlMap.queryForObject ("getItem", itemId);

                           item.setDescription (newDescription);

                           sqlMap.update ("updateItem", item);

                           sqlMap.commitTransaction ();

             } finally {

                           sqlMap.endTransaction ();

             }

}

 

 

MySqlConfig.java

public class MySqlConfig {

            

             private static SqlMapClient sqlMap;

             static {

                           Reader reader = null;

                           try {

                                        String resource = "SqlMapConfig.xml";

                                        reader = Resources.getResourceAsReader (resource);

                                        sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);

                           } catch (Exception e) {

                                        e.printStackTrace();

                           }finally{

                                        if(reader != null){

                                                     try {

                                                                  reader.close();

                                                     } catch (IOException e) {

                                                                  e.printStackTrace();

                                                     }

                                        }

                           }

             }

             public static SqlMapClient getSqlMapInstance () {

                           return sqlMap;

             }

}

 

'개발자 센터 > iBatis' 카테고리의 다른 글

간결한 데이터 매퍼의 대명사, iBATIS SQLMaps 활용법  (0) 2009.12.13
iBatis 에서 페이징하는 방법  (0) 2009.12.13
iBatis 참고 사이트  (0) 2009.12.13
iBatis Framework의 구성  (0) 2009.12.13
iBatis Framework  (0) 2009.12.13
Posted by 피곤키오
,

iBatis Framework 구성

 

수행과정

1.     파라미터로서의 객체를 제공한다(자바빈,MAP, 원시래퍼[String, Integer, etc... ]),

-  파라미터 객체는 쿼리문 내의 입력값을 셋팅하거나 where절을 셋팅하기 위해 사용된다

2.     매핑된 statement실행

-  Data Mapper프레임웤은 PreparedStatment인스턴스생성

-  제공된 파라미터객체를 이용해 파라미터 설정

-  statement실행

-  ResultSet으로 부터 결과 객체 생성

3.     update의 경우 영향을 받은 row수 반환

4.     select문일 경우 한개의 객체 또는 컬렉션 객체 반환

-  결과객체의 타입은 파라미터 객체와 같은 타입이 될수 있다

 

SqlMapConfig.xml 파일

데이터소스에 대한 설정, 쓰레드 관리와 같은 SQL Maps와 다른 옵션에 대한 설정을 제공하는 중앙집중적인 XML 설정 파일.

 

SIMPLE 방식

db.properties

driver = com.mysql.jdbc.Driver

url = jdbc:mysql://127.0.0.1:3306/struts

username = scott

password = tiger

 

SqlMapConfig.xml

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

<!DOCTYPE sqlMapConfig

             PUBLIC "- / / ibatis.apache.org/ /DTD SQL Map Config 2.0/ /EN"

             "http:/ / ibatis.apache.org/dtd/sql- map- config- 2.dtd">

            

<sqlMapConfig>

<properties resource="db.properties"/>

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

                          lazyLoadingEnabled="false" useStatementNamespaces="false" />

                          <transactionManager type="JDBC" commitRequired="false">

                                       <dataSource type="SIMPLE">

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

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

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

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

                                        </dataSource>

                           </transactionManager>

<sqlMap resource= "Product.xml"/>

</sqlMapConfig>

 

JNDI 방식

context.xml

<?xml version="1.0" encoding="euc-kr"?>

<Context>

 

             <Resource name="jdbc/StrutsDB"

                           auth="Container"

                           type="javax.sql.DataSource"

                           username="scott"

                           password="tiger"

                           driverClassName="com.mysql.jdbc.Driver"

                           url="jdbc:mysql://127.0.0.1:3306/struts"

                           maxActive="5"

                           maxIdle="3"/> 

                          

</Context>

 

SqlMapConfig.xml

<transactionManager type="JDBC">

    <dataSource type="JNDI">

      <property name="DataSource" value="java:comp/env/jdbc/StrutsDB"/>

    </dataSource>

</transactionManager>

 

내장 별칭트랜잭션 매니저

별칭

Fully Qualified Class Name

JDBC

com.ibatis.sqlmap.engine.transaction.jdbc.JdbcTransactionConfig

JTA

com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig

EXTERNAL

com.ibatis.sqlmap.engine.transaction.external.ExternalTransactionConfig

 

내장 별칭 데이터 타입

별칭

Fully Qualified Class Name

string

java.lang.String

byte

java.lang.Byte

long

java.lang.Long

short

java.lang.Short

int

java.lang.Integer

double

java.lang.Double

float

java.lang.Float

boolean

java.lang.Boolean

decimal

java.math.BigDecimal

object

java.lang.Object

map

java.util.Map

hashmap

java.util.HashMap

list

java.util.List

arraylist

java.util.ArrayList

collection

java.util.Collection

iterator

java.util.Iterator

 

 

내장 별칭 데이터소스 팩토리 타입

별칭

Fully Qualified Class Name

SIMPLE

com.ibatis.sqlmap.engine.datasource.SimpleDataSourceFactory

DBCP

com.ibatis.sqlmap.engine.datasource.DbcpDataSourceFactory

JNDI

com.ibatis.sqlmap.engine.datasource.JndiDataSourceFactory

 

내장 별칭 캐시 컨트롤러 타입

별칭

Fully Qualified Class Name

FIFO

com.ibatis.sqlmap.engine.cache.fifo.FifoCacheController

LRU

com.ibatis.sqlmap.engine.cache.Iru.LruCacheController

MEMORY

com.ibatis.sqlmap.engine.cache.memory.MemoryCacheController

OSCACHE

com.ibatis.sqlmap.engine.cache.OSCacheController

 

내장 별칭 – XML결과 타입

별칭

Fully Qualified Class Name

Dom

com.ibatis.sqlmap.engine.type.DomTypeMarker

domCollection

com.ibatis.sqlmap.engine.type.DomCollectionTypeMarker

Xml

com.ibatis.sqlmap.engine.type.XmlTypeMarker

XmlCollection

com.ibatis.sqlmap.engine.type.XmlCollectionTypeMarker

 

Sql Map xml파일

Product.xml

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

<!DOCTYPE sqlMap

PUBLIC "- / / ibatis.apache.org/ /DTD SQL Map 2.0/ /EN"

             "http:/ / ibatis.apache.org/dtd/sql- map- 2.dtd">

            

<sqlMap id=Product>

             <cacheModel id=productCache type=LRU>

                           <flushInterval hours=24/>

                           <property name=size value=1000 />

             </cacheModel>

            

             <typeAlias alias=product type=com.ibatis.example.Product />

            

             <parameterMap id=productParam class=product>

                           <parameter property=id/>

             </parameterMap>

            

             <resultMap id=productResult class=product>

                           <result property=id column=PRD_ID/>

                           <result property=description column=PRD_DESCRIPTION/>

             </resultMap>

            

             <select id=getProduct parameterMap=productParam

                           resultMap=productResult cacheModel=product- cache>

                           select * from PRODUCT where PRD_ID = ?

             </select>

</sqlMap>

 

 

Statement 타입

Statement Element

Attributes

Child Elements

Methods

<statement>

id

parameterClass

resultClass

listClass

parameterMap

resultMap

cacheModel

All dynamic elements

Insert

Update

Delete

All query methods

<insert>

id

parameterClass

parameterMap

All dynamic elements

<selectKey>

<generate>

Insert

Update

Delete

<update>

id

parameterClass

parameterMap

extends

All dynamic elements

<generate>

Insert

Update

Delete

<delete>

id

parameterClass

parameterMap

extends

All dynamic elements

<generate>

Insert

Update

Delete

<select>

id

parameterClass

resultClass

listClass

parameterMap

resultMap

cacheModel

extends

All dynamic elements

<generate>

All query methods

<procedure>

id

parameterMap

resultClass

resultMap

cacheModel

All dynamic elements

Insert

Update

Delete

All query methods

 

iBatis에서 처리가능한 데이터 타입

-       Bean, Map, Primitive, XML

-       대부분의 경우 Bean, Map이 가장 많이 사용된다.

 

장점

단점

Bean

성능

컴파일 시 타입 및 이름 검사

형변환이 줄어듬

코드량 증가

Map

코드량 감소

느림

컴파일시 검사하지 않음

오류가 실행 시 체크됨

잦은 형 변환

 

파라미터 처리인라인 파라미터

-       인라인 파라미터

# propertyName#

# propertyName:jdbcType#

# propertyName:jdbcType:nullValue#

-       대개는 jdbcType이나 nullValue값에 대해 명시하지 않고 프로퍼티명만 명시해주는 방법을 사용

 

Director.xml

<select id="selectDirectorById1" resultMap="DirectorResultMap1"

             parameterClass="string">

             select *

             from director

             Director.xml

             where director_id = # value#

</select>

 

파라미터 처리외부 파라미터 맵

-       인라인 파라미터 매핑과 동일한 기능을 제공하면서 SQL Map 적재시 속도가 빨라지고 유지보수가 용이하다.

-       파라미터에 관련된 사항을 중앙집중적으로 관리 가능하게 해준다.

 

Director.xml

<parameterMap class="Director" id="DirectorParameterMap">

             <parameter property="id" javaType="string" jdbcType="varchar"/>

             <parameter property="name" javaType="string" jdbcType="varchar"/>

             <parameter property="age" javaType="int" jdbcType="int"/>

             <parameter property="winnerCount" javaType="int" jdbcType="int"

                           nullValue="0"/>

</parameterMap>

 

<insert id="insertDirector2" parameterMap="DirectorParameterMap">

             insert into director(

                           director_id,

                           director_name,

                           director_age,

                           director_winnerCount

             ) values(?,?,?,?)

</insert>

 

결과 데이터 처리인라인 결과 맵

-       간단하고 쉽게 사용할 수 있다.

-       프레임워크에 의해 자동 매핑 되어진다.

-       런타임시 오류 발생할 확률이 높다.

 

Director.xml

<select id="selectDirectorCount" resultClass="int">

             select

                           count(*) as value

             from director

</select>

 

<select id="selectDirectorById" resultClass="Director" parameterClass="string">

             select

                           director_id as id,

                           director_name as name,

                           director_age as age,

                           director_winnerCount as winnerCount

             from director

             where director_id = #value#

</select>

 

결과 데이터 처리외부 결과 맵

-       외부 파라미터 맵 처럼 객체의 프로터피와 SQL 결과의 컬럼명을 설정하고 각각의 데이터 타입 지정 및 null값에 대한 처리가 가능하다.

-       좀 더 나은 성능 및 유지보수가 용이하다.

 

Director.xml

<resultMap class="Director" id="DirectorResultMap2">

<result property="id" column="director_id" javaType="String" jdbcType="varchar"/>

<result property="name" column="director_name" javaType="String"

             jdbcType="varchar"/>

<result property="age" column="director_age" javaType="int" jdbcType="int"/>

<result property="winnerCount" column="director_winnerCount" javaType="int"

             jdbcType="int" nullValue="0"/>

</resultMap>

<select id="selectDirectorById2" resultMap="DirectorResultMap2"

             parameterClass="string">

             select * from director

             where director_id = #value#

</select>

'개발자 센터 > iBatis' 카테고리의 다른 글

간결한 데이터 매퍼의 대명사, iBATIS SQLMaps 활용법  (0) 2009.12.13
iBatis 에서 페이징하는 방법  (0) 2009.12.13
iBatis 참고 사이트  (0) 2009.12.13
iBatis SqlMapClient API  (1) 2009.12.13
iBatis Framework  (0) 2009.12.13
Posted by 피곤키오
,
ibatis

 

iBatis 환경설정

http://ibatis.apache.org/ 에서 java 버전의 2.x 라이브러리를 다운로드 받는다.
압축을 풀고 /lib 폴더에 있는 ibatis-2xxxx.jar 라이브러리를 클래스패스에 추가하거나 웹어플리케이션의 WEB-INF/lib 에 저장합니다.


iBatis 특징

  • 간단하면서 단순한 퍼시스턴트 프레임워크이다.
  • JDBC에 비해 약 60%이상의 코드를 줄일 수 있도 간단한 설정으로 생산성을 높일 수 있다.
  • join, 적재지연 등을 활용하여 데이터 접근 속도를 높일 수 있다.
  • 관심사의 분리로 차후에 유지보수가 용이하다.
  • 전문성을 강화하기 위한 작업의 분배가 용이하다.
  • 특정 프로그래밍 언어에 종속적이지 않다.

iBatis 장점

  • SQL문과 소스 코드 분리로 소스 코드의 간결함을 유지할 수 있다
  • 데이터베이스 자원에 대한 제어를 자동으로 해결해 준다.
  • SQL Injection으로부터 보호된다

 

JDBC와 iBatis 비교

다음은 JDBC와 iBatis를 비교해보도록 하겠습니다.


JDBC 이용

UserDAO.java
public List findUserList() throws SQLException {

	Connection con = null;
	PreparedStatement pstmt = null;
	ResultSet rs = null;
	List userList = new ArrayList();

	StringBuffer query = new StringBuffer();
	query.append("SELECT USERID,PASSWORD,NAME,EMAIL FROM USERINFO ");

	try {
		con = source.getConnection();
		pstmt = con.prepareStatement(query.toString());
		rs = pstmt.executeQuery();

		while (rs.next()) {
			User user = new User();
			user.setPassword(rs.getString("PASSWORD"));
			user.setName(rs.getString("NAME"));
			user.setEmail(rs.getString("EMAIL"));
			user.setUserId(rs.getString("USERID"));
			userList.add(user);
		}

	} finally {
		if (rs != null)
			rs.close();
		if (pstmt != null)
			pstmt.close();
		if (con != null)
			con.close();
	}
	return userList;
}

iBatis 이용

UserDAO.java
…
public List findUserList() throws SQLException{

             SqlMapClient sqlMapper = MySqlConfig.getSqlMapInstance();
             
             return sqlMapper.queryForList("selectAllUser");

}
…

User.xml
…
<select id="selectAllUser" resultClass="User">

             select * from userinfo

</select>
…
iBatis이용시 소스가 더 간결해진 것을 확인할수 있습니다.
Posted by 피곤키오
,

Reference Documentation

 

한글 문서

 

영문 문서

Posted by 피곤키오
,

Database 연동

-       스프링은 JDBC를 비롯하여 ORM 프레임워크를 직접적으로 지원하고 있기 때문에 간단하게 JDBC뿐만 아니라 ORM 프레임워크를 스프링과 연동할 수 있다.

-       스프링은 JDBC, ORM 프레임워크 등의 다양한 기술을 이용해서 손쉽게 DAO클래스를 구현할 수 있도록 지원한다.

-       템플릿 클래스 지원

n  개발자가 중복된 코드를 입력해야 하는 성가신 작업을 줄일수 있도록 한다.

n  JDBC : JdbcTemplate

n  iBatis : SqlMapClientTemplate

n  Hibernate : HibernateTemplate

-       DaoSupport 클래스 지원

n  DAO에서 기본적으로 필요로 하는 기능을 제공한다.

n  JDBC : JdbcDaoSupport

n  iBatis : SqlMapClientDaoSupport

n  Hibernate : HibernateDaoSupport

n  이러한 DaoSupport 클래스를 상속받아 DAO클래스를 구현한 뒤, DaoSupport 클래스가 제공하는 기능을 사용하여 보다 편리하게 코드를 작성할 수 있게 된다.

-       의미 있는 예외 클래스 제공한다.

-       스프링은 데이터베이스 처리 과정에서 발생한 예외가 발생했는지를 구체적으로 확인 있도록 하기위해, 데이터베이스 처리와 관련된 예외 클래스를 제공하고 있다.

n  데이터베이스 처리 과정에서 SQLException 발생하면 스프링이 제공하는 예외 클래스 알맞은 예외 클래스로 변환해서 예외를 발생 시킨다.

n  스프링의 모든 예외 클래스들은 DataAccessException 상속 받는다.

n  BadSqlGrammerException, DataRetrievalFailureException

 

 

 

DataSource 설정

-       스프링은 DataSource를 통해서 Connection을 제공한다.

-       따라서, DataSource 정보를 설정해야 한다.

-       스프링은 다음과 같은 3가지 설정 방식을 제공한다.

1.     커넥션 풀을 이용한 DataSource 설정

2.     JNDI를 이용한 DataSource 설정

3.     DriverManager를 이용한 DataSource 설정

 

스프링의 iBatis지원

-       스프링은 SqlMapClient를 스프링 설정 파일에서 쉽게 설정하도록 돕는 SqlMapclientFactoryBean클래스를 제공한다.

-       이 클래스를 사용하면 SqlMapClient를 스프링의 빈으로 설정할 수 있다.

 

DataSource설정

applicationContext.xml

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

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

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

        xsi:schemaLocation="http://www.springframework.org/schema/beans                            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

 

        <bean id="dataSource"

               class="org.apache.commons.dbcp.BasicDataSource"

               p:driverClassName="com.mysql.jdbc.Driver"

               p:url="jdbc:mysql://127.0.0.1:3306/struts"

               p:username="scott"

               p:password="tiger" />

       

</beans>

 

 

SqlMapConfig.xml

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

 

<!DOCTYPE sqlMapConfig     

    PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"     

    "http://ibatis.apache.org/dtd/sql-map-config-2.dtd">

 

<sqlMapConfig>

        <sqlMap resource="movie/Director.xml"/>

</sqlMapConfig>

 

 

SqlMapClientTemplate 클래스이용

-       SqlMapClient를 사용할 때에 관련된 반복적인 코드를 제거 할 수 있도록 해준다.

-       SqlMapClientTemplate클래스를 제공한다.

-       SqlMapClientTemplate클래스는 내부적으로 iBatisSqlMapClient를 사용한다.

-       SqlMapClientTemplate클래스를 빈으로 설정하고 이미 설정한 SqlMapClient빈을 설정한다.

 

SqlMapConfig.xml

<bean id="sqlMapClient"

        class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"

        p:dataSource- ref="dataSource"

        p:configLocation="SqlMapConfig.xml />

 

<bean id="sqlMapClientTemplate"

        class="org.springframework.orm.ibatis.SqlMapClientTemplate"

        p:sqlMapClient- ref="sqlMapClient />

 

<bean id="directorDao"

        class="myspring.ibatis.DirectorDAOImpl1"

        p:sqlMapClientTemplate- ref="sqlMapClientTemplate />

DAO 작성시 SqlMapClientTemplate 클래스를 주입 받을 수 있도록 프로퍼티와 setter메소드를 작성한다.

SqlMapClientTemplate 클래스가 제공하는 메소드를 이용해서 DAO를 작성한다.

DAO 클래스를 빈으로 설정하고 미리 설정한 SqlMapClientTemplate 빈을 전달 받도록 설정한다.

 

DirectorDAOImpl1.java

public class DirectorDAOImpl1 implements DirectorDAO {

       

        private SqlMapClientTemplate sqlMapClientTemplate;

       

        public void setSqlMapClientTemplate(SqlMapClientTemplate qlMapClientTemplate){

               this.sqlMapClientTemplate = sqlMapClientTemplate;

        }

        public List<Director> selectAllDirector() {

                sqlMapClientTemplate.queryForList("selectAllDirector");

        }

}

 

 

SqlMapClientDaoSupport 클래스이용

-       SqlMapClientTemplate 클래스를 DAO 클래스에서 좀 더 쉽게 사용할 수 있도록 SqlMapClientDaoSupport 클래스를 제공한다.

-       SqlMapClientDaoSupport 클래스가 제공하는 getSqlMapClientTemplate() 메소드를 이용해서 SqlMapClientTemplate 객체를 얻어내어 DAO를 작성한다.

-       따라서, SqlMapClientTemplate객체를 주입받을 생성자나 setter메소드가 필요 없다.

-       DAO 클래스를 빈으로 설정하고 미리 설정한 SqlMapClient빈을 전달 받도록 설정한다.

 

DirectorDAOImpl2.java

public class DirectorDAOImpl2 extends SqlMapClientDaoSupport implements DirectorDAO {

       

        public List<Director> selectAllDirector() {

               return getSqlMapClientTemplate().queryForList("selectAllDirector");

        }

              

}

 

applicationContext.xml

<bean id="sqlMapClient"

        class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"

        p:dataSource- ref="dataSource"

        p:configLocation="SqlMapConfig.xml />

 

<bean id="directorDao2"

        class="myspring.ibatis.DirectorDAOImpl2"

        p:sqlMapClient- ref="sqlMapClient />

Posted by 피곤키오
,

Aspect Oriented Programming

AOP는 프로그램 구조에 대한 다른 방식의 생각을 제공함으로써 OOP를 보완한다.

-       AOP는 문제를 바라보는 관점을 기준으로 프로그래밍하는 기법을 말한다.

-       문제를 해결하기 위한 핵심 관심 사항[1]과 전체에 적용되는 공통관심 사항[2]을 기준으로 프로그래밍함으로써 공통모듈을 여러 코드에 쉽게 적용할 수 있도록 도와준다.

-       AOP에서 중요한 개념은 「횡단 관점의 분리(Separation of Cross- Cutting Concern)」이다.

-       OOP를 더욱 OOP답게 만들어 준다.

 

OOP

OOP에서는 황단관점 분리를 위해 공통기능들을 하나의 클래스라는 단위로 모으고 그것들을 모듈로부터 분리함으로써 재사용성과 보수성을 높인다

각 모듈로부터 공통기능으로 분리하는 것으로 성공했지만 그 기능을 사용하기 위해 공통 기능을 호출하는 코드까지는 각 모듈로부터 분리할 수 없다. 그렇기 때문에 분리한 공통 기능을 이용하기 위한 코드가 각 모듈에 횡단으로 산재하게 된다.

 

AOP

AOP에서는 핵심 로직을 구현한 코드에서 공통 기능을 직접적으로 호출하지 않는다.

AOP에서는 분리한 공통 기능의 호출까지도 관점으로 다룬다. 그리고 이러한 각 모듈로 산재한 관점을 횡단 관점이라 부르고 있다.

AOP에서는 이러한 횡단 관점까지 분리함으로써 각 모듈로부터 관점에 관한 코드를 완전히 제거하는 것을 목표로 한다.

AOP에서는 핵심 로직을 구현한 코드를 컴파일 하거나, 컴파일된 클래스를 로딩하거나 또는 로딩한 클래스의 객체를 생성할때 핵심 로직 구현 코드 안에 공통기능이 삽입된다.

-       Advice - Joinpoint에 삽입되어져 동작할 수 있는 코드.

-       Joinpoint - 「클래스의 인스턴스 생성 시점」, 「메소드 호출 시점」및 「예외 발생 시점」과 같이 애플리케이션을 실행할 때 특정 작업이 시작되는 시점으로 Advice를 적용 가능한 지점.

-       Pointcut - 여러 개의 Joinpoint를 하나로 결합한(묶은).

-       Advisor - Advice Pointcut를 하나로 묶어 취급한 것.

-       Weaving - Advice를 핵심 로직 코드에 삽입하는 것.

-       Target - 핵심 로직을 구현하는 클래스.

-       Aspect - 여러 객체에 공통으로 적용되는 공통 관점 사항.

 

스프링 API를 이용한 AOP

스프링에서 AOP를 구현하는 과정은 다음과 같다.

1.     Advice 클래스를 작성한다.

2.     설정 파일에 Pointcut을 설정한다.

3.     설정 파일에 Advice Pointcut을 묶어 놓는 Advisor를 설정한다.

4.     설정 파일에 ProxyFactoryBean 클래스를 이용하여 대상 객체에 Advisor를 적용한다.

5.     getBean( ) 메소드로 빈 객체(프록시 객체)를 가져와 사용한다.

 

 

Advice 작성

스프링 AOP는 메소드 호출 관련 Advice만 제공하며 이들 Advice는 인터페이스형태로 제공된다.

인터페이스

설명

MethodBeforeAdvice

대상 객체의 메소드를 실행하기 전에 공통기능을 실행할때 사용되는 Advice

AfterReturningAdvice

대상 객체의 메소드 실행 이후에 공통기능을 실행할때 사용되는 Advice.

ThrowsAdvice

대상 객체의 메소드가 실행하는 도중 예외가 발생할 경우 공통기능을 실행할 때 사용되는 Advice.

MethodInterceptor

위 세가지를 하나로 묶은 Advice로 메소드 실행 전, , 예외 발생시 공통 기능을 실행할 수 있다.

 

 

MethodInterceptorm로 구현하는 방법

 

PerformanceCheckAdvice.java

public class PerformanceCheckAdvice implements MethodInterceptor {

 

        public Object invoke(MethodInvocation invocation) throws Throwable {

              

               String methodName = invocation.getMethod().getName();

               long start = System.nanoTime();

               System.out.println("[Log]Method Before --> " + methodName + "time check start");

               Object o = null;

               try {

                       //대상 객체의 메소드를 실행하기 전에 사용되는 코드

                       o = invocation.proceed();

                       //대상 객체의 메소드 실행 이후에 사용되는 코드

               } catch (Exception e) {

                       e.printStackTrace();

                       System.out.println("[Log]Method Error --> " + methodName );

               }

               long end =  System.nanoTime();

               System.out.println("[Log]Method After --> " + methodName + "processing time is "+(end-start)+"ns");         

              

               return o;

        }

 

}

 

GreetingServiceImpl.java

public class GreetingServiceImpl implements GreetingService {

 

        private String greeting;

       

        public GreetingServiceImpl(String greeting) {

               this.greeting = greeting;

        }

        public void sayHello(String name) {

               System.out.println(greeting + "\t" + name);

        }

}

 

applicationContext.xml

<bean id="greetingTarget" class="myspring.aop.GreetingServiceImpl">

        <constructor-arg>

               <value>Hello</value>

        </constructor-arg>

</bean>

 

<!-- 1. Advice 빈으로 등록 -->

<bean id="performanceAdvice" class="myspring.aop.PerformanceCheckAdvice" />

 

<!-- 2. pointcut 빈으로 등록 -->

<bean id="helloPointCut"

        class="org.springframework.aop.aspectj.AspectJExpressionPointcut">

        <property name="expression">

               <value>execution(public void say*(..))</value>

        </property>

</bean>

 

<!-- 3. Advisor 설정 -->

<bean id="helloAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">

        <property name="advice">

               <ref bean="performanceAdvice" />

        </property>

        <property name="pointcut">

               <ref bean="helloPointCut" />

        </property>

</bean>

 

<!-- 4. target객체에 위빙 -->

<bean id="greeting" class="org.springframework.aop.framework.ProxyFactoryBean">

        <property name="target">

               <ref bean="greetingTarget" />

        </property>

        <property name="interceptorNames">

               <list>

                       <value>helloAdvisor</value>

               </list>

        </property>

</bean>

 

 

참고자료

-       객체지향을 넘어 관점지향으로 AOP - ZDNET

 



[1] 핵심관심사항(core concern)

-       핵심로직, 핵심 비즈니스 로직(계좌이체, 이자계산, 대출처리 등)

[2] 공통관심사항(cress-cutting concern)

-       공통기능으로 어플리케이션 전반에 걸쳐 필요한 기능(로깅, 트랜잭션, 보안 등)

Posted by 피곤키오
,

라이프 사이클

-       스프링 컨테이너에 저장되는 빈 객체는 최소한 생성,초기화, 소멸의 라이프 사이클을 갖게 된다.

-       스프링은 빈 객체의 생성, 초기화, 소멸뿐만 아니라 추가적인 단계를 제공하고 있으며 이를 통해 라이프 사이클에 따른 빈 객체의 상태를 정교하게 제어할 수 있다.

-       빈 객체의 라이프 사이클은 빈 클래스가 구현한 인터페이스와 관리되는 컨테이너에 따라 달라진다.

 

 

1.     빈의 인스턴스화(생성자 호출)

2.     필드값 설정

3.     setBeanName() 메소드 호출(BeanNameAware 인터페이스를 구현하고 있을 경우)

4.     setBeanFactory() 메소드 호출(BeanFactoryAware 인터페이스를 구현하고 있을 경우)

5.     BeanPostProcessorpostProcessBeforeInitialization() 메소드 호출 ( BeanFacotry BeanPostProcessor 클래스가 관련되어 있을 경우 )

6.     afterPropertiesSet() 메소드 호출(InitializingBean 인터페이스를 구현하고 있을 경우)

7.     Custom 초기화 메소드 호출(Custom 초기화 메소드가 정의되어 있을 경우)

8.     BeanPostProcessor postProcessAfterInitialization() 메소드 호출 (BeanFactory BeanPostProcessor 클래스가 관련되어 있을 경우)

9.     빈 사용

 

그리고 컨테이너가 종료할 때에는 다음 순서로 메소드가 호출된다.

1.     destroy() 메소드 호출 (DisposableBean 인터페이스를 구현하고 있을 경우)

2.     Custom Destroy 메소드 호출

 

BeanFactory일 때의 setBeanFactory() 단계 이후에 다음과 같은 4단계의 라이프 사이클을 추가로 수행한다.

1.     setResourceLoader() 메소드 호출 (ResourceLoaderAware 인터페이스를 구현하고 있을 경우)

2.     setApplicationEventPublisher() 메소드 호출 (ApplicationEventPublisherAware 인터페이스를 구현하고 있을 경우)

3.     setMessageSource() 메소드 호출(MessageSourceAware 인터페이스를 구현하고 있을 경우)

4.     setApplicationContext() 메소드 호출 (ApplicationContextAware 인터페이스를 구현하고 있을 경우)

 

Posted by 피곤키오
,

제어의 역행 / 의존성 주입(Inversion of Control / Dependency Injection)

-       스프링 프레임워크가 지원하는 핵심 기능 하나이다.

-       객체 사이의 의존관계가 객체 자신이 아닌 외부에 의해 설정된다는 개념이다.

-       컨테이너는 어떤 객체(A) 필요로 하는 의존관계에 있는 다른 객체(B) 직접 생성하여 어떤 객체(A) 주입(설정) 해주는 역할을 담당하게 된다.

 

스프링은 객체간의 의존성을 설정 파일로 쉽게 관리한다. 의존성 삽입은 크게 두가지 종류가 존재한다.

 

Setter Ijcection

-       bean setter 메소드를 이용한 의존관계 설정 방식이다. Spring은 많은 수의 생성자 인자를 다루기 어렵고, 몇몇 프라퍼티가 선택사항일 때 까다롭기 때문에 대개 setter 기반 의존성 삽입의 사용을 권장한다.

 

Constructor Injection

-       생성자를 이용한 의존관계 설정 방식이다.

 

 

DI Constructor Injection

의존하는 객체를 생성자를 통해서 전달 받는 방법.

1.     의존하는 객체를 전달받을 생성자를 작성한다.

2.     설정 파일에 <constructor- arg> 태그를 이용한다.

-        객체인 경우 <ref> 태그 이용

-        문자열이나 기본 데이터 타입이라면 <value> 태그 이용

 

GreetingServiceImpl.java

public class GreetingServiceImpl implements GreetingService {

 

           private String greeting;

           private int loopCount;

          

           public GreetingServiceImpl(String greeting, int loopCount) {

                     this.greeting = greeting;

                     this.loopCount =loopCount;

           }

 

}

 

applicationContext.xml

<bean id="greeting1" class="myspring.sample1.hello.GreetingServiceImpl">

           <constructor-arg index="0">

                     <value>Hello</value>

           </constructor-arg>

           <constructor-arg-arg index="1" type="int" value="3"/>

</bean>

 

-       생성자로 전달할 객체나 값이 여러개인 경우에는 <consturctor- arg>태그 반복하여 선언한다.

-       이때 선언한 순서대로 생성자의 매개변수로 전달된다.

-       순서를 명시하려면 index 속성을 이용한다. index 0부터 시작한다.

-       type을 명시하려면 type 속성을 이용한다.

 

 

DI – Setter Injection

setXxx() 형태의 설정 메소드를 통해서 전달받는 방법으로 프로퍼티 설정 방식이라고도 한다.

1.     의존하는 객체를 전달받을 setter 메소드를 작성한다.

2.     설정파일에 <property>태그를 이용한다.

-       객체인 경우 <ref>태그 이용

-       문자열이나 기본데이터 타입이라면 <value>태그 이용

-       생성자 오버로딩 시 기본 생성자가 반드시 필요하다.

 

GreetingServiceImpl2.java

public class GreetingServiceImpl2 implements GreetingService {

 

           private String greeting;

          

           public void setGreeting(String greeting) {

                     this.greeting = greeting;

           }

          

}

 

applicationContext.xml

<bean id="greeting2" class="myspring.sample1.hello.GreetingServiceImpl2">

           <property name="greeting">

                     <value>Hi</value>

           </property>

</bean>

 

 

DI – 컬렉션 타입의 프로퍼티 설정

태그

컬렉션 타입

<list>

java.util.List 배열

<set>

java.util.Set

<map>

java.util.Map

<properties>

java.util.Properties

 

-       컬렉션 원소가 객체인 경우 <ref> 태그 이용

-       컬렉션 원소가 기본타입인 경우 <value> 태그 이용

n  type 속성 이용하여 타입 지정 가능하다.

n  제네릭을 이용하면 타입 지정하지 않아도 된다.

 

GreetingServiceImpl5.java

public class GreetingServiceImpl5 implements GreetingService {

 

        private List greetingList;

       

        public void setGreetingList(List greetingList) {

               this.greetingList = greetingList;

        }

 

}

 

applicationContext.xml

<bean id="greeting5" class="myspring.sample1.hello.GreetingServiceImpl5">

        <property name="greetingList">

               <list>

                       <value>Hello</value>

                       <value>안녕</value>

                       <value>Hi</value>

               </list>

        </property>

</bean>

 

 

DI – 의존관계 자동 설정

-       의존하는 빈객체의 타입이나 이름을 이용하여 의존객체를 자동으로 설정할 있는 기능으로 4가지 방식 제공한다.

-       autowire속성을 이용한다.

-       자동설정과 직접설정의 혼합도 가능하다.

-        

방식

설명

byName

프로퍼티의 이름과 같은 이름을 갖는 빈객체를 설정한다.

byType

프로퍼티의 타입과 같은 타입을 갖는 객체를 설정한다.

constructor

생성자 파라미터 타입과 같은 타입을 갖는 객체를 생성자에 전달한다.

autodetect

Constructor 방식을 먼저 적용하고, 적용할 없을 경우 byType방식을 적용하여 빈객체를 설정한다.

 

GreetingServiceImpl3.java

public class GreetingServiceImpl3 implements GreetingService {

 

        private OutputService outputer;

       

        public void setOutputer(OutputService outputer) {

               this.outputer = outputer;

        }

       

}

 

applicationContext.xml

<bean id="greeting8" class="myspring.sample1.hello.GreetingServiceImpl3"

        autowire="byName" />

<bean id="outputer" class="myspring.sample1.hello.OutputServiceImplConsole" />

 

 

DI – 빈 객체 범위

-       기본적으로 컨테이너에 한 개의 빈 객체를 생성한다.

-       빈의 범위를 설정할 수 있는 방법을 제공한다.

-       scope 속성을 이용한다.

-        

방식

설명

singleton

컨테이너에 한 개의 빈 객체만 생성한다.(기본값)

prototype

빈을 요청할 때마다 빈 객체를 생성한다.

request HTTP

요청마다 빈 객체를 생성한다.(WebApplicationContext에서만 적용)

session HTTP

세션마다 빈 객체를 생성한다.(WebApplicationContext에서만 적용)

 

GreetingServiceImpl2.java

public class GreetingServiceImpl2 implements GreetingService {

 

        private String greeting;

       

        public void setGreeting(String greeting) {

               this.greeting = greeting;

        }

       

}

 

applicationContext.xml

<bean id="greeting2" class="myspring.sample1.hello.GreetingServiceImpl2"         scope="prototype">

        <property name="greeting">

               <value>Hi</value>

        </property>

</bean>

 

 

BeanFactory

빈 객체를 관리하고 각 빈 객체 간의 의존 관계를 설정해 주는 가장 단순한 컨테이너

인스턴스를 생성하고 설정하고 많은 수의 bean을 관리하는 실질적인 컨테이너이다. bean들은 일반적으로  서로 협력하고 그들 사이의 의존성을 가진다. 그 의존성은 BeanFactory에 의해 사용된 설정 데이터에 반영된다.

-       대표적인 구현 클래스

n  XmlBeanFactory

 

BeanFactory를 사용하여 객체 가져오기

Resource res = new ClassPathResource("applicationContext.xml");

BeanFactory factory = new XmlBeanFactory(res);

 

GreetingService bean = (GreetingService) factory.getBean("greeting");

 

 

ApplicationContext

-       BeanFactory 인터페이스를 상속받은 하위 인터페이스

-       BeanFactory의 빈관리 기능 이외에 여러 개의 편리한 기능이 추가되었다.

n  MessageSource, i18n 스타일로 메시지에 대한 접근을 제공

n  자원에 대한 접근, URL이나 파일과 같은 형태

n  ApplicationListener 인터페이스를 구현하는 bean을 위한 이벤트 처리(propagation)

n  리소스로의 액세스 수단 간편화

n  다중(구조적) 컨텍스트이 로딩(애플리케이션의 웹 레이어처럼, 각각을 하나의 특정 레이어에 집중 될수 있도록 허용)

-       대표적인 구현 클래스

n  ClassPathXmlApplicationContext

 

ApplicationContext를 사용하여 객체 가져오기

ApplicationContext factory =

        new ClassPathXmlApplicationContext("applicationContext.xml");

 

GreetingService bean = (GreetingService) factory.getBean("greeting");

 

'개발자 센터 > Spring' 카테고리의 다른 글

Spring에서 iBatis 연동하기  (7) 2009.12.13
AOP : Aspect Oriented Programming  (0) 2009.12.13
빈 라이프 사이클  (0) 2009.12.13
Spring Framework의 개요  (0) 2009.12.13
Spring Framework 특징  (0) 2009.12.12
Posted by 피곤키오
,

Spring Framework의 개요

Spring 7개의 잘 정의된 모듈들로 구성되며 전체적으로 이들 모듈은 엔터프라이즈 애플리케이션 개발에 필요한 모든 것을 제공한다. 이는 애플리케이션이 완전히 Spring 프레임워크를 기반으로 해야 하는 것은 아니다. , 애플리케이션에 적합한 모듈을 선택하여 적용하고 나머지 모듈들은 무시해도 된다. Spring 모듈은 모두 핵심 컨테이너 위에 구축되어있다. 핵심 컨테이너는 Bean 생성, 설정, 관리하는 방법을 정의하는데 이는 곧 Spring의 근본적인 기능이다.

 

 

 

Spring Core

-       Spring 프레임워크의 핵심 기능을 제공한다.

-       코어 컨테이너의 주요 컴포넌트는 Bean-Factory(Factory 패턴의 구현)이다. BeanFactory Inversion of Control (IOC) 패턴을 사용하여 애플리케이션의 설정 / 의존성 스팩을 실제 애플리케이션 코드에서 분리시킨다.

 

Spring Context

-       spring을 컨테이너로 만든 것이 핵심 모듈의 BeanFactory라면, Spring을 프레임워크로 만든 것은 컨텍스트 모듈이다. 이 모듈은 국제화된 메시지, 애플리케이션 생명주기 이벤트, 유효성 검증 등을 지원함으로써 BeanFactory의 개념을 확장한다.
이 모듈은 이메일, JNDI 접근, EJB 연계, 리모팅, 스케쥴링 등과 같은 다수의 엔터프라이즈 서비스를 추가로 제공한다. 또한 템플릿 프레임워크와의 통합도 지원한다.

 

Spring DAO

-       Spring JDBC DAO 추상 레이어는 다른 데이터베이스 벤더들의 예외 핸들링과 오류 메시지를 관리하는 중요한 예외 계층을 제공한다. 이 예외 계층은 오류 핸들링을 간소화하고, 예외 코드의 양도 줄여준다. Spring DAO JDBC 예외는 일반 DAO 예외 계층에 순응한다.

 

Spring ORM

-       선언적 트랜잭션 관리와 같은 Spring이 제공하는 다른 모든 기능을 사용해서 혼합하여 모든 O/R매퍼를 사용 할수 있다.

-       프레임워크는 여러 ORM 프레임워크에 플러그인 되어, Object Relational (JDO, Hibernate, iBatis SQL Map)을 제공한다. 이 모든 것은 Spring의 일반 트랜잭션과 DAO 예외 계층에 순응한다.

 

Spring AOP

-       AOP 제휴 호환 aspect-지향 프로그래밍 구현물을 제공 (코드를 명백하게 분리하기 위한 메소드-인터셉터와 pointcut은 논리적으로 구별되어야 할 기능을 구현)

-       소스레벨 메타데이터 기능을 사용하여 .NET속성과 다소 비슷한 모든 종류의 행위적 정보를 코드와 결합

-       설정 관리 기능을 통해 aspect 지향 프로그래밍 기능을 Spring 프레임워크와 직접 통합시킨다. 따라서 Spring 프레임워크에서 관리되는 모든 객체에서 AOP가 가능하다. Spring AOP 모듈은 Spring 기반 애플리케이션에서 객체에 트랜잭션 관리 서비스를 제공한다.

-       Spring AOP에서는 EJB 컴포넌트에 의존하지 않고도 선언적 트랜잭션 관리를 애플리케이션과 결합할 수 있다.

 

Spring Web

-       멀티 파티 기능, 서블릿 리스너를 사용한 컨텍스트 초기화 그리고 웹-기반 애플리케이션 컨텍스트와 같은 기본적인 웹-기반 통합 기능들을 제공한다.

-       웹 컨텍스트 모듈은 애플리케이션 컨텍스트 모듈의 상단에 구현되어, 웹 기반 애플리케이션에 컨텍스트를 제공한다. Spring 프레임워크는 Jakarta Struts와의 통합을 지원한다.

-       웹 모듈은 다중 요청을 핸들링하고, 요청 매개변수를 도메인 객체로 바인딩하는 작업을 수월하게 한다.

 

Spring Web MVC

-       웹 애플리케이션을 위한 Model-View-Controller 구현물을 제공 (도메인 모델 코드와 Web Form사이의 분명한 구분을 제공하고 유효성 체크와 같은 Spring 프레임워크의 다른 모든 기능을 사용하도록 허용한다.)

 

MVC 프레임워크는 완전한 기능을 갖춘 MVC 구현이다. MVC 프레임워크는 전략 인터페이스를 통해 설정할 수 있으며, JSP, Velocity, Tiles, iText, POI 같은 다양한 뷰 기술을 허용한다.

Posted by 피곤키오
,