1. 동적인 SQL 사용하기
1.1. 동적인 WHERE 조건절 다루기
다음 예제에서 장바구니 어플리케이션의 Category 테이블을 조회한다. parentCategoryId 칼럼은 자기 자신을 참조하는 칼럼이다. 다시 말하면 parentCategoryId는 그림에서 보듯이 동일한 Category 테이블의 categoryId를 참조한다.
요구사항은 간단하다. 전달받은 Category객체의 parentCategoryId 프로퍼티가 null 이면 이것은 가장 상위 레벨의 카테고리를 모두 다 조회한다는 의미이다.
parentCategoryId 프로퍼티가 null이 아닌 숫자 값이라면 부모 category 의 자식 category 객체를 모두 조회한다는 것을 뜻한다. 부모 category란 parentCategoryId 의 값을 의미한다.
SQL 에서는 =기호를 null 값 비교에 사용할 수 있다. NULL값인지 여부는 IS 키워드로 확인해야 한다. 우리는 NULL인지 혹은 NULL이 아닌지를 비교하기 위해 동일한 SQL 구문을 사용하고자 한다. 이를 하나의 매핑 구문으로 처리하기 위해서 동적 SQL 을 사용할 것이다. 동적 SQL의 뼈대를 알아보기 위해 다음의 매핑 구문을 사용할 것이다.
…
…
동적 SQL의 뼈대는 언제나 부모 요소로 시작한다. 부모 요소는 동적 SQL 요소 중 어떤 것이라도 될 수 있다. 위 코드의 경우 부모 요소로
부모 요소의 몸체에는 간단한 SQL 문법이나 다른 동적 SQL 요소가 온다. 이 예제에서
이 시점에서, 언제 prepend 속성의 값이 다른 SQL 구문 앞에 출력하게 되는지 알고 있어야 한다.
동적 SQL의 이점 중 하나는 SQL 코드의 재사용성을 높여준다는 것이다. 이 예제에서 동적 SQL을 사용하지 않고서 위와 같은 기능을 구현하려면 두 개의 select 구문을 작성해야 했을 것이다. 게다가 DAO계층에서 category 객체의 parentCategoryId 값을 먼저 확인하고, 이 parentCategoryId값이 null인지 아닌지 여부에 따라 적당한 매핑 구문을 선택해서 호출해야 한다. 비록 이번 예제는 간단해서 동적 SQL이 없더라도 별로 불편할 게 없지만, 여러 서로 다른 프로퍼티의 조합으로 인해 SQL구문이 기하급수적으로 증가할 가능성이 있을 경우에는 동적 SQL의 진정한 가치가 분명하게 드러난다. 동적 SQL을 사용하여 매핑 구문을 재사용하도록 하고 정적인 SQL 구문을 여러 개 작성하는 것은 피하도록 한다.
이제 동적 SQL의 사용법과 강력함에 대해 이해했을 것이다. 모든 요소와 속성을 좀 더 상세히 알아보자.
1.2. 동적 요소들과 친숙해지기
iBATIS는 강력한 XML 요소들을 통해 동적 SQL문제를 처리한다. 이 요소들은 매핑 구문에 넘어온 파라미터 객체에 관한 다양한 조건들을 평가하는 역할을 한다. 존재하는 모든 요소들을 이해하고 그것들이 올바르게 SQL을 출력하는 데 어떤 역할을 하는지 이해하고 있어야 한다. 다음 절에서는 요소들을
모든 동적 요소는 prepend, open, close 속성을 포함한다. Open과 close속성은 각각의 요소에서 똑같이 작동한다. 그 두 개의 속성은 동적으로 생성된 결과 내용의 시작이나 끝에 무조건 값은 붙인다. Prepend 속성은
…
…
y=#y#
prepend="AND" open="(" close=")">
a=#x.a#
a=#x.b#
a=#x.c#
…
…
. (1)에서 시작
첫 번째 prepend 제거 기능은 모든 요소에서 자동으로 혹은 명시적 지정을 통해 사용할 수 있다.
공통 기능의 마지막 부분은 모든 요소를 서로 독립적으로 사용할 수 있다는 것이다. 이것은
그럼 이제부터 요소들의 카테고리 각각을 분석해보자.
8.2.1 요소
이제 요소에서 사용할 수 있는 속성들을 모두 살펴보았다. 다음은
…
…
여기서 동적인 SQL을 사용해서, parentCategoryId 프로퍼티를 검사하여 SQL을 생성하는 select 구문의 WHERE절을 구성하였다.
8.2.2 이항연산 요소
이항연산 요소는 파라미터 프로퍼티의 값을 다른 값이나 다른 파라미터 프로퍼티 값과 비교한다. 요소의 내용은 비교 결과가 true일 때 사용된다. 모든 이항연산 요소는 property, compareProperty, compareValue 속성을 공통으로 가지고 있다. Property 속성은 비교할 기본값을 제공한다. compareProperty 와 compareValue 는 비교 대상이 되는 값을 제공한다. compareProperty 속성에는 기본값에 대해 비교할 값을 가지고 있는 파라미터 객체의 프로퍼티를 명시한다. compareValue 에는 기본값에 대해 비교할 정적인 값을 명시한다. 요소의 이름은 값의 비교 방법을 의미한다. 요소의 속성은 표에서 볼 수 있다.
| Property (필수) | compareValue 나 compareProperty 와 비교하는데 사용할 파라미터 프로퍼티 |
| Prepend (선택적) | 이 값은 보통 요소 내용의 가장 앞에 출력된다. 다음의 경우에는 prepend의 값이 출력되지 않고 무시된다. (a) 요소의 결과 내용이 공백인 경우, (b)요소가 처음으로 내용을 만들어 냈고 removeFirstPrepend 속성이 true 인 요소에 내포되어 있는 경우, (c) |
| Open (선택적) | Open 값은 요소 내용에 접두사로 붙이기 위해 사용한다. Open 값은 요소의 결과 내용이 공백일 때는 접두사로 붙지 않는다. Open 값은 prepend 속성 값보다는 뒤에 붙는다. 예를 들어 prepend=”OR” 이고 open=”(“ 라면 조합된 결과는 “OR(“가 될 것이다. |
| Close(선택적) | Close 값은 요소 결과 내용의 뒤에 덧붙이는데 사용한다. 이 값은 요소 내용이 공백이라면 덧붙여지지 않을 것이다. |
| removeFirstPrepend( 선택적) | 이 값은 처음으로 내용을 출력하는 자식 요소의 prepend 속성 값을 출력할지 여부를 결정한다. |
| compareProperty(compareValue가 명시되지 않았다면 필수) | compareProperty는 property 속성에 의해 명명된 프로퍼티와 비교할 파라미터 객체의 프로퍼티를 명시한다. |
| compareValue(compareProperty가 명시되지 않았다면 필수) | Property 속성에 의해 명명된 프로퍼티와 비교할 정적인 비교값 |
모든 이항연산 동적 요소는 위의 속성들을 공유하고, 요소 이름 자체는 다음의 표에서 볼 수 있다.
| | Property 속성값이 compareProperty 값이나 compareValue 값과 같은지 검사 |
| | Property 속성값이 compareProperty 값이나 compareValue 값과 같지 않은지 검사 |
| | Property 속성값이 compareProperty 값이나 compareValue 값보다 큰지 검사 |
| | Property 속성값이 compareProperty 값이나 compareValue 값보다 크거나 같은지 검사 |
| | Property 속성값이 compareProperty 값이나 compareValue 값보다 작은지 검사 |
| | Property 속성값이 compareProperty 값이나 compareValue 값보다 작거나 같은지 검사 |
위 표는 좋은 참조가 되겠지만 예제가 충분치 않다. 아래에서 어떻게 이 요소들을 함께 사용하는지 보자.
…
…
위 에서 select 구문을 생성하고 사용할 shippingType 의 값이 100보다 작은 standard 타입인지, 100보다 큰 freight 타입인지 결정하기 위해 weight 프로퍼티를 검사한다.
8.2.3 단항연산 요소
단항연산 동적 요소는 빈즈 프로퍼티의 상태를 검사만하고 다른 값에 대한 비교는 수행하지 않는다. 상태의 결과가 true 라면 내용이 포함된다. 모든 단항연산 요소는 property 속성을 공유한다. Property 속성에는 상태를 검사할 파라미터 객체의 프로퍼티 이름을 지정해준다.요소의 이름은 어떤 상태를 검사할지를 의미한다. 표는 단항연산 요소의 속성을 보여준다.
| Property(필수) | 상태 비교를 위해 사용되는 파라미터의 변수명 |
| Prepend(필수) | 이 값은 요소 내용의 가장 앞에 출력된다. 다음의 경우에는 prepend 의 값이 출력되지 않고 무시된다. (a) 요소의 결과 내용이 공백인 경우, (b) 요소가 처음으로 내용을 만들어 냈고 removeFirstPrepend 속성이 true 인 요소에 내포되어 있는 경우, (c) |
| Open(선택적) | Open 값은 요소 내용에 접두사로 붙이기 위해 사용한다. Open 값은 요소의 결과 내용이 공백일 때는 접두사로 붙지 않는다. open값은 prepend 속성값보다는 뒤에 붙는다. 예를 들어, prepend=”OR” 이고 open=”(“ 라면 조합된 결과는 “OR (“가 될 것이다. |
| Close(선택적) | Close 값은 요소 결과 내용의 뒤에 덧붙이는데 사용한다. 이 값은 요소 내용이 공백이라면 덧붙여지지 않을 것이다. |
| removeFirstPrepend(선택적) | 이 값으로 처음으로 내용을 출력하는 자식 요소의 prepend 속성 값을 출력할지 여부를 결정한다. |
위 표의 모든 속성은 아래의 표에 있는 모든 단항연산 동적 SQL 요소에서 사용 가능하다.
| | 명시된 프로퍼티가 파라미터에 존재하는지 검사. 빈즈에서는 프로퍼티를 찾고 Map에서는 키를 찾는다. |
| | 명시된 프로퍼티가 파라미터에 존재하지 않는지 검사. 빈즈에서는 프로퍼티를 찾고 Map에서는 키를 찾는다. |
| | 명시된 프로퍼티가 null 인지 검사. 빈즈에서는 프로퍼티를 찾고 Map에서는 key를 찾는다. 키가 존재하지 않으면 true를 반환한다. |
| | 명시된 프로퍼티가 null이 아닌 다른 값인지를 검사. 빈즈에서는 프로퍼티를 찾고 Map에서는 키를 찾는다. 키가 존재하지 않는다면 false를 반환한다. |
| | 명시된 프로퍼티가 null 이거나 빈 문자열(“”), 빈 컬렉션이나 빈 String, valueOf() 인지를 검사 |
| | 명시된 프로퍼티가 null이 아니거나 빈 문자열(“”), 빈 컬렉션, 빈 String, valueOf() 가 아닌지 검사 |
아래는 단항연산 동적 SQL 요소를 어떻게 사용하는지 보여준다.
…
…
여기서는 간단한 select 매핑 구문을 생성하고, 동적 SQL 요소를 사용하여 productType 프로퍼티의 값에 따라 선택적으로 결과를 걸러서 가져온다.
8.2.4 파라미터 요소
파라미터 없이 매핑 구문을 정의하는 것도 가능하다. 파라미터 요소는 파라미터가 매핑 구문으로 전달되는지를 검사하기 위해 만들어졌다. 표는 요소의 속성을 보여준다.
| Prepend(선택적) | 이 값은 요소 내용의 가장 앞에 출력된다. 다음의 경우에는 prepend 값이 출력되지 않고 무시된다. (a) 요소의 결과 내용이 공백인 경우, (b) 요소가 처음으로 내용을 만들어 냈고 removeFirstPrepend 속성이 true인 요소에 내포되어 있는 경우, (c) |
| Open(선택적) | Open 값은 요소 내용에 접두사로 붙이기 위해 사용한다. Open 값은 요소의 결과 내용이 공백일 때는 접두사로 붙지 않는다. Open 값은 prepend 속성 값보다는 뒤에 붙는다. 예를 들어 prepend=”OR” 이고 open=” (“ 라면 조합된 결과는 “OR (“ 가 될 것이다. |
| Close(선택적) | Close 값은 요소 결과 내용의 뒤에 덧붙이는데 사용한다. 이 값은 요소 내용이 공백이라면 덧붙여지지 않을 것이다. |
| removeFirstPrepend(선택적) | 이 값으로 처음으로 내용을 출력하는 자식 요소의 prepend 속성 값을 출력할지 여부를 결정한다. |
모든 속성은 아래에 있는 모든 요소에서 사용 가능하다.
다음은 select 구문에서 파라미터 요소를 사용하는 방법을 보여준다.
…
…
이 예제에서 다시 간단한 select 구문을 생성하고, 이번에는 productType 파라미터를 기반으로 해서 결과 값을 걸러내는 WHERE 절을 선택적으로 생성해준다.
8.2.5 요소
| Property(필수) | 리스트(컬렉션 혹은 배열)을 포함하고 있는 파라미터의 프로퍼티 |
| Prepend(선택적) | 이 값은 요소 내용의 가장 앞에 출력된다. 다음의 경우에는 prepend 의 값이 출력되지 않고 무시된다. (a) 요소의 결과 내용이 공백인 경우, (b) 요소가 처음으로 내용을 만들어 냈고 removeFirstPrepend 속성이 true 인 요소에 내포되어 있는 경우, (c) |
| Open(선택적) | Open 값은 요소 내용에 접두사로 붙이기 위해 사용한다. Open 값은 요소의 결과 내용이 공백일 때는 접두사로 붙지 않는다. Open 값은 prepend 속성값보다는 뒤에 붙는다. 예를 들어 prepend=”OR” 이고 open=”(“라면 조합된 결과는 “OR (“가 될 것이다. |
| Close(선택적) | Close 값은 요소 결과 내용의 뒤에 덧붙이는데 사용한다. 이 값은 요소 내용이 공백이라면 덧붙여지지 않을 것이다. |
| Conjunction(선택적) | 이 속성의 값은 값 목록의 사이 사이에 출력되어 SQL 문장을 구성한다. |
| removeFirstPrepend(선택적) | 이 값으로 처음으로 내용을 출력하는 자식 요소의 prepend 속성 값을 출력할지 여부를 결정한다. |
아래는
…
…
이 예제에서는 select 문을 만들고, 그 다음에 제품 타입(product type) 목록을 반복적으로 돌아서, 제품 타입에 따라 결과를 필터링하는 좀 더 복잡한 조건절을 생성한다.
1.3. 모두 적용한 간단한 예제
동적 SQL의 기본 지식을 모두 배웠다. 간단한 검색 기능을 구현하여 애플리케이션에서 사용해보자. 이 예제에서는 JGameStore 라는 애플리케이션을 사용할 것이다. 이 애플리케이션은 14장에서 정식으로 소개할 예정이다. 이 애플리케이션을 구축하면서 동적 SQL의 개념을 이해하고 조합해서 사용하는데 도움이 될 수 있도록, 간결한 접근 방법을 적용할 것이다.
예제를 보기전에, 우리가 사용할 프로세스를 살펴보자. 프로세스 그 자체는 상당히 간단하다. 그리고 애플리케이션이 성숙해감에 따라 각 처리 과정들은 서로 다른 순서와 다양한 수준의 노력을 요할 수 있다. 초반에 해줘야 하는 작업이 약간 더 있다. 먼저 기반을 다져놔야 하기 때문이다. 일단 기반을 다지고 나면 그 위에 무언가를 구축하는 것은 별로 복잡하지 않다.
프로세스는 몇 가지 기본적인 단계로 구성된다.
1. 데이터를 가져오고 표시하는 방법을 지정한다.
2. 어떤 데이터베이스 구조를 사용할 지 결정한다.
3. 정적인 형태로 SQL을 작성한다.
4. 정적인 SQL에 동적 SQL 요소를 적용한다.
상당히 간단한 페이지이지만, 모두 알다시피 항상 눈에 보이는 것보다 더 많은 일이 뒤에서 이뤄지게 마련이다. 다음 절에서 위의 작업을 수행하는 코드를 살펴볼 것이다.
8.3.1 데이터를 가져오고 표시하는 방법을 정의하기
JGameStore 애플리케이션의 각 페이지에 간단한 검색 필드와 검색 버튼을 구현할 것이다. 검색 필드는 공백으로 각 단어를 구분한다. 예를 들어 ‘Adventure Deus’ 를 입력했다면 ‘Adventure’ 와 ‘Deus’ 라는 두 개의 단어로 분리될 것이다. 각각의 단어를 제품의 카테고리 변호 (CategoryId), 제품명(name), 그리고 설명(description) 내에서 찾을 수 있는지 검사할 것이다. 일단 검색 버튼을 클릭하면 결과로 나온 모든 제품들을 한 페이지당 네 개씩 출력한다.
8.3.2 데이터베이스 구조 결정하기
테이블 구조를 어떻게 정의할 지 알아보자. 우리는 categoryId, name, 그리고 description에 대해 검색을 한다. 따라서 Product 테이블이 이러한 요구사항을 충족시킬 수 있는 정도가 되게끔만 만들자. Product 테이블은 검색하고 출력할 필요가 있는 모든 제품 정보를 포함하고 있다.
8.3.3 정적인 형태로 SQL 작성하기
처음 시작할 때는 검색 결과에서 표시해줄 모든 필드를 다 가져오는 정적인 SQL 을 만든다. 다음에서 제품 정보를 출력하는데 사용할 정적인 쿼리를 볼 수 있다.
SELECT
PRODUCTID,
NAME,
DESCRIPTION,
IMAGE,
CATEGORYID
FROM PRODUCT
WHERE
lower(name) like 'adventure%' OR
lower(categoryid) like 'adventure%' OR
lower(description) like 'adventure%' OR
lower(name) like 'Deus%' OR
lower(categoryid) like 'deus%' OR
lower(description) like 'deus%'
이제 입력과 출력 그리고 데이터 베이스 테이블을 모두 갖추었다. SQL 구문의 모형을 만들때가 된 것이다. 앞서 언급한 정보를 가지고 있다면 SQL을 생성하는 것은 지극히 간단하다. 우리에게 필요한 제품 목록은 SELECT 구문을 통해 받을 수 있다. Name, categoryId 그리고 description 과 제공받은 단어를 비교하는 검색 조건은 WHERE 절에 나온다.
8.3.4 동적인 SQL 요소를 정적인 SQL에 적용하기
모의 SQL 구문을 살펴보면 SQL 구문을 상세히 분석하고 어디에서 동적인 요소를 사용할지 결정할 수 있다. SELECT 절은 정적이기 때문에 여기에선 아무런 동적인 요소도 필요 없다. WHERE 절은 동적으로 변경해줄 필요가 있다.
동적 요소들로 매핑 구문에 전달받은 파라미터를 활용할 수 있다. 따라서 어떤 파라미터들이 있고, 이 파라미터들에 동적 요소를 어떻게 적용할 지 생각해 볼 필요가 있다. 매핑 구문이 받는 파라미터는 String(java.lang.String) 의 리스트이다. 간단한 리스트를 직접 사용하기 때문에
sqlMap.queryForPaginatedList(
“searchProductList”, parameterObject, PAGE_SIZE);
마지막 요구 사항으로 한 번에 네 개의 레코드만을 반환해야 한다. 이를 위해 pageSize 파라미터를 받는 queryForPaginatedList() 메서드를 사용해서 매핑 구문을 호출하여 이 요구 사항을 처리할 것이다. 이렇게 하면 반환되는 레코드의 개수를 제어할 수 있다.
간단한 예제를 전체적으로 둘러보면서 동적 SQL 작성 계획을 세우고 개발하는 방법을 공부해 보았다. 동적 SQL 은 여러가지 선택사항이 필요한 한 가지 목적에만 사용해야 한다는 것을 기억하라. 여러 가지 목적을 달성하기 위해 동적 SQL을 사용해서는 안 된다. 예제에서 동적 SQL의 목적은 사용자가 어떻게 제품을 선택할지를 처리하는 것이다. 명심해야할 것은 동적 SQL은 복잡한 것을 간단하게 만들려고 나온 것이지, 간단한 것을 더 복잡하게 만들려고 나온 것이 아니라는 점이다. 다음으로 동적 SQL의 좀 더 상세한 사용법을 살펴보자.
1.4. 고급 동적 SQL 기법
8.4.1 결과 데이터 정의하기
일반적인 말로 우리가 출력하고자 하는 것을 정의해보자. 우리의 장바구니 애플리케이션은 사용자가 지정한 검색 조건에 따라 출력할 제품 목록을 필요로 한다. 출력은 categories, products, 그리고 manufacturers 를 포함한 선택 조건에 기초하여 결정될 것이다. 제품은 페이지당 4개씩 표시되어야만 한다. 동적 SQL은 페이지 처리된 제품 목록을 생성하고 사용자가 입력하는 복잡하고 다양한 값을 처리할 수 있어야만 한다.
그렇다면 검색 페이지는 데이터베이스와 어떤 식으로 작동할까? 아래에서 이 물음에 대한 답변의 일부를 볼 수 있다.
public class ProductSearchCriteria {
private String[] categoryIds;
private String productName;
private String productDescription;
private String itemName;
private String itemDescription;
…
// setters and getters
}
본래, 검색 페이지의 각 필드는 간단한 자바빈즈인 검색 기준 클래스의 프로퍼티에 매핑된다.
8.4.2 정적인 형태로 SQL 작성하기
이제 입력과 출력을 모두 정의했으니, 데이터베이스 쿼리 툴에서 실행할 수 있는 간단한 정적 SQL을 사용하여 SQL을 작성할 수 있다. 다음에서 개발의 시작점이 되면서 동적 SQL을 구성하는 기초가 될 정적 SQL 구문을 볼 수 있다.
SELECT
p.PRODUCTID AS PRODUCTID,
p.NAME AS NAME,
p.DESCRIPTION AS DESCRIPTION,
p.IMAGE AS IMAGE,
p.CATEGORYID AS CATEGORYID
FROM Product p
INNER JOIN Category c ON
c.categoryId=p.categoryId
INNER JOIN Item i ON
i.productId = p.productId
WHERE
c.categoryId IN ('ACTADV')
AND
p.name LIKE '007'
AND
p.description LIKE '007'
AND
i.name LIKE 'PS2'
AND
i.description LIKE 'PS2'
우리는 이제 필요한 입력이 무엇이고, 어떤 테이블이 필요한지 알고 있으므로 모의로 정적 SQL 쿼리를 만들어 볼 수 있다. 우리의 정적 SQL 예제에서 WHERE 조건절이 조합 가능한 모든 조건들을 모두 다 포함하여 작성되었음을 알 수 있다. 무엇을 동적으로 만들어야 하는지 알려면 정적 SQL을 모든 사항을 고려해서 말들어 보는 것이 중요하다. 동적 SQL을 조합하는 부분을 보자.
8.4.3 동적 SQL 요소를 정적 SQL에 적용하기
모의로 정적 SQL을 모두 만들었으니, 이제 동적 요소들을 적용해보자. 다듬에서 볼 수 있듯이 동적 SQL 은 조금 더 복잡해진다. 가장 상위 레벨의 요소로
시작 select 요소 (1)에서 파라미터 클래스 타입은 productSearch, 그리고 결과 클래스 타입은 product라는 별칭을 사용하여 정의하였다. 이 SQL 부분 (2)은 이 매핑 구문에서 생성할 수 있는 가장 작은 단위의 SQL이다. 예를 들면 사용자가 모든 것을 반환하도록 검색하고자 하는 경우에 이 SQL을 사용한다.
그렇다면 저 괴물 같은 구문을 어떻게 호출할까?
queryForPaginatedList (
“product.searchProductsWithProductSearch”,
productSearch, PAGE_SIZE);
앞의 예제에서 우리의 마지막 요구사항은 한 번에 네 개의 레코드만을 반환해야 한다는 것이었다. 이를 위해 페이지 크기 파라미터를 받는 queryForPaginatedList 메서드를 사용하여 구문을 호출한다. 범위를 지정하는 queryForPaginatedList 를 사용하는 방법에 대해서는 8.3.4절의 예제를 보라.
복잡한 예제를 보며 다소 복잡한 동적 SQL을 어떻게 수행하는지 공부하였다. 여기서는 동적 SQL이 사용자가 입력한 검색 기준에 따라 제품 목록을 제공한다는 단 하나의 목적만을 수행하도록 하였다. 비록 이 예제가 조금 복잡해 보이긴 하지만, 모든 것을 자바 코드로만 작성하는 것이 훨씬 더 어렵다는 것은 명백하다. 이제 당신 자신의 동적 SQL을 구성할수 있는 준비가 되었으리라 믿는다.
iBATIS의 동적 SQL을 동적 SQL을 처리하는 다른 솔루션들과 비교하는 방법을 이해하는것도 중요하다. 동적 SQL을 수행하는 몇몇 다른 방법들을 간단히 알아보자.
1.5. 동적 SQL에 대안이 되는 접근법
동적 SQL은 결코 새로운 개념이 아니다. 조건에 따라 생성되는 SQL 쿼리는 항상 발생하는 복잡한 요구사항이다. 과거에는 동적 SQL을 효율적으로