# 레코드 수집(JavaScript)

음악 모음을 정렬하는 간단한 데이터베이스 프로그램을 만듭니다
그놈 문서 프로젝트
gnome-doc-list@gnome.org
Johannes Schmid
jhs@gnome.org
Marta Maria Casetti
mmcasettii@gmail.com 2013
조성호
shcho@gnome.org 2017-2019.

# 레코드 수집

      이 지침서를 통해 다음을 배웁니다:

        libgda로 데이터베이스 연결하기
        데이터베이스 테이블로의 레코드 삽입 및 테이블 탐색

# 도입부

이 시연 프로그램에서는 Javascript 언어를 활용합니다. GTK 프로그램에서 GDA(그놈 데이터 액세스) 라이브러리로 데이터베이스를 연결하고 활용하는 방법을 보여드리겠습니다. 고로, 이 라이브러리도 설치해야합니다.

그놈 데이터 액세스(GDA)는 다양한 종류와 형식의 데이터 공급원에 통합 방식으로 접근하는 수단을 제공합니다. 전통적인 관계형 데이터베이스 시스템에서 시작하여 메일 서버, LDAP 디렉터리 등과 같은 상상할 수 있는 데이터 공급원을 다룹니다. 더 많은 내용, 전체 API, 문서를 확인해보시려면 GDA 웹사이트를 방문해보십시오.

비록 코드 상당 부분이 사용자 인터페이스(GUI)와 관련 있지만, 데이터베이스 부분의 따라하기 지침에 역점을 두었습니다(아마도 관련이 있는 다른 부분에도 언급할지도 모릅니다). 그놈 Javascript 프로그래밍을 더 알아보려면 그림 보기 프로그램 따라하기 지침 부분을 참고하십시오.

# 안주타에서 프로젝트 만들기

코딩을 시작하기 전에 안주타에서 새 프로젝트를 설정해야합니다. 이 프로그램은 빌드에 필요한 모든 파일을 만들고 그 다음 코드를 실행합니다. 또한 이 모든 상태를 유지 관리하는데 쓸만합니다.

안주타를 시작하고 파일새로 만들기프로젝트 를 눌러 프로젝트 마법사를 여십시오.

JS 탭에서 일반 Javascript를 선택하고, 앞으로을 누른 다음, 나타난 페이지에서 몇가지 자세한 내용을 입력하십시오. 프로젝트 이름과 디렉터리에 record-collection을 입력하십시오.

마침을 누르면 프로젝트를 만들어줍니다. 프로젝트나 파일탭에서 src/main.js 파일을 여십시오. 매우 기본적인 예제 코드가 있습니다.

# 프로그램 구조

이 시연 프로그램은 데이터베이스 테이브의 모든 레코드를 탐색할 뿐만 아니라 레코드를 넣는 기능도 있는 간단한(단일 창) GTK 프로그램입니다. 테이블에는 정수형 값을 넣는 `id` 필드와 가변 문자열(varchar) 형식의 `name` 필드가 있습니다. 프로그램의 (상단) 처음 섹션에서는 테이블에 레코드를 넣을 수 있습니다. 프로그램의 마지막 섹션(바닥)에서는 테이블의 모든 레코드를 살펴볼 수 있습니다. 이 내용은 새 레코드를 넣을때와 프로그램을 시작할 때마다 새로 고쳐서 보여줍니다.

# 재미로 시작하기

프로그램 뼈대 검토를 우선 시작하겠습니다:

const GLib = imports.gi.GLib; const Gtk = imports.gi.Gtk; const Gda = imports.gi.Gda; const Lang = imports.lang;

function Demo () { this._init (); }

Demo.prototype = {

_init: function () { this.setupWindow (); this.setupDatabase (); this.selectData (); } }

Gtk.init (null, null);

var demo = new Demo ();

Gtk.main ();

1~4번째 줄: 초기 임포팅입니다. JavaScript로 GDA 라이브러리를 가져오는 이 따라하기 지침의 핵심부분인 세번째 줄에 특히 주목하십시오.

