Table of Contents
- GORM Implementation Guide
- Inhaltsverzeichnis
- Installation
- Datenbankverbindung
- Modelle und Beziehungen
- Migrationen
- Repository-Pattern
- Verschiedene Datenbanken
- Erweiterte Features
- Best Practices
- 1. Connection Pooling konfigurieren
- 2. Logging konfigurieren
- 3. Indizes für Performance
- 4. Batch-Operationen
- 5. Prepared Statements verwenden
- 6. Fehlerbehandlung
- Migration von sqlx zu GORM
GORM Implementation Guide
Inhaltsverzeichnis
- Installation
- Datenbankverbindung
- Modelle und Beziehungen
- Migrationen
- Repository-Pattern
- Verschiedene Datenbanken
- Erweiterte Features
- Best Practices
Installation
Grundlegende GORM-Installation
go get -u gorm.io/gorm
Datenbank-Driver installieren
SQLite:
go get -u gorm.io/driver/sqlite
PostgreSQL:
go get -u gorm.io/driver/postgres
MySQL:
go get -u gorm.io/driver/mysql
SQL Server:
go get -u gorm.io/driver/sqlserver
Datenbankverbindung
SQLite (Empfohlen für Entwicklung)
package main
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
}
PostgreSQL (Empfohlen für Produktion)
import (
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func connectPostgreSQL() {
dsn := "host=localhost user=gorm password=gorm dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
}
MySQL
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func connectMySQL() {
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
}
Modelle und Beziehungen
GORM Struct Tags
type User struct {
ID uint `gorm:"primaryKey"` // Primärschlüssel
Username string `gorm:"unique;not null;size:255"` // Eindeutig, nicht null, max 255 Zeichen
Email string `gorm:"uniqueIndex"` // Eindeutiger Index
Age int `gorm:"check:age > 13"` // Check constraint
CreatedAt time.Time `gorm:"autoCreateTime"` // Automatisch bei Erstellung
UpdatedAt time.Time `gorm:"autoUpdateTime"` // Automatisch bei Update
DeletedAt gorm.DeletedAt `gorm:"index"` // Soft delete
}
Beziehungen definieren
Has One (1:1):
type User struct {
gorm.Model
Profile Profile
}
type Profile struct {
gorm.Model
UserID uint
User User
}
Has Many (1:n):
type User struct {
gorm.Model
Articles []Article `gorm:"foreignKey:UserID"`
}
type Article struct {
gorm.Model
UserID uint
User User
}
Many to Many (n:m):
type User struct {
gorm.Model
Languages []Language `gorm:"many2many:user_languages;"`
}
type Language struct {
gorm.Model
Users []User `gorm:"many2many:user_languages;"`
}
Migrationen
AutoMigrate (Empfohlen für Entwicklung)
AutoMigrate erstellt Tabellen, fehlende Spalten und Indizes automatisch. Es löscht KEINE bestehenden Spalten oder Daten.
func autoMigrate() {
db.AutoMigrate(&User{}, &Article{}, &Comment{})
}
Vorteile von AutoMigrate:
- Einfach zu verwenden
- Sicher (löscht keine Daten)
- Gut für Entwicklung
Nachteile von AutoMigrate:
- Begrenzte Kontrolle über Änderungen
- Kann keine Spalten umbenennen oder löschen
- Nicht ideal für komplexe Produktionsänderungen
Manuelle Migrationen
Für mehr Kontrolle kannst du manuelle Migrationen verwenden:
func manualMigration() {
// Tabelle erstellen
db.Migrator().CreateTable(&User{})
// Spalte hinzufügen
db.Migrator().AddColumn(&User{}, "NewColumn")
// Index erstellen
db.Migrator().CreateIndex(&User{}, "idx_user_email")
// Spalte löschen (VORSICHT!)
db.Migrator().DropColumn(&User{}, "OldColumn")
// Tabelle umbenennen
db.Migrator().RenameTable(&User{}, &NewUser{})
}
Migration-Strategien
Entwicklungsumgebung:
func initDevelopmentDB() {
db.AutoMigrate(&User{}, &Article{}, &Comment{})
// Entwicklungsdaten einfügen
seedDevelopmentData()
}
Produktionsumgebung:
func initProductionDB() {
// Versionierte Migrationen
runMigrationV1()
runMigrationV2()
runMigrationV3()
}
func runMigrationV1() {
if !db.Migrator().HasTable(&User{}) {
db.AutoMigrate(&User{})
}
}
Repository-Pattern
Basis Repository Interface
type Repository[T any] interface {
Create(entity *T) error
GetByID(id uint) (*T, error)
GetAll() ([]T, error)
Update(entity *T) error
Delete(id uint) error
}
Spezifische Repository Implementierung
type UserRepository struct {
db *gorm.DB
}
func NewUserRepository(db *gorm.DB) *UserRepository {
return &UserRepository{db: db}
}
func (r *UserRepository) Create(user *User) error {
return r.db.Create(user).Error
}
func (r *UserRepository) GetByUsername(username string) (*User, error) {
var user User
err := r.db.Where("username = ?", username).First(&user).Error
return &user, err
}
Verschiedene Datenbanken
Konfiguration für verschiedene Umgebungen
package database
import (
"os"
"gorm.io/gorm"
"gorm.io/driver/sqlite"
"gorm.io/driver/postgres"
"gorm.io/driver/mysql"
)
func InitDatabase() (*gorm.DB, error) {
dbType := os.Getenv("DB_TYPE")
switch dbType {
case "postgres":
return initPostgres()
case "mysql":
return initMySQL()
case "sqlite", "":
return initSQLite()
default:
return initSQLite() // Fallback
}
}
func initPostgres() (*gorm.DB, error) {
dsn := os.Getenv("DATABASE_URL")
if dsn == "" {
dsn = "host=localhost user=postgres password=postgres dbname=myapp port=5432 sslmode=disable"
}
return gorm.Open(postgres.Open(dsn), &gorm.Config{})
}
func initMySQL() (*gorm.DB, error) {
dsn := os.Getenv("DATABASE_URL")
if dsn == "" {
dsn = "root:password@tcp(localhost:3306)/myapp?charset=utf8mb4&parseTime=True&loc=Local"
}
return gorm.Open(mysql.Open(dsn), &gorm.Config{})
}
func initSQLite() (*gorm.DB, error) {
dbPath := os.Getenv("SQLITE_PATH")
if dbPath == "" {
dbPath = "./app.db"
}
return gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
}
Umgebungsvariablen (.env Datei)
Entwicklung (SQLite):
DB_TYPE=sqlite
SQLITE_PATH=./dev.db
Produktion (PostgreSQL):
DB_TYPE=postgres
DATABASE_URL=postgres://user:password@localhost/dbname?sslmode=disable
Testing (In-Memory SQLite):
DB_TYPE=sqlite
SQLITE_PATH=:memory:
Erweiterte Features
Hooks (Callbacks)
func (u *User) BeforeCreate(tx *gorm.DB) error {
// Hash password before saving
hashedPassword, err := hashPassword(u.Password)
if err != nil {
return err
}
u.Password = hashedPassword
return nil
}
func (u *User) AfterFind(tx *gorm.DB) error {
// Load additional data after finding
return nil
}
Scopes
func ActiveUsers(db *gorm.DB) *gorm.DB {
return db.Where("active = ?", true)
}
func AdminUsers(db *gorm.DB) *gorm.DB {
return db.Where("is_admin = ?", true)
}
// Verwendung
var users []User
db.Scopes(ActiveUsers, AdminUsers).Find(&users)
Transaktionen
func TransferArticle(fromUserID, toUserID, articleID uint) error {
return DB.Transaction(func(tx *gorm.DB) error {
// Article ownership überprüfen
var article Article
if err := tx.First(&article, articleID).Error; err != nil {
return err
}
if article.UserID != fromUserID {
return errors.New("not authorized")
}
// Ownership ändern
article.UserID = toUserID
return tx.Save(&article).Error
})
}
Soft Delete
type User struct {
gorm.Model // Enthält DeletedAt
Username string
}
// Soft delete (setzt DeletedAt)
db.Delete(&user)
// Include soft deleted records
db.Unscoped().Find(&users)
// Permanently delete
db.Unscoped().Delete(&user)
Best Practices
1. Connection Pooling konfigurieren
func configureDB(db *gorm.DB) error {
sqlDB, err := db.DB()
if err != nil {
return err
}
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)
return nil
}
2. Logging konfigurieren
import "gorm.io/gorm/logger"
func newDB() *gorm.DB {
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
return db
}
3. Indizes für Performance
type User struct {
ID uint `gorm:"primaryKey"`
Username string `gorm:"uniqueIndex"` // Eindeutiger Index
Email string `gorm:"index"` // Normaler Index
Age int `gorm:"index:idx_age_name"` // Composite Index
Name string `gorm:"index:idx_age_name"` // Teil des Composite Index
}
4. Batch-Operationen
// Batch Insert
users := []User{{Username: "user1"}, {Username: "user2"}}
db.CreateInBatches(users, 100)
// Batch Update
db.Model(&User{}).Where("active = ?", false).Update("status", "inactive")
5. Prepared Statements verwenden
// Prepared statement für wiederholte Queries
stmt := db.Session(&gorm.Session{PrepareStmt: true})
for i := 0; i < 100; i++ {
stmt.First(&user, i)
}
6. Fehlerbehandlung
func GetUserByID(id uint) (*User, error) {
var user User
err := db.First(&user, id).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("user with id %d not found", id)
}
if err != nil {
return nil, fmt.Errorf("database error: %w", err)
}
return &user, nil
}
Migration von sqlx zu GORM
1. Dependencies aktualisieren
# Alte Dependencies entfernen
go mod tidy
# GORM hinzufügen
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
2. Struct Tags ändern
// Vorher (sqlx)
type User struct {
ID int `db:"id" json:"id"`
Username string `db:"username" json:"username"`
}
// Nachher (GORM)
type User struct {
ID uint `gorm:"primaryKey" json:"id"`
Username string `gorm:"unique;not null" json:"username"`
}
3. Repository-Methoden anpassen
// Vorher (sqlx)
func GetUser(id int) (User, error) {
var user User
err := Db.Get(&user, "SELECT * FROM users WHERE id = ?", id)
return user, err
}
// Nachher (GORM)
func GetUser(id uint) (User, error) {
var user User
err := DB.First(&user, id).Error
return user, err
}
Diese Implementierung bietet dir eine solide Basis für die Verwendung von GORM mit verschiedenen Datenbankbackends und modernen Go-Praktiken.