제목 그대로다

 PreparedStatement 에서 LIKE 를 쓰고 싶을때, '?' 와 '%' 합치는 법

출처 :  http://mainia.tistory.com/557 녹두장군님



웹 어플리케이션 작업시 sql 쿼리를 보통 properties xml 로 문자열을 만들어 빼놓는다.

예전처럼 무식하게 쿼리를 문자열 합치기로 소스상에서 사용하지는 않는다여러가지 문제가

많기 때문이다.

 

쿼리에서 동적으로 변하는 데이타는 ? 를 써서 문자열을 만들고 PreparedStatement 사용해

파라미터 값을 맵핑시킨다아래는 스프링의 예이다스프링은 framework 내부에 PreparedStatement

사용해서 맵핑을 구현해 놓았고 우리가 사용하는 부분은 아래와 같이 함수에 파라미터로

갯수에 맞게 Objec 배열로 넘기면 된다.

 

쿼리문자열

test.srch.select = \n\

           select * from board where id = ? \n\

 

String sql = message.getMessage("test.srch.select");

getJdbcTemplate().queryForList(sql, new String[]{srchText});

 

일반적인 쿼리문자열은 이렇게 적용을 하면 되는데 문제는 like 를 사용할때 이다.

보통 사용하는 것처럼 like %?% 로 하면 안된다. % 문자와 ? 맵핑할 문자를 합쳐야 된다.

RDBMS 프로그램에 따라 차이가 있다. Oracle 일때에는  like '%' || ? || '%' 사용하면 된다.

중간에 ORM framework 인 iBatis 를 사용할 경우에는 like '%' || #?# || '%' 이다.

 

 

하지만 MySql 일 경우에는 이것이 통하지 않는다그래서 문자열을 합치는 함수를 사용해

해결하였다일반적인 경우는 like concat ('%', ?, '%') 이며 

iBatis 
라고 한다면

