목차
1.서론
GORM은 Go에서 널리 사용되는 ORM(Object Relational Mapping) 라이브러리로, SQL을 직접 작성하지 않아도 데이터베이스 작업을 손쉽게 처리할 수 있습니다. 하지만 복잡한 쿼리나 예기치 못한 에러가 발생했을 때는 디버깅이 필수적입니다. GORM의 디버깅 기법을 활용하면 실행되는 SQL과 에러 정보를 쉽게 파악할 수 있어 문제 해결이 수월해집니다. 이 글에서는 GORM 디버깅 기법 네 가지를 예제와 함께 소개합니다.
2.GORM 디버깅 기법
2.1 Debug 모드 활성화
GORM에서는 Debug() 메서드를 사용해 SQL 쿼리 로그를 출력할 수 있습니다. 디버깅을 시작할 때 가장 간단하고 강력한 방법입니다. 특정 쿼리에서 db.Debug()를 직접 호출하거나, GORM 객체를 설정할 때 db = db.Debug()로 기본 디버깅 모드를 활성화할 수 있습니다.
//예시 코드
package main
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint
Name string
Age int
}
func main() {
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// 특정 쿼리에만 Debug 활성화
db.Debug().Find(&[]User{})
// 전체 작업에서 Debug 활성화
db = db.Debug()
db.Create(&User{Name: "Alice", Age: 25})
db.First(&User{})
}
--출력 결과 (콘솔)
SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL
INSERT INTO `users` (`name`,`age`) VALUES ("Alice",25)
SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY `users`.`id` LIMIT 1
- 특정 작업(예: 조회, 생성)에만 Debug()를 사용하면 불필요한 로그 출력을 줄일 수 있습니다.
- db = db.Debug()처럼 초기 설정으로 활성화하면 개발 환경에서 모든 작업의 SQL 로그를 확인할 수 있어 유용합니다.
2.2 SQL 로그 직접 확인
Logger 설정을 통해 SQL 로그를 더 정교하게 관리할 수 있습니다. 기본 로그 수준을 조정하면 필요한 정보만 출력할 수 있습니다.
// 예시 코드
package main
import (
"log"
"time"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
func main() {
newLogger := logger.New(
log.New(log.Writer(), "\r\n", log.LstdFlags), // 로그 출력 설정
logger.Config{
SlowThreshold: time.Second, // 느린 쿼리 기준
LogLevel: logger.Warn, // Warn 레벨 이상의 로그 출력
Colorful: true, // 로그 색상 출력 여부
},
)
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
Logger: newLogger,
})
var user User
db.First(&user)
}
출력 결과 (콘솔)
[2024-11-27 12:34:56] [WARN] Slow SQL >= 1s
[1.000ms] SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY `users`.`id` LIMIT 1
- 느린 쿼리(Slow SQL)를 자동으로 감지하여 성능 최적화에 도움을 줍니다.
- 로그 수준을 Info, Warn, Error로 설정해 디버깅 범위를 좁힐 수 있습니다.
- 사용 가능한 옵션의 종류에는 SlowThreshold, Colorful, IgnoreRecordNotFoundError, ParameterizedQueries, LogLevel이 있습니다. 공식 문서
package logger
type Config struct {
SlowThreshold time.Duration // 느린 쿼리 기준 시간
LogLevel logger.LogLevel // 로그 출력 레벨
Colorful bool // 로그 색상 출력 여부
IgnoreRecordNotFoundError bool // 레코드 없음 에러 무시 여부
ParameterizedQueries bool // 매개변수화된 쿼리 사용 여부
}
2.3 에러 핸들링
GORM에서는 .Error 필드를 사용해 쿼리 실행 중 발생한 에러를 감지할 수 있습니다. 이를 통해 SQL 실행 실패의 원인을 추적하거나, 데이터베이스의 특정 에러 코드를 기반으로 문제를 세부적으로 처리할 수 있습니다.
일반적인 Error 핸들링
쿼리 실행 결과에서 발생한 에러를 추적하는 기본적인 방법은 아래와 같습니다.
// 예시 코드
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func main() {
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
var user User
result := db.First(&user, "name = ?", "nonexistent")
if result.Error != nil {
fmt.Println("Error occurred:", result.Error)
} else {
fmt.Println("User found:", user)
}
}
출력 결과 (콘솔)
Error occurred: record not found
에러 코드 처리
데이터베이스는 종종 에러와 함께 특정 에러 코드를 반환합니다. 이 코드는 데이터베이스 제약 조건 위반, 연결 문제, SQL 구문 오류 등의 원인을 나타냅니다. GORM에서 이러한 에러 코드를 처리하려면 데이터베이스에서 반환한 에러를 파싱하고 코드를 추출해야 합니다.
// MySQL 에러 코드 처리 예제
package main
import (
"fmt"
"github.com/go-sql-driver/mysql"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
dsn := "user:password@tcp(127.0.0.1:3306)/dbname"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
newRecord := User{Name: "Alice", Age: 25}
result := db.Create(&newRecord)
if result.Error != nil {
if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok {
switch mysqlErr.Number {
case 1062: // 중복 항목 에러 코드
fmt.Println("Duplicate entry error")
default:
fmt.Printf("MySQL Error: %d, Message: %s\n", mysqlErr.Number, mysqlErr.Message)
}
} else {
fmt.Println("Other error:", result.Error)
}
}
}
- MySQL 이외의 데이터베이스도 해당하는 에러 코드를 찾아 같은 방식으로 처리할 수 있습니다.
- 특정 에러 코드에 따라 비즈니스 로직을 분기하거나 사용자 메시지를 커스터마이징할 수 있습니다.
TranslateError를 통해 일반화된 에러로 변환
GORM은 TranslateError 옵션을 활성화하면 데이터베이스 별도로 발생하는 에러를 GORM의 일반화된 에러로 변환해줍니다. 이를 통해 데이터베이스와 상관없이 에러를 일관되게 처리할 수 있습니다.
// PostgreSQL 에러 변환 예제
package main
import (
"fmt"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func main() {
dsn := "host=localhost user=username password=secret dbname=test port=5432 sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
TranslateError: true, // 에러 변환 활성화
})
if err != nil {
fmt.Println("Failed to connect to database:", err)
return
}
var user User
result := db.First(&user, "id = ?", 123)
if result.Error != nil {
fmt.Println("Translated error:", result.Error)
}
}
출력 결과 (콘솔)
존재하지 않는 데이터 조회 시
Translated error: record not found
PostgreSQL 연결 오류 발생 시 (예: 잘못된 비밀번호)
Failed to connect to database: pq: password authentication failed for user "username"
- TranslateError는 주로 여러 데이터베이스를 지원하는 애플리케이션에서 유용합니다.
- 번역된 에러를 사용하면 데이터베이스에 따라 에러 처리를 변경하지 않아도 됩니다.
정리
GORM의 에러 핸들링 기능은 단순한 에러 감지에서부터 데이터베이스별 에러 코드 처리, 그리고 Translated Error 로의 변환까지 다양한 수준의 디버깅을 지원합니다. 이를 통해 데이터베이스 작업 중 발생하는 문제를 빠르게 파악하고 해결할 수 있습니다.
- 기본 .Error로 에러를 감지하고,
- 데이터베이스별 에러 코드를 처리하며,
- TranslateError로 에러를 일반화하여 일관성을 확보할 수 있습니다.
이러한 기능들을 조합해 더 안정적이고 효율적인 데이터베이스 처리를 구현할 수 있습니다. 공식문서
2.4 Hooks를 활용한 흐름 분석
GORM은 데이터 생성, 업데이트, 삭제 시 특정 로직을 추가할 수 있는 Hook 기능을 제공합니다. 이를 활용해 데이터 흐름을 추적하고 문제를 파악할 수 있습니다.
//예시 코드
package main
import (
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint
Name string
Age int
}
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
log.Println("Before creating user:", u.Name)
return
}
func main() {
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.Create(&User{Name: "Alice", Age: 25})
}
출력 결과 (콘솔)
Before creating user: Alice
- 복잡한 트랜잭션의 실행 순서를 확인하거나 특정 데이터 조건을 검증할 때 유용합니다.
- Pre/Post hooks를 결합하여 데이터베이스 처리 전후의 모든 상태를 점검할 수 있습니다.
- 공식 문서
3.결론
GORM 디버깅 기법은 SQL 쿼리와 데이터 흐름을 파악하는 데 필수적인 도구입니다.
- Debug()로 빠르게 SQL 로그를 확인하고,
- Logger를 통해 더 정교한 디버깅 환경을 구성하며,
- Error 핸들링으로 쿼리 실패 원인을 정확히 파악하고,
- Hooks를 사용해 데이터 흐름을 추적할 수 있습니다.
이런 디버깅 기법을 적극 활용하면 데이터베이스 작업 중 발생하는 문제를 빠르고 정확히 해결할 수 있습니다.