var WhatIsThe = AnswerToLife()

func AnswerToLife() int {
    return 42
}

func init() {
    WhatIsThe = 0
}

func main() {
    if WhatIsThe == 0 {
        fmt.Println("It's all a lie.")
    }
}

AnswerToLife()함수는 init()함수가 호출되기 이전에 호출이 됩니다. 그리고 init() 함수는 main() 함수가 호출되기 이전에 호출이 됩니다.

명심해야 할 것은 main()이 있던 없던 init()함수는 항상 호출됩니다. 따라서 init()함수를 가지고 있는 패키지를 import할 경우 init()함수가 실행됩니다.


또한, 패키지 당 여러 init () 함수를 가질 수 있으며, 모든 변수가 초기화 된 후 코드에 나타나는 순서대로 실행됩니다. Go 패키지 대부분은 테이블 같은 값을 초기화 하기 위해서 아래와 같이 init()를 사용합니다.

func init() {
	const poly = 0x04C11DB7
	for i := range crctab {
		crc := uint32(i) << 24
		for j := 0; j < 8; j++ {
			if crc&0x80000000 != 0 {
				crc = (crc << 1) ^ poly
			} else {
				crc <<= 1
			}
		}
		crctab[i] = crc
	}
}


슬라이스는 가변적으로 변하기 때문에 연속된 메모리 공간을 활용합니다. 따라서 용량에 제한이 있습니다.

만약 슬라이스를 make([]int, 5)와 같이 선언하면 용량도 5가 됩니다. 여기에 임의의 정수값 하나를 덧붙인다면 용량이 부족하므로 현재 상주하고 있는 메모리 공간이 아닌 더 넓은 메모리 공간으로 이사를 가게 됩니다.

이러한 작업이 빈번하게 일어나면 당연히 성능이 저하됩니다. make([]int, 5, 10)과 같이 가능하면 슬라이스를 사용할 때 최대로 사용할 메모리 공간을 미리 설정하는 것이 성능에 도움이 됩니다.

'언어 > GO' 카테고리의 다른 글

[GO] 파라미터 ... 의미  (0) 2017.01.18
[GO] init() 함수 호출 시점  (0) 2017.01.18
[GO]슬라이스 용량 주의사항  (0) 2017.01.18
[GO] 현재 폴더 경로 확인  (0) 2017.01.17
[GO] Command line arguments 사용하기 (인자값 사용하기)  (1) 2016.12.01
[GO] Database/sql  (0) 2016.11.26


import (
	"os"
)
 
func main() {
	path, _ := os.Getwd()
)


'언어 > GO' 카테고리의 다른 글

[GO] init() 함수 호출 시점  (0) 2017.01.18
[GO]슬라이스 용량 주의사항  (0) 2017.01.18
[GO] 현재 폴더 경로 확인  (0) 2017.01.17
[GO] Command line arguments 사용하기 (인자값 사용하기)  (1) 2016.12.01
[GO] Database/sql  (0) 2016.11.26
[GO] Defer  (0) 2016.11.25

go를 실행할 때 사용자임의로 인자값을 추가하여 실행할 수 있습니다. go에서 이러한 인자값을 받아서 처리하고자 할 때, 다음과 같이 실시합니다.

package main
 
import (
 "os"
 "strings"
)

func main() string{
 var argu string
 for _, v := range os.Args { // 커맨드라인으로 받은 한 줄의 명령어를 스페이스바 단위로 쪼개어 반복문 실행.
 if strings.Index(v, "-com") == 0 { // -com이라는 값이 있으면 아래와 같이 사용자가 원하는 값 이외의 값을 제거
 argu = strings.Replace(v, "-com=", "",-1)
 break
 }
 }
 fmt.Println(argu)
}
 
 
# 실행
go run test.go -com=test
 
# 결과
test


하지만 위처럼 받은 인자값을 일일이 처리하기가 불편합니다. 그래서 go에서는 flag라는 패키지를 제공하고 있습니다.

아래는 flag 패키지를 사용해서 받은 인자값을 처리하는 코드입니다.

package main
import (
 "flag"
 "fmt"
)
func main() {
 // 옵션명, 기본값, 설명
 zipcode := flag.String("zipcode", "", "주소") // 명령줄 옵션을 받은 뒤 문자열로 저장
 release := flag.Bool("release", false, "boolean") // 명령줄 옵션을 받은 뒤 불로 저장
 flag.Parse() // 명령줄 옵션의 내용을 각 자료형별로 분석
 if flag.NFlag() == 0 { // 명령줄 옵션의 개수가 0개이면
 flag.Usage() // 명령줄 옵션 기본 사용법 출력
 return
 }
 fmt.Printf(
 "주소: %s\n", *zipcode, // 포인터이므로 값을 꺼낼 때는 역참조 사용
 ) // 명령줄 옵션으로 받은 값을 출력
 if *release == true {
 fmt.Println("boolean: 참")
 } else {
 fmt.Println("boolean: 거짓")
 }
}
 
# 실행
go run testgo.go -zipcode=1nr1r3on -release=true
 