like concat ('%', #?#, '%') 사용해서 처리 하면된다.



 
YOUR COMMENT IS THE CRITICAL SUCCESS FACTOR FOR THE QUALITY OF BLOG POST



앞서 아이바티스를 사용해서 DAO 처리리를 하기위해서 설정을 하는데 시간이 좀 걸렸다. 

하지만 초기에 이렇게 작업을 해놓으면 유지보수에 있어서는 정말 편리할 것이다.

처음에 개발을 빨리 편하게 할려면 이 짓을 할필요가 없지만 

나중에 유지보수에 들어가는 시간과 노력과 짜증을 생각하면 할만하다




이제 실제로 DAO 처리를 함 해보자.... Data Access Object


앞에서 아이바티스가 로드되는 순간은 DAOManager 를 얻어서 구현클래스를 얻는 것이라 했다.

IBATISDAO iBATIS = (IBATISDAO)DAOService.getDao(IBATISDAO.class);


iBATIS 는 IBATISDAO 를 구현한 클래스이다.

(스트럿츠에 대한 설명은 안합니다. DAO 부분만)
일단은 제일 간단하면서 이것 저것 다 해볼 수 있는 게시판 리스트를 만드는 뷰를 처리해보자.




ListAction.java

ArrayList<ItemBean> array = null;

IBATISDAO iBATIS = (IBATISDAO)DAOService.getDao(IBATISDAO.class);

array = iBATIS.getArticles(index  * 10);

액션 클래스에서 구현클래스를 얻고, 그클래스안의 모든글을 다 가져오는 getAtricles() 메소드를 이용해서

모든글을 자바빈인 ItemBean 에 담아서 그놈을 arraylist 에 담아서 리턴받는다.





IBATISDAOimpl.java  ( 구현 클래스 )
public class IBATISDAOimpl extends SqlMapDaoTemplate implements IBATISDAO {

	
	public IBATISDAOimpl(DaoManager daoManager) {
		super(daoManager);
		
	}

	@Override
	public ArrayList getArticles(int index) {
		
		return (ArrayList)queryForList("Allarticles", new Integer(index));
	}

	
코드를 보면 12라인에 queryForList() 메소드가 보인다. ( 구현 클래스에서는 리턴에 대한 작업만 최소한으로 코딩한다)

저 메소드는 SELECT 쿼리를 이용할 때 사용하는 메소드인데, 몇가지가 더있다. 

케이스메소드명
결과세트를 반환할 때 queryForList(), queryForObject()
한개 이상의 결과 객체를 반환할 때(여러개 레코드를 묶어서 받을때 편함) queryForList()
한개의 결과 객체를 반환할 때, 여러개의 레코드가 반환될 때는 에러발생시킴 queryforObject()
결과세트를 반환하지 않거나 OUT 파라미터에 결과세트를 셋팅하지 않을 때 update()


코드 12라인에 보면

첫 번째 파라미터는 "Allarticles" 라고 이름을 지정하고, 2번째는 정수를 파라미터로 주고있다.


이것이 바로 sqlMap 에 정의해놓은 쿼리문들을 부르는 방법이다.

 
쿼리문에 정의되어있는 아이디값과 매핑되는 쿼리문을 실행시키면서 파라미터로 2번째 인자인 정수를 가지고 가는 것이다. 





그럼 이제 매핑되는 sqlMap 파일안의 쿼리를 살펴보자.

Margo.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/dao-2.dtd">

<sqlMap>
<!-- Struts_BBS.ItemBean 클래스를 alias 속성을 줘서 artcle 이라는 이름으로 쓰겠다고 정의 -->
<typeAlias alias="article" type="Struts_BBS.ItemBean"/>

<select          id="Allarticles"         parameterClass="int"            resultMap="getContent">
SELECT * FROM 
(SELECT ROWNUM rnum,A.*  
FROM 
(SELECT * FROM margo ORDER BY article_num DESC) A ) 
WHERE rnum BETWEEN #index#+1 AND #index# + 10
 
</select>

 select 문에 해당하는 id 가 자바코드에서 부른것과 동일한 엘리먼트로 parameterClass="int"  값을 가지면서
온다. 이 값을 이용하는 부분은 이 게시물 아래 다른글에 보면 있다. (http://winmargo.tistory.com/104)


↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

SELECT 엘리먼트 속성을 보면 id, parameterClass, resultMap 이렇게 되어있는데

id 는 얘기했듯이  자바코드에서 insert(), queryForList(), delete(), update() 등 여러 DB관련 메소드에서

부를때 키값으로 매핑되는 이름이다. 이 메소드들은 SqlMapDaoTemplate 를 상속 받았기 때문에

쓸 수 있는 것이다. 



parameterClass 는 인자로 달고오는 값이고, 이 값들은 프리미티브 타입일수도있고, 클래스일수있다.

resultMap 은 resultClass 와 두가지중 하나를 쓸수 있는데

resultMap 은 자바빈과 DB Column 명을 미리 매핑시켜놓고, 자동으로 불러쓸수있는 방법이다.

이방법에 대해서 자세한건 ( http://winmargo.tistory.com/107 )


동작되는 방식은 queryForList() 이기 때문에 자동으로 ArrayList 로 반환한다.

실렉트 쿼리로인해서 다수의  레코드들이 튀어나올것이다.

이렇게 나온 코드를 resultMap 이나, resultClass 가 받아서  클래스들을 ArrayList 에 넣어서 

result 값을 만들어준다.



저 위에 코드 12라인을 보면 return (ArrayList<ItemBean>)queryForList("Allarticles", new Integer(index));

ArrayList 를 리턴하고 있다. 


여기까지가 DaoManager 를 이용해서 구현클래스를 얻은다음 , 수행메소드를 이용해서

DB에서 참조해서 가져올 값을 넘겨서, result 를 얻어오기 까지의 수행과정이다. 



IBATIS DAO 수행방법 간단요약


1. DAO Manager 로부터 DAO 처리 메소드들이 담긴 구현클래스를 얻어온다.


2. 메소드에서 sqlMap 에 정의된 쿼리문을 매핑되는 "ID"를 통해서 부른다.
      관련된 메소드 본체와     구현한 클래스(iBATISDAOimpl.java)를 는 아래에 따로 정리해놓았다.


3. sqlMap 에 정의된 쿼리에서 파라미터값을 이용해서 쿼리를 수행한 후
    리턴처리가 있으면 리턴값을 생성후에 리턴한다. 

 




Statement 처리를 위한 메소드 정리

SqlMapClient API를 통해 statement를 수행하기.
 
SqlMapClient는 이것에 관련된 모든 맵핑된 statement를 수행하기 위한 API를 제공한다. 그 메소드들은 다음과 같다. 

 
public int insert(String statementName, Object parameterObject)
throws SQLException
 
public int update(String statementName, Object parameterObject)
throws SQLException
 
public int delete(String statementName, Object parameterObject)
throws SQLException
 
public Object queryForObject(String statementName,Object parameterObject)
throws SQLException
 
public Object queryForObject(String statementName,Object parameterObject, Object resultObject)
throws SQLException

public List queryForList(String statementName, Object parameterObject)
throws SQLException
 
public List queryForList(String statementName, Object parameterObject,int skipResults, int maxResults)
throws SQLException
 
public List queryForList (String statementName,Object parameterObject, RowHandler rowHandler)
throws SQLException
 
public PaginatedList queryForPaginatedList(String statementName,Object parameterObject, int pageSize)
throws SQLException
 
public Map queryForMap (String statementName, Object parameterObject,String keyProperty)
throws SQLException
 
public Map queryForMap (String statementName, Object parameterObject,String keyProperty, String valueProperty)
throws SQLException




위 메소드들의 설명 

각각의 경우에 맵핑된 statement의 이름은 첫번째 파라미터로 넘겨진다. 이 이름은 위에서 서술된 <statement>요소의 
name속성에 대응된다. 추가적으로 파라미터객체는 옵션적으로 전달할수 있다. null파라미터객체는 만약 기대되는 파라미
터가 없다면 전달될수 있다. 행위의 남겨진 차이점은 아래에서 간단하게 설명된다. 

 
insert(), update(), delete() : 이 메소드들은 update statement를 위해 특별히 의미된다. 밑의 쿼리 메소드중에 하나를 사용
해서 update statement를 수행하는 것은 불가능하다. 어쨌든 이것은 애매한 의미이고 드라이버에 의존적이다. 
 
executeUpdate()의 경우에 statement는 간단하게 수행되고 영향을 받는 많은 수의 row가 반환된다. 
 
 
queryForObject() : executeQueryForObject()의 두가지 버전이 있다. 하나는 새롭게 할당된 객체를 반환하는 것이고 다른 
하나는 파라미터처럼 전달된 미리할당된 객체를 사용하는것이다. 후자의 경우 하나의 statement보다 많은 수에 의해 생성되
는 객체에 유용하다. 
 
queryForList() : queryForList()에는 세가지 버전이 있다. 첫번째는 쿼리를 실행하고 쿼리로부터 모든 결과를 반환하는것이
고 두번째는 스킵되는 결과물의 수(이를 테면 시작지점)를 지정할수 있고 반환되는 레코드의 최대갯수도 지정할수 있다. 이것
은 전체데이터를 반환하고 싶지 않은 굉장히 큰 데이터셋과 작업을 할 때 가치가 있다. 마지막으로 세번째는 row핸들러를 가
진다. 이 메소드는 대개의 칼럼과 rows보다 result객체를 사용해서 row에 의해 결과를 처리하도록 한다. 이 메소드는 전형적인 
이름과 파라미터 객체를 넘기지만 RowHandler를 가진다. row핸들러는 RowHandler인터페이스를 구현하는 클래스의 인스턴
스이다. RowHandler인터페이스는 다음처럼 오직 하나의 메소드만 가진다. 
public void handleRow (Object object, List list); 
이 메소드는 데이터베이스로부터 반환되는 각각의 row를 위한 RowHandler에서 호출될것이다. 이것은 쿼리결과를 처리하기 
위해 깔끔하고 간단하며 확장가능한 방법이다. RowHandler의 사용법 에제를 위해 아래 섹션의 예제를 보라. 리스트 파라미터
는 queryForList()메소드로부터 반환될 List인터페이스의 인스턴스이다. 당신은 리스트의 result객체의 아무것도, 몇몇 또는 모
든 것을 추가할수도 있다. 만약 당신이 100만개의 row로 작업을 한다면 리스트에 그것들을 모두 넣는 것은 좋은 생각이 아니
다. 
 
queryForPaginatedList(): 이것은 이전, 다음 버튼으로 데이터를 탐색할 때 데이터의 일부를 관리할수 있는 리스트를 반환
하는 매우 유용한 메소드이다. 이것은 쿼리로부터 반환된 레코드의 일부만을 표시하는 사용자 인터페이스를 구현하는데 유용
하다. 10,000개의 필드를 반환하는 검색엔진이 있지만 한번에 100개만 표시해야 하는 예제가 있다. PaginatedList인터페이스
는 페이지를 통한 탐색(nextPage(), previousPage(), gotoPage())과 페이지의 상태를 체크(isFirstPage(), isMiddlePage(), 
isLastPage(), isNextPageAvailable(), isPreviousPageAvailable(), getPageIndex(), getPageSize())하는 메소드를 포함한다. 유효
한 레코드의 총 개수가 PaginatedList인터페이스로부터 접근가능하지 않더라도 이것은 기대되는 결과갯수를 세는 두번째 
statement를 간단히 수행함으로써 쉽게 달성할 수 있을 것이다. 반면에 PaginatedList를 사용하면 지나친 부하가 발생할수도 
있다. 
 
queryForMap(): 이 메소드는 리스트로 결과의 collection을 로드하는 대안을 제공한다. 대신에 이것은 keyProperty처럼 전달
된 파라미터에 의해 결과를 키(key)화된 map으로 로드한다. 예를 들면 만약 Employee객체의 collection을 로드한다면 당신은 
그것들을 employeeNumber프라퍼티에 의해 키(key)화된 map으로 로드할것이다. Map의 값은 전체 employee객체가 될수도 
있고 valueProperty라고 불리는 두번째 파라미터내 정의된 employee개체로 부터의 다른 프라퍼티가 될수도 있다. 예를들면 당
신은 employee숫자에 의해 키(key)화된 employee이름의 map을 원할지도 모른다. result객체처럼 Map타입을 사용하는 개념을 
사용하는 메소드를 혼란스러워하지 마라. 이 메소드는 result객체가 자바빈즈나 Map인지에따라 사용될수 있다. 


출처 : IBATIS SQL Maps 개발자 가이드 (번역 : 이동국)

 


iBATISDAOimpl.java
package Struts_BBS;

import java.util.ArrayList;
import com.ibatis.dao.client.DaoManager;
import com.ibatis.dao.client.template.SqlMapDaoTemplate;

public class IBATISDAOimpl extends SqlMapDaoTemplate implements IBATISDAO {

	// 생성이되면서, 
	// 저렇게 설정해놓은 IBATISDAOimpl 클래스가 인자로 들어온다
	public IBATISDAOimpl(DaoManager daoManager) {
		super(daoManager);
		// TODO Auto-generated constructor stub
	}

	@Override
	public ArrayList getArticles(int index) {
		// TODO Auto-generated method stub
		return (ArrayList)queryForList("Allarticles", new Integer(index));
	}

	
	
	@Override
	public int getArticleCount(){
		
		return (Integer)this.queryForObject("getCount");
	}
	
	
	
	@Override
	public ItemBean getContent(int contentNum){
		
		return (ItemBean)this.queryForObject("getContent", new Integer(contentNum));
	}
	
		
	
	@Override
	public Object setContent(ItemBean itemBean){
		
		return (String)insert("setContentTest", itemBean);
	}
	
	
	
	@Override
	public int checkID(String id, String password){
	
		int state = 3;
		String tmp= null;
		
		tmp = (String)this.queryForObject("checkID", id);
			
		if(tmp == null) return state;
			
		if(tmp.equals(password)) {
			
			return 1;
			
		}else return 0;
		
	}
	
	
	
	@Override
	public int setReply(ItemBean itemBean){
			
		int firstNumber = itemBean.article_num - 1;
		
		itemBean.setArticle_num(firstNumber);
			
		//글 번호 정리
		this.update("reply_articleNumControl", firstNumber);
		
		//댓글 입력
		this.insert("setReply", itemBean);
				
		return 0;
	}
	
	
	
	@Override
	public int deleteContent(int number, String password){
		
		int state = 3;
		
		String tmp = (String)this.queryForObject("checkPASS", new Integer(number));
		
		
		if(tmp == null) return state;
		
		
		if(tmp.equals(password)) {
			
			this.delete("deleteContent", new Integer(number));
			
			return 1;
			
		}else return 0;
		
	}
	
	
	
	@Override
	public int editContent(ItemBean itemBean){
		
		this.update("editContent", itemBean);
		
		return 0;
	}
	
}





YOUR COMMENT IS THE CRITICAL SUCCESS FACTOR FOR THE QUALITY OF BLOG POST



DB에 값 을 넣을때 INSERT INTO 를 이용해서 값을 넣을때

sqlMap 에서 넣을려면 ,

<insert id="insertBbs" parameterClass="bbsvo">
<selectKey keyProperty="idx" resultClass="int">
SELECT BBS_NEW_IDX.NEXTVAL as idx FROM DUAL
</selectKey>
INSERT INTO BBS 
(idx, subject, writer, writedate, content, s_file, pwd, readHit, ip,re_level)
VALUES 
(#idx#, #subject#, #writer#, sysdate, #content#, #s_file#, #pwd#, #readHit#, #ip#,#re_level#)
</insert>


위 처럼 이런 코드가 있다고 치면, 지저분하지 않은가?

이런 작업이 많아지면? 추가해야되면? 유지보수가 지랄같아진다.


이런걸 편하게 하기위해서 준비된게 있다!!            

<parameterMap>

바로 코드를 보자.

<typeAlias alias="article" type="Struts_BBS.ItemBean"/>

<parameterMap id="setContent" class="article">

    <parameter property="id"             jdbcType="VARCHAR"/>    // 1

<parameter property="title"           jdbcType="VARCHAR"/>   //  2
<parameter property="body"         jdbcType="VARCHAR"/>      ....
<parameter property="password"  jdbcType="VARCHAR"/>
<parameter property="count"        jdbcType="NUMBER"/>
<parameter property="depth"        jdbcType="NUMBER"/>
<parameter property="fileName"   jdbcType="VARCHAR"/>  // 7
</parameterMap>


딱 봐도 그냥 알 것 같지 않은가?

프로퍼티는 자바빈의 변수명이고, jdbcType 은 컬럼타입이다.

저런식으로 해주면 "setContent"를 parameterMap 에서 부르면 알아서 매핑되어서 순서대로 

자바코드에서 preparedStatement 를 이용해서 .setString(index, "value") 하듯이

자기가 알아서 파라미터가 코딩된 순서대로 그냥 들어간다. 



그럼 어떻게 불러쓰는지 함보자.

<insert id="setContent" parameterMap="setContent" >
INSERT INTO margo 
VALUES(margo_sequence.NEXTVAL,?,?,?,?,?,?,sysdate, ?)
</insert>


맨위에 그 지저분한 코드가 이렇게 변했다...

VALUES 안에 '?' 들에 차례로 프로퍼티 값이 들어가서 글이 써지게 되겠음.



머 호출 코드는 그냥
(ItemBean)this.queryForObject("getContent", new Integer(contentNum)); 












 
YOUR COMMENT IS THE CRITICAL SUCCESS FACTOR FOR THE QUALITY OF BLOG POST



DB 에서 자료 긁어올 때, 매핑시킬 자바빈 변수명과, 디비의 컬럼명이 다른경우가 종종 있다.

이럴때는 resulMap 을 이용하여 정의해주면 된다. 

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

<sqlMap>
<!-- Struts_BBS.ItemBean 클래스를 alias 속성을 줘서 artcle 이라는 이름으로 쓰겠다고 정의 -->
<typeAlias alias="article" type="Struts_BBS.ItemBean"/>
<resultMap id="getContent" class="article">                                                                                     바빈


    <result property="article_num" column="article_num"/>

<result property="id" column="id"/>

<result property="title" column="title"/>
<result property="body" column="body"/>


<result property="password" column="password"/>
<result property="count" column="hit"/>
<result property="depth" column="depth"/>
<result property="fileName" column="fileName"/>
<result property="write_date" column="write_date"/>
</resultMap>
 
</sqlMap>

컬럼명은 "hit" 이고, 자바빈의 변수명은 "count" 이다.



그렇기 때문에 그냥 클래스를 지정하고 resultClass="article" 이라고 해버리면 

자바빈의 count 변수에 hit 이 당연히 매핑되지 않고, 기본값인 '0' 이 들어간다.


그렇기 때문에 저런식으로 디비컬럼명과, 자바빈 변수명을 매핑시켜주는 작업을 해주면 

편하다.

resultMap 속성에  위에서 지정한 resultMap 아이디를 쓰면 그놈이 리절트값이 된다.

<select id="Allarticles" parameterClass="int" resultMap="getContent">

<include refid="select-all-index"/>

WHERE rnum BETWEEN #index#+1 AND #index# + 10

 

</select>





YOUR COMMENT IS THE CRITICAL SUCCESS FACTOR FOR THE QUALITY OF BLOG POST




DAOManager 에서 생성된 실제 DAO 작업을 처리할 클래스의 메소드가 실행되면

그에 해당하는 SQL 이 정의되어 있는 XML 파일이 실행될 텐데

실행시키면서 인자값을 던져 줄 수있다. 클래스가 됐던, 기본형식 이 됐던.





* 아래파일은 실제 수행하는 클래스이다.*
package Struts_BBS;



import java.util.ArrayList;

import com.ibatis.dao.client.DaoManager;
import com.ibatis.dao.client.template.SqlMapDaoTemplate;

public class IBATISDAOimpl extends SqlMapDaoTemplate implements IBATISDAO {

	// 생성이되면서, 
	// 저렇게 설정해놓은 IBATISDAOimpl 클래스가 인자로 들어온다??????
	public IBATISDAOimpl(DaoManager daoManager) {
		super(daoManager);
		// TODO Auto-generated constructor stub
	}

	@Override
	public ArrayList getArticles(int index) {
		// TODO Auto-generated method stub
		return (ArrayList)queryForList("Allarticles", index);
	}

}

위 코드에서 처럼 


queryForList("Allarticles", index);


xml 의 ID 와 일치되는 스트링값과, 그 인자로 int 값을 넘겨주고있다.

이제 xml 파일에서 저 인자값을 빼다쓰는 작업을 해보자.  






*아래는 SQL 작업을 수행하는 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/dao-2.dtd">


<sqlMap>

<!-- Struts_BBS.ItemBean 클래스를 alias 속성을 줘서 artcle 이라는 이름으로 쓰겠다고 정의 -->

<typeAlias alias="article" type="Struts_BBS.ItemBean"/>

<!-- 긴 sql 문이나 자주쓰는 sql문장을 sql 엘리먼트를 이용해서 정의 -->

<sql id="select-all-index">

SELECT * FROM 

(SELECT ROWNUM rnum,A.*  

FROM 

(SELECT * FROM margo ORDER BY article_num DESC) A ) 

</sql>

<select id="Allarticles" parameterClass="int" resultClass="article">

  <include refid="select-all-index"/>

WHERE rnum BETWEEN #index# + 1 AND  #index# + 10

 

</select>


</sqlMap>


위코드에서 parameterClass="int" 처럼 파라미터를 int 형으로 받겠다고 선언하면 int 가 되고 

저기에 클래스를 쓴다면 클래스가 된다. 일단은 int 형으로 받을때는 #index#
형식으로 써주면 넘어온 값을 쓸 수있다. (index 로 넘겼다고, index 로 쓰지않아도 되더라..#a# 해도 값이 나오긴 마찬가지임, 단 기본타입만 되는듯)


클래스를 쓰는 방법은 



만약 맵을 넘긴다고 치자.

map.put("a", 1);

    map.put("b", 20);


그러면 저걸 xml 에서 뽑아 쓸때는 

parameterClass 에는 map 클래스를 써줘야한다. 

parameterClass="java.util.Map"

 이렇게 설정해주고 쓸때는 맵에 KEY 값을 지정한 이름을 #KeyName# 하면 값이 뽑힌다.

<select id="Allarticles" parameterClass="java.util.Map" resultClass="article">
<include refid="select-all-index"/>
 
WHERE rnum BETWEEN #a# AND  #b#
 
</select>




















YOUR COMMENT IS THE CRITICAL SUCCESS FACTOR FOR THE QUALITY OF BLOG POST