6~17번째 줄: `Demo` 클래스를 정의합니다. 전체 작업을 담당하는 메서드 3개를 호출하는 13~15번째 줄에 주목하십시오. 자세한 내용은 아래에서 설명하겠습니다.

19~23번째 줄: 프로그램을 시작합니다.

# 프로그램 설계

`setupWindow` 메서드를 살펴보겠습니다. 이 메서드는 사용자 인터페이스(UI)를 만드는 역할을 합니다. UI는 중요한 부분이 아니니 관련 부분만 설명하겠습니다.

setupWindow: function () { this.window = new Gtk.Window ({title: "Data Access Demo", height_request: 350}); this.window.connect ("delete-event", function () { Gtk.main_quit(); return true; });

// main box var main_box = new Gtk.Box ({orientation: Gtk.Orientation.VERTICAL, spacing: 5}); this.window.add (main_box);

// first label var info1 = new Gtk.Label ({label: "<b>Insert a record</b>", xalign: 0, use_markup: true}); main_box.pack_start (info1, false, false, 5);

// "insert a record" horizontal box var insert_box = new Gtk.Box ({orientation: Gtk.Orientation.HORIZONTAL, spacing: 5}); main_box.pack_start (insert_box, false, false, 5);

// ID field insert_box.pack_start (new Gtk.Label ({label: "ID:"}), false, false, 5); this.id_entry = new Gtk.Entry (); insert_box.pack_start (this.id_entry, false, false, 5);

// Name field insert_box.pack_start (new Gtk.Label ({label: "Name:"}), false, false, 5); this.name_entry = new Gtk.Entry ({activates_default: true}); insert_box.pack_start (this.name_entry, true, true, 5);

// Insert button var insert_button = new Gtk.Button ({label: "Insert", can_default: true}); insert_button.connect ("clicked", Lang.bind (this, this._insertClicked)); insert_box.pack_start (insert_button, false, false, 5); insert_button.grab_default ();

// Browse textview var info2 = new Gtk.Label ({label: "<b>Browse the table</b>", xalign: 0, use_markup: true}); main_box.pack_start (info2, false, false, 5); this.text = new Gtk.TextView ({editable: false}); var sw = new Gtk.ScrolledWindow ({shadow_type:Gtk.ShadowType.IN}); sw.add (this.text); main_box.pack_start (sw, true, true, 5);

this.count_label = new Gtk.Label ({label: "", xalign: 0, use_markup: true}); main_box.pack_start (this.count_label, false, false, 0);

this.window.show_all (); },

22번째, 27번째 줄: 어떤 사용자가 데이터베이스에 들어간 내용을 입력하는 항목 2개(필드 2개)를 만듭니다.

31~34번째 줄: 삽입 단추를 만듭니다. `clicked` 시그널을 클래스의 `_insertClicked` private 메서드에 연결했습니다. 이 메서드는 아래에서 자세히 설명하겠습니다.

39번째 줄: 테이블의 내용을 보여줄 위젯(`TextView`)을 만듭니다.

44번째 줄: 테이블의 여러 레코드를 보여줄 레이블을 만듭니다. 초기에는 비어있습니다만, 나중에 업데이트합니다.

# 데이터베이스 연결 및 초기화

`setupDatabase` 메서드에서 데이터베이스를 연결하는 코드는 아래와 같습니다:

setupDatabase: function () { this.connection = new Gda.Connection ({provider: Gda.Config.get_provider("SQLite"), cnc_string:"DB_DIR=" + GLib.get_home_dir () + ";DB_NAME=gnome_demo"}); this.connection.open ();

try { var dm = this.connection.execute_select_command ("select * from demo"); } catch (e) { this.connection.execute_non_select_command ("create table demo (id integer, name varchar(100))"); } },

2~3번쨰 줄: GDA의 `Connection` 객체를 만듭니다. 생성자에 속성 몇가지를 넣어야합니다:

`provider`: GDA에서 지원하는 프로파이더 중 하나입니다. GDA에서는 SQLite, MySQL, PostSQL, Oracle 등 수많은 데이터베이스 엔진을 지원합니다. 시연 목적으로, 대부분의 배포판에서 기본적으로 SQLite를 설치하며 쓰기 쉽기 때문에(파일을 데이터베이스로 활용) SQLite 데이터베이스를 사용하겠습니다.