# 결과
주소: 1nr1r3on
boolean: 참


위처럼 옵션명, 기본값, 설명로 설정하고 string, boolean타입 이외의 다른 타입으로도 값들을 저장할 수 있습니다. 

'언어 > GO' 카테고리의 다른 글

[GO]슬라이스 용량 주의사항  (0) 2017.01.18
[GO] 현재 폴더 경로 확인  (0) 2017.01.17
[GO] Command line arguments 사용하기 (인자값 사용하기)  (1) 2016.12.01
[GO] Database/sql  (0) 2016.11.26
[GO] Defer  (0) 2016.11.25
[GO] fmt print  (0) 2016.11.11
  1. 2016.12.16 13:42

    비밀댓글입니다

Package

SQL 데이타베이스를 사용하기 위해서는 표준패키지 database/sql을 사용합니다. database/sql 패키지는 관계형 데이타베이스들에게 공통적으로 사용되는 인터페이스들을 제공하고 있습니다.

database/sql 패키지는 여러 종류의 SQL 데이타베이스를 지원하는데, 각각의 데이타베이스 Driver와 함께 사용됩니다.


아래는 Postgresql을 연결하기 위해 사용한 예제입니다.

import (
	_ "github.com/lib/pq"
	"database/sql"
)

만약 드라이버 ("github.com/lib/pq") 부분을 사용하지 않는다는 표시로 앞에 _를 붙였는데 쓰지 않는다고 지우면 에러납니다. ( 연결을 위해 명시적으로 드라이버를 지정해야 함)

Connect

database/sql 패키지에서 가장 중요한 Type은 sql.DB 인데, 일반적으로 sql.Open() 함수를 사용하여 sql.DB 객체를 얻습니다. 즉, sql.Open(드라이버, Connection) 함수에서 어떤 DB 드라이버를 사용할 것인지, 그리고 해당 DB의 연결 정보를 제공하면, 결과로 sql.DB 객체를 얻게 됩니다. 일단 이 sql.DB 객체를 얻은 후, sql.DB의 여러 메서드들을 사용하여 쿼리를 하고, SQL문을 실행합니다.

주목할 점은 sql.Open()은 실제 DB Connection을 Open하지 않는다는 점입니다. 즉, sql.DB는 드라이버종류와 Connection 정보를 가지고는 있지만, 실제 DB를 연결하지 않으며, 많은 경우 Connection 정보 조차 체크하지도 않습니다. 실제 DB Connection은 Query 등과 같이 실제 DB 연결이 필요한 시점에 이루어지게 됩니다.


아래는 Postgresql에 연결하는 예시입니다. 

// 예시
db, err := sql.Open(드라이버명, `host= db의 ip port= db포트 user=접속할 db 유저 password=접속할 db pwd dbname=접속할 db명 sslmode=disable`)
 
// sql.DB 객체 생성
db, err := sql.Open("postgres", `host= localhost port=5432
	user=postgres password=postgres dbname=postgres sslmode=disable`)
 
// db가 전부 사용한 다음 맨 마지막에 닫도록 해주는 명령
defer db.Close()
 
//db.ping() 만약 db연결이 실패할 경우 왜 실패한건지 에러찍는 용도
if err != nil || db.Ping() != nil {
		panic(err.Error())
}

이후 설명은 위의 정보가 유지된다는 가정하에 진행하겠습니다.

Select

Go에서는 조회를 할 때 2개의 함수를 제공합니다. 하나의 Row만 리턴할 경우( 또는 1개의 Row만 리턴이 될 것을 예상한 경우) QueryRow() 메서드, 복수개의 Row를 리턴한다면 Query() 메서드를 사용합니다. 하나의 Row에서 실제 데이타를 읽어 로컬 변수에 할당하기 위해 Scan() 메서드를 사용하며, 복수 Row에서 다음 Row로 이동하기 위해 Next() 메서드를 사용합니다.

QueryRow()

하나의 Row를 출력하는 예제입니다.

var name string
err := db.QueryRow("SELECT name FROM test WHERE id = 10").Scan(&name)
if err != nil {
        panic(err.Error())
    }
fmt.Println(name)

Query()

다수의 Row에서 다음 Row로 이동하기위해 Next() 메서드를 사용하는데, 반복문을 사용하여 체크합니다.

	var id int
    var name string
    rows, err := db.Query("SELECT id, name FROM test1 where id >= $1", 1)
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close() //반드시 닫는다 (지연하여 닫기)
 
    for rows.Next() {
        err := rows.Scan(&id, &name)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println(id, name)
    }

여기서 한가지 주목할 것은 SQL 쿼리에서 $1 (Placeholder)를 사용하여 Parameterized Query를 사용하고 있다는 점입니다. 이는 SQL Injection과 같은 문제를 방지하기 위해 파라미터를 문자열 결합이 아닌 별도의 파라미터로 대입시키는 방식입니다. 위의 예제에서 Placeholder $1 에는 1이 대입됩니다. Placeholder는 데이타베이스의 종류에 따라 다르게 사용하는데, 예를 들어 MySql은 ? 를 사용하고, Oracle은 :val1, :val2 등을 사용하고, PostgreSQL은 $1, $2 등을 사용합니다.


