12장. 데이터베이스와 자이썬: 객체 관계형 대응 및 JDBC 사용

이번 장에서는 버전 2.1에서부터 자이썬의 표준의 일부이며 파이썬 2.0 DBI 표준을 준수하는 zxJDBC 패키지에 대해서 알아보도록 하겠다. zxJDBC는 데이터베이스 이식성을 고려하지 않는 환경에서 사용하는 단순한 일회성 스크립트를 위한 좋은 선택이 될 수 있다. 덧붙이자면, (일반적으로) SQLAlchem​​y 또는 Django에 대해 새 방언dialect을 작성할 때 zxJDBC를 사용할 필요가 있다. (엄밀히 말하자면 사실이 아니다. 순수 파이썬 DBI 드라이버인 pg8000를 사용할 수 있으며, 물론 직접 제작한 DBI 드라이버를 사용할 수 있다. 하지만 권하지는 않는다.) 따라서 zxJDBC가 어떻게 동작하는지를 알아두면 이러한 꾸러미들을 사용할 때에 도움이 된다. 그렇지만, 그러한 것은 일반적인 사용에 비하여 너무 하부적인 내용에 해당한다. 그러니 가능하다면 SQLAlchem​​y나 Django를 사용하자. 마지막으로, JDBC 자체도 역시 다른 Java 패​​키지와 마찬가지로 자이썬에서 직접 접근할 수 있다. 그저 java.sql 꾸러미를 사용하면 된다. 실제로는 좀처럼 필요하지 않을 것이다.

이번 장에서 두 번째로 다룰 것은 자이썬과 객체 관계형 대응object relational mapping을 사용하는 방법이다. 자이썬 2.5 릴리스는 객체 관계형 대응에 대한 여러 가지 새로운 옵션을 제시했다. 이 장에서 우리는 자이썬과 SQLAlchem​​y를 사용하는 것 뿐만 아니라 하이버네이트와 같은 자바 기술을 사용하는 방법도 살펴볼 것이다. 최종적으로는 여러분의 자이썬 애플리케이션에서 사용할 수 있는 객체 관계형 대응을 위한 두 가지 다른 선택을 할 수 있다.

ZxJDBC — JDBC를 통해 파이썬의 DB API 사용

zxJDBC 꾸러미는 사용하기 쉽도록 만들어진 JDBC 파이썬 래퍼를 제공한다. zxJDBC는 다음 두 표준을 이어준다.

JDBC는 자바에서 데이터베이스 접근을 위한 표준 플랫폼이다.

DBI는 파이썬 애플리케이션의 표준 데이터베이스 API이다.