`cnc_string`: 연결 문자열. 프로바이더를 바꿉니다. SQLite용 문법은 `DB_DIR=PATH;DB_NAME=FILENAME` 입니다. 시연 프로그램에서는 사용자 주 디렉터리(GLib의 `get_home_dir` 함수 호출 참고)에 둔 gnome_demo 데이터베이스에 접근하겠습니다.

GDA에서 지원하지 않는 프로바이더가 있고, 연결 문자열에 어떤 구성 요소가 빠지면, 2번째 줄에 예외가 일어납니다. 따라서 실제로 JavaScript의 구문 `try`...`catch`로 처리해야합니다.

4번째 줄: 연결합니다. SQLite 프로바이더에서 데이터베이스가 없으면 이 단계에서 만듭니다.

6~10번째 줄: 테이블이 있는지(7번째 줄) 간단하게 SELECT 문을 시도해봅니다. (데이터베이스를 방금 만들어서)테이블이 없으면, 이 명령에서 `try`...`catch` 블록에서 처리할 예외가 나타납니다. 이 경우 CREATE TABLE 구문을 실행합니다(9번째 줄).

GDA 연결용 `execute_select_command` 메서드와 `execute_non_select_command` 메서드로 위 SQL 명령을 실행합니다. 사용법은 단순하며 `Connection` 객체와 해석할 SQL 명령을 두 매개변수 값으로 활용하므로 단순합니다.

이 시점에 데이터베이스를 설정했고 사용할 준비가 끝났습니다.

# 선택

데이터베이스를 연결하고 나면, 데모 생성자에서 `selectData` 메서드를 호출합니다. 이 메서드는 테이블의 모든 레코드를 가져와서 `TextView` 위젯에 나타냅니다. 이제 살펴보도록 하겠습니다:

selectData: function () { var dm = this.connection.execute_select_command ("select * from demo order by 1, 2"); var iter = dm.create_iter ();

var text = "";

while (iter.move_next ()) { var id_field = Gda.value_stringify (iter.get_value_at (0)); var name_field = Gda.value_stringify (iter.get_value_at (1));

text += id_field + "\t=>\t" + name_field + '\n'; }

this.text.buffer.text = text; this.count_label.label = "<i>" + dm.get_n_rows () + " record(s)</i>"; },

2번째 줄: `SELECT` 명령. GDA 연결의 `execute_select_command` 메서드를 이 목적으로 활용합니다. 나중에 레코드 행을 가져올 때 사용할 `DataModel`을 반환합니다.

3번째 줄: `DataModel`의 여러 레코드를 순회할 때 활용할 `Iter` 객체를 만듭니다.

7번째 줄: 레코드 전체를 반복문으로 돌고 `Iter` 객체의 도움으로 가져옵니다. 여기서 `iter` 변수에는 실제로 가져온 데이터가 들어갑니다. `move_next` 메서드에서 마지막 레코드에 도달하면 `false`를 반환합니다.

8~9번째 줄: 다음 두가지 동작을 각 줄 별로 행합니다:

하나의 인자만 필요한 `Iter`의 `get_value_at` 메서드를 활용합니다. 이 인자에는 가져올 열 번호가 들어가며 0번 부터 시작합니다. `SELECT` 명령에서 열 두개만 반환하므로 0번과 1번 열만 가져옵니다.

`get_value_at` 메서드에서 GLib의 `GValue` 형식 필드를 반환합니다. 이 형식을 문자열로 변환하는 가장 쉬운 방법은 GDA의 전역 함수 `value_stringify`를 사용하면 됩니다. 이게 여기서 우리가 처리하려는 일이며, `id_field`, `name_field` 변수에 결과를 저장합니다.

11번째 줄: 두 필드 문자열을 하나로 합치며, 사이에 `"=>"` 문자열을 넣고 `text`변수에 저장합니다.

14번째 줄: 반복문 실행이 끝나면 `text` 변수에 형식을 갖추어 저장한 모든 레코드를 확보합니다. 이 줄에서는 해당 변수를 `TextView` 의 내용으로 설정합니다.

