ORM(Object Relation Mapping)이란?
- 객체와 RMDB의 데이터를 자동으로 매핑해주는 것을 말한다.
- 객체 지향 프로그래밍은 클래스를 사용하고, 관계형 데이터베이스는 테이블을 사용한다. (golang은 struct로 클래스를 대신한다)
- 객체 모델과 관계형 모델 간의 불일치가 존재한다.
- ORM을 통해 객체 간의 관계를 바탕으로 SQL을 자동으로 생성하여 불일치를 해결한다.
- 객체 지향 프로그래밍은 클래스를 사용하고, 관계형 데이터베이스는 테이블을 사용한다. (golang은 struct로 클래스를 대신한다)
- 매핑된 정보를 바탕으로 자동으로 SQL을 작성해준다.
- Object를 통해 간접적으로 데이터베이스를 다룰 수 있다.
- Object를 통해 간접적으로 데이터베이스를 다룰 수 있다.
ORM의 장단점
장점
- 객체 지향적인 코드이기에 비즈니스 로직에 더 집중할 수 있다.
- ORM을 사용하면 SQL Query가 아닌 작성 중인 언어로 DB에 접근하여 데이터를 접근할 수 있다.
- 각종 객체에 대한 코드를 별도로 작성하기 때문에 코드의 가독성이 올라간다.
- SQL의 절차적이고 순차적인 접근이 아닌 객체 지향적인 접근으로 인해 생산성이 증가한다.
- ORM을 사용하면 SQL Query가 아닌 작성 중인 언어로 DB에 접근하여 데이터를 접근할 수 있다.
- 재사용 및 유지보수의 편리성이 증가한다.
- ORM은 독립적으로 작성되어 있고, 해당 객체들을 재활용할 수 있다.
- MVC 모델을 사용할 경우 Model에서 가공된 데이터를 Controller에 의해 뷰와 합쳐지는 형태로 디자인 패턴을 견고하게 다지는데 유리하다.
- 매핑 정보가 명확하여, ERD를 보는 것에 대한 의존도를 낮출 수 있다.
- ORM은 독립적으로 작성되어 있고, 해당 객체들을 재활용할 수 있다.
- DBMS에 대한 종속성이 줄어든다.
- 객체 간의 관계를 바탕으로 SQL을 자동으로 생성하기 때문에 RDBMS의 데이터 구조와 코드의 객체지향 모델 사이의 간격을 좁힐 수 있다.
- 대부분의 ORM 솔루션은 DB에 종속적이지 않다.
- Object에 집중함으로써 극단적으로는 DBMS를 교체하는 거대한 작업에도 비교적 적은 리스크와 시간이 소요된다.
- 객체 간의 관계를 바탕으로 SQL을 자동으로 생성하기 때문에 RDBMS의 데이터 구조와 코드의 객체지향 모델 사이의 간격을 좁힐 수 있다.
단점
- 완벽한 ORM으로만 서비스를 구현하기가 어렵다.
- 사용하기는 편하지만 설계는 매우 신중하게 해야한다.
- 프로젝트의 복잡성이 커질 경우 난이도 또한 올라갈 수 있다.
- 잘못 구현된 경우 속도 저하 및 심각할 경우 일관성이 무너지는 문제점이 생길 수 있다.
- 일부 자주 사용되는 대형 쿼리는 속도를 위해 SP를 쓰는 등의 별도의 튜닝이 필요한 경우가 있다.
- DBMS의 고유 기능을 사용하기 다소 어렵다. (-> 특정 DBMS의 고유 기능을 이용한다면, 이식성이 저하된다는 문제점이 있다.)
- 사용하기는 편하지만 설계는 매우 신중하게 해야한다.
- 프로시저가 많은 시스템이라면 ORM의 객체 지향적인 장점을 활용하기 어렵다.
gORM이란?
gORM은 golang에서 사용 가능한 ORM 라이브러리 이다.
DB 환경 세팅
1) root 계정으로 mysql 접속
$sudo su - # su를 통해 관리자 권한 가져옴
$mysql -u root # mysql에 root 사용자로 접속 (비밀번호를 설정해뒀다면, mysql -u roor -p)
2) 사용자 생성 및 권한 후여
# 유저 생성, CREATE USER '{username}'@'{hostname}' IDENTIFIED BY '{password}'
CREATE user 'tester'@'localhost' IDENTIFIED BY 'password123'
# 모든 권한을 해당 유저, 호스트 조합에 허용
GRANT ALL PRIVILLEGES ON *.* TO 'tester'@'localhost';
# grant 테이블을 reload하여 변경 사항을 즉시 반영하도록 함
FLUSH PRIVILLEGES
3) 생성 확인
SELECT user, host FROM user;
결과
+------------------+-----------+
| USER | HOST |
+------------------+-----------+
| mysql.infoschema | localhost |
| mysql.session | localhost |
| mysql.sys | localhost |
| root | localhost |
| tester | localhost |
+------------------+-----------+
관련 패키지 설치
1) gORM 설치
go의 프로젝트 경로에서 다음 명령어를 작성한다.
$ go get -u gorm.io/gorm
해당 명령어를 수행하면, 현재 환경 기준으로 GOPATH/pkg/mod/gorm 에 해당 패키지가 설치되고 프로젝트 경로에서는 go.mod와 go sum에서 이를 관리하게 된다.
-u 명령어의 경우, 패키지 및 관련된 종속성을 업데이트하는 명령어로 go get과 세트로 자주 쓰이는 옵션이다.
2) gORM 용 MySQL 드라이버 설치
데이터 베이스 별로 해당하는 드라이버가 필요하다.
$ go get -u github.com/go-sql-driver/mysql
3. 쿼리 로그 설정
- 작성된 쿼리를 추적하기 위해, sql로 수행되는 쿼리를 파일에 로그로 담도록 지정
- gorm을 사용할 경우, 실제 sql이 어떻게 수행되고 있는지가 궁금할 수 있다.
mysql> SET GLOBAL general_log = 1;
Query OK, 0 rows affected (0.03 sec)
mysql> SELECT @@log_output, @@general_log, @@general_log_file;
+--------------+---------------+-----------------------------------------------------+
| @@log_output | @@general_log | @@general_log_file |
+--------------+---------------+-----------------------------------------------------+
| FILE | 1 | /usr/local/var/mysql/youngmki-MacBookPro.log |
+--------------+---------------+-----------------------------------------------------+
로그를 사용하도록 설정하고, 설정 여부를 확인했다.
다음 명령어를 통해 실제 로그 파일이 존재하는지를 확인할 수 있다.
$cd /usr/local/var/mysql && find "youngmki-MacBookPro.log"
DB 연결 예제
테스트를 위해 다음과 같이 main.go를 작성했다.
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
// "{username}:{password}@tcp(127.0.0.1)
dsn := "tester:password123@tcp(127.0.1:3306)/"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("could not connect to the database")
}
_ = db // 일단 컴파일이 되도록 설정
}
간단한 웹서버 예제
먼저 fiber라는 웹서버 라이브러리를 사용할 예정이므로, 다음 명령어를 통해 모듈을 설치한다.
$ go get -u "github.com/gofiber/fiber/v2"
그 다음으로는 mysql 내에 테이블을 생성하고자 한다.
gorm에서는 AutoMigrate라는 메서드를 통해, struct를 테이블로 옮길 수 있도록 지원한다.
그러기 위해서 먼저 다음을 models/user.go 에 작성했다.
package models
type User struct {
Id uint
Name string
Email string
Password []byte
}
그 다음에 이를 database/connection.go 라는 파일에서 DB 연결과 함께 다뤄줬다.
package database
import (
"github.com/fatih/color"
"github.com/mukmookk/simple-rest/models"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var DB *gorm.DB
func Connect() {
// "{username}:{password}@tcp(127.0.0.1)
dsn := "tester:password123@tcp(127.0.1:3306)/"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("could not connect to the database")
}
DB = connection
color.Green("Connection Opened to Database... Success!")
connection.AutoMigrate(&models.User{})
color.Green("Database Migrated... Success!")
}
여기서 color라는 모듈을 추가로 사용했는데, 터미널 출력에 다음처럼 색을 깔끔하게 입혀준다.
routes/routes.go 라는 파일을 생성하고, REST-API 메소드, url, handler를 정의해주었다.
package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/mukmookk/simple-rest/models"
)
func Setup(app *fiber.App) {
app.Post("/api/register", controllers.Register)
}
여기까지 작성했다면, 이젠 실제 핸들러를 구현해야 한다.
controller/controller.go를 생성하고 핸들러를 구현한다.
func Register(c *fiber.Ctx) error {
var data map[string]string
err := c.BodyParser(&data)
if err != nil {
return err
}
password, _ := bcrypt.GenerateFromPassword([]byte(data["password"]), 14)
user := models.User{
Name: data["name"],
Email: data["email"],
Password: password,
}
database.DB.Create(&user)
return c.JSON(user)
}
먼저 map을 정의하여 들어온 JSON을 담아준다.
-> c.BodyParser(&data)
비밀번호의 경우, 그냥 넣어지지가 않는다. 때문에 한번 해쉬 변환을 거쳐준다.
-> bcrypt.GenerateFromPassword([]byte(data["password"]), 14)
data에 담아진 정보를 model/user.go에 정의한 struct 형식으로 옮겨주고,
실제 ROW를 생성한다.
-> database.DB.Create(&user)
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/mukmookk/simple-rest/database"
"github.com/mukmookk/simple-rest/routes"
)
func main() {
app := fiber.New()
database.Connect()
routes.Setup(app)
app.Listen(":3000")
}
서버를 실행시키고
$ go run main.go
POSTMAN으로 동작 여부를 다음과 같이 확인할 수 있다.
id의 경우는 autoincrement이기에 여러번 실행하였더니 5가 되었다.
앞서 설정한 로그 파일을 출력해보면 다음과 같은 쿼리가 실행되었음을 알 수 있다.
$ cat /usr/local/var/mysql/gim-yeongmug-ui-MacBookPro.log
/usr/local/Cellar/mysql/8.0.27_1/bin/mysqld, Version: 8.0.27 (Homebrew). started with:
Tcp port: 3306 Unix socket: /tmp/mysql.sock
Time Id Command Argument
2022-01-27T15:25:46.193725Z 62 Query SELECT VERSION()
2022-01-27T15:25:46.200413Z 62 Query SELECT DATABASE()
2022-01-27T15:25:46.236453Z 62 Prepare SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE ? ORDER BY SCHEMA_NAME=? DESC limit 1
2022-01-27T15:25:46.238041Z 62 Execute SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'testdb%' ORDER BY SCHEMA_NAME='testdb' DESC limit 1
2022-01-27T15:25:46.246686Z 62 Close stmt
2022-01-27T15:25:46.262921Z 62 Prepare SELECT count(*) FROM information_schema.tables WHERE table_schema = ? AND table_name = ? AND table_type = ?
2022-01-27T15:25:46.262984Z 62 Execute SELECT count(*) FROM information_schema.tables WHERE table_schema = 'testdb' AND table_name = 'users' AND table_type = 'BASE TABLE'
2022-01-27T15:25:46.276699Z 62 Close stmt
2022-01-27T15:25:46.276761Z 62 Query SELECT DATABASE()
2022-01-27T15:25:46.278598Z 62 Prepare SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE ? ORDER BY SCHEMA_NAME=? DESC limit 1
2022-01-27T15:25:46.278646Z 62 Execute SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'testdb%' ORDER BY SCHEMA_NAME='testdb' DESC limit 1
2022-01-27T15:25:46.279462Z 62 Close stmt
2022-01-27T15:25:46.284871Z 62 Prepare SELECT column_name, is_nullable, data_type, character_maximum_length, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = ? AND table_name = ? ORDER BY ORDINAL_POSITION
2022-01-27T15:25:46.284923Z 62 Execute SELECT column_name, is_nullable, data_type, character_maximum_length, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'testdb' AND table_name = 'users' ORDER BY ORDINAL_POSITION
2022-01-27T15:25:46.292756Z 62 Close stmt
2022-01-27T15:26:21.872960Z 62 Query START TRANSACTION
2022-01-27T15:26:21.893559Z 62 Prepare INSERT INTO `users` (`name`,`email`,`password`) VALUES (?,?,?)
2022-01-27T15:26:21.893656Z 62 Execute INSERT INTO `users` (`name`,`email`,`password`) VALUES ('name','author','$2a$14$DuauYNQjhtLiYn1Pa7NGyupKiG2YA.lCp3Tjh84BPjAD/jADnzy8i')
2022-01-27T15:26:21.916066Z 62 Close stmt
2022-01-27T15:26:21.916193Z 62 Query COMMIT
이처럼 복잡한 SQL 쿼리문을 코드 단계에서 비교적 편하게 관리할 수 있다는 것이 직관적으로 느껴지는 ORM의 가장 큰 장점이다.
'Programming Language > Go' 카테고리의 다른 글
grpc 소개 (0) | 2022.11.15 |
---|---|
protocol 버퍼에 대한 소개 (0) | 2022.11.15 |
[go 모듈 소개 - 2. logrus] 더 자세한 로깅 모듈 (0) | 2022.11.15 |