Go web 13. SQL query

JINSOO PARK·2021년 10월 29일
0

Go 로 만드는 웹

목록 보기
12/16

SQL query이란?

SQL이란?

Structured Query Language
구조적 질의 언어라고 하며 간단하게 데이터베이스에서 자료를 불러오고 처리하기 위한 언어이다.

Query란?

사전적 정의는 '질의/질문' 이며, 데이터 관련 분야에서는 '데이터베이스로부터 정보를 요청'하는 것을 의미한다.

앞서 말한 자료를 처리하고 불러오는 과정에서 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}
}
profile
개린이

0개의 댓글