Datenbankzugriffe mit Go implementieren

Datenbankzugriffe mit Go implementieren

Go bietet mit dem Package sql eine Datenbankabstraktion an, mit deren Hilfe man herstellerunabhängig auf SQL Datenbanken zugreifen kann. Dies geschieht über den type sql.DB

Package sql provides a generic interface around SQL (or SQL-like) databases.

Der type sql.DB stellt ein Handle zu einem Datenbankpool dar, der die darunterliegende Datenbankverbindungen verwaltet. Ein konkurrierender Zugriff, z. B. von mehreren Go-Routinen (goroutine) heraus, ist ausdrücklich möglich.

Importiert werden kann der type mittels:

import "database/sql"

Zusätzlich zum sql Package wird ein Datenbanktreiber benötigt. Eine Liste von verfügbaren Treibern findet man auf den GitHub Seiten des Golang Projektes.

Die Installation eines Treibers erfolgt im Normalfall über die Installation einer Go Abhängigkeit. Ein MySQL Treiber kann so installiert werden:

go get -u github.com/go-sql-driver/mysql

Anschließend muss der Treiber noch in der Anwendung importiert werden.

import "database/sql"
import _ "github.com/go-sql-driver/mysql"

Verbinden!

Nachdem die Vorbereitungen für eine Datenbankverbindung geschaffen wurden kann man eine Verbindung “öffnen”. Öffnen bedeutet hier nicht, dass sofort eine physikalische Verbindung aufgebaut wird. Dies geschieht erst, wenn tatsächlich eine Abfrage/Query abgesetzt wird.

Allgemein:

db, err := sql.Open(driver, dataSourceName)

Der Parameter driver gibt den Treibernamen an, mit dem Parameter dataSourceName werden Datenbankverbindungsparameter wie URL, Username, Passwort etc übergeben.

Für MySQL sieht es so aus:

db, err := sql.Open("mysql", "user:password@/dbname")

Da, wie oben erwähnt, nicht sofort eine Verbindung aufgebaut wird, gibt es einen sogenannten PingContext Aufruf, mit dem eine Verbindung validiert werden kann.

ctx := context.Background()
if err := db.PingContext(ctx); err != nil {
  log.Fatal(err)
}

Schreiben und Lesen

Sobald eine Verbindung aufgebaut ist können Daten gelesen oder geschrieben werden. Folgender Code erzeugt einen neuen Kunden in der Datenbank mit der ID ‘1’ und dem Vornamen ‘gopher’:

result, err := db.ExecContext(ctx,
	"INSERT INTO CUSTOMER (id, firstname) VALUES (?, ?)",
	1,
	"gopher"
)

Die Variable result ist vom type Result und enthält zwei Werte (über entsprechende Methoden):

  • LastInsertId
  • RowsAffected

    lastInsertId, err := result.LastInsertId()
    rowsAffected, err := result.RowsAffected()
    

Zum Auslesen der Werte kann die Methode QueryContext verwendet werden.

rows, err := db.QueryContext(ctx, "SELECT firstname FROM customer where id = ?", 2)
if err != nil {
	log.Fatal(err)
}
defer rows.Close()

for rows.Next() {
	var firstname string
	var id int
	if err := rows.Scan(&firstname); err != nil {
		log.Fatal(err)
	}
	log.Printf("%s is %d\n", firstname, id)
}
if err := rows.Err(); err != nil {
	log.Fatal(err)
}

Alternativ, wenn nur ein Ergebnis erwartet wird, gibt es noch die Methode QueryRowContext, die analog verwendet werden kann.

Die Schnittstelle enthält weiterhin Methoden wie z. B. PrepareContext mit der man Prepared Statements für eine spätere Nutzung anlegen kann.

Transaktionen

Natürlich unterstützt das sql Package auch Transaktionen. Diese können durch Aufruf von BeginTx() gestartet und durch Commit() oder Rollback() beendet werden.

tx, err := db.BeginTx(ctx, nil)
if err != nil {
	log.Fatal(err)
}

The provided context is used until the transaction is committed or rolled back. If the context is canceled, the sql package will roll back the transaction. Tx.Commit will return an error if the context provided to BeginTx is canceled.

tx.Commit()

Komplettes Beispiel

Quellcode

package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"

	_ "github.com/ibmdb/go_ibm_db"
)

func main() {
	con := "HOSTNAME=127.0.0.1;DATABASE=GOTEST;PORT=50000;UID=db2ins1;PWD=superGeheimesPasswort"
	db, err := sql.Open("go_ibm_db", con)
	if err != nil {
		log.Fatal(err)
	}

	ctx := context.Background()
	if err := db.PingContext(ctx); err != nil {
		log.Fatal(err)
	}

	result, err := db.ExecContext(ctx,
		"INSERT INTO CUSTOMER (id, firstname) VALUES (?, ?)",
		1,
		"gopher")

	if err != nil {
		log.Fatal(err)
	}
	lastInsertID, err := result.LastInsertId()
	rowsAffected, err := result.RowsAffected()

	fmt.Println("Insert", lastInsertID, rowsAffected)

	rows, err := db.QueryContext(ctx, "SELECT firstname FROM customer where id = ?", 2)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
	for rows.Next() {
		var firstname string
		if err := rows.Scan(&firstname); err != nil {
			log.Fatal(err)
		}
		log.Printf("%s", firstname)
	}
	if err := rows.Err(); err != nil {
		log.Fatal(err)
	}

	db.Close()
}

Passendes Dockerfile

FROM ibmcom/db2express-c:latest

RUN su - db2inst1 -c "db2start \
    && db2 'create db GOTEST' \
    && db2 'connect to GOTEST' \
    && db2 +c 'create table customer (id integer not null, firstname varchar(256), lastname varchar(256), birthday date, data blob)' \
    && db2 LIST TABLES"

version: '2'
services:
  db:
    build: db-docker
    ports:
      - "50000:50000"
    environment:
      - DB2INST1_PASSWORD=superGeheimesPasswort
      - LICENSE=accept
    command: db2start

Projekt bei GitHub

Das komplette Beispiel kann auch in GitHub gefunden werden.

Weiteres

Weitere Informationen und Dokumentation findet man z. B. hier:

25.01.2019

 

Kennen Sie schon das Buch zum Thema?

Der praktische Soforteinstieg für Developer und Softwarearchitekten, die direkt mit Go produktiv werden wollen.

  • Von den Sprachgrundlagen bis zur Qualitätssicherung
  • Architekturstil verstehen und direkt anwenden
  • Idiomatic Go, gRPC, Go Cloud Development Kit
  • Cloud-native Anwendungen erstellen
Microservices mit Go Buch

zur Buchseite beim Rheinwerk Verlag Rheinwerk Computing, ISBN 978-3-8362-7559-0 (als PDF, EPUB, MOBI und Papier)

Kontakt

Source Fellows GmbH

Source Fellows GmbH Logo

Lerchenstraße 31

72762 Reutlingen

Telefon: (0049) 07121 6969 802

E-Mail: info@source-fellows.com