메뉴

문서정보

목차

ORM

ORM(Object-relation mapping)은 객체지향 프로그래밍 언어를 사용해서 서로 다른 시스템간에 데이터를 호환성있게 변환하기 위해서 사용하는 프로그래밍 기술이다. ORM을 사용 할 경우 실실적으로 프로그래밍 언어에서 사용 할 수 있는 가상 객체 데이터베이스를 생성한다. 이 가상 객체 데이터베이스는 클래스(혹은 스트럭처)와 맵핑된다. 결과적으로 개발자는 객체를 다루는 것처럼 데이터를 다룰 수 있으며, 데이터베이스를 코드에 자연스럽게 녹여낼 수 있게 된다.

ORM을 사용하면 데이터베이스 프로그래밍이 편해지기 때문에 다양한 무료/상영업 패키지들이 있다.

객체지향 프로그래밍에서 데이터는 스칼라가 아닌 객체단위로 관리한다. 예를 들어 유저 정보를 관리하는 프로그램이라면 UserInfo 클래스를 만들고 여기에 이름, 주소, 나이 등을 객체요소로 묶는다. 여기에 메서드를 이용해서 이들 객체 데이터를 관리/조작한다.

반면 RDBMS는 테이블내에 구성된 정수와 문자열 같은 스칼라 값만 저장하고 조작 할 수 있다. 따라서 프로그래머는 객체를 스칼라 값으로 변환하는 등의 추가적인 작업을 해야 한다. 전혀 다른 타입의 두 데이터를 묶는 까다로운 작업으로 많은 실수들이 발생 할 수 있다.

ORM을 이용해서 개발자는 객체의 논리적 표현을 데이터베이스에 저장 할 수 있는 형태로 변환하는 동시에 객체의 속성과 관계를 유지할 수 있다.

데이터베이스에서의 ORM

Go언어에서 SQL을 이용한 코드 예제다.
package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "os"
)

type Person struct {
    Id         int
    First_name string
    Last_name  string
    Phone      string
    Age        int
    Birth_date string
}

func main() {
    db, err := sql.Open("mysql", "root:1234@tcp(172.17.0.2:3306)/testdb")
    if err != nil {
        os.Exit(1)
    }

    rows, err := db.Query("SELECT id, first_name, last_name, phone, birth_date, age FROM persons WHERE id = 1")

    if err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }
    for rows.Next() {
        p := Person{}
        rows.Scan(&p.Id, &p.First_name, &p.Last_name, &p.Phone, &p.Birth_date, &p.Age)
        fmt.Printf("%#v\n", p)
    }
}
Person 구조체와 데이터베이스 필드가 따로 놀고 있다. 쿼리가 복잡해지거나 변경이 있을 때, 관리가 어려워질 것이다. ORM을 이용해서 대략 아래와 같은 느낌으로 객체(struct)와 맵핑 할 수 있다.
type User struct {
    gorm.Model
    First_name string    `gorm:"column:first_name"`
    Last_name  string    `gorm:"column:last_name"`
    Phone      string    `gorm:"column:phone"`
    Age        int       `gorm:"column:age"`
    Birth_date time.Time `gorm:"column:birth_date"`
}

users := User{}
db.Where(&User{First_name: "yun"}).First(&users)

gorm

gorm은 go언어를 위한 ORM이다.

Table Create

객체(struct)로 부터 테이블을 만들 수 있다. 아래 코드를 보자.
type User struct {
    gorm.Model                                   
    First_name string    `gorm:"column:first_name"`
    Last_name  string    `gorm:"column:last_name"`
    Phone      string    `gorm:"column:phone"`
    Age        int       `gorm:"column:age"`
    Birth_date time.Time `gorm:"column:birth_date"`
}

db, _ := gorm.Open("mysql", "root:1234@tcp(172.17.0.2:3306)/testdb?parseTime=true")  
db.CreateTable(&User{})  
이 코드는 User구조체와 매핑되는 데이터베이스 테이블을 만든다.
mysql> desc users;
+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| id         | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| created_at | datetime         | YES  |     | NULL    |                |
| updated_at | datetime         | YES  |     | NULL    |                |
| deleted_at | datetime         | YES  | MUL | NULL    |                |
| first_name | varchar(255)     | YES  |     | NULL    |                |
| last_name  | varchar(255)     | YES  |     | NULL    |                |
| phone      | varchar(255)     | YES  |     | NULL    |                |
| age        | int(11)          | YES  |     | NULL    |                |
| birth_date | datetime         | YES  |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+
gorm 태그를 이용해서 각 필드에 대한 자세한 설정을 할 수 있다.

