MS SQL Server 사용

1. Microsoft SQL Server 사용

Go에서 MSSQL을 사용하기 위해서는 표준패키지 database/sql과 MSSQL 드라이버를 import해야 한다. MSSQL Driver를 처음 사용하는 경우는 아래와 같이 (Atom의 경우 terminal plus를 실행하고) go get 명령을 통해 드라이버 패키지를 먼저 다운받아야 한다. 패키지가 정상적으로 다운로드되면 아무런 에러가 표시되지 않고 새 프롬프트만 표시된다. 이 MSSQL 드라이버는 현재 SQL Server 2005 이상과 SQL Azure를 지원하고 있다.

go get mssql

MSSQL 드라이버가 설치된 후, 아래와 같이 database/sql과 MSSQL 드라이버를 import하는데, MSSQL 드라이버 패키지는 _ 로 alias를 주어 개발자가 드라이버 패키지를 직접 사용하지 않게 한다.

package main

import (
	"database/sql"	
	_ "github.com/denisenkom/go-mssqldb"	
)

2. MSSQL 쿼리

Go DB 쿼리에는 2종류의 메서드가 있는데, 하나의 Row만을 리턴하는 QueryRow() 메서드와 복수개의 Row를 리턴하는 Query() 메서드가 있다. 하나의 Row에서 실제 데이타를 읽어 로컬 변수에 할당하기 위해 Scan() 메서드를 사용하며, 복수 Row에서 다음 Row로 이동하기 위해 Next() 메서드를 사용한다.

package main

import (
	"database/sql"
	"fmt"
	_ "github.com/denisenkom/go-mssqldb"
	"log"
)

func main() {
	// sql.DB 객체 생성
	db, err := sql.Open("mssql", "server=(local);user id=sa;password=pwd;database=pubs")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// 하나의 Row를 갖는 SQL 쿼리 : QueryRow()
	var lname, fname string
	err = db.QueryRow("SELECT au_lname, au_fname FROM authors WHERE au_id='172-32-1176'").Scan(&lname, &fname)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(fname, lname)

	// 복수 Row를 갖는 SQL 쿼리 : Query()
	rows, err := db.Query("SELECT au_lname, au_fname FROM authors WHERE au_lname=?", "Ringer")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close() //반드시 닫는다 (지연하여 닫기)

	for rows.Next() {
		err := rows.Scan(&lname, &fname)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(fname, lname)
	}
}

여기서 한가지 주목할 것은 복수 Row 쿼리 Query() 메서드에서 ? (Placeholder)를 사용하여 Parameterized Query를 사용하고 있다는 점이다. 이는 SQL Injection과 같은 문제를 방지하기 위해 파라미터를 문자열 결합이 아닌 별도의 파라미터로 대입시키는 방식이다. 위의 예제에서 Placeholder ? 에는 문자열 Ringer가 대입된다. Placeholder는 데이타베이스와 그 드라이버 종류에 따라 다른데, 이 MSSQL 드라이버는 ?, ?n, :n, $n (여기서 n은 숫자) 등의 형식을 지원한다.

3. DML과 Prepared Statement 사용

데이타를 INSERT, UPDATE, DELETE (DML Operation)하기 위해서 sql.DB 객체의 Exec() 메서드를 사용한다. DML과 같이 리턴되는 데이타가 없는 경우는 Exec() 메서드를 사용해야 한다.

Prepared Statement는 데이타베이스 서버에 Placeholder를 가진 SQL문을 미리 준비시키는 것으로, 차후 해당 Statement를 호출할 때 준비된 SQL문을 빠르게 실행하도록 하는 기법이다. Go에서 Prepared Statement를 사용하기 위해서는 sql.DB의 Prepare() 메서드를 써서 Placeholder를 가진 SQL문을 미리 준비시키고, sql.Stmt 객체를 리턴받는다. 차후 이 sql.Stmt 객체의 Exec (혹은 Query/QueryRow) 메서드를 사용하여 준비된 SQL문을 실행한다.

package main

import (
	"database/sql"
	"fmt"
	_ "github.com/denisenkom/go-mssqldb"
	"log"
)

func main() {
	// sql.DB 객체 생성
	db, err := sql.Open("mssql", "server=(local);user id=sa;password=pwd;database=pubs")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// INSERT 문 실행 : Exec()
	result, err := db.Exec("INSERT INTO discounts(discounttype,discount) VALUES(?, ?)", "Test", 10)
	if err != nil {
		log.Fatal(err)
	}

	n, err := result.RowsAffected() // sql.Result.RowsAffected() 체크
	if n == 1 {
		fmt.Println("Successfully inserted.")
	}

	// Prepared Statement 생성
	stmt, err := db.Prepare("UPDATE discounts SET discount=?2 WHERE discounttype=?1")
	if err != nil {
		log.Fatal(err)
	}
	defer stmt.Close()

	// Prepared Statement 실행
	_, err = stmt.Exec("Test", 15) //Placeholder 파라미터 ?1, ?2 전달
	if err != nil {
		log.Fatal(err)
	}
}

4. MSSQL 트랜잭션

복수 개의 SQL 문을 하나의 트랜잭션으로 묶기 위하여 sql.DB의 Begin() 메서드를 사용한다. 트랜잭션은 복수 개의 SQL 문을 실행하다 중간에 어떤 한 SQL문에서라도 에러가 발생하면 전체 SQL문을 취소하게 되고 (이를 롤백이라 한다), 모두 성공적으로 실행되어야 전체를 커밋하게 된다. sql.Tx 타입의 Begin() 메서드는 sql.Tx 객체를 리턴하는데, 마지막에 최종 Commit을 위해 Tx.Commit() 메서드를, 롤백을 위해 Tx.Rollback() 메서드를 호출한다.

package main

import (
	"database/sql"
	_ "github.com/denisenkom/go-mssqldb"
	"log"
)

func main() {
	// sql.DB 객체 생성
	db, err := sql.Open("mssql", "server=(local);user id=sa;password=pwd;database=pubs")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// 트랜잭션 시작
	tx, err := db.Begin()
	if err != nil {
		log.Fatal(err)
	}
	defer tx.Rollback() //중간에 에러시 롤백

	// INSERT 문 실행
	_, err = db.Exec("INSERT discounts(discounttype,discount) VALUES(?, ?)", "Test1", 12)
	if err != nil {
		log.Fatal(err)
	}

	_, err = db.Exec("INSERT discounts(discounttype,discount) VALUES(?, ?)", "Test2", 11)
	if err != nil {
		log.Fatal(err)
	}

	// 트랜잭션 커밋
	err = tx.Commit()
	if err != nil {
		log.Fatal(err)
	}
}