경험담으로 아래와 같이 데이터베이스의 스키마를 변경한 후에 Query() 메서드를 호출하고 난 다음 함수를 빠져나가면 스키마가 다시 public으로 변경됩니다. (QueryRow()는 그대로 유지)

func setSearchPath() {
	//스키마 변경 
	_, err := db.Exec("SET SEARCH_PATH TO abcd")
	if err != nil {
panic(err)
}
}

func queryRow() {
	var name string
	err := db.QueryRow("SELECT name FROM test WHERE id = 10").Scan(&name)
	if err != nil {
        panic(err.Error())
    }
}

func query() {
	var id int
    var name string
    rows, err := db.Query("SELECT id, name FROM test1 where id >= $1", 1)
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close() //반드시 닫는다 (지연하여 닫기)
 
    for rows.Next() {
        err := rows.Scan(&id, &name)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println(id, name)
    }
}

func main() {
setSearchPath()
	query() // 함수호출한 다음, 변경된 스키마가 다시 public으로 돌아옴
queryRow() // 함수호출한 다음 변경된 스키마 유지
}

Insert, Delete, Update

데이터를 INSERT, UPDATE, DELETE (DML Operation)하기 위해서 sql.DB 객체의 Exec() 메서드를 사용합니다. Query/QueryRow 메서드는 데이타를 리턴할 때 사용하는 반면, DML과 같이 리턴되는 데이터가 없는 경우는 Exec 메서드를 사용해야 합니다.


	// INSERT 문 실행
result, err := db.Exec("INSERT INTO test1 VALUES ($1, $2)", 11, "Jack")
if err != nil {
log.Fatal(err)
}

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

Exec 메서드의 첫번째 파라미터에는 SQL문을 적고, 그 SQL문 안에 $1, $2 이 있는 경우 계속해서 상응하는 파라미터를 넣어 줍니다. Exec 메서드는 sql.Result와 error 객체를 리턴하며, sql.Result 객체로부터 갱신된 레코드수(RowsAffected())와 새로 추가된 Id (LastInsertId())를 구할 수 있습니다.

Prepared Statement

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


	// Prepared Statement 생성
stmt, err := db.Prepare("UPDATE test1 SET name=? WHERE id=?")
checkError(err)
defer stmt.Close()

// Prepared Statement 실행
_, err = stmt.Exec("Tom", 1) //Placeholder 파라미터 순서대로 전달
checkError(err)
_, err = stmt.Exec("Jack", 2)
checkError(err)
_, err = stmt.Exec("Shawn", 3)
checkError(err)
}

func checkError(err error) {
if err != nil {
panic(err)
}

트랜잭션

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


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

// INSERT 문 실행
_, err = db.Exec("INSERT INTO test1 VALUES (?, ?)", 15, "Jack")
if err != nil {
log.Fatal(err)
}

_, err = db.Exec("INSERT INTO test2 VALUES (?, ?)", 15, "Data")
if err != nil {
log.Fatal(err)
}

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



레퍼런스: https://golang.org/pkg/database/sql/

'언어 > GO' 카테고리의 다른 글

[GO] 현재 폴더 경로 확인  (0) 2017.01.17
[GO] Command line arguments 사용하기 (인자값 사용하기)  (1) 2016.12.01
[GO] Database/sql  (0) 2016.11.26
[GO] Defer  (0) 2016.11.25
[GO] fmt print  (0) 2016.11.11
[GO] 기본 디렉터리 설정  (0) 2016.10.26

defer문은 주변 함수가 반환될 때까지 함수의 실행을 연기시킵니다. 지연호출의 인수는 즉시 평가되지만 주변 함수가 반환 될 때까지 함수 호출이 실행되지 않습니다.


예시1

package main
import "fmt"
func main() {
	defer fmt.Println("world")
	fmt.Println("hello")
}


// 결과
hello
world

예시2

package main
import (
	_ "github.com/lib/pq"
	"database/sql"
)
func main() {
	// sql.DB 객체 db 생성
	db, err := sql.Open("mysql", "root:pwd@tcp(127.0.0.1:3306)/testdb")
 
	// db 차후에 닫기
	defer db.Close()
 
	// SELECT 쿼리
	rows, err := db.Query("SELECT id, name FROM test")
 
	// INSERT 실행
	db.Exec("INSERT INTO test(id, name) VALUES (1, 'Alex')")
}

// 결과
insert까지 실행된 다음 더이상 db를 사용하지 않으므로 defer 문을 호출해 db.Close()를 수행합니다.


'언어 > GO' 카테고리의 다른 글

[GO] Command line arguments 사용하기 (인자값 사용하기)  (1) 2016.12.01
[GO] Database/sql  (0) 2016.11.26
[GO] Defer  (0) 2016.11.25
[GO] fmt print  (0) 2016.11.11
[GO] 기본 디렉터리 설정  (0) 2016.10.26
[GO] GO언어란?  (1) 2016.10.26

+ Random Posts