EJB 2.x는 CMP 엔티티 Bean의 파인더 및 선택 메소드에 대해 EJB 조회 언어(EJB QL)라고 하는 조회 구문을 제공합니다. 파인더 메소드는 데이터베이스에서 하나 이상의 엔티티 Bean 인스턴스를 확보하며 홈 인터페이스에 정의되어 있습니다.
<query> 요소를 사용하여 findByPrimaryKey(key)를 제외한 각 파인더 메소드마다 배치 디스크립터에 파인더 메소드에 대한 조회를 정의합니다. 배치 디스크립터에 지정된 조회는 배치 중 SQL로 컴파일됩니다. 조회 구문은 <query> 요소의 <ejb-ql> 요소에 있습니다.
<query>
<query-method>
<method-name></method-name>
<method-params>
<method-param></method-param>
</method-params>
</query-method>
<result-type-mapping></result-type-mapping>
<ejb-ql></ejb-ql>
</query>
자세한 도움말은 EJB QL을 사용하여 EJB 2.x Bean에 파인더 메소드 추가를 참조하십시오.
EJB QL에 대한 자세한 내용을 보려면 WebSphere Application Server Information Center로 이동하여 키워드 "EJB QL"을 검색하십시오.
현재 지원되고 조합으로 사용할 수 있는 EJB 사용자 제어 파인더에는 세 가지 유형이 있습니다.
EJB 홈 인터페이스에 정의된 각 파인더 메소드(findByPrimaryKey 및 연관을 지원하기 위해 생성된 파인더 메소드 제외)마다 파인더 헬퍼 인터페이스(beanClassNameFinderHelper.java 파일)에 다음의 조회 문자열 또는 선언 중 하나를 정의해야 합니다.
홈 인터페이스의 파인더에서 리턴 유형 java.util.Enumeration 또는 java.util.Collection은 이 파인더가 둘 이상의 Bean을 리턴할 수 있음을 표시합니다. 원격 인터페이스를 리턴 유형으로 사용하는 경우 단일 Bean이 리턴됨을 표시합니다. 이 사항은 지원되는 모든 사용자 제어 파인더 유형에 해당됩니다. persister로 생성된 코드가 이러한 특성을 처리합니다.
기존 EJB 1.0 JAR 파일에 대해 작업하는 경우 헬퍼 파인더 인터페이스에서 SQL 조회 문자열 또는 메소드 선언을 계속 정의할 수 있음을 유의해야 합니다. (SQL 조회 문자열은 실제로 전체 SELECT 문 또는 WHERE 절만을 설명하는 인터페이스의 한 필드입니다.) 그러나 1.0보다 높은 레벨에서 EJB JAR 파일에 대해 작업해야 하는 새 개발 작업의 경우 파인더 헬퍼 인터페이스보다 확장 문서를 사용하여 조회 및 메소드 선언을 정의해야 합니다. 이에 대한 설명은 이 주제의 후반부를 참조하십시오.
여러 데이터베이스에서 SQL 호환성 유지
SELECT, WHERE 및 메소드 사용자 제어 파인더의 경우 파인더 메소드가 여러 가지 다른 데이터베이스에 액세스하는 상황이 있습니다. 이러한 경우 다른 데이터베이스에서 SQL 호환성이 유지되는지 확인해야 합니다. 예를 들어, 각 데이터베이스에서 사용하는 SQL 구문이 다를 수 있습니다. 이러한 경우 JDBC에서 정의한 SQL 확장기능을 사용하여 데이터베이스 차이를 해결하십시오.
예를 들어, 시간소인/날짜 필드가 관련된 파인더 메소드가 필요한 CMP 엔티티 Bean을 개발한다고 가정하십시오. 또한 이 Bean이 DB2® 및 Oracle 데이터베이스로 배치된다고 가정하십시오. 문제는 DB2와 Oracle에서 시간소인/날짜 필드의 형식이 다르므로 DB2 및 Oracle 양쪽에서 사용할 하나의 WHERE 절을 정의하기 어렵다는 점입니다. 이러한 특정 문제를 해결하려면 SQL 이스케이프 시퀀스를 사용해야 합니다.
SELECT, WHERE 및 메소드 사용자 제어 파인더에 대한 추가 정보는 다음 섹션을 참조하십시오.
주: 사용자 제어 파인더에 대해 작업하는 경우 NULL을 전달하지 마십시오.
SELECT 사용자 제어 파인더는 SQL 조회를 정의하기 위해 파인더 헬퍼 인터페이스에 전체 SELECT 문을 입력하는 데 사용됩니다.
이전 릴리스와 호환성을 위해 SELECT 사용자 제어 파인더의 사용이 지원됩니다. 이 릴리스 및 이후 릴리스에서는 SELECT 사용자 제어 파인더의 사용은 권장되지 않습니다.
필터링 WHERE 절만을 파인더 헬퍼 인터페이스에 입력하는 사용자 제어 파인더를 WHERE 사용자 제어 파인더라고 합니다. 예를 들어, CAPACITY라는 열이 있는 데이터베이스 테이블에 맵핑된 VAPGarage CMP 엔티티 Bean이 있는 경우 파인더 헬퍼 인터페이스는 다음과 같습니다.
public interface VapGarageBeanFinderHelper {
public final static String
findCapacityGreaterThanWhereClause =
"T1.CAPACITY > ?";
}
결과의 모양에 대한 종속성이 문자열에서 제거된다는 점을 유의하십시오. 두 개의 종속성은 계속 존재합니다.
열의 이름은 변경 조치를 수행하는 경우에만 변경됩니다.
테이블의 별명은 테이블이 맵핑에 추가되거나 맵핑에서 제거되지 않는 한 생성과 다른 생성에서 동일합니다. 단일 테이블의 경우 별명이 중요하지 않지만(별명은 항상 T1임) 여러 테이블을 사용하는 경우에는 매우 중요합니다.
수동으로 작성한 SQL 코드의 테이블 참조는 genericFindSqlString 필드에 설정된 테이블 별명과 일치해야 합니다. 엔터프라이즈 Bean의 생성된 persister에 선언되어 있습니다.
SELECT 사용자 정의 형식과 마찬가지로 파인더 매개변수 수는 WHERE 절의 인젝션 지점(? 문자) 수와 일치해야 합니다. 또한 SELECT 형식과 마찬가지로 매개변수의 형식을 사용하여 각 매개변수를 삽입하는 데 사용되는 java.sql.PreparedStatement 세트 호출을 결정합니다. 매개변수 유형은 열 유형과 호환 가능해야 합니다. 유형이 오브젝트 유형이며 매개변수가 널(null)인 경우 setNull 호출을 사용합니다.
예를 들어, 홈 인터페이스에는 다음의 메소드가 포함될 수 있습니다.
public java.util.Enumeration findGreaterThan (int threshold) throws
java.rmi.RemoteException, javax.ejb.FinderException;
이 파인더 헬퍼 인터페이스에서 WHERE 사용자 제어 파인더는 사용자가 제공할 수 있는 형식 중 하나입니다. 예를 들어, 다음과 같습니다(보기 편하게 행이 구분됨).
public static final String findGreaterThanWhereClause =
"T1.VALUE > ?";
그러나 WHERE 절이 없는 SQL 문이 있는 경우(예: SELECT * FROM MYTABLE) 항상 true로 평가되는 조회 문자열을 사용해야 합니다. 예를 들어, 다음과 같습니다.
public static final String findALLWhereClause = "1 = 1";
파인더 헬퍼 인터페이스에 메소드 시그너처를 입력하는 사용자 제어 파인더를 메소드 사용자 제어 파인더라고 합니다. 가장 유연한 사용자 제어 파인더 유형이지만 사용자 측에서 추가 작업이 필요합니다. 이전과 동일한 garage 예제를 사용할 경우 헬퍼 인터페이스는 다음과 같습니다.
public interface VapGarageBeanFinderHelper {
public java.sql.PreparedStatement findCapacityGreaterThan(int threshold)
throws Exception;
}
그러나 SELECT 및 WHERE 형식과 달리 이 정도로는 메소드 사용자 제어 파인더에 충분하지 않습니다. 이 메소드를 구현해야 합니다. 다음 규칙을 준수하는 클래스에서 메소드 구현을 제공하십시오.
예제를 완료하면 파인더 오브젝트는 다음과 같습니다.
/**
* Implementation class for methods in
* VapGarageBeanFinderHelper.
*/
public class VapGarageBeanFinderObject extends
com.ibm.vap.finders.VapEJSJDBCFinderObject implements
VapGarageBeanFinderHelper {
public java.sql.PreparedStatement
findCapacityGreaterThan(int threshold)
throws Exception {
PreparedStatement ps = null;
int mergeCount = getMergedWhereCount();
int columnCount = 1;
ps = getMergedPreparedStatement("T1.CAPACITY > ?");
for (int i=0; i<(columnCount*mergeCount); i=i+columnCount) {
ps.setInt(i+1, threshold);
}
return ps;
}
}
메소드 사용자 제어 파인더의 경우 생성된 persister는 사용자의 구현을 사용하여 실행할 PreparedStatement를 작성합니다. persister는 PreparedStatement를 실행하고 결과를 처리합니다. 조회의 결과 세트가 올바른 모양을 갖추려면 구현 시 persister의 도움이 필요합니다. com.ibm.vap.finders.VapEJSJDBCFinderObject 기본 클래스는 여러 가지 중요한 헬퍼 메소드를 제공하며 일부는 위의 예제에 표시되어 있습니다. 다음 표는 전체 헬퍼 메소드 세트 및 이에 대한 설명입니다.
| 메소드 | 설명 |
|---|---|
| getMergedPreparedStatement | WHERE 절을 사용하고 WHERE 절이 적합한 위치에 병합된 PreparedStatement를 리턴합니다. PreparedStatement에는 올바른 결과 세트 모양이 있습니다. |
| getMergedWhereCount | WHERE 절이 PreparedStatement에 병합된 횟수를 리턴합니다. PreparedStatement에 조회 매개변수가 삽입된 횟수를 알려면 이 메소드가 필요합니다. |
| getPreparedStatement | 전체 조회 문자열을 사용하고 PreparedStatement를 리턴합니다. 어떤 이유로 인해 사용자가 직접 WHERE 절 병합을 수행해야 하는 경우에 이 메소드를 사용할 수 있습니다. 이러한 경우는 매우 드문 경우입니다. 이러한 극한 경우를 지원하기 위해 다음의 두 기능이 제공됩니다. |
| getGenericFindSqlString | WHERE 절이 병합되는 조회 문자열을 리턴합니다. |
| getGenericFindInsertPoints | WHERE 절이 병합되는 getGenericFindSqlString 리턴 조회 문자열의 지점을 정의하는 정수 배열을 리턴합니다. 배열의 첫 번째 지점은 문자열의 마지막 지점입니다. 끝의 병합으로 문자열의 초기 병합 위치가 변경되지 않으므로 보통 조회 문자열의 끝에서 병합하는 것이 가장 적합합니다. 배열의 크기는 getMergedWhereCount에서 리턴하는 값과 동일합니다. |
WHERE 사용자 제어 파인더에서 쉽게 처리할 수 있는 단순한 경우입니다. WHERE 사용자 제어 파인더가 간단하게 처리할 수 없는 복잡한 예제도 가능합니다. 예를 들어, 복잡한 오브젝트를 사용하여 WHERE 절의 여러 열에 삽입하는 파인더를 원한다고 가정하십시오. 이 경우 파인더 메소드는 다음과 같습니다.
public java.sql.PreparedStatement
findWithComplexObject(BigObject big) throws Exception {
PreparedStatement ps = null;
int mergeCount = getMergedWhereCount();
int columnCount = 3;
int anInt = big.getAnInt();
String aString = big.getAString();
String aLongAsString =
com.ibm.vap.converters.VapStringToLongConverter.
singleton().dataFrom(big.getLongObject());
ps = getMergedPreparedStatement("(T1.ANINT > ?) AND
(T1.ASTRING = ?) AND (T2.ALONGSTR < ?)");
for (int i=0; i<(columnCount*mergeCount); i=i+columnCount) {
ps.setInt(i+1, anInt);
if (aString == null)
ps.setNull(1, java.sql.Types.VARCHAR);
else
ps.setString(i+2, aString);
if (aLongAsString == null)
ps.setNull(1, java.sql.Types.VARCHAR);
else
ps.setString(i+3, aLongAsString);
}
return ps;
}
더욱 복잡한 예제도 가능합니다. 예를 들어, 데이터 외에 WHERE 절(또는 이 절을 작성하는 방법에 대한 지시사항)이 있는 오브젝트를 전달할 수 있습니다. 또는 WHERE 절에서 각각 여러 가지 다른 조건을 표시하는 여러 매개변수가 있습니다.
예제: 복잡한 메소드 사용자 제어 파인더
EJB 배치 도구를 사용하여 연관이 작성되지 않지만 다음 예제는 복잡한 메소드 사용자 제어 파인더를 사용하여 다대다 연관을 수행할 수 있는 방법에 대한 논리적 표시입니다. 이 예제에서는 중간 Bean(ProdCustLink)을 사용하는 제품 및 고객 Bean의 다대다 연관과 두 개의 1:m 연관을 사용합니다.

하나의 메소드 호출만을 사용하여 어느 방향으로나 관계를 연결하는 메소드 사용자 제어 파인더를 작성할 수 있습니다. 이 예제에서는 한 방향만을 고려하십시오. 제공된 제품 키와 연관된 모든 고객 인스턴스를 검색하는 고객의 파인더입니다.
다음과 같이 고객 홈 인터페이스에는 적합한 메소드 시그너처가 있습니다.
java.util.Enumeration
findCustomersByProduct(prod.cust.code.ProductKey inKey)
throws java.rmi.RemoteException, javax.ejb.FinderException;
고객 파인더 헬퍼 인터페이스에는 해당 파인더 메소드의 시그너처가 있습니다.
public java.sql.PreparedStatement
findCustomersByProduct(prod.cust.code.ProductKey inKey)
throws Exception;
파인더 오브젝트(CustomerBeanFinderObject)는 파인더의 조회 문자열을 빌드 및 캐시하고 파인더 메소드를 구현합니다.
public class CustomerBeanFinderObject
extends com.ibm.vap.finders.VapEJSJDBCFinderObject
implements CustomerBeanFinderHelper {
private String cachedFindCustomersByProductQueryString = null;
.
.
.
}
파인더 오브젝트의 초기화 지연 기능을 통해 조회 문자열 필드의 액세서 메소드는 먼저 WHERE 조건을 조회 템플리트에 병합하고 중간 테이블에 대한 참조를 FROM 절에 추가하여 조회 문자열을 빌드합니다.
액세서 메소드의 처음 절반은 genericFindInsertPoints 배열을 사용하여 각 WHERE 절을 찾고 업데이트합니다. 그런 다음 메소드의 나머지 절반에서는 각 FROM 절의 처음부터 계수하고 필요하면 중간 테이블에 대한 참조를 조회 문자열에 삽입하고 조회 문자열 필드를 업데이트합니다.
protected String getFindCustomersByProductQueryString() {
if (cachedFindCustomersByProductQueryString == null) {
// Do the WHERE first
// so that the genericFindInsertPoints are correct.
int i;
int[] genericFindInsertPoints = getGenericFindInsertPoints();
StringBuffer sb = new StringBuffer(getGenericFindSqlString());
for (i = 0; i < genericFindInsertPoints.length; i++) {
sb.insert(genericFindInsertPoints[i],
"(T1.id = T2.Customer_id) AND (T2.Product_id = ?)");
}
// Make sure to update every FROM clause.
String soFar = sb.toString();
int fromOffset = soFar.indexOf(" FROM ");
while (fromOffset != -1) {
sb.insert((fromOffset+5)," ProdCustLink T2, ");
soFar = sb.toString();
fromOffset = soFar.indexOf(" FROM ", (fromOffset+5));
}
cachedFindCustomersByProductQueryString = sb.toString();
}
return cachedFindCustomersByProductQueryString;
}
이 메소드 호출 이후에 조회 문자열은 다음과 같습니다.
SELECT <columns> FROM ProdCustLink T2, CUSTOMER T1
WHERE((T1.id = T2.Customer_id) AND (T2.Product_id = ?))
또한 파인더 오브젝트에서 구현된 파인더는 조회 문자열을 사용하여 PreparedStatement를 작성합니다. 마지막으로 중요한 점은 반복 루프의 수퍼 클래스 메소드 getMergedWhereCount()를 사용하여 제품 ID가 각 WHERE 절에 추가된다는 점입니다.
public java.sql.PreparedStatement
findCustomersByProduct(ProductKey inKey)
throws java.lang.Exception {
// Get the full query string and make a PreparedStatement.
java.sql.PreparedStatement ps =
getPreparedStatement(getFindCustomersByProductQueryString());
// Inject the product id parameter into each merged WHERE clause.
for (int i = 0; i > getMergedWhereCount(); i++) {
if (inKey != null)
ps.setInt(i+1, inKey.id);
else
ps.setNull(i+1, 4);
}
return ps;
}