15번째 줄: `DataModel`의 `get_n_rows` 메서드를 활용하여 테이블의 수많은 레코드를 표시합니다.

# 삽입

좋습니다. 데이터베이스에 연결하고 테이블에서 행을 선택하는 방법을 알았습니다. 이제 테이블에 대해 `INSERT` 구문을 수행할 시간입니다. `setupWindow` 메서드에서 Insert 단추의 `clicked` 시그널을 `_insertClicked` 메서드에 연결한 부분을 기억하시죠? 이 메서드의 구현부를 살펴보겠습니다.

_insertClicked: function () { if (!this._validateFields ()) return;

// Gda.execute_non_select_command (this.connection, // "insert into demo values ('" + this.id_entry.text + "', '" + this.name_entry.text + "')");

var b = new Gda.SqlBuilder ({stmt_type:Gda.SqlStatementType.INSERT}); b.set_table ("demo"); b.add_field_value_as_gvalue ("id", this.id_entry.text); b.add_field_value_as_gvalue ("name", this.name_entry.text); var stmt = b.get_statement (); this.connection.statement_execute_non_select (stmt, null);

this._clearFields (); this.selectData (); },

GDA 연결의 메서드 `execute_select_command`, `execute_non_select_command`를 활용하여 데이터베이스에서 SQL 명령을 빠르게 실행하는 방법을 알았습니다. GDA 에서는 `SqlBuilder` 객체로 SQL 구문을 간접적으로 만듭니다. 어떤 장점이 있을까요? GDA에서는 SQL 구문을 동적으로 만들어, 활용하는 연결 제공자에서 올바른 구문으로 간주합니다(프로바이더에서 사용하는 동일한 SQL 변형 문법도 할용합니다). 코드를 살펴보겠습니다:

2~3번쨰 줄: 사용자가 모든 필드를 채웠는지 확인합니다. 자체 메서드 `_validateFields`의 코드는 정말 단순하며 전체 시연 소스코드에서 알아볼 수 있습니다.

5번째 줄: `INSERT` 구문을 실행하는 빠른 방법입니다. 전 데이터베이스에 걸쳐 어디든 이식할 수 있는 SQL 구문을 만드는 `SqlBuilder` 활용법을 보고 싶으면 이 부분 주석을 제거하시면 됩니다.

7번째 줄: `SqlBuilder` 객체를 만듭니다. 빌드할 구문 형식을 넘겨야합니다. `SELECT`, `UPDATE`, `INSERT`, `DELETE` 중 하나입니다.

8번째 줄: 만들어둔 구문을 수행할 테이블 이름을 설정합니다(`INSERT INTO demo` 구문을 만듬)

9~10번째 줄: 구문의 일부로 필드 이름과 값을 설정합니다. 첫번째 인자는 필드 이름(테이블에 있는 그대로), 두번째 인자는 필드의 값입니다.

11번째 줄: 동적으로 만든 SQL 구문 `Statement` 객체를 가져옵니다.

12번째 줄: 마지막으로 SQL 구문(`INSERT`)을 실행합니다.

14번째 줄: 화면의 id와 name 필드를 지웁니다. private 메서드 `_clearFields`의 코드는 정말 단순하며 전체 시연 소스 코드에서 확인해볼 수 있습니다.

15번쨰 줄: 다른 `SELECT` 구문을 실행하며 화면의 뷰를 새로 고칩니다.

구문을 만들 때 매개변수를 사용할 수도 있습니다. `SqlBuilder` 객체와 매개변수를 활용하여 SQL 인젝션 같은 공격을 덜 받을 수 있습니다. 매개변수에 대한 자세한 정보는 GDA 문서를 참고하십시오.

# 프로그램 실행

필요한 모든 코드를 제자리에 넣었으니 코드를 실행해보겠습니다. 제대로 동작할겝니다. 이렇게 하여 레코드 수집용 데이터베이스를 만들었습니다!

# 참조 구현체

지침서를 따라하는 실행하는 과정에 문제가 있다면, 참조 코드와 여러분의 코드를 비교해보십시오.