ZxJDBC, 자이썬의 일부는 JDBC에 대한 DBI 2.0 표준 호환 인터페이스를 제공한다. 200 개 이상의 드라이버를 JDBC(http://developers.sun.com/product/jdbc/drivers)에 사용할 수 있으며, 그것들은 모두 zxJDBC와 함께 사용할 수 있다. 고성능 드라이버는 DB2, Derby, MySQL, 오라클, PostgreSQL, SQLite, SQL 서버 및 Sybase를 포함한 모든 주요 관계형 데이터베이스에 대해 사용할 수 있다. 또한 비관계형 및 특수 데이터베이스를 위한 드라이버도 있다.

그러나, JDBC와는 달리, zxJDBC는 가능한 단순하게 사용할 때조차, SQL 주입 공격을 차단하고, 과부하를 최소화하며, 자원 고갈을 방지한다. 또한, zxJDBC는 autocommit 대신에 트랜잭션 모델을 기본으로 한다(사용 가능할 때).

먼저 우리는 단지 다른 DBI 꾸러미처럼 zxJDBC 작업의 주요 자료를 접속하고 커서에서 볼 수 있다. 그리고 일반적인 질의와 데이터 조작 트랜잭션의 관점에서, 그것들을 가지고 할 수 있는 일들을 살펴볼 것이다.

시작하기

데이터베이스 뒷단을 활용하는 애플리케이션 개발의 첫 단계는 애플리케이션이 어떤 데이터베이스를 사용할지 정하는 것이다. zxJDBC나 여타의 JDBC 구현을 사용할 경우에는, 어느 데이터베이스를 사용할지 결정하는 것이 전반적인 개발 과정에 큰 영향을 끼친다. 많은 애플리케이션 개발자들이 이러한 이유로 객체 관계형 대응을 사용할 것이다. 애플리케이션을 JDBC 구현을 이용하여 코딩할 때에는, SQL을 손으로 코딩해야 하므로, 특정 데이터베이스에서만 통하는 SQL 방언을 사용하게 된다. 객체 관계형 대응(ORM) 기술의 장점 중 하나는 개발자가 SQL에 신경 쓸 필요가 없다는 것이다. ORM 기술은 보이지 않는 곳에서 서로 다른 방언에 대한 처리를 해준다. 이것은 ORM 기술이 많은 다른 데이터베이스에 대한 지원을 구현하는 것이 더뎌지는 이유이기도 한다. 예컨대 SQLAlchemy나 장고와 같은 기술을 지원하기 위해서는 각각의 데이터베이스에서 쓰이는 방언을 코딩해야만 한다. ORM을 사용하면 여러 다른 데이터베이스 간의 이식성이 높은 애플리케이션을 만들 수 있다. 어쨌든, 여러분의 애플리케이션이 한두 개의 데이터베이스만을 대상으로 삼을 것이라면 서두에서 기술한 zxJDBC를 사용하는 것도 좋은 선택이 될 것이다.

자바를 위한 JDBC를 사용하기 위해서는 드라이버를 찾고 등록하는 작업이 필요하다. 주요 데이터베이스들은 대부분 JDBC 드라이버를 쉽게 사용할 수 있도록 해두었다. 어떤 곳에서는 드라이버를 다운로드하기 전에 등록을 거치도록 하거나, 구입하도록 하기도 한다. zxJDBC는 JDBC의 대체 구현이기 때문에, 하나는 API를 사용하기 위해서는 JDBC 드라이버를 사용해야 한다. 대부분의 JDBC 드라이버는 애플리케이션 서버 컨테이너 및 IDE에 설치할 수 있도록 JAR 형식으로 제공된다. 특정 데이터베이스 드라이버를 이용하기 위해서는, CLASSPATH 내에 위치시켜야 한다. 앞서 말했듯이, 특정 데이터베이스에 맞는 JDBC 드라이버를 찾으려면, 썬 마이크로 시스템즈 JDBC 드라이버 검색 페이지 (http://developers.sun.com/product/jdbc/drivers)를 보라. 오늘날 사용되는 대부분의 JDBC 드라이버의 목록을 담고 있다.

Note

이 섹션의 예제는 자이썬 2.5.1 이상을 위한 것이다. 자이썬 2.5.1는 접속 및 커서 작업을 위한 몇 가지 simplifications을 소개했다. 또한, 대부분의 예제는 PostgreSQL의 world 예제 데이터베이스를 사용함을 가정하였다(MySQL에서도 가능). 다음 섹션에 있는 예제를 수행하기 위해서는 world 데이터베이스 예제를 사용할 수 있는 PostgreSQL 데이터베이스가 있어야 한다. 데이터베이스를 다운로드하려면 http://www.postgresql.org의 홈페이지로 이동하기 바란다. 이 책에서 필요한 world 데이터베이스 샘플 및 소스를 구할 수 있다. psql을 열어서 다음의 명령을 수행하여 PostgreSQL에 데이터베이스에 설치할 수 있다.

postgres=# \\i <path to world sql>/world.sql

이전에 언급한 바와 같이, 드라이버를 구했으면 반드시 classpath에 위치하도록 해야 한다. 다음으로는 가장 인기있는 두 가지 데이터베이스를 위한 JDBC 드라이버를 CLASSPATH에 추가하는 예제이다.

예제 12-1. 주요 데이터베이스를 위한 JDBC 드라이버를 CLASSPATH에 추가

# Oracle
# Windows
set CLASSPATH=<PATH TO JDBC>\\ojdbc14.jar;%CLASSPATH%
# OS X
export CLASSPATH=<PATH TO JDBC>/ojdbc14.jar:$CLASSPATH
# PostgreSQL
# Windows
set CLASSPATH=<PATH TO JDBC>\\postgresql-x.x.jdbc4.jar;%CLASSPATH%
# OS X
export CLASSPATH=<PATH TO
JDBC>/postgresql-x.x.jdbc4.jar:$CLASSPATH

대상 데이터베이스에 해당하는 JAR 파일이 CLASSPATH에 추가된 후에는, 개발을 시작할 수 있다. zxJDBC은 (그리고 다른 모든 JDBC 구현에서도) 데이터베이스 작업을 위한 유사한 절차를 사용한다는 것이 중요하다. JDBC 구현을 사용하여 다음과 같은 작업을 수행해야 한다.

  • 접속을 생성
  • 질의 또는 문장을 생성
  • 질의 또는 문장의 결과를 얻음
  • 검색어를 사용하는 경우, 커서에 결과를 얻어내고 작업을 수행하기 위해 자료를 탐색
  • 커서를 닫음
  • 접속을 닫음(2.5.1 버전 이전의 자이썬에서 with_statement 구문을 사용하지 않는 경우)

각 단계와 JDBC를 직접 사용하는 것에 비하여 zxJDBC가 어떤 점이 더 편리한지 살펴보자.

접속

데이터베이스 접속은 단순히 데이터베이스 시스템에 대한 접근을 관리하는 자원 개체이다. 데이터베이스 자원은 일반적으로 할당에 드는 비용이 높고 쉽게 소진될 수 있으므로, 사용을 마친 후에는 즉시 종료하는 것이 중요하다. 데이터베이스 접속을 만드는 데에는 두 가지 방법이 있다.

  • 직접 생성. 스크립트와 같은 독립 실행 코드는 접속을 직접 생성한다.
  • JNDI. 컨테이너에 의해 관리되는 코드는 접속 생성을 위해 JNDI를 사용해야 한다.

이러한 컨테이너에는 글래스피쉬, JBoss, 톰캣, 웹로직, 웹스피어 등이 있다. 보통은 이 문맥 하에서 실행될 때 접속이 풀링되며 또한 주어진 보안 문맥에 연관된다.

다음은 자이썬 2.5.1를 사용하여 관리되는 컨테이너의 외부 데이터베이스 접속을 만들 수 있는 가장 좋은 방법의 예이다. 2.5.1 이전에서는 with_statement 구문을 사용할 수 없음에 유의하라. 이것은 이전에 2.5.1로 자이썬의 버전에서 PyConnection의 기본 구현 때문이다. 어떤 개체가 with_statement*를 통하여 쓰여질 수 있도록 하기 위한 규칙은, *__exit__ 메서드와 같은 특정 기능을 반드시 구현해야 한다는 것이다. 이전 2.5.1로 버전에서이 기능을 구현하는 방법을 알아 다음 노트를 참조하라. 통지에 또 한가지 접속하기 위해, 우리는이 경우에는 주어진 데이터베이스, PostgreSQL을의 기준을 준수하는 JDBC URL을 사용해야한다는 것이다.

예제 12-2.

from __future__ import with_statement
from com.ziclix.python.sql import zxJDBC

# for example
jdbc_url = "jdbc:postgresql:test"
username = "postgres"
password = "jython25"
driver = "org.postgresql.Driver"

# with 문을 사용하여 접속을 얻음
with zxJDBC.connect(jdbc_url, username, password, driver) as conn:
    with conn:
        with conn.cursor() as c:
            c.execute("select name from country")
            c.fetchone()

몇 단계를 거쳐, 접속을 얻기 위하여 with_statement와 zxJDBC를 가져온 것을 볼 수 있다. 다음 단계는 접속 activity에 사용할 일련의 문자열 값을 정의하는 것이다. 전역으로 설정하는 경우에는 단 한 번만 하면 된다. 드디어, 접속을 얻었으며 할 일이 끝났다. 이제 비교를 위해 동일한 절차를 Java로 코딩한 것을 살펴보자.

예제 12-3.

import java.sql.*;
import org.postgresql.Driver;
...
// In some method
Connection conn = null;
String jdbc_url = "jdbc:postgresql:test";
String username = "postgres";
String password = "jython25";
String driver = "org.postgresql.Driver";
try {
  DriverManager.registerDriver(new org.postgresql.Driver());
  conn = DriverManager.getConnection(jdbc_url,
               username, password);
  // do something using statement and resultset
  conn.close();
} catch(Exception e) {
  logWriter.error("getBeanConnection ERROR: ",e);
}

Note

파이썬 버전 2.5.1 이전에서는 with_statement 구문은 사용할 수 없다. 그러므로, 우리는 접속을 직접 다루어야 한다(즉, 접속이 완료되면 직접 닫아주어야 한다). 다음의 코드는 with_statement 기능 없이 zxJDBC 접속을 사용하는 예제이니 한 번 살펴보기 바란다.

from __future__ import with_statement from
com.ziclix.python.sql import zxJDBC

# for example
jdbc_url = "jdbc:postgresql:test"
username = "postgres"
password = "jython25"
driver = "org.postgresql.Driver"

conn = zxJDBC.connect(jdbc_url, username, password, driver)
do_something(conn) # 접속(및 커서)을 닫아주어야 한다
conn.close()

with 문을 사용하는 경우에는 작업이 끝나는 즉시 접속을 중단한다는 것이 보장된다. 다른 방법으로는 finally를 써서 접속을 닫을 수도 있다. 후자의 기법을 사용하면 예외 처리를 더 탄탄하게 작성할 수도 있지만, 그만큼 코드의 양이 더 늘어나게 된다. 앞서 말한 바와 같이 자이썬 2.5.1 이전 버전에서는 with 문을 사용할 수 없으므로 그러한 버전에서는 다음과 같이 작성하기를 권한다.

예제 12-4.

try:
    conn = zxJDBC.connect(jdbc_url, username, password, driver)
    do_something(conn)
finally:
    conn.close()

zxJDBC의 접속 (PyConnection) 객체는 다양한 메서드와 속성을 가지고 있어서, 다양한 기능을 수행하고 메타 자료 정보를 얻는 데 사용할 수 있다. 예를 들어, close 메소드는 접속을 닫는 데 사용할 수 있다. 표 12-1 및 12-2에 접속에 대하여 사용할 수 있는 모든 메소드 및 속성을 각각 나열하였다.

표 12-1. Connection 메소드

메서드 기능
close (__del__가 호출될 때마다가 아니라) 지금 접속을 닫음
commit 해당 접속을 거쳐 수행된 모든 작업을 확정
cursor 접속으로부터 새 커서 개체를 반환
rollback 데이터베이스가 트랜잭션을 제공하는 경우, 이 메서드는 데이터베이스로 하여금 pending 트랜잭션의 시작점으로 되돌리도록 함
nativesql 표준 SQL 구문을 시스템 고유의 SQL 문법에 맞도록 변환

표 12-2. Connection 속성

속성 기능
autocommit 접속에서 자동확정을 사용하거나 사용하지 않도록 설정. 기본값은 비활성화되어 있음.
dbname 데이터베이스의 이름을 반환
dbversion 데이터베이스의 버전을 반환
drivername 데이터베이스 드라이버 이름을 반환
driverversion 데이터베이스 드라이버 버전을 반환
url 사용중인 데이터베이스 URL을 반환
__connection__ 사용 중인 접속의 유형을 반환
__cursors__ 접속에서 열려있는 모든 커서의 목록을 반환
__statements__ 접속에서 열려있는 모든 문장의 목록을 반환
closed 접속이 닫혀 있는지 여부를 나타내는 부울을 반환

물론, 우리는 항상 예제 12-5에 표시된 구문과 같이 접속을 사용하여 모든 메서드와 속성의 목록을 얻을 수 있다.

예제 12-5.

>>> conn.__methods__
['close', 'commit', 'cursor', 'rollback', 'nativesql']
>>> conn.__members__
['autocommit', 'dbname', 'dbversion', 'drivername',
'driverversion', 'url', '__connection__', '__cursors__',
'__statements__', 'closed']

Note

접속 풀은, 접속이 실제로 유효하다는 것을 보증할 수 있는 동안에 접속의 재사용을 제공함으로써, 보다 튼튼한 작동을 보증해준다. 종종 순진한 코드는 접속을 만드는 오버헤드를 피한다는 명목으로 아주 긴 시간 동안 접속을 유지하곤 하는데, 네트워크 또는 서버에 문제가 발생하는 경우에는 재접속을 처리하는 데에 곤란을 겪게 된다. 이미 만들어진 접속 풀 하부구조를 이용하여 관리하는 것이, 밑바닥부터 새로 만드는 것보다야 낫다.

모든 트랜잭션은, 지원되는 경우, 접속의 문맥 내에서 수행된다. 트랜잭션에 대해서는 데이터 수정에 관한 하위 섹션에서 다루겠지만, 기본적인 용법은 예제 12-6과 같다.

예제 12-6. 트랜잭션 예제

try:
    # 자동 확정을 사용하지 않는 접속을 구한다(zxJDBC에 대한 기본값)
    conn = zxJDBC.connect(jdbc_url, username, password, driver)
    # 접속을 통하여 모든 작업을 수행 do_something(conn)
    do_something(conn)
    # 모든 작업 완료 후 확정
    conn.commit()
except:
    # 문제가 발생할 경우, 이전의 모든 작업을 되돌림
    conn.rollback()

ZxJDBC.lookup

관리되는 컨테이너에서는 zxJDBC.lookup 대신 zxJDBC.connect을 사용한다. 내부와 외부 컨테이너 모두를 실행하는 데 필요한 코드가 있다면, 이를 추상화해주는 팩토리를 사용하기를 권한다. 앱 서버와 같은 컨테이너 내에서는, 자원을 할당하기 위하여 JDNI를 사용해야 한다. 일반적으로 접속은 접속 풀을 통해 관리한다(예제 12-7 참조).

예제 12-7.

factory = "com.sun.jndi.fscontext.RefFSContextFactory"
db = zxJDBC.lookup('jdbc/postgresDS',
    INITIAL_CONTEXT_FACTORY=factory)

이 예제는 “jdbc/postgresDS” 컨테이너에 정의된 데이터 원본의 이름이라고 가정하고 Sun 파일시스템 JNDI 참조 구현을 사용한다. 이 조회 프로세스는 JDBC URL 또는 드라이버 팩토리 클래스를 알 필요는 없다. 아마도 이러한 측면뿐만 아니라 사용자 이름과 암호는 해당 컨테이너에 특정 도구를 사용하여 컨테이너의 관리자에 의해 구성된다. JNDI 이름은 jdbc/이름 형식을 따르는 것이 일반적인 관습이다.

커서

일단 접속이 되면, 아마도 그것을 가지고 뭔가를 하려고 할 것이다. 한 트랜잭션 내에서 여러 가지 작업-테이블을 질의하고, 다른 테이블을 갱신하는 등-을 처리할 수 있기 때문에, 한 가지 자원이 더 필요한데, 그것은 커서이다. zxJDBC의 커서는 JDBC 구문 및 결과집합 개체를 감싸는 래퍼로서, 데이터베이스 작업을 위한 매우 파이썬다운 구문을 제공한다. 그 결과는 사용하기 쉬우면서도 극도로 융통성있는 API이다. 커서는 데이터베이스로부터 획득한 자료를 가지고 있다가, 앞으로 보게 될 다양한 방법으로 사용할 수 있도록 해준다. 커서에는 정적 커서와 동적 커서의 두 가지 유형이 있다. 기본 유형은 정적 커서이며, 그것은 기본적으로 한번에 전체 결과 집합에서 반복을 수행한다. 후자의 동적 커서는 게으른 커서로 알려져 있으며 그것은 필요할 때에만 가져오는 방식으로 결과 집합을 통해 반복된다. 다음 목록은 커서의 각 유형을 만드는 예제이다.

예제 12-8. 가능한 모든 커서 유형 만들기

# 필요한 import가 수행되었으며 접속을 얻어
# 변수 'conn'에 할당되었다고 가정
cursor = conn.cursor() # 정적 커서를 생성
cursor = conn.cursor(True) # 부울 인수를 사용하여 동적 커서를 생성

메모리의 제약으로 인하여 동적 커서의 성능이 더 좋은 경향을 보인다. 그러나, 때에 따라서는 정적 커서에 비하여 사용이 불편한 경우도 있다. 예를 들어, 정적 커서에서는 모든 행을 한번에 얻어오기 때문에, 행 갯수를 얻기 위해 데이터베이스를 질의하는 것은 매우 쉽다. 그러나 동적 커서에서는 한번에는 불가능하며 동일한 결과를 얻기 위해 두 번의 질의를 수행해야 한다.

예제 12-9.

# 정적 커서를 사용하여 rowcount를 구함
>>> cursor = conn.cursor()
>>> cursor.execute("select * from country")
>>> cursor.rowcount
239
# 동적 커서를 사용하여 rowcount를 구함
>>> cursor = conn.cursor(1)
>>> cursor.execute("select * from country")
>>> cursor.rowcount
0
# rowcount는 동적으로 작동하지 않기 때문에,
# 정보를 얻기 위해 별도의 카운트 질의를 수행해야 한다
>>> cursor.execute("select count(*) from country")
>>> cursor.fetchone()
(239L,)

커서는 질의, 삽입, 갱신, 삭제를 실행하며 데이터베이스 명령을 수행하는 데 사용된다. 접속과 마찬가지로, 커서는 작업을 수행하거나 메타 데이터 정보를 얻는 데 사용할 수 있는 여러 가지 메서드와 속성을 갖고 있다. 표 12-3과 12-4를 참조하라.

표 12-3. 커서 메서드

메서드 기능
tables 테이블의 목록을 뽑아냄. (카탈로그, 스키마 패턴, 테이블 패턴, 유형)
columns column의 목록을 뽑아냄. (카탈로그, 스키마 패턴, 테이블 이름 패턴, 컬럼 이름 패턴)
primarykeys primary 키 목록을 뽑아냄. (카탈로그, 스키마, 테이블)
foreignkeys 외부foreign 키 목록을 뽑아냄. (주 카탈로그, 주 스키마, 주 테이블, 외부 카탈로그, 외부 스키마, 외부 테이블)
procedures 프로시저의 목록을 뽑아냄. (카탈로그, 스키마, 테이블)
procedurecolumns 프로시저 열의 목록을 뽑아냄. (카탈로그, 스키마 패턴, 프로시저 패턴, 컬럼 패턴)
statistics 질의에 대한 통계를 얻음. (카탈로그, 스키마, 테이블, 고유값, 근사값)
bestrow 고유 행row을 식별하는 최적의 집합
versioncolumns 행의 어느 값이라도 갱신되면 자동으로 갱신되는 열
close 커서를 닫음
execute 커서에 포함된 코드를 실행
executemany 매개 변수 목록을 예비문 또는 SQL을 실행하는 데 사용
fetchone 질의 결과 집합의 다음 행을 가져와서 단일한 순서형을 반환하거나, 더 이상 자료가 존재하지 않는 경우에는 None을 반환
fetchall 질의 결과의 (남아있는) 모든 행을 가져와서, 순서형들의 순서형으로 반환
fetchmany 질의 결과 행의 다음 집합을 가져와서, 순서형들의 순서형을 반환
callproc 저장 프로시저(Stored Procedure)를 실행
next 커서의 다음 행으로 이동
write 이 유사 파일 개체에 쓰여진 SQL을 실행

표 12-4. 커서 속성

속성 기능
arraysize Number of rows fetchmany() should return without any arguments
rowcount 결과 행의 개수를 반환
rownumber 현재 행 번호를 반환
description 질의의 각 열에 관한 정보를 반환
datahandler 지정된 datahandler를 반환
warnings 커서에 대한 모든 경고를 반환
lastrowid 마지막으로 가져온 행의 rowid를 반환
updatecount 현재 커서에서 수행한 갱신 횟수를 반환
closed 커서가 닫혔는지의 여부를 나타내는 부울값을 반환
connection 접속 커서를 포함하는 접속 객체를 반환

위의 메서드와 속성 중 상당수는 질의 또는 문장을 가지고 커서를 실행하기 전까지는 사용할 수 없다. 대부분의 경우에, 특정 메서드 및 속성의 이름이 그 기능을 잘 나타내준다.

질의 생성 및 실행

앞에서 본 것과 같이, 지정된 커서에 대해 질의를 시작하는 것은 매우 쉽다. 단순히 문자열 형식의 select 문을 커서 execute() 또는 executemany()의 매개변수로 제공하고, 반환되는 결과에 대하여 fetch 메서드 중 하나를 사용하여 이터레이션을 하면 된다. 다음 예제에서는 world 데이터를 질의하고 커서에 관련된 속성 및 메서드를 통해 커서에 대한 자료를 표출한다.

예제 12-10.

>>> cursor = conn.cursor()
>>> cursor.execute("select country, region from country")
# 다음(next) 레코드를 조회
>>> cursor.fetchone()
((AFG,Afghanistan,Asia,"Southern and Central Asia",652090,1919,22720000,45.9,5976.00,,Afganistan/Afqanestan,"Islamic
Emirate","Mohammad Omar",1,AF), u'Southern and Central Asia')
# 매개 변수없이 fetchmany()를 호출하면 다음 레코드를 반환
>>> cursor.fetchmany()
[((NLD,Netherlands,Europe,"Western
Europe",41526,1581,15864000,78.3,371362.00,360478.00,Nederland,"Constitutional
Monarchy",Beatrix,5,NL), u'Western Europe')]
# 다음 두 레코드를 가져오기
>>> cursor.fetchmany(2)
[((ANT,"Netherlands Antilles","North America",Caribbean,800,,217000,74.7,1941.00,,"Nederlandse
Antillen","Nonmetropolitan Territory of The Netherlands",Beatrix,33,AN), u'Caribbean'),
((ALB,Albania,Europe,"Southern
Europe",28748,1912,3401200,71.6,3205.00,2500.00,Shqip?ria,Republic,"Rexhep Mejdani",34,AL), u'Southern Europe')]
# fetchall()를 호출하여 레코드의 나머지 부분을 뽑아냄
>>> cursor.fetchall()
...
# description을 사용하여 커서의 질의에 대한 자료를 얻음
>>> cursor.description
[('country', 1111, 2147483647, None, None, None, 2), ('region', 12, 2147483647, None, None, None, 0)]

with_statement을 사용하면 손쉽게 커서를 생성할 수 있다. 자이썬 2.5.1 이후에서는 다음의 예제를 살펴보기 바란다.

예제 12-11.

with conn.cursor() as c:
    do_some_work(c)

접속과 마찬가지로, 자원을 적절하게 닫았는지 확인할 필요가 있다. 아래의 짤막한 예제를 따라해보도록 하자.

예제 12-12.

>>> c = conn.cursor()
>>> # work with cursor

이와 같이, 커서를 사용하여 쉽게 질의를 할 수 있다. 이전 예제에서, 우리는 질의의 결과를 모두 검색할 수있는 fetchall() 메서드를 사용한다. 그러나, fetchone()fetchmany() 옵션을 포함하여 모든 결과를 원하는 없다 경우에 사용 가능한 다른 옵션이 있다. 때로는 별도로 각 레코드 작업하기 위해서는 질의의 결과를 통해 반복하는 것이 최선이다. 예제 12-13은 country 테이블에 포함된 국가에 대하여 이터레이트한다.

예제 12-13.

>>> from com.ziclix.python.sql import zxJDBC
>>> conn = zxJDBC.connect("jdbc:postgresql:test","postgres","jython25","org.postgresql.Driver")
>>> cursor = conn.cursor()
>>> cursor.execute("select name from country")
>>> while cursor.next():
...     print cursor.fetchone()
...
(u'Netherlands Antilles',)
(u'Algeria',)
(u'Andorra',)
...

종종 질의는 하드 코딩되지 않으며, 우리는 우리의 애플리케이션에 필요한 데이터를 선택 질의에서 값을 대체할 수 있는 방법이 필요하다. 개발자는 또한 때로는 동적 SQL 문을 만드는 방법이 필요하다. 물론, 이러한 것들을 수행하는 여러 가지 방법이 있다. 변수를 대체하거나 동적 질의를 만드는 가장 쉬운 방법은 단순히 문자열 접속을 사용하는 것이다. 결국, execute() 메서드는 문자열로 작성된 질의를 매개변수로 취한다. 예제 12-14는 동적 질의를 형성하며 변수를 대체에 대한 문자열 접속을 사용하는 방법을 보여준다.

예제 12-14. 동적 질의 형성에 대한 문자열 접속

...
# 사용자가 결과 데이터베이스 중 대륙 또는 국가 이름에서 검색할 무엇을 결정하는 풀다운 메뉴 선택을 선택한 것으로 가정한다.
# 선택한 선택은 selectedChoice 변수에 저장된다.
# 우리는 문자 "A"로 시작하는 모든 대륙 또는 국가에 관심이 있다고 가정하자
>>> qry = "select " + selectedChoice + " from country where " + selectedChoice + " like 'A%'"
>>> cursor.execute(qry)
>>> while cursor.next():
... print cursor.fetchone()
...
(u'Albania',)
(u'American Samoa',)
...

이 기술은 동적 질의를 만드는 아주 잘 작동하지만, 또한 문제를 안고 있다. 예를 들어, 코드의 접속된 문자열을 통해 읽는 것은 눈에 골칫거리가 될 수 있다. 이러한 코드를 유지하는 것은 지루한 작업이다. 그보다도, 문자열 이어붙이기는 SQL 주입 공격의 빌미를 제공하므로 그다지 안전한 방법이 못된다. SQL 주입은 원치 않는 작업을 수행하는 것과 같은 방법으로 좋지 않은 SQL 코드를 애플리케이션에 전달하는 데에 사용되는 기법이다. 사용자가 텍스트 입력란에 자유롭게 입력할 수 있도록 하여 그 텍스트를 포함하는 문자열 이어붙이기를 통하여 질의를 생성하는 경우, 특정 열쇠말이나 주석 기호를 포함하지 못하도록 걸러내는 별도의 수단을 강구하는 것이 최선이다. 이러한 문제를 피할 수 있는 더 좋은 방법은 예비문을 사용하는 것이다.

Note

이상적으로는, 사용자 자료로부터 직접적으로 질의문을 구축하지 않아야 한다. SQL 주입 공격이 그러한 부분의 헛점을 파고들기 때문이다. 악의가 없는 경우라 하더라도, 사용자 자료는 따옴표와 같이, 제대로 이스케이프 처리를 하지 않을 경우 질의가 실패하는 요인이 되곤 한다. 모든 경우에 있어서, 질의에 사용하기에 앞서 scrub하고 escape하는 것이 중요하다.

또 다른 고려사항은, 그러한 질의는 데이터베이스 문장 캐쉬가 되지 않을 경우에는 더 많은 자원을 소모한다는 점이다.

그러나 이러한 권고사항에도 두 가지 중요한 예외가 있다.

SQL 문의 요구사항: 바인드 변수를 어디서나 사용할 수는 없다. 사용가능여부는 데이터베이스에 따라 다르다.

임의의 질의 또는 대표성이 없는 질의: 오라클과 같은 데이터베이스에서는, 문장 캐쉬는 색인된 값의 account lopsided distribution을 취하는 것이 아니라 실행 계획을 캐쉬하며, 리터럴하게 표현될 경우 데이터베이스에 알려진다. 이러한 경우에서는, 문장에 값을 직접 넣음으로써 보다 효율적인 실행계획을 얻을 수 있을 것이다.

그러나, 이러한 예외적인 경우에 있어서도, 사용자 자료는 완전히 scrub되어야 한다. 좋은 방법은 대응 테이블, 내부 사전 하나 또는 데이터베이스 자체에서 구동 대응 테이블의 일종을 사용하는 것이다. 어떤 경우에는 신중하게 만들어진 정규 표현식도 작동한다. 주의를 기울이도록 하자.

예비문

변수를 대체에 대한 문자열 접속 기술을 사용하여 주위를 얻으려면, 예비문prepared statements으로 알려진 기술을 사용할 수 있다. 준비 문장 하나는 데이터 치환에 대한 바인드 변수를 사용할 수 있도록하고, 대부분의 보안 고려 사항은 개발자의 개입없이 처리하기 때문에 그들은 일반적으로 사용하는 안전하고 있다. 그러나, 그것은 항상 위험을 줄일 수 있도록 입력을 필터링하는 것이 좋다. 그들은 JDBC, 그냥 간단한 문법처럼 zxJDBC의 예비문은 동일하게 작동한다. 예제 12-15에서, 우리는 예비문을 사용하여 country 테이블에 질의를 수행한다. 물음표가 치환 변수를위한 장소 홀더로 사용된다. 예비문을 사용할 때에는 executemany() 메서드가 호출됨에 유의하기 바란다. 예비문에 전달되는 모든 대체 변수는 튜플 또는 목록의 형태로해야 한다.

예제 12-15. 예비문 사용하기

...
# 문자열 값을 질의에 전달
qry = "select continent from country where name = ?"
>>> cursor.executemany(qry,['Austria'])
>>> cursor.fetchall()
[(u'Europe',)]

# 질의에 몇 가지 변수를 전달
>>> continent1 = 'Asia'
>>> continent2 = 'Africa'
>>> qry = "select name from country where continent in (?,?)"
>>> cursor.executemany(qry, [continent1, continent2])
>>> cursor.fetchall()
[(u'Afghanistan',), (u'Algeria',), (u'Angola',), (u'United Arab Emirates',), (u'Armenia',), (u'Azerbaijan',),
...

자원 관리

접속과 커서는 항상 닫아주어야 한다. 이것은 단지 좋은 습관일 뿐만 아니라, 컨테이너에 있어서도 접속 풀의 소모를 방지하기 위해서는 더 이상 필요로 하지 않는 접속을 반환하는 것이 필수적이다. with 문은 쉽게 작성할 수 있다. 예제 12-16을 보자.

예제 12-16. with 문을 사용하여 접속 관리

from __future__ import with_statement
from itertools import islice
from com.ziclix.python.sql import zxJDBC
# externalize
jdbc_url = "jdbc:oracle:thin:@host:port:sid"
username = "world"
password = "world"
driver = "oracle.jdbc.driver.OracleDriver"
with zxJDBC.connect(jdbc_url, username, password, driver) as conn:
    with conn:
        with conn.cursor() as c:
            c.execute("select * from emp")
            for row in islice(c, 20):
                print row # let's redo this w/ namedtuple momentarily...

예전의 방법으로도 구현이 가능하다. 좀 더 자세하고, 자바에서 자원의 종료를 확인하기 위하여 작성하는 코드와 흡사하다. 예제 12-17을 보자.

예제 12-17. with 문을 사용하지 않고 접속 관리

try:
    conn = zxJDBC.connect(jdbc_url, username, password, driver)
    cursor = conn.cursor()
    # 커서를 가지고 작업
    # 접속(및 커서)을 종료함으로써 정리할 것
finally:
    if cursor:
        cursor.close()
    if conn:
        conn.close()

메타데이터

이 장에서 이전에 언급했듯이, 그것은 접속과 커서 개체를 모두 사용할 수있는 특정 속성의 사용을 통해 메타데이터 정보를 얻을 수 있다. zxJDBC는 JDBC java.sql.DatabaseMetaData 개체에서 발견되는 속성이 속성을 일치시킨다. 따라서 이러한 속성 중 하나가 호출되었을 때, JDBC DatabaseMetaData 개체는 실제로 정보를 얻는 것이다.

예제 12-18는 접속, 커서, 심지어 특정 질의에 대한 메타데이터를 검색하는 방법을 보여준다. 커서에 대한 메타데이터를 취득 때마다, 당신이 속성을 설정 후 데이터를 페치해야 한다.

예제 12-18. 접속, 커서 또는 특정 질의에 대한 메타데이터를 검색

# 접속 속성을 사용하여 접속에 대한 정보를 얻는다
>>> conn.dbname
'PostgreSQL'
>>> conn.dbversion
'8.4.0'
>>> conn.drivername
'PostgreSQL Native Driver'
# 기존 커서 확인
>>> conn.__cursors__
[<PyExtendedCursor object instance at 1>]

# 커서 및 질의에 대한 정보를 얻음
>>> cursor = conn.cursor()
# 모든 테이블을 리스트
>>> cursor.tables(None, None, '%', ('TABLE',))
>>> cursor.fetchall()
[(None, u'public', u'city', u'TABLE', None), (None, u'public', u'country', u'TABLE', None), (None, u'public', u'countrylanguage',
u'TABLE', None), (None, u'public', u'test', u'TABLE', None)]

데이터 조작 언어와 데이터 정의 언어

RDBMS에 포함된 데이터를 조작하는 모든 애플리케이션은 데이터 조작 언어 (DML)를 발행할 수 있어야한다. 물론, DML은 INSERT, UPDATE 및 DELETE와 같이 CRUD 프로그래밍의 기초를 이루는 문장들로 구성된다. zxJDBC는 표준 커서 개체에 DML을 사용하는 것이 오히려 쉽다. 이렇게 하면, 커서는 결과에 대한 정보를 제공하는 값을 반환한다. JDBC의 표준 DML 트랜잭션이 커서 개체 예비문을 사용하고, 문장이 성공 여부를 결정하기 위해 나중에 읽을 수있는 변수에 결과를 할당한다.

또한 ZxJDBC는 데이터 정의 언어 (DDL)를 사용하여 데이터베이스에 새로운 구조를 정의하기 위해 커서를 사용한다. 이러한 일의 예로는, 테이블을 만들거나 변경하고, 인덱스를 생성하는 것과 같은 것들을 들 수 있다. 마찬가지로 zxJDBC로 DML을 수행하면, 결과 DDL 문은 문장이 성공하였는지의 여부를 판단하는 데에 도움이 될 수 있는 값을 반환한다.

다음 예제들에서는, 테이블을 만들고, 값을 삽입하고, 값을 삭제하고, 끝으로 테이블을 제거한다.

예제 12-19. DML 사용하기

>>>
# PYTHON_IM​​PLEMENTATIONS라는 이름의 테이블을 생성
>>> stmt = "create table python_implementations (id integer, python_implementation varchar, current_version varchar)"
>>> result = cursor.execute(stmt)
>>> print result
None
>>> cursor.tables(None, None, '%', ('TABLE',))
# 테이블이 생성되었는지 확인
>>> cursor.fetchall()
[(None, u'public', u'city', u'TABLE', None), (None, u'public', u'country', u'TABLE', None), (None, u'public', u'countrylanguage',
u'TABLE', None), (None, u'public', u'python_implementations', u'TABLE', None), (None, u'public', u'test', u'TABLE', None)]
# 테이블에 몇 가지 값을 삽입
>>> stmt = "insert into PYTHON_IMPLEMENTATIONS values (?, ?, ?)"
>>> result = cursor.executemany(stmt, [1,'Jython','2.5.1'])
>>> result = cursor.executemany(stmt, [2,'CPython','3.1.1'])
>>> result = cursor.executemany(stmt, [3,'IronPython','2.0.2'])
>>> result = cursor.executemany(stmt, [4,'PyPy','1.1'])
>>> conn.commit()
# 데이터베이스에 질의
>>> cursor.execute("select python_implementation, current_version from python_implementations")
>>> cursor.rowcount
4
>>> cursor.fetchall()
[(u'Jython', u'2.5.1'), (u'CPython', u'3.1.1'), (u'IronPython', u'2.0.2'), (u'PyPy', u'1.1')]
# 값을 갱신한 후 다시 질의
>>> stmt = "update python_implementations set
python_implementation = 'CPython -Standard Implementation' where id = 2"
>>> result = cursor.execute(stmt)
>>> print result
None
>>> conn.commit()
>>> cursor.execute("select python_implementation, current_version from python_implementations")
>>> cursor.fetchall()
[(u'Jython', u'2.5.1'), (u'IronPython', u'2.0.2'), (u'PyPy', u'1.1'), (u'CPython -Standard Implementation', u'3.1.1')]

삽입과 갱신은 대량으로 사용하는 것이 바람직하다. 확정을 처리할 때마다 성능의 감소를 유발하기 때문이다. 여러 DML 문장을 묶어서 그 뒤에 확정을 하면, 트랜잭션의 성능향상을 꾀할 수 있다. 대량 DML 구문을 사용하는 또 다른 좋은 이유는 트랜잭션의 안전을 보장하는 것이다. 트랜잭션에서는 한 문장이라도 실패하면, 모두 되돌려야하는 경우가 많다. 앞에서 언급한 바와 같이, try/except 절은 트랜잭션에 대한 의존성을 띤다. 한 문장이 실패하면 나머지는 모두 되돌리게 된다. 마찬가지로, 모든 문장이 성공하면 마지막에 가서 한 번만 데이터베이스에 확정을 하게 된다.

프로시저 호출

데이터베이스 애플리케이션은 종종 데이터베이스 내부에 있는 프로시저 및 함수를 사용한다. 이러한 프로시저는 대부분 오라클의 PL/SQL 또는 PostgreSQL의 PL/pgSQL과 같은 SQL 절차적 언어로 작성된다. 파이썬과 자바 등 외부 애플리케이션을 통하여 데이터베이스 프로시저를 작성하고 사용하는 것은 일리가 있는데, 이는 때때로 프로시저가 자료를 다루는 가장 쉬운 방법이 되곤 하기 때문이다. 데이터베이스에 맞닿아있기 때문이기도 하지만, 데이터베이스에 대한 접속을 맺고 끊어야하는 자이썬 애플리케이션에 비해 빠르게 수행되기 때문이기도 하다. 프로시저가 데이터베이스 내에 있기 때문에 접속을 맺느라 성능이 저하되는 것을 피할 수 있다.

ZxJDBC는 JDBC가 할 수 있는 것과 마찬가지로 데이터베이스 프로시저를 쉽게 호출할 수 있다. 이는 개발자로 하여금, 프로시저와 같이 데이터베이스에 상주하는 데이테베이스 중심적인 코드를 갖는 애플리케이션을 생성하도록 도움을 주며, 애플리케이션 서버에서 실행되면서 데이터베이스와 이음매 없이 상호작용하는 다른 애플리케이션 중심 코드를 작성하는 데에도 도움이 된다. 데이터베이스 프로시저를 호출하기 위해서, zxJDBC가 호출하는 프로시저의 이름을 소요 callproc() 방법을 제공한다. 예제 12-20에서, 우리는 상대적으로 쓸모없는 프로시저를 만든 다음 그것은 자이썬을 사용하여 호출한다(예제 12-21).

예제 12-20. PostgreSQL 프로시저

CREATE OR REPLACE FUNCTION proc_test(
    OUT out_parameter CHAR VARYING(25) )
AS $$
DECLARE
BEGIN
    SELECT python_implementation
    INTO out_parameter
    FROM python_implementations
    WHERE id = 1;
    RETURN;
END;
$$ LANGUAGE plpgsql;

예제 12-21. 자이썬 호출 코드

>>> result = cursor.callproc('proc_test')
>>> cursor.fetchall()
[(u'Jython',)]

상대적으로 사소한 예제라 할 지라도, zxJDBC에서 데이터베이스 프로시저를 사용하는 것이 얼마나 쉽게 중요성을 갖게 되는지 쉽게 알 수 있다. 데이터베이스 프로시저 및 함수를 애플리케이션 코드와 결합하는 것은 강력한 기술이기는 하나, 애플리케이션이 특정 데이터베이스에 매일 수 있으므로 현명하게 사용하여야 할 것이다.

zxJDBC 호출 커스터마이즈

때때로, SQL 문을 자동으로 변경 또는 조작할 수 있으면 편리하다. 이러한 것은 데이터베이스로 문장을 보내기 전에 이루어질 수도 있고, 데이터베이스에 보낸 후에도, 심지어는 이미 보낸 문장에 대한 정보를 얻어내는 것도 가능하다. 자료 호출을 조작하거나 커스터마이즈하기 위해서는 zxJDBC에서 사용 가능한 DataHandler 인터페이스를 사용할 수 있다. DataHandler를 사용하면 유형 매핑을 처리하기위한 세 가지 방법이 기본적으로 있다. 그것들은 프로세스 내의 서로 다른 시간에 호출되는데, 하나는 fetching할 때, 다른 하나는 예비문에서 사용하기 위해 개체를 바인딩할 때이다. 이러한 데이터 유형 대응 콜백은 라이프 사이클, 개발자 지원, 예비문 바인딩과 building 결과의 네 가지로 분류된다.

At first mention, 문장에 대한 커스터마이즈와 조작은 방대하여 주눅이 들 수도 있다. 그러나, zxJDBC DataHandler에게 이 작업은 꽤 쉽다. 간단히 핸들러 클래스를 만들고 특정 처리기 메서드를 재정의하여 필요한 기능을 구현한다. 어떤 다음은 재정의될 수있는 다양한 방법의 목록이며, 우리는 나중에 간단한 예를 들어 보겠다.

수명주기

public void preExecute(Statement stmt)**throws SQLException;

각 문장 수행 전 콜백. 문장이 PreparedStatement인 경우(매개변수들이 execute 메서드에 전달될 때 생성), 모든 매개변수가 설정된다.

public void postExecute**(Statement stmt) throws SQLException;

문장을 성공적으로 실행 후 콜백. 이것은 문장이 삽입된 값을 알고 있는 자동 증가 컬럼과 같은 경우에 특히 유용하다.

개발자 지원

public String getMetaDataName**(String name);

getTables()와 같은 DatabaseMetaData 메서드에서 사용되는 이름에 대하여 적절한 대소문자를 결정하기 위한 콜백. 이것은 모든 이름이 대문자일 것으로 예상하는 오라클에서 특히 유용하다.

public PyObject getRowId**(Statement stmt) throws SQLException;

마지막으로 삽입된 문장의 row id를 반환하는 콜백.

예비문 바인딩

public Object getJDBCObject**(PyObject object, int type);

PreparedStatement가 execute 메서드의 사용을 통해 만들 어질 때 이 메서드가 호출된다. 매개 변수가 문장에 바인딩될 때, DataHandler는 그 유형에 대응하는 콜백을 얻는다. 유형 바인딩이 존재하는 경우에만 호출된다.

public Object getJDBCObject**(PyObject object);

PreparedStatement가 실행되는 동안 어떠한 유형 바인딩도 나타나지 않는 경우 이 메서드가 호출된다.

빌드 결과

public PyObject ge**tPyObject(**ResultSet set, int col, int type);

데이터베이스에서 데이터를 가져올 때에 이 메서드를 호출한다. 주어진 JDBC 유형에 따라, ResultSet set의 열 col에 해당하는 Java 개체의 하위 클래스 PyObject를 반환한다.

이제 우리는 이 기술을 활용하는 단순한 에제 코드를 살펴보자. 방법은 기본적으로 다음과 같은 단계를 따른다.

  1. 특정 기능을 구현하는 핸들러 클래스를 만든다(DataHandler 인터페이스를 구현해야 한다).
  2. 주어진 커서 개체를 만든 처리기 클래스를 지정한다.
  3. 데이터베이스 호출을 만들기 위해 커서 개체를 사용한다.

예제 12-22에서는, 기능이 변경되었음을 알리는 메시지를 출력하기 위하여 preExecute 메서드를 재정의한다. 여기서 볼 수 있듯이, 그것은 매우 쉽고 또 수많은 가능성을 열어준다.

예제 12-22. PyHandler.py

from com.ziclix.python.sql import DataHandler

class PyHandler(DataHandler):
    def __init__(self, handler):
        self.handler = handler
        print 'Inside DataHandler'
    def getPyObject(self, set, col, datatype):
        return self.handler.getPyObject(set, col, datatype)
    def getJDBCObject(self, object, datatype):
        print "handling prepared statement"
        return self.handler.getJDBCObject(object, datatype)
    def preExecute(self, stmt):
        print "calling pre-execute to alter behavior"
        return self.handler.preExecute(stmt)

자이썬 번역기 코드

>>> cursor.datahandler = PyHandler(cursor.datahandler)
Inside DataHandler
>>> cursor.execute("insert into test values (?,?)", [1,2])
calling pre-execute

역사

zxJDBC는 한때 자이썬의 수석 커미터였던 브라이언 짐머의 공헌에 힘입고 있다. 이 API는 자이썬 개발자들이보다 밀접하게 파이썬 DB API를 닮은 그 기술을 사용하여 데이터베이스 작업의 기능을 수 있도록 작성되었다. 꾸러미는 결국 자이썬 배포판의 일부가되었고, 오늘날에는 Django와 같은 높은 수준의 프레임 워크 작업을위한 가장 중요한 기본 API의 하나이다. zxJDBC API는 지금도 계속 발전하고 있으며, 그것은 향후 릴리스에 더 유용하게 될 가능성이 높다.

객체 관계형 대응

zxJDBC 확실히 자이썬을 통해 데이터베이스 접근을 위해 가능한 옵션을 제공하고 있지만, 가능한 많은 다른 해결책이 있다. 많은 개발자들은 현재 데이터베이스에서 작동하도록 ORM (객체 관계형 대응) 솔루션의 사용을 선택하고 있다. 이 절은 ORM에 대한 소개는 아니며, 그 주제에 대하여 약간이라도 알고있는 것으로 가정한다. 또한, 논의하게 될 ORM 솔루션에 대해서는 웹 또는 도서 형식으로 이미 엄청난 양의 아주 좋은 문서들이 나와있다. 따라서, 이 섹션은 자이썬 이러한 기술을 사용하는 방법에 대한 통찰력을 줄 것이다,하지만 각각의 ORM 솔루션을 작동하는 방법에 대한 세부 사항은 적지 않는다. 이 솔루션은 모두 매우 강력하고 모두에게 독립 실행형 및 엔터프라이즈 애플리케이션을 위한 능력을 갖게 되었다는에는 의심의 여지가 없다.

다음의 몇 섹션에서는, 자이썬에서 사용할 수 오늘날 가장 인기있는 ORM 솔루션의 일부를 사용하는 방법을 다룰 것이다. 자이썬에서 각 ORM을 설정하고 코드를 작성하는 방법을 배울 수 있을 것이다. 이 장을 마칠 때 쯤이면 자이썬을 사용하여 이러한 ORM들을 다룰 뿐 아니라 자이썬 ORM 애플리케이션을 구축하는 데에 충분한 지식을 갖게 될 것이다.

SqlAlchemy

SqlAlchemy는 파이썬 프로그래밍 언어에서 가장 폭 넓게 알려지고 사용되는 ORM 솔루션임에 의심의 여지가 없다. SqlAlchemy는 여러분의 애플리케이션에 사용하기에 충분할 만큼의 오랜 안정기를 거쳤다. 데이터베이스를 새로 구축하든 기존의 데이터베이스를 사용하든 쉽게 구성해서 사용할 수 있다. 아주 잠깐이면 SqlAlchemy를 다운로드하여 설치할 수 있다. 이 솔루션을 사용하기 위한 구문은 매우 직설적이며, 다른 ORM 기법과 같이, 데이터베이스 엔티티에 대한 작업은 특수한 자이썬 클래스를 데이터베이스의 특정 테이블로 연결하는 사상(mapper)을 사용함으로서 이루어진다. 결과적으로는 애플리케이션을 데이터베이스의 SQL 트랜잭션이 아닌 엔티티 클래스를 사용함으로써 지속시킬 수 있게 된다.

이 절에서는 SqlAlchemy를 자이썬에서 설치하고 구성하는 방법을 다룬다. 이 절에서는 몇 가지 짧은 예제를 통해 어떻게 시작할 수 있는지에 대해서만 다루며, SqlAlchemy에 대해서는 훌륭한 자료가 많이 있기 때문에 깊이 들어가지 않을 것이다. 그렇지만, 이 절은 자이썬에서 이러한 훌륭한 솔루션을 사용할 수 있도록 갭을 메워줄 것이다.

설치

우선 웹 사이트(www.sqlalchemy.org)에서 SqlAlchemy를 다운로드하자. 이 글을 쓰고 있는 현재는 0.6 버전을 사용해야 한다. 이 버전은 자이썬 2.5.0 릴리스와 함께 설치 및 테스트하였다. 이 꾸러미를 내려받았으면, 워크스테이션의 디렉토리에 압축을 풀고 터미널 또는 명령 프롬프트에서 해당 디렉토리로 이동하라. SqlAlchem​​y 디렉토리에 들어가서, 다음 명령을 실행하여 설치한다.

jython setup.py install

이 과정을 마치면 SqlAlchem​​y가 자이썬의 Libsite-packages 디렉토리에 설치될 것이다. 이제 자이썬에서 SqlAlchem​​y 모듈에 접근할 수 있을 것이다. 터미널을 열고 sqlalchem​​y를 가져와서 버전을 확인함으로써 설치가 잘 되었는지를 확인할 수 있다. 예제 12-23을 보자.

예제 12-23.

>>> import sqlalchemy
>>> sqlalchemy.__version__
'0.6beta1'
>>>

설치가 잘 되었다고 확인이 되었으면, 단말기를 통해 SqlAlchem​​y 작업을 시작해보도록 하자. 그러나 시작하기 전에 한 가지 더 확인해둘 것이 있다. 자이썬은 파이썬 데이터베이스 API를 구현하는 zxJDBC를 자바에서 사용한다. SqlAlchem​​y에서 사용할 수 있는 방언이 대부분인 최종 결과는 자이썬에서 바로 얻을 수는 없다. zxJDBC를 구현하기 위해 각 방언을 다시 작성해야하기 때문이다. 이 글을 작성할 당시에는, zxJDBC를 위해 다시 쓰여진 zxoracle 밖에 없었기 때문에, zxoracle을 기반으로 한 예제를 시연할 것이다. 그러나, 다른 방언으로 SQLServer와 MySQL 등이 있다. 아쉽게도 SqlAlchemy는 모든 데이터베이스에 대하여 사용할 수 있는 것은 아니다. 한편, 오라클은 시작하기에 아주 좋으며 새 방언을 구현하는 것은 그리 어렵지 않다. 이 책의 소스에 포함된 zxoracle.py 방언을 찾을 수 있을 것이다. 예제를 살펴보면 여러분이 사용하는 데이터베이스에 대해서도 비슷한 방언을 구현하는 것이 그리 어렵지만은 않음을 알 수 있을 것이다. zxoracle은 여러분의 자이썬 경로 어딘가에 두거나, Lib 디렉토리에 갖다놓아도 된다.

마지막으로, 우리가 사용할 데이터베이스를 위한 JDBC 드라이버에 대한 경로를 설정하여 자이썬에서 접근할 수 있도록 하자. 이 섹션에 포함된 절차를 수행했으면, 자이썬을 시작하고 SqlAlchem​​y의 기본 몇 가지를 연습해보도록 하자.

SqlAlchem​​y 사용하기

우리는 터미널이나 명령줄을 통해 SqlAlchem​​y와 직접 작업할 수 있다. 당신이 그것과 함께 작업하기 위해 수행하는 데 필요한 단계를 상대적으로 기본 설정되어있다. 첫째, 수행하려는 작업에 필요한 모듈을 가져온다. 둘째, 데이터베이스를 액세스하는 동안 사용하는 엔진을 만들 수 있다. 셋째, 당신이 아직 완료하지 않은 경우 데이터베이스 테이블을 만들고, SqlAlchem​​y 맵퍼를 사용하여 파이썬 클래스로 매핑한다. 마지막으로, 데이터베이스 작업 시작한다.

지금은 다른 것처럼,이 기술의 일을 다른 몇 가지 방법이 있다. 예를 들어, 각각에 대해 별도의 단계를 포함 테이블 생성, 클래스 생성 및 매핑에 대한 매우 세분화된 프로세스를 수행하거나 선언적 절차로 알려져 무엇을 사용함과 동시에 이러한 작업을 모두 수행할 수 있다. 우리는 SqlAlchem​​y를 사용하여 기본적인 데이터베이스 활동을 수행과 함께,이 장에서 이러한 각 작업을 수행하는 방법을 보여준다. 당신이 SqlAlchem​​y 새로운있다면, 우리는이 섹션을 통해 읽고 다음 sqlalchem​​y.org로 가서 거기에 사용 가능한 문서의 대규모 라이브러리의 일부를 읽는 것이 좋다. 이 섹션의 나머지 부분은 ORM 솔루션 자체의 기본적인 튜토리얼이기 때문에 그러나, 당신이 이미 SqlAlchem​​y 익숙한면, 당신이 원하는 경우에 이동할 수 있다.

우리의 첫 번째 단계는 데이터베이스와 함께 사용할 수있는 엔진을 만드는 것이다. 일단 엔진을 생성한 다음에는 그것을 사용하는 데이터베이스 작업의 수행을 시작할 수 있다. 개발 데이터베이스의 세부 데이터베이스를 특정 정보를 교체 터미널에서 다음 코드 라인 (예제 12-24)를 입력한다.

예제 12-24. 데이터베이스 엔진을 생성하고 작업을 수행

>>> import zxoracle
>>> from sqlalchemy import create_engine
>>> db = create_engine('zxoracle://schema:password@hostname:port/database)

다음으로, SQLAlchemy를 사용하여 데이터베이스를 생성하는 데에 필수적인 메타데이터를 생성하도록 하겠다(예제 12-25). 메타데이터를 통하여 한 개 이상의 테이블을 생성할 수 있는데, 메타데이터에 대하여 create_all()을 사용하여 데이터베이스 엔진에 적용하기 전까지는 실제로 생성되지 않는다. 이 예제에서는, 다음 섹션의 애플리케이션 예제에서 사용할 Player라는 이름의 테이블을 생성하는 과정을 살펴본다.

예제 12-25. 데이터베이스 테이블 만들기

>>>player = Table('player', metadata,
... Column('id', Integer, primary_key=True),
... Column('first', String(50)),
... Column('last', String(50)),
... Column('position', String(30)))
>>> metadata.create_all(engine)

우리의 테이블은 이제 데이터베이스 내에 존재하며, 다음 단계는 이 테이블에 접근하기 위해 파이썬 클래스를 생성하는 것이다. 예제 12-26을 보라.

예제 12-26. 데이터베이스 테이블에 접근하는 파이썬 클래스 만들기

class Player(object):
    def __init__(self, first, last, position):
        self.first = first
        self.last = last
        self.position = position
    def __repr__(self):
        return "<Player('%s', '%s', '%s')>" %(self.first, self.last, self.position)

그 다음 단계는 Player라는 파이썬 개체와 player라는 데이터베이스 테이블을 연관시키는 mapper를 만드는 것이다. 그렇게 하려면, mapper() 함수를 사용하여 클래스와 테이블을 엮는 Mapper 개체를 새로 생성한다(예제 12-27). mapper 함수는 다음 나중에 참조할 수 있도록 개체를 저장해둔다.

예제 12-27. 파이썬 객체와 데이터베이스 테이블을 연관시키는 mapper 생성

>>> from sqlalchemy.orm import mapper
>>> mapper(Player, player)
<Mapper at 0x4; Player>

mapper를 생성하는 것은 우리 테이블에서 작동하도록 환경을 설정하는 과정의 마지막 단계이다. 자, 되돌아가서 모든 단계를 수행하는 것을 좀 더 쉬운 방법으로 살펴보자. 테이블, 클래스, mapper를 한번에 생성하려면, 우리는 선언적으로 할 수 있다. Oracle 방언과 함께, 자동으로 증가하는 id 컬럼을 생성하기 위한 시퀀스를 사용할 필요가 있다는 점에 유의하라. 그렇게 하려면 sqlalchemy.schema.Sequence 개체를 가져와서 생성 시에 id 컬럼에 전달한다. 오라클 데이터베이스에서 이 시퀀스를 수동으로 생성하여야만 올바로 작동하며 그렇지 않으면 자동하지 않을 것이다. 예제 12-28을 보라.

예제 12-28. 테이블, 클래스 및 매퍼 한번에 만들기

SQL> create sequence id_seq
2 start with 1
3 increment by 1;

Sequence created.

# 테이블, 클래스, mapper를 선언적으로 생성
>>> from sqlalchemy.ext.declarative import declarative_base
>>> from sqlalchemy.schema import Sequence
>>> Base = declarative_base()
>>> class Player(object):
...     __tablename__ = 'player'
...     id = Column(Integer, Sequence(‘id_seq’), primary_key=True)
...     first = Column(String(50))
...     last = Column(String(50))
...     position = Column(String(30))
...     def __init__(self, first, last, position):
...         self.first = first
...         self.last = last
...         self.position = position
...     def __repr__(self):
...         return "<Player('%s','%s','%s')>" % (self.first, self.last, self.position)
...

세션을 생성하여 데이터베이스 작업을 시작하는 시점이다. 우리는 세션 클래스를 생성하고 이전 create_engine로 정의한 데이터베이스 엔진에 바인딩해야 한다. 일단 생성, 세션 클래스는 데이터베이스에 대한 새로운 세션 객체를 생성한다. 세션 클래스는 또한이 섹션에 대한 범위 밖에있는 다른 것들을 할 수 있지만 웹상에서 사용할 수 sqlalchem​​y.org이나 다른 큰 참조에서 그들에 대한 자세한 내용을 볼 수 있다. 예제 12-29를 보라.

예제 12-29. 세션 클래스 만들기

>>> from sqlalchemy.orm import sessionmaker
>>> Session = sessionmaker(bind=db)

우리는 지금 선수 개체를 만들고 우리의 세션에 저장 시작할 수 있다. 그들이 필요 한 개체는 데이터베이스에 계속된다, 이것은 또한 flush()로 알려져 있다. 우리가 세션에서 개체를 만든 다음 그것을 질의하는 경우 SqlAlchem​​y 먼저 데이터베이스에 개체를 유지하고 질의를 수행한다. 예제 12-30을 보라.

예제 12-30. Player 개체를 생성 및 질의

# sqlalchem​​y 및 zxoracle 모듈 가져오기
>>> import zxoracle
>>> from sqlalchemy import create_engine
>>> from sqlalchemy import Table, Column, String, Integer, MetaData, ForeignKey
>>> from sqlalchemy.schema import Sequence

# 엔진 생성
>>> db = create_engine('zxoracle://schema:password@hostname:port/database’)
# 메타데이터 및 테이블 생성
>>> metadata = MetaData()
>>> player = Table('player', metadata,
... Column('id', Integer, Sequence('id_seq'), primary_key=True),
... Column('first', String(50)),
... Column('last', String(50)),
... Column('position', String(30)))
>>> metadata.create_all(db)

# 테이블 개체를 hold하는 클래스를 생성
>>> class Player(object):
... def __init__(self, first, last, position):
... self.first = first
... self.last = last
... self.position = position
... def __repr__(self):
... return "<Player('%s','%s','%s')>" % (self.first, self.last, self.position)

# 클래스에 테이블을 매핑하는 mapper 생성
>>> from sqlalchemy.orm import mapper
>>> mapper(Player, player)
<Mapper at 0x4; Player>

# 세션 클래스를 만들고 데이터베이스에 바인딩
>>> from sqlalchemy.orm import sessionmaker
>>> Session = sessionmaker(bind=db)
>>> session = Session()

# player 개체를 생성하여, 세션에 추가
>>> player1 = Player('Josh', 'Juneau', 'forward')
>>> player2 = Player('Jim', 'Baker', 'forward')
>>> player3 = Player('Frank', 'Wierzbicki', 'defense')
>>> player4 = Player('Leo', 'Soto', 'defense')
>>> player5 = Player('Vic', 'Ng', 'center')
>>> session.add(player1)
>>> session.add(player2)
>>> session.add(player3)
>>> session.add(player4)
>>> session.add(player5)

# 개체를 질의
>>> forwards = session.query(Player).filter_by(position='forward').all()
>>> forwards
[<Player('Josh','Juneau','forward')>, <Player('Jim','Baker','forward')>]
>>> defensemen = session.query(Player).filter_by(position='defense').all()
>>> defensemen
[<Player('Frank','Wierzbicki','defense')>, <Player('Leo','Soto','defense')>]
>>> center = session.query(Player).filter_by(position='center').all()
>>> center
[<Player('Vic','Ng','center')>]

이 예제를 통해 SqlAlchem​​y를 사용하는 것의 이점을 알 수 있기를 바란다. 물론 여러분은 그 개체에 대하여 insert, update, select, delete 등 필요한 SQL은 모두 수행할 수 있겠지만, 이러한 것들을 하는 방법을 배울 수 있는 아주 좋은 자습서들이 있으니 참고하는 것이 좋을 것이다. 우리는 SqlAlchemy로 할 수 있는 일을 겉핡기로 살펴보았는데, 자이썬 및 파이썬 개발자의 매우 강력한 무기로 삼을 만한 것이다.

하이버네이트

하이버네이트Hibernate는 자바 세계에서 사용되는 매우 인기있는 객체 관계형 대응 솔루션이다. 사실, 하이버네이트는 매우 인기가 있어서, 많은 다른 ORM 솔루션들에서 그것을 이용하거나 여러 방법으로 확장하고 있다. 자이썬 개발자로서, 우리는 강력한 하이브리드 애플리케이션을 만드는 데에 하이버네이트를 사용할 수 있다. 하이버네이트는 데이터베이스 테이블에 POJO(plain old java object: 일반적인 오래된 자바 객체) 클래스를 대응시킴으로써 작동하기 때문에, 자이썬 개체를 직접 매핑할 수는 없다. 자이썬 개체를 하이버네이트가 사용할 수 있는 형식의 개체로 억지맞춤하기 위하여 객체 팩토리를 사용할 수도 있지만, 이러한 접근은 바람직한 방법과는 거리가 멀다. 따라서, 전적으로 자이썬을 사용하여 코딩된 애플리케이션을 만들려면, 이것은 아마도 최고의 ORM 솔루션이 아닐 수도 있다. 그러나 대부분의 자이썬 개발자는 Java에서 일을 조금씩 하고 있으므로, 그들은 일류 하이브리드 애플리케이션을 만들 수 있는 하이버네이트 API의 성숙과 능력을 활용할 수 있다. 이 섹션에서는 하이버네이트 및 Java를 사용하여 데이터베이스 지속성 개체를 만드는 방법을 보인 다음, 자이썬 애플리케이션에서 그것들을 직접 사용한다. 최종 결과는, 엔티니 POJO는 자바로 코딩하여, 그것들을 하이버네이트 및 필요한 대응 문서들과 함께 JAR 파일에 집어넣고, 그 JAR를 자이썬 애플리케이션으로 들여와서 사용하게 된다.

우리는 이러한 이클립스 또는 넷빈 같은 IDE를 이용하는 것이 그러한 애플리케이션을 만드는 가장 손쉬운 방법임을 알게 되었다. 그런 다음 두 개의 프로젝트를 만드는데, 그중 첫 번째는 엔티티 빈을 포함하는 순수 자바 애플리케이션이 될 프로젝트이다. 또 다른 프로젝트는 다른 모든 것들을 포함하는 순수한 자이썬 애플리케이션이 될 것이다. 이 상황에서는, 간단하게 자이썬 프로젝트의 sys.path에 자바 프로젝트에서 JAR를 추가하면 준비가 된 것이다. 어쨌든, IDE를 사용하고자 하지 않는다면 문제가 없을 것이다.

자이썬, 자바, 하이버네이트를 함께 사용하는 용례를 제공하는 것이 이번 섹션의 목적임을 주지하기 바란다. 기술의 조합은 단지뿐만 아니라면 더 잘 것이있는 많은 시나리오가있을 수 있다. 또한 이 섹션에서는 하이버네이트에 대하여 깊이 다루지는 않는다. 우리는 단지 그것이 어떤 능력을 갖고 있는지 겉핡기로 알아보기만 할 것이다. 이 솔루션이 유용하다는 것을 알게 된다면, 하이버네이트에 대한 자습서는 웹에 차고 넘친다.

엔티티 클래스와 하이버네이트 구성

우리의 하이버네이트 엔티티 빈은 자바로 코딩해야하기 때문에, 하이버네이트 구성의 대부분은 자바 프로젝트에 있는 것이다. 하이버네이트는 간단한 방식으로 작동한다. 기본적으로 테이블을 POJO에 대응하고 설정 파일을 통해 그 둘을 대응시킨다. XML 구성 파일 외에 어노테이션을 사용하는 것도 가능하지만, 여기서는 구성 파일을 사용하는 방법을 보이도록 하겠다.

우리가 조립해야 할 첫 번째 구성 파일은 여러분의 자바 프로젝트 디렉터리 트리의 루트에서 찾을 수있는 hibernate.cfg.xml이다. 이 파일의 목적은 데이터베이스 접속 정보를 정의하는 것 뿐만 아니라 프로젝트에서 사용되는 엔티티 구성 파일을 선언하는 것이다. 이 예제의 목적을 위해, 우리는 PostgreSQL 데이터베이스를 사용하며, 고전적인 예제인 하키 선수 명단 애플리케이션을 사용한다. 이는 단 하나의 테이블인 Player 테이블만을 다루는 매우 단순한 용례를 갖는다. 하이버네이트는 여러 테이블을 가지고 작업하는 것이 가능할 뿐 아니라 그것들을 여러 방법으로 연관시킬 수도 있다.

예제 12-31.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate
Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
    <!-- Database connection settings -->
    <property
    name="connection.driver_class">org.postgresql.Driver</property>
    <property
    name="connection.url">jdbc:postgresql://localhost/database-name</property>
    <property name="connection.username">username</property>
    <property name="connection.password">password</property>
    <!-- JDBC connection pool (use the built-in) -->
    <property name="connection.pool_size">1</property>
    <!-- SQL dialect -->
    <property
    name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>
    <mapping resource="org/jythonbook/entity/Player.hbm.xml"/>
</session-factory>
</hibernate-configuration>

다음 단계는 데이터베이스 테이블에 대한 일반적이고 오래된 자바 객체를 코딩하는 것이다. 여기서는 단지 id, first, last, position의 네 개의 데이터베이스 열을 갖는 Player라는 이름의 개체를 코딩한다. 여기서는 클래스에서 private 변수를 갖는 표준적인 public 접근 메서드를 사용하는 것을 보게 될 것이다.

예제 12-32.

package org.jythonbook.entity;

public class Player {

    public Player(){}

    private long id;
    private String first;
    private String last;
    private String position;

    public long getId(){
        return this.id;
    }
    private void setId(long id){
        this.id = id;
    }
    public String getFirst(){
        return this.first;
    }
    public void setFirst(String first){
        this.first = first;
    }
    public String getLast(){
        return this.last;
    }
    public void setLast(String last){
        this.last = last;
    }
    public String getPosition(){
        return this.position;
    }
    public void setPosition(String position){
        this.position = position;
    }
}

끝으로, 하이버네이트에서 우리의 POJO를 데이터베이스 테이블 자체에 대응하는 데에 사용할 구성 파일을 생성한다. 우리는 증가하는 생성기*generator*클래스 형식을 사용함으로써 기본 키 값이 항상 채워진다는 것을 확실히 해두려고 한다. 하이버네이트 또한 원하는 경우 시퀀스를 포함하여 다른 생성기*generator*의 사용을 허용한다. player.hbm.xml 파일이 경우 org.jythonbook.entity 꾸러미에서 우리는 POJO와 같은 꾸러미로 가야한다.

*예제 12-33. 하이버네이트 구성 파일 만들기 *

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.jythonbook.entity">
    <class name="Player" table="player" lazy="true">
        <comment>Player for Hockey Team</comment>
        <id name="id" column="id">
            <generator class="increment"/>
        </id>
        <property name="first" column="first"/>
        <property name="last" column="last"/>
        <property name="position" column="position"/>
    </class>
</hibernate-mapping>

우리의 간단한 예제를 위해서 자바 프로젝트 내에서 해야 할 일은 이것이 전부이다. 물론, 프로젝트에서 필요로 하는 만큼 가능한 많은 엔티티 클래스를 추가할 수 있다. 기억해야할 주요한 포인트는 모든 엔티티 클래스는 자바에서 코딩되며 애플리케이션의 나머지 부분은 자이썬에서 코드를 작성한다는 점이다.

자바 엔티티 클래스를 사용하여 자이썬 구현

유스-케이스의 나머지 부분은 자이썬에서 코딩한다. 모든 하이버네이트 설정 파일과 엔티티 클래스는 자바로 코딩되며 자바 프로젝트 내에 위치하기는 하지만, 우리는 자이썬의 프로젝트에 해당 프로젝트를 가져올 필요가 있으며, 또한 우리는 데이터베이스 세션 및 트랜잭션 유틸리티를 엔티티들과 함께 사용 할 수 있도록 하이버네이트 JAR 파일을 가져올 필요가 있다. 넷빈의 경우에는, 다음 자이썬 2.5.0로 파이썬 플랫폼을 설정 파이썬 애플리케이션을 만들 것이다. 그 후, 프로젝트 속성 내에서 파이썬 경로에 필요한 최대 절전 JAR 파일뿐만 아니라 자바 프로젝트 JAR 파일을 모두 추가해야 한다. 일단 프로젝트를 설정하고 의존성을 처리하였다면 구현에 들어갈 준비가 된 것이다.

앞서 말했듯이, 이 예제를 위하여 우리는 하키 선수 명단 구현을 코딩할 것이다. 애플리케이션은 명령행에서 실행되며 기본적으로 명단에 선수를 추가하거나 명단에서 선수를 삭제할 수 있으며, 현재의 명단을 확인할 수 있다. 데이터베이스 트랜잭션의 모든 건 우리가 자바 애플리케이션에서 코딩한 Player 엔티티의 사용을 하며, 우리는 자이썬 코드 내에서 하이버네이트의 트랜잭션 관리를 사용할 것이다.

예제 12-34. 하키 선수 명단 애플리케이션 코드

from org.hibernate.cfg import Environment
from org.hibernate.cfg import Configuration
from org.hibernate import Query
from org.hibernate import Session
from org.hibernate import SessionFactory
from org.hibernate import Transaction
from org.jythonbook.entity import Player


class HockeyRoster:

    def __init__(self):
        self.cfg = Configuration().configure()
        self.factory = self.cfg.buildSessionFactory()

    def make_selection(self):
        '''
        우리의 애플리케이션을 위한 선택 보기를 만든다. 이 함수는 출력을 명령행에
        print한다. 그런 다음 우리의 애플리케이션 옵션을 선택하기 위하여 명령행에서
        키보드 입력으로 매개 변수를 취한다.
        '''
        options_dict = {1:self.add_player,
                    2:self.print_roster,
                    3:self.search_roster,
                    4:self.remove_player}
        print "Please chose an option\\n"

        selection = raw_input('''Press 1 to add a player, 2 to print the roster,
                3 to search for a player on the team,
                4 to remove player, 5 to quit: ''')
        if int(selection) not in options_dict.keys():
            if int(selection) == 5:
                print "Thanks for using the HockeyRoster application."
            else:
                print "Not a valid option, please try again\\n"
                self.make_selection()
        else:
            func = options_dict[int(selection)]
            if func:
                func()
            else:
                print "Thanks for using the HockeyRoster application."

    def add_player(self):
        '''
        키보드 입력을 받아 선수 개체를 roster 목록에 추가한다.
        이 함수는 호출될 때마다 새로운 player 개체를 생성하여
        해당 데이터베이스 테이블에 레코드를 삽입한다.
        '''
        addNew = 'Y'
        print "Add a player to the roster by providing the following information\\n"
        while addNew.upper() == 'Y':
            first = raw_input("First Name: ")
            last = raw_input("Last Name: ")
            position = raw_input("Position: ")
            id = len(self.return_player_list())
            session = self.factory.openSession()
            try:
                tx = session.beginTransaction()
                player = Player()
                player.first = first
                player.last = last
                player.position = position
                session.save(player)
                tx.commit()
            except Exception,e:
                if tx!=None:
                    tx.rollback()
                    print e
            finally:
                session.close()

            print "Player successfully added to the roster\\n"
            addNew = raw_input("Add another? (Y or N)")
        self.make_selection()

    def print_roster(self):
        '''
        Player 데이터베이스 테이블의 내용을 출력
        '''
        print "====================\\n"
        print "Complete Team Roster\\n"
        print "======================\\n\\n"
        playerList = self.return_player_list()
        for player in playerList:
            print "%s %s - %s" % (player.first, player.last, player.position)
        print "\\n"
        print "=== End of Roster ===\\n"
        self.make_selection()

    def search_roster(self):
        '''
        선수의 이름을 데이터베이스 내에서 검색하기 위하여 명령행으로부터 입력을
        받는다. 목록에 있는 선수를 찾을 경우 긍정적인 메시지를 출력한다. 찾을
        수 없다면 부정적인 메시지를 출력한다.
        '''
        index = 0
        found = False
        print "Enter a player name below to search the team\\n"
        first = raw_input("First Name: ")
        last = raw_input("Last Name: ")
        position = None
        playerList = self.return_player_list()
        while index < len(playerList):
            player = playerList[index]
            if player.first.upper() == first.upper():
                if player.last.upper() == last.upper():
                    found = True
                    position = player.position
            index = index + 1
        if found:
            print '%s %s is in the roster as %s' % (first, last, position)
        else:
            print '%s %s is not in the roster.' % (first, last)
        self.make_selection()

    def remove_player(self):
        '''
        데이터베이스에서 지정된 선수를 제거한다
        '''
        index = 0
        found = False
        print "Enter a player name below to remove them from the team roster\\n"
        first = raw_input("First Name: ")
        last = raw_input("Last Name: ")
        position = None
        playerList = self.return_player_list()
        found_player = Player()
        while index < len(playerList):
            player = playerList[index]
            if player.first.upper() == first.upper():
                if player.last.upper() == last.upper():
                    found = True
                    found_player = player
            index = index + 1

        if found:
            print '''%s %s is in the roster as %s,
                are you sure you wish to remove?''' % (found_player.first,
                                                    found_player.last,
                                                    found_player.position)
            yesno = raw_input("Y or N")
            if yesno.upper() == 'Y':
                session = self.factory.openSession()
                tx = None
                try:
                    delQuery = "delete from Player player where id = %s" % (found_player.id)

                    tx = session.beginTransaction()
                    q = session.createQuery(delQuery)
                    q.executeUpdate()
                    tx.commit()
                    print 'The player has been removed from the roster', found_player.id
                except Exception,e:
                    if tx!=None:
                        tx.rollback()
                        print e
                finally:
                s   ession.close
            else:
                print 'The player will not be removed'
        else:
            print '%s %s is not in the roster.' % (first, last)
        self.make_selection()

    def return_player_list(self):
        '''
        데이터베이스에 접속하여 player 테이블의 내용을 검색
        '''
        session = self.factory.openSession()
        try:
            tx = session.beginTransaction()
            playerList = session.createQuery("from Player").list()
            tx.commit()
        except Exception,e:
            if tx!=None:
                tx.rollback()
            print e
        finally:
            session.close
        return playerList

    # main
    #
    # 이것은 애플리케이션 진입점으로서, 단순히 명령행에 애플리케이션
    # 제목을 인쇄한 다음, makeSelection() 함수를 호출한다.
    if __name__ == "__main__":
        print "Hockey Roster Application\\n\\n"
        hockey = HockeyRoster()
        hockey.make_selection()

구현은 main 블록으로부터 시작하며, 그곳에서 HockeyRoster 클래스의 실체가 만들어진다. 보는 바와 같이, 하이버네이트 설정이 초기화되며 세션 팩토리가 클래스 initializer 내에 구축된다. 다음으로, 프로그램의 실제 실행을 시작하는 make_selection() 메소드가 호출된다. 하이버네이트의 전체적인 구성은 자바 프로젝트 내에 있으므로, 여기서는 XML을 다루지 않고 사용하기만 하겠다. 그런 다음, 코드는 여러 가지 작업이 수행되도록 분기를 시작한다. 명단에 선수를 추가하는 경우, 사용자는 명령 프롬프트에서 숫자 1을 입력할 수 있다. addPlayer() 함수는 단순히 새로운 선수 개체를 생성하여 데이터베이스에 저장한다는 것을 알 수 있다. 마찬가지로, searchRoster() 함수는 하이버네이트 질의 언어를 사용하여 선수 테이블을 질의하고 선수 개체의 목록을 반환하는 returnPlayerList()라는 다른 함수를 호출한다.

결국, 우리는 완전히 확장 가능한 해결책을 갖게 되었다. 우리는 성숙하고 널리 사용되는 자바 ORM 솔루션을 사용하여 우리의 엔티티 코드, 그리고 자이썬에서 애플리케이션의 나머지 부분을 구현할 수 있다. 이것은 파이썬 언어의 최고의 기능을 사용할 수 있도록 해줄 뿐만 아니라 자바를 사용하여 데이터를 지속시킬 수 있도록 해준다.

요약

오늘날에는 너무나 많은 대기업용 애플리케이션이 있으며, 그것들은 관계형 데이터베이스를 여러 가지 형태로 사용한다. 요즘 사용되는 애플리케이션 중 다수는 자료를 저장하기 위하여 데이터베이스를 사용하는데, 이는 견고한 솔루션을 제공하는 데에 도움이 되기 때문이다. 이 장에서 다룬 주제는 어떤 개발자에게든 매우 중요하다. 이 장에서, 우리는 특별히 자바 데이터베이스 접속 API 또는 객체 관계형 대응 솔루션을 통해 자이썬에서 데이터베이스 애플리케이션을 구현하는 방법에는 여러 가지가 있다는 것을 알게 되었다.