2010년 2월 4일 목요일

ODP.NET을 통한 Oracle 액세스

Developer개발자 ODP.NET

ODP.NET을 통한 Oracle 액세스

By Robert P. Lipschutz and Gregg D. Harrington

이제 .NET 개발자들은 Oracle 고유의 데이타 액세스를 사용하여 애플리케이션 성능을 개선할 수 있습니다.

Oracle이 지원하는 기업용 애플리케이션으로부터 기대했던 효과와 고급 데이타베이스 기능을 얻으려면, 일반 데이타베이스 연결에 대해 Oracle이 지정한 연결을 선택해야 합니다. Java 개발자들은 자신들에게 주어진 오랫동안의 옵션 즉 JDBC를 확장하고 개발자들에게 LOB(large objects) 및 참조 커서와 같은 고급 Oracle 기능에 액세스할 수 있도록 해주는 API를 사용함으로써, Oracle 데이타베이스에 연결할 수가 있습니다.

2002년 2월 Microsoft는 3개 언어(C# .NET, Visual Basic .NET, C++ .NET)를 사용하는 Visual Studio .NET과 기본 .NET 프레임워크를 출시했습니다. Microsoft .NET은 엔터프라이즈 급 데스크톱, 웹, 클라이언트/서버 애플리케이션을 구축하는 데 필요한 객체 지향적인 개발 플랫폼을 제공합니다. 그러나 유감스럽게도 이러한 Microsoft 언어와 .NET 프레임워크를 사용하는 개발자들은 Oracle이 지정하는 데이타베이스 연결 옵션을 사용할 수가 없습니다.

이러한 문제를 시정하기 위해 Oracle은 Microsoft .NET 환경에서 사용할 수 있는 Oracle 데이타베이스 액세스 API와 .NET용 Oracle Data Provider(ODP.NET)를 작성했습니다. 본문에서 우리는 ODP.NET의 사용법, 기능 및 개선된 성능에 대해, 그리고 ODP.NET를 사용함으로써 얻을 수 있는 획기적인 이점에 대해 논하게 될 것입니다.

ODP.NET의 기본 기능

우리는 Oracle 데이타베이스 연결을 위해 ODP.NET을 사용하여 Microsoft Visual Studio .NET에서 dejavu라는 간단한 C# 애플리케이션을 구축하였습니다. 자세한 코드는 목록 1목록2에 나와 있습니다. 본문에 나오는 모든 예제는 기본적으로 이 애플리케이션과 그에 수반하는 데이타베이스를 사용하여 만들어 졌습니다.

데이타베이스의 각 레코드에는 직원들에 대한 정보가 들어 있습니다. 이 애플리케이션에는 표 1에 간략하게 소개되어 있는 4개의 필드와 함께 기본 테이블이 사용되었습니다.

여러분은 ODP.NET을 사용하면 기본 INSERT와 SELECT 문을 비교적 쉽게 연결하고 사용할 수 있다는 점을 알게 될 것입니다. 이 작업은 JDBC 또는 OLE DB .NET을 사용하는 경우와 유사합니다.
dejavu 작성 시 ODP.NET에 액세스하려면, 먼저 다음과 같은 기본 단계를 실행해야 합니다.

  1. ODP.NET 소프트웨어를 설치합니다.
  2. Visual Studio의 Solution Explorer에서, Oracle.DataAccess DLL에 참조 사항을 추가합니다.
  3. C# 클래스에서, 절(Oracle.DataAccess.Client, Oracle.DataAccess.Types)을 사용하여 2개의 이름 공간을 코드 파일 상단에 추가합니다. (Visual Basic .NET 또는 다른 .NET 순응 언어를 사용하는 경우, 이름 공간을 추가하기 위한 구문이 약간 다를 수도 있습니다.)
ODP.NET에 액세스한 후에는, 다음과 같이 기본 삽입을 위한 단계를 실행합니다.
  1. 데이타베이스 연결을 생성하고 엽니다.
  2. 명령을 생성합니다.
  3. 위의 명령을 실행합니다.
여기에서는 먼저 데이타베이스 연결을 생성하고 여십시오.

OracleConnection dbConn = new OracleConnection(
	"Data Source=" + txtDataSource.Text + ";" + 
	"User Id=" + txtUsername.Text + ";" + 
	"Password=" + txtPassword.Text + ";");
dbConn.Open();

그런 다음 OracleConnection object를 사용하여 데이타베이스에 연결하십시오. 이 객체의 인스턴스를 생성하는 경우, OracleConnection 객체는 Oracle 데이타베이스 서버를 찾고 인증하기 위해 사용하는 연결 문자열을 입력합니다.

이 경우 연결 문자열은 세 가지 연결 매개변수인 Data Source, User Id, Password를 갖으며, Oracle Data Provider는 OCI(Oracle Call Interface)를 사용합니다. 따라서 Data Source는 Oracle 클라이언트 소프트웨어가 사용하는 tnsnames.ora 파일로부터 사용할 수 있는 TNS 이름이 되고, User Id는 이름이 지정된 Data Source에 사용할 수 있는 사용자 이름이 되며, Password는 제공되는 User Id와 일치하는 암호 값이 됩니다.

그런 후, 다음과 같이 OracleConnection 객체에서 Open() 메소드를 호출하여 연결을 열고, 이미 생성한 OracleConnection 객체와 관련된 OracleCommand 객체를 생성해 보십시오.

OracleCommand cmd = new OracleCommand(
	"insert into dejavu_employee values " + 
	"(1, 'Gregg D. Harrington', empty_blob())", dbConn);

OracleCommand 객체를 생성하는 경우에는 생성자에게 2개의 매개변수를 제공해야 합니다. 첫 번째 매개변수는 실행하고자 하는 SQL 명령어로, 여기에는 문자열로서 작성된 유효한 SQL 문을 사용할 수 있습니다. 이 경우 사원을 DEJAVU_EMPLOYEE 테이블에 삽입합니다. 두 번째 매개변수는 데이타베이스에 연결된 OracleConnection 객체입니다.

마지막으로, 다음과 같이 OracleCommand 객체를 실행해 보십시오.

int rows = cmd.ExecuteNonQuery();
ShowInfo(rows + " where added to the 
database");
이 경우, ExecuteNonQuery() 메소드를 사용할 수 있도록 데이타베이스에 행 하나를 삽입하십시오. 이 메소드는 SQL INSERT, UPDATE, 또는 DELETE 문과 같이 행을 반환하는 질의가 예상되지 않는 경우 사용합니다(SELECT 문의 경우, ExecuteReader() 메소드가 사용됩니다). ExecuteNonQuery() 메소드는 우리가 생성한 SQL 문을 취하여, 실행을 위해 데이타베이스로 보내고, 관련된 행 번호를 반환합니다.

이제 데이타베이스에 삽입한 레코드를 읽고, 사용자에게 이름 열을 표시해 보이십시오. 또한 데이타베이스에 새로 연결하지 않고 기존의 연결과 명령어 객체를 계속 사용하십시오.

SQL 문을 변경하려면, 다음과 같이 실행하고자 하는 새 SQL 문의 텍스트로 OracleCommand 객체의 CommandText 속성을 변경해 보겠습니다.

cmd.CommandText = "select * from dejavu_employee where " + 
"id = 1";

이 방법은 단일 데이타베이스 연결을 사용하여 하나의 OracleCommand 객체로 여러 가지 SQL 문을 실행하고자 하는 경우에 더욱 유용합니다.

그런 다음, 명령을 실행하고 그 결과를 읽어 보십시오.

OracleDataReader reader = cmd.ExecuteReader();
reader.Read();

ShowInfo("The selected user's name was " + 
	reader["name"].ToString());
dbConn.Close();
여기에서 새로운 객체 OracleDataReader를 볼 수 있습니다. 이 객체는 하나 이상의 레코드를 다시 읽는 경우 사용됩니다. 예제에서는 기본 키 열에 있는 하나의 특정 값을 가져왔기 때문에 단 하나의 레코드만이 반환되었습니다. ExecuteReader() 메소드는 하나 이상의 결과 레코드를 포함하는 OracleDataReader 객체를 반환합니다.

OracleDataReader 객체를 일단 갖게 되면 원하는 데이타 즉, 위의 경우 이름 열을 얻을 수 있습니다. OracleDataReader를 처음 생성하는 경우, 이것은 첫 번째 레코드에 위치하지 않기 때문에 첫 번째 레코드를 읽기 전에 Read() 메소드를 먼저 불러 와야 합니다. Read() 메소드는 다음 레코드로 이동하고, 다른 레코드가 있는 경우 참 부울 값을 반환합니다.

다음으로, OracleDataReader 객체를 위해 기본 문자열-배열 속성을 사용하여, 이름 열 값을 가져와 이 이름을 원하는 열(위의 예제의 경우 name 열)에 전달하십시오. 마지막으로 OracleConnection 객체에서 Close() 메소드를 호출하고 연결을 닫으십시오.

기본 ODP.NET 클래스와 동종 클래스의 비교

ODP.NET에 사용되는 기본 클래스는 다른 연결 옵션에서 사용되는 클래스와 유사합니다. 메소드 이름과 구현 방법이 다른 경우에도 기본 논리는 같습니다. 표 2에는 객체를 표시하기 위해, ODP .NET, OLE DB .NET 및 Oracle thin JDBC 드라이버가 사용하는 객체와 몇몇 데이타베이스 작업이 나열되어 있습니다.

고급 ODP.NET의 기능

지금까지 ODP.NET의 기본 기능을 살펴 보았습니다. 이제 ODP.NET을 강력하게 만들어 주는 주요 기능인 LOB 및 참조 커서 처리 기능에 대해 살펴보도록 하겠습니다.

Oracle LOB, BLOB 및 Bfile. Oracle LOB는 그림과 텍스트 문서를 포함하는 다양한 종류의 대형 문자(large character)나 바이너리 데이타를 저장하는데 사용됩니다. ODP.NET은 LOB를 가져오고 관리하는 고유 메소드를 제공함으로써, LOB를 처리하고 개발자들의 코드 작성 절차를 보다 쉽게 만들어 주는 코드 성능을 개선시켜 줍니다.

ODP.NET에는 OracleBlob 객체와 OracleClob 객체가 포함되며, 이 객체들은 System.IO.Stream 객체의 기능을 확장시켜 줍니다. 개발자들은 이러한 데이타 유형을 사용함으로써 적은 성능 오버헤드만을 허용하여 데이타베이스에 LOB를 쉽게 작성하고 읽을 수 있습니다.

BLOB(Binary Large Objects) 및 CLOB(Character Large Objects)를 처리하는 ODP.NET의 능력을 보려면, 간단한 예제를 참고하십시오. 이 예제에서 우리는 사진 파일(photo file)을 열고 이 파일을 데이타베이스에 작성할 수가 있습니다. 이 프로세스의 처음 두 단계 즉, 데이타베이스 연결을 생성하고 개시하는 단계와 명령어를 생성하는 단계는 이전 예제에서 실행한 방법으로 실행합니다.

그런 다음, 다음과 같이 트랜잭션을 생성해 보십시오.

OracleTransaction trans = dbConn.BeginTransaction();
BLOB를 업그레이드하는 경우, 데이타 무결성을 유지하기 위해 트랜잭션의 내부에서 모든 조작을 실행해야 합니다. (단순히 BLOB 데이타베이스 객체를 가져오는 경우에는 트랜잭션이 필요없습니다.) 지역 트랜잭션을 생성하려면, OracleConnection 객체의 BeginTransaction() 메소드를 호출하고, 그러면 변수에 할당한 OracleTransaction 객체가 이후 사용을 위해 반환됩니다.

이제 다음과 같이 레코드를 열고 OracleBlob 객체를 얻어 보도록 하겠습니다.

cmd.CommandText = "select * from dejavu_employee where " + 
	"id = 1 for update";

OracleDataReader reader = cmd.ExecuteReader();
reader.Read();
OracleBlob lob = reader.GetOracleBlob(2); // ordinal position of blob
위의 예제에서는, 사진 파일에서 스트림하기 위해 OracleBlob 객체를 가져왔습니다. 먼저 원하는 레코드인 ID가 1인 사원 레코드를 선택합니다. BLOB를 업데이트할 것이기 때문에 데이타베이스 행은 잠겨 있어야 합니다. 여기서, SELECT 문의 업데이트 절(update clause)을 사용하여 이 작업을 실행합니다.
그런 다음, OracleDataReader를 열고 Read() 메소드를 불러 처음 레코드로 이동하십시오. 마지막으로 OracleDataReader.GetOracleLob() 메소드를 사용하여 OracleDataReader로부터 OracleBlob 객체를 얻습니다.

이제 목록 3에서 보는 바와 같이 사진을 서버에 삽입할 준비가 되었습니다. 이 때 애플리케이션은 사용자에게 데이타베이스에 업로드하고자 하는 파일 이름을 입력할 것을 요구합니다. 그러면 Visual Studio 형식 디자이너에서 생성된 OpenFileDialog 객체(위의 예제의 경우, ofdPicture)를 사용하십시오. 그런 다음, System.IO.FileStream 객체를 사용하여 파일을 열고, WHILE 루프가 있는 Read() 및 Write() 메소드를 사용하여 FileStream 객체로부터 OracleBlob 객체로 데이타를 복사하십시오.

OracleClob 객체는 OracleBlob 객체와 유사하게 사용됩니다. CLOB 데이타베이스 객체를 읽는 경우, OracleClob 객체를 사용하여 데이타베이스로부터 문자 데이타(character data)를 읽을 수 있습니다. OracleClob 객체는 Read() 메소드를 우선적으로 적용하고, 개발자에게 CLOB를 갖는 문자열이나 문자 배열을 채울 수 있는 방법을 제공합니다.
ODP.NET은 또한 Oracle 데이타베이스로부터 Bfile 데이타베이스 객체를 읽는 데 필요한 OracleBlob과 유사한 객체를 제공합니다. 규모가 큰 파일을 저장하는 데 유용한 Bfile은 파일 시스템의 데이타베이스 외부에 저장되는 LOB입니다.

참조 커서와 저장된 프로시저. 참조 커서는 저장된 프로시저를 통해 열리고, 출력 매개변수를 사용하여 애플리케이션에 전달됩니다.

ODP.NET에서의 참조 커서 사용에 대한 자세한 내용은 목록 4의 PL/SQL 코드를 참조하십시오. 이 코드는 매개변수의 n_empid 및 n_empid2에 근거하여 DEJAVU_EMPLOYEE 테이블에서 2개의 레코드를 불러 온 다음, 출력 매개변수 io_cursor, io_cursor2를 통해 참조 커서를 반환합니다.

개발자들은 OracleDataReaders와 같이 io_cursor 및 io_cursor2 와 같은 커서 출력 매개변수를 사용할 수 있기 때문에 ODP.NET를 사용하여 참조 커서를 쉽게 작성할 수 있습니다. 참조 커서를 불러 오려면, 첫 번째 예제에서와 같이 먼저 데이타베이스 연결을 열고, 명령어를 생성하여 저장된 프로시저를 불러 온 다음, 목록 5에 있는 필요한 매개변수를 전달하십시오. 이 목록에서는 우리에게 이미 친숙한 객체인 OracleCommand을 사용하여 저장된 프로시저를 불러 오고, 그에 따른 참조 커서를 얻으십시오. 그러나 SQL 문을 사용하여 데이타베이스로부터 레코드를 얻는 대신 저장된 프로시저의 출력 매개변수를 사용하십시오.

목록 5의 코드를 행별로 살펴 보겠습니다. 먼저, 1행에서는 새로운 OracleCommand 객체를 생성하십시오. 그러나 SQL 문을 포함하는 문자열을 사용하는 대신 문자열 이름, curspkg.open_two_cursor를 입력하여 저장된 프로시저를 불러오십시오.

2행에서는 OracleCommand 객체를 SQL 문 호출이 아닌 저장된 프로시저 호출로 지정하십시오. 이 명령이 저장된 프로시저에 대한 호출임을 지시하려면 OracleCommand 객체의 CommandType 속성을 적절하게 설정해야 합니다. CommandType 속성은 여러 가지 허용 가능한 값을 갖습니다. 이 값은 CommandType 열거 객체를 통해 액세스할 수 있습니다. 우리의 예제에서는 CommandType.StoredProcedure를 사용하고 있습니다.

3행과 4행에서는 반환되는 참조 커서에 액세스하기 위해 필요한 2개의 매개변수 변수를 생성하십시오. 이 변수는 데이타베이스의 저장된 프로시저로부터 참조 커서 결과에 대한 참조를 얻는 데 사용됩니다.

5행에서 8행까지는 4개의 필요한 매개변수를 OracleCommand 객체에 추가하십시오. cmd.Parameters.Add() 메소드가 있는 행에 2개의 n_empid 매개변수를 위한 OracleParameter 객체를 생성하십시오.

OracleParameter 생성자는 2개의 매개변수를 취합니다. 첫 번째 매개변수는 PL/SQL 저장 프로시저 매개변수 이름이며, 이것은 저장 프로시저 매개변수가 선언된 순서와 다른 순서로 매개변수를 융통성 있게 추가할 수 있음을 의미합니다. 두 번째 매개변수는 PL/SQL 저장 프로시저 매개변수의 데이타 유형입니다. 여기서 Value 속성은 Value 매개변수가 OracleDbType 열거 객체에 위치해 있기 때문에 받아들일 수 있는 유형으로 해석되어야 합니다. 이 두 가지 생성자 매개변수는 OracleParameter 객체 및 OracleCommand 객체에 저장된 프로시저를 호출하는 방법과 저장된 프로시저로부터 반환되는 값을 해석하는 방법을 알려 줍니다.

다음 단계

ODP.NET 다운로드
otn.oracle.com/tech/windows/odpnet

ODP.NET 예제코드
otn.oracle.com/sample_code/tech/windows/odpnet

ODP.NET 세미나 참여
oracle.com/iseminars
.NET 용 Oracle Data Provider 사용하기

본문의 예제에서는 4개의 OracleParameter 객체를 OracleCommand 객체에 추가하였습니다. 이 중 두 가지는 open_two_cursor 저장된 프로시저를 위한 n_empid 및 n_empid2 input 매개변수이고, OracleDbType.Int64 유형입니다. 그 밖에도 OracleParameter 객체의 Value 속성을 사용하여 매개변수 값을 1과 2로 설정해야 합니다.

세 번째 및 네 번째 매개변수(io_cursor와 io_cursor2)는 open_two_cursor 저장 프로시저의 출력 매개변수입니다. io_cursor 매개변수인 경우, 값을 저장 프로시저로 전달하지 않고, 대신 저장 프로시저가 참조 커서를 반환합니다. 이 단계를 처리하기 위해 OracleParameter 객체를 OracleDbType.RefCursor유형으로 추가합니다. OracleCommand 객체가 이 객체를 출력 매개변수로 인식하도록 하려면, OracleParameter 객체의 Direction 속성을 ParameterDirection.Output으로 설정하십시오.

그런 다음, 목록 6에서와 같이 저장 프로시저를 실행하고 그 결과를 불러 오십시오. 목록 6의 1행에서는 OracleCommand 객체의 ExecuteNonQuery() 메소드를 호출하십시오. 이 메소드는 저장된 프로시저를 호출하고, 그 결과를 적절한 OracleParameter 객체로 다시 읽어 들입니다.
2행과 3행에서는 반환 값을 OracleRefCursor 객체로 전달하십시오. ODP.NET이 값을 다시 전달하는 저장된 프로시저를 실행하면, 관련된 OracleParameter 객체의 Value 속성에 이 값이 저장됩니다.
4행에서 9행까지에서는 OracleDataReader 객체들을 생성하고 이 객체들을 참조 커서 결과에 설정하며, Read() 메소드를 호출하여 첫번째 레코드로 이동시키고, OracleDataReader 객체로부터 이름 열을 불러오십시오.
OracleDataReader 객체에는 NextResult()라고 불리는 메소드가 포함되어 있습니다. 이 메소드는 ExecuteReader() 메소드와 함께 사용할 수 있으며 저장된 프로시저로부터 참조 커서를 통해 결과를 연속적으로 반복할 수 있습니다. NextResult() 메소드를 사용하면, 한 가지 OracleDataReader 객체를 사용하여 모든 참조 커서에 액세스할 수 있습니다. 그러나 이렇게 하면 연속적으로 참조 커서에 액세스할 수가 없습니다. 여러 참조 커서 결과에 액세스하는데 사용되는 메소드는 애플리케이션에 따라 다릅니다.

결론

.NET용 Oracle Data Provider는 사용하기 쉬우며 강력한 API를 제공할 뿐 아니라, OLE DB .NET에 대해 획기적으로 개선된 성능을 제공합니다. 새로운 Oracle 데이타베이스나 기존의 Oracle 데이타베이스에 액세스가 가능한 .NET 프레임워크에서 애플리케이션을 개발하고자 하는 경우에는, 이 신기술을 친숙하게 사용함으로써 여러분의 삶을 보다 편리하게 만들 수 있습니다.

Robert P. Lipschutz (rob@thing7.com) 씨는 기술 백서와 프로토타입을 전문적으로 다루는 Thing 7의 대표이며, Gregg D. Harrington (gregg@thing7.com) 씨는 Thing 7의 선임 이사입니다.

표 1: DEJAVU_EMPLOYEE 테이블
FIELD TYPE PURPOSE
ID NUMBER(10) 직원들의 ID 보유(데이타베이스의 기본 키)
NAME VARCHAR2(32) 직원 이름 보유
PHOTO BLOB 직원들의 사진 보유
BIGNUMBER NUMBER(38,37) High-precision(고밀도) 번호 보유
표 2: 널리 사용되는 Oracle 연결 옵션 간의 기본 옵션 명령 비교
작업 ODP.NET OLE DB .NET ORACLE THIN JDBC DRIVER
연결 OracleConnection OleDbConnection OracleConnection database
SQL 생성 OracleCommand OleDbCommand OracleStatement commands
데이타 읽기 OracleDataReader OleDbDataReader OracleResultSet
Oracle용 .NET Framework Data Provider

Oracle뿐 아니라 Microsoft에서도 또한 Oracle용 .NET Framework Data Provider라 불리는 Oracle을 위한 데이타베이스 드라이버를 출시하였습니다. .NET용 Oracle 데이타베이스 연결은 매우 중요하기 때문에 Oracle과 Microsoft가 모두 이와 관련된 문제를 해결하고자 노력하는 것은 놀라운 일이 아닙니다. Oracle의 ODP.NET과 관련하여 Microsoft 제품은 보다 구체적인 Oracle 기능을 제공하고 있으며, OLE DB .NET 보다 나은 성능을 제공하고 있습니다. 그러나 Microsoft 공급자는 ODP.NET보다 못한 기능을 제공하고 있는 것 또한 사실입니다.

예를 들어, ODP.NET API는 트랜잭션 저장점, 고유의 참조 커서 데이타 유형, 프록시 사용자 인증, 글로벌 기능을 지원합니다. 또한 ODP.NET는 Oracle BLOB와 CLOB 데이타베이스 객체에 대해 특정한 객체를 공급하는 반면, Microsoft 솔루션은 동일한 방식으로 모든 Oracle LOB의 데이타베이스 객체를 처리하는 OracleLob 객체를 제공합니다. 이는 ODP.NET이 Oracle 데이타 유형에 대해 보다 나은 지원을 제공한다는 것을 의미합니다. 예를 들어, OracleClob 객체는 read 메소드를 우선적으로 적용하여 문자 배열을 채웁니다. 이 기능은 유니코드와 멀티바이트 문자를 처리할 때 더 유용합니다.

Microsoft의 솔루션과 ODP.NET는 모두 작성 및 LOB 실행 측면에서 가장 뛰어난 OLE DB .NET보다 더 나은 데이타베이스 액세스 성능을 제공합니다. Microsoft는 Visual Studio .NET의 다음 버전에 새로운 버전의 .NET Framework Data Provider를 포함시킬 예정입니다.

댓글 없음:

댓글 쓰기