05.JSP2009. 3. 10. 12:49
반응형

SQL에 parameter를 삽입하여 실행하는 기능

SQL에 parameter를 삽입하여 실행하는 방법이 있습니다.

String sSql = "select * from mytable where name = ? and address = ?";
Connection conn = get database connection;
PreparedStatement prestmt = null;
Statement stmt = null;
prestmt = conn.prepareStatement(sSql);
prestmt.setString(1,"Mike");
prestmt.setString(2,"LA");
rs = prestmt.executeQuery();
while (rs.next())
{
 xxx = rs.getString(1);
 yyy = rs.getString(2);
}

위와 같은 parameter name,address를 삽입하여 SQL을 실행할 수 있습니다.
실행되는 SQL은 다음과 같습니다.

select * from mytable where name = 'Mike' and address = 'LA'

위와 같은 방식으로 많은 개발자들이 web application을 개발합니다.
위와 같은 방식은 개발 생산성 측면에서 몇가지 문제가 있었습니다.
가장 큰 문제가 실제 실행되는 SQL을 개발자가 알 수 없습니다.
물론 parameter를 일일이 대입해서 볼수는 있지만 실제 값이 대입된 SQL을 개발자가 볼 수
없다는 것입니다.
문제가 되는 경우를 보겠습니다.

update mytalbe set name=? where id=?
prestmt.setString(1,"123456789012345678901234567890");
prestmt.setString(2,"2334");

그런데 mytable.name field는 varchar(16) 으로 선언이 되어 있다고 하겠습니다.
즉 입력한 값이 16 byte만 설정이 될 수 있습니다.
이와 같은 경우 개발자는 parameter가 설정된 실행되는 SQL을 trace log에서 볼 수 없기때문에
오류를 쉽게 파악하기 힘들 수 있습니다.

그래서 필자는 자체적으로 parameter가 설정된 SQL을 build하는 program을 개발해 보았습니다.
WCStmt.makeQuery method가 이것입니다.
parameter는 WCProperties로 전달하도록 하였습니다.

String sSql = "insert into table_name (name,address) values ('$name','$address')";
WCProperties pParam = new WCProperties();
pParam.setValue("name","jane");
pParam.setValue("address","L'A");
WCStmt oStmt = new WCStmt();
sSql = oStmt.makeQuery(sSql,pParam);

sSql 값 "insert into table_name (name,address) values ('jane','L''A')" 이와 같이 됩니다.
개발자는 sSql의 값을 확인하여 정확한 SQL이 생성되었는지를 점검할 수 있습니다.
그러나 개발을 하고 보니 이 방법도 몇가지 문제가 있었습니다.
parameter value값이 SQL에 삽입될때 몇가지 변환이 되어야 하는데 이 변환이
database의 type에 따라 약간씩 다르다는 것입니다.
L'\A 이와 같은 값이 있다고 하겠습니다.

- oracle,mssql : L''\A
update table_name set field_name = 'L''\A' where id = 37

- mysql : L''\\A
update table_name set field_name = 'L''\\A' where id = 37

이와 같이 변환이 되어야 합니다. 그래서 위의 makeQuery 함수를 다음과 같이 변경해야 했습니다.

String sDsn = "WDLDatabasePool";
sSql = oStmt.makeQuery(sSql,pParam,sDsn);

위에서 처럼 makeQuery에 database connection pool 이름을 전달하여 database type을 얻어와서
해당 database type에 대한 변환을 하도록 하였습니다.

-- JSP sample code 시작
<%@ page language="java" import="wdl.*,java.util.*,java.sql.*,java.lang.*,
java.io.*,java.io.File " contentType="text/html; charset=EUC-KR"%><%
 insertRecord();
%>
<%!
public void insertRecord()
{
 WCProperties rSqlParam = new WCProperties();
 rSqlParam.setValue("parentid",100);
 rSqlParam.setValue("text","hello");
 rSqlParam.setValue("url","-");
 rSqlParam.setValue("guid","g");
 rSqlParam.setValue("idx",0);
 rSqlParam.setValue("depth",0);
 rSqlParam.setValue("child",0);

 String sSql = "insert into mytable (\n";
 sSql += "parentid\n";
 sSql += ",text\n";
 sSql += ",url\n";
 sSql += ",guid\n";
 sSql += ",idx\n";
 sSql += ",depth\n";
 sSql += ",child\n";
 sSql += ")\n";
 sSql += "values(\n";
 sSql += "'$parentid' -- parentid\n";
 sSql += ",'$text' -- text\n";
 sSql += ",'$url' -- url\n";
 sSql += ",'$guid' -- guid\n";
 sSql += ",'$idx' -- idx\n";
 sSql += ",'$depth' -- depth\n";
 sSql += ",'$child' -- child\n";
 sSql += ")";
 WCStmt oStmt = new WCStmt();
 String sSql2 = oStmt.makeQuery(sSql,rSqlParam,"WDLDatabasePool");
 oStmt.executeUpdate(sSql2, null,"WDLDatabasePool");
}
%>
-- JSP sample code 끝

