SQL query이란?
Structured Query Language
구조적 질의 언어라고 하며 간단하게 데이터베이스에서 자료를 불러오고 처리하기 위한 언어이다.
사전적 정의는 '질의/질문' 이며, 데이터 관련 분야에서는 '데이터베이스로부터 정보를 요청'하는 것을 의미한다.
앞서 말한 자료를 처리하고 불러오는 과정에서 SQL을 통한 Query 작성을 요구한다.
즉 데이터베이스와의 의사소통(Query)을 위해
SQL이란 언어를 배운다.
SQL: 데이터베이스에서 자료를 불러오고 처리하기 위한 언어
Query: 데이터베이스로부터 정보를 요청하는 것
실습하기
Go web 12에 이어서 todo의 DB를 만들어 보자
ex) main.go
package main
import (
"net/http"
"web10/app"
"github.com/urfave/negroni"
)
func main() {
m := app.MakeHandler("./test.db")
defer m.Close()
n := negroni.Classic()
n.UseHandler(m)
http.ListenAndServe(":3000", n)
}
ex) app.go
package app
import (
"net/http"
"strconv"
"web10/model"
"github.com/gorilla/mux"
"github.com/unrolled/render"
)
var rd *render.Render = render.New()
type AppHandler struct {
http.Handler
db model.DBHandler
}
func (a *AppHandler) indexHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/todo.html", http.StatusTemporaryRedirect)
}
// 리스트를 받아서 제이슨형태로 넘겨줌
func (a *AppHandler) getTodoListHandler(w http.ResponseWriter, r *http.Request) {
list := a.db.GetTodos() // model 패키지에서 불러옴
rd.JSON(w, http.StatusOK, list)
// json형태로 반환
}
func (a *AppHandler) addTodoHandler(w http.ResponseWriter, r *http.Request) {
name := r.FormValue("name") // 리퀘스트의 키로 name을 읽음
todo := a.db.AddTodo(name)
rd.JSON(w, http.StatusCreated, todo) // json형태로 반환
}
type Success struct {
Success bool `json:"success"`
}
func (a *AppHandler) removeTodoHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, _ := strconv.Atoi(vars["id"])
ok := a.db.RemoveTodo(id)
if ok {
rd.JSON(w, http.StatusOK, Success{true})
} else {
rd.JSON(w, http.StatusOK, Success{false})
}
}
func (a *AppHandler) completeTodoHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, _ := strconv.Atoi(vars["id"])
complete := r.FormValue("complete") == "true"
ok := a.db.CompleteTodo(id, complete)
if ok {
rd.JSON(w, http.StatusOK, Success{true})
} else {
rd.JSON(w, http.StatusOK, Success{false})
}
}
func (a *AppHandler) Close() {
a.db.Close()
}
func MakeHandler(filepath string) *AppHandler {
r := mux.NewRouter()
a := &AppHandler{
Handler: r,
db: model.NewDBHandler(filepath),
}
r.HandleFunc("/todos", a.getTodoListHandler).Methods("GET")
r.HandleFunc("/todos", a.addTodoHandler).Methods("POST")
r.HandleFunc("/todos/{id:[0-9]+}", a.removeTodoHandler).Methods("DELETE")
r.HandleFunc("complete-todo/{id:[0-9]+", a.completeTodoHandler)
r.HandleFunc("/auth/google/login", googleLoginHandler)
r.HandleFunc("/auth/google/callback", googleAuthCallback)
r.HandleFunc("/", a.indexHandler)
return a
}
ex) model.go
package model
import (
"time"
)
type Todo struct {
ID int `json:"id"`
Name string `json:"name"`
Completed bool `json:"completed"`
CreatedAt time.Time `json:"created_at"`
}
// 인터페이스 생성
type DBHandler interface {
GetTodos() []*Todo
AddTodo(name string) *Todo
RemoveTodo(id int) bool
CompleteTodo(id int, complete bool) bool
Close()
}
func NewDBHandler(filepath string) DBHandler {
// handler = newMemoryHandler()
return newSqliteHandler(filepath)
}
ex) sqliteHandler.go
package model
import (
"database/sql"
"os"
"time"
_ "github.com/mattn/go-sqlite3"
)
type sqliteHandler struct {
db *sql.DB
}
func (s *sqliteHandler) GetTodos() []*Todo {
todos := []*Todo{} // 반환값을 가지고 있을 리스트
rows, err := s.db.Query("SELECT id, name, completed, createdAt FROM todos")
//SELECT 로 데이터를 가져오고 , FROM 어디서 가져올 껀지
//id, name, completed를 todos라는 테이블에서 가져옴
if err != nil {
panic(err)
}
defer rows.Close()
for rows.Next() { //다음 행이 없을때 까지 반복
var todo Todo // 가져온 데이터를 담을 Todo 객체
rows.Scan(&todo.ID, &todo.Name, &todo.Completed, &todo.CreatedAt)
// 데이터를 읽어 온다
todos = append(todos, &todo) // todos에 데이터를 저장한다
}
return todos
}
func (s *sqliteHandler) AddTodo(name string) *Todo {
stmt, err := s.db.Prepare("INSERT INTO todos (name, completed, createdAt) VALUES (?, ?, datetime('now'))")
//Prepare로 스테이트먼트를 만든다.
//INSERT INTO todos: todos 테이블에 값을 추가한다.
//(name, completed, createdAt) : 추가할 컬럼 값
//VALUES (?, ?, datetime('now')) : 추가할 벨류 값
//datetime('now') : sqlite의 내장함수
if err != nil {
panic(err)
}
rst, err := stmt.Exec(name, false)
if err != nil {
panic(err)
}
id, _ := rst.LastInsertId() // 자동으로 추가된 id의 제일 마지막 값
var todo Todo
todo.ID = int(id) // 새로 만든 id는 int64 타입이기 때문에 바꿔줌
todo.Name = name
todo.Completed = false
todo.CreatedAt = time.Now()
return &todo
}
func (s *sqliteHandler) RemoveTodo(id int) bool {
stmt, err := s.db.Prepare("DELETE FROM todos WHERE id=?")
// Prepare 로 스테이트먼트 만들기
// DELETE FROM todos: todos 테이블의 레코드를 삭제한다.
// WHERE id=? : 특정 id의 레코드 값만 삭제한다.
if err != nil {
panic(err)
}
rst, err := stmt.Exec(id)
if err != nil {
panic(err)
}
cnt, _ := rst.RowsAffected()
// RowsAffected : 영향 받은 레코드가 있는지 없는지의 여부
// 업데이트 된 레코드 갯수
return cnt > 0
}
func (s *sqliteHandler) CompleteTodo(id int, complete bool) bool {
stmt, err := s.db.Prepare("UPDATE todos SET completed = ? WHERE id=?")
// Prepare 로 스테이트먼트 만들기
// UPDATE todos: todos의 값을 변경
// SET completed: completed 항목을 변경
// = ? : 어떻게 업데이트 할것이냐
// WHERE id=? : 인자로 받은 id를 특정함
if err != nil {
panic(err)
}
rst, err := stmt.Exec(complete, id)
if err != nil {
panic(err)
}
cnt, _ := rst.RowsAffected()
// RowsAffected : 영향 받은 레코드가 있는지 없는지의 여부
// 업데이트 된 레코드 갯수
return cnt > 0
//업데이트된 레코드가 있으면 true
}
// 데이터 베이스를 열면 사라지기전에 닫아줘야함
func (s *sqliteHandler) Close() {
s.db.Close()
}
func newSqliteHandler(filepath string) DBHandler {
os.Remove("./test.db")
database, err := sql.Open("sqlite3", filepath)
if err != nil {
panic(err)
}
statement, _ := database.Prepare(
`CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
completed BOOLEAN,
createdAt DATETIME
)`)
// 테이블을 만드는 쿼리문
// KEY AUTOINCREMENT: DB안에서 자동으로 값을 증가시킴
statement.Exec()
return &sqliteHandler{db: database}
}