created_at, updated_at, deleted_at, id 가 기본으로 만들어지는데, gorm.Model에 선언되어 있다.
type Model struct {
	ID        uint `gorm:"primary_key"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt *time.Time `sql:"index"`
}
gorm.Model은 옵션이긴 하지만, created_at, updated_at, deleted_at 등의 연산을 수행해주니 사용해서 나쁠 건 없겠다.

INSERT

    data := Info{
        First_name: "yundream",
        Last_name:  "yun",
        Phone:      "01012345678",
        Age:        38,
        Birth_date: time.Date(1989, 1, 2, 0, 0, 0, 0, time.UTC),
    }
    db.Create(&data)

기본 값(default value)을 설정 할 수도 있다.
    type Info struct {
        gorm.Model
        First_name string    `gorm:"column:first_name"`
        Last_name  string    `gorm:"column:last_name"`
        Phone      string    `gorm:"column:phone"`
        Age        int       `gorm:"column:age;default:20"`
        Birth_date time.Time `gorm:"column:birth_date"`
    }

Query

SELECT

일반적인 SELECT
resData := Info{}

// 일치하는 첫번째 레코드를 가져온다.
res := db.Where("first_name=?", "yundream").First(&resData)
if res.Error != nil {
    fmt.Println(res.Error.Error())
}
//// SELECT * FROM infos WHERE first_name='yundream' LIMIT 1

// IN Query
db.Where("first_name IN (?)", []string{"yundream", "bar"}).Find(&user)
//// SELECT * FROM infos WHERE first_name in ('yundream', 'bar')


// 모든 레코드를 가져온다. 배열이어야 할 것이다.
resData := []Info{}
res := db.Where("first_name=?", "yundream").Find(&resData)
//// SELECT * fROM infos WHERE first_name='yundream'

// primary key로 order by 하고 첫번째 결과를 가져온다.
user := Info{}
db.First(&user)
//// SELECT * FROM infors ORDER BY id LIMIT 1;

// primary key 값을 특정해서 가져올 수 있다. integer 값만 가능하다.
db.First(&user, 4)
//// SELECT * FROM infors WHERE id=4

// time 필드에 대한 select, 1989년 1월 1일 이후 출생자
db.Where("birth_date > ?", time.Date(1989, 1, 1, 0, 0, 0, 0, time.UTC)).Find(&user)
//// SELECT * FROM infors WHERE birth_date > '1989-01-01 00:00:00';

Struct와 map으로 쿼리를 만들 수 있다. SQL의 일부를 사용하는 것보다는 이 방법이 좋아보인다.
// struct
db.Where(&Info{First_name: "yundream", Age: 20}).First(&user)
//// SELECT * FROM infos WHERE first_name = 'yundream' AND age = 20 ORDER BY id LIMIT 1;

// map
db.Where(map[string]interface{}{"first_name": "yundream", "age": 20}).First(&user)
//// SELECT * FROM infos WHERE first_name = 'yundream' AND age = 20 

// Slice
db.Where([]int64{3, 4, 5}).Find(&user)
//// SELECT * FROM infos WHERE id IN (3,4,5);

NOT

db.Not("first_name", "yundream").First(&user)
//// SELECT * FROM infos WHERE first_name <> "yundream" ORDER BY id LIMIT 1;

db.Not("first_name", []string{"yundream", "foo"}).Find(&user)
//// SELECT * FROM infos WHERE first_name NOT IN ("yundream", "foo");

// plain SQL
db.Not("first_name = ?", "yundream").First(&user)
//// SELECT * FROM infos WHERE NOT(first_name = "yundream") ORDER BY id LIMIT 1;

OR

db.Where(Info{First_name: "yundream"}).Or(Info{Age: 20}).Find(&user)
//// SELECT * FROM infos WHERE first_name = "yundream" OR age=20;

Assign

데이터가 없을 경우 Insert를 하고, 데이터가 있을 경우 Update 한다.
// 데이터가 없을 경우 Insert 한다. 
db.Where(Info{First_name: "not_existing"}).Assign(                                      
   Info{Age: 22,                                                                              
       Birth_date: time.Date(1992, 2, 2, 0, 0, 0, 0, time.UTC),                       
}).FirstOrCreate(&user) 
//// SELECT * FROM infos WHERE first_name = 'not_existing' ORDER BY id LIMIT 1;
//// 값이 없으니 INSERT를 수행한다. 
//// INSERT INTO infos (first_name, age) VALUES('not_existing', 22);

// 데이티가 있을 경우 Update 한다.  
db.Where(Info{First_name: "not_existing"}).Assign(
    Info{Age: 38,
         Birth_date: time.Date(1979, 2, 2, 0, 0, 0, 0, time.UTC),
}).FirstOrCreate(&user)

DELETE

db.Where(4).Delete(&user)
//// UPDATE infos SET deleted_at=NOW() WHERE id = 4; 
gorm.Model은 deleted_at 필드를 가지고 있다. 지우는 대신에 deleted_at을 현재 시간으로 설정한다.

Order

user := []Info{}
db.Order("age desc").Find(&user)
//// SELECT * FROM infos ORDER BY age desc;

db.Order("age asc, first_nam").Find(&user)
//// SELECT * FROM infos ORDER BY age desc, first_name;

Limit & Offset

게시판 기반 애플리케이션을 만들 때 응용 할 수 있을 것이다.
db.Limit(3).Find(&user)
//// SELECT * FROM infos LIMIT 3;

db.Limit(3).Offset(2).Find(&user)
//// SELECT * FROM infos LIMIT 3 OFFSET 2;

db.Order("updated_at desc").Limit(3).Offset(0).Find(&user)
//// SELECT * FROM infos WHERE deleted_at IS NULL ORDER BY  updated_at desc LIMIT 3 OFFSET 0;
gorm은 삭제되지 않은 레코드(deleted_at IS NULL)들만을 가져온다.

Count

db.Where("first_name=?", "yundream").Find(&user).Count(&count)
//// SELECT * FROM infos WHERE first_name = "yundream"; 
//// SELECT count(*) FROM infos WHERE first_name = "yundream";