아래의 소스코드는 WCStmt.java의 일부분 코드입니다. makeQuery와 toSqlValue 입니다.

-- WCStmt.java에서 makeQuery 부분 추출 시작
public String makeQuery(String sSql,WCProperties rParam,String sDsn)
{
 if (WCString.isEmpty(sSql) || WCString.isEmpty(sDsn))
  return null;

 String sTail = "";
 String sHead = "";
 String sMid = "";

 String sRet = new String();
 String sSrc = sSql;
 do
 {
  int nIndex = sSrc.indexOf("$");
  int nSqlLen = sSrc.length();
  if (nIndex < 0)
   break;
  sMid = sSrc.substring(nIndex+1,nSqlLen);
  String sTok = getTok(sMid); // getToken
  int nTok = sTok.length();
  sTail = sSrc.substring(nIndex + nTok+1,nSqlLen);
  sHead = sSrc.substring(0,nIndex);
  // sSrc = sHead;
  String sVal = rParam.GetStrValue(sTok);
  if (sVal == null)
  {
   sVal = "";
   if (nIndex > 0)
   {
    char c = sSrc.charAt(nIndex-1);
    if (c == '\'' || c == '\"')
     sVal = "";
   }
  }
  else
  {
   if (sVal.length() <= 0)
   {
    sVal = "";
    if (nIndex > 0)
    {
     char c = sSrc.charAt(nIndex-1);
     if (c == '\'' || c == '\"')
      sVal = "";
    }
   }
  }
  sVal = WCString.nullCheck(WCStmt.toSqlValue(sVal,sDsn));
  sRet += sHead;
  sRet += sVal;
 
  sSrc = sTail;
 } while (true);
 if (WCString.isEmpty(sRet))
 {
  sRet = sSql;
 }
 else
 {
  sRet += sTail;
 }
 return sRet;
}


public static String toSqlValue(String sSrc,String sDsn)
{
 if (WCString.isEmpty(sSrc) || WCString.isEmpty(sDsn))
 {
  return null;
 }
 String sRet = new String();
 int nLen = sSrc.length();
 String sDatabaseType = WCDBConnectionManager.getDatabaseType(sDsn);
 if (WCString.compareNoCase(sDatabaseType,"mysql") == 0)
 {
  for (int i=0;i<nLen;i++)
  {
   char cChar = sSrc.charAt(i);
   if (cChar == '\'')
   {
    sRet += "'";
   }
   else if (cChar == '\\')
   {
    sRet += "\\";
   }
   sRet += cChar;
  }
 }
 else if (WCString.compareNoCase(sDatabaseType,"mssql") == 0
  || WCString.compareNoCase(sDatabaseType,"oracle") == 0)
 {
  for (int i=0;i<nLen;i++)
  {
   char cChar = sSrc.charAt(i);
   if (cChar == '\'')
    sRet += "'";
   sRet += cChar;
  }
 }
 return sRet;
}
-- WCStmt.java에서 makeQuery 부분 추출 끝

아래의 소스가 완벽히 실행하기 위해서는 WCProperties,WCVector,WCLog,WCSystem 등의 source코드가 필요합니다.
이러한 java source는 Web Development Library(www.webdevlib.net)에서 download 받아 사용하실 수 있습니다.
물론 www.webdevlib.net에서 소스코드를 받지 않고, 아래의 소스 코드를 수정하여 사용하실 수도 있습니다.
의도적으로 소스코드의 일부만 글로 쓰는 것은 아닙니다. 모든 소스에 대한 설명은 차차 하겠습니다.

첨부파일 : sql_makeQuery.zip
sql_makeQuery.jsp
WCDBConnection.java
WCDBConnectionPool.java
WCDBConnectionManager.java
WCStmt.java
WCProperties.java

출처 : 고급 웹 UI 개발 라이브러리 Web Development Library 소스공개 : http://www.webdevlib.net

Posted by 1010