#Golang a přístup k databázi

Publikováno 09-27-2016 05:31:55

V dnešní době nad neexistuje aplikace, která by nepřistupovala do databáze. Samozřejmě si můžeme ukládat data do souboru a mnohdy je to i efektivnější řešení, ale databáze nám luxusně umožní data ukládat a načítat a nestarat se přitom o detaily. V dnešní době rozdělujeme databáze do 2 proudů.

  • SQL databáze (nebo také relační databáze) - tedy databáze, kde jsou data ukládána jako relace mezi entitami. Většina z nich také garantuje ACID (atomičnost operací, konzistenci stav, integritu dat a durabilitu uložení). Mezi nejznámější patři MySql, PostgreSQL, Oracle, MS SQL Server atd.
  • NoSQL databáze - tedy databáze, které nevyžadují striktní schéma (ad hoc ukládání dat) místo ACID používají BASE (pouze základní dostupnost, měnící se (měkký) stav a eventuální konzistenci) - což samo osobě je slovní hříčka - prostě kyselost a zásaditost :-). Představily jsou MongoDB, Redis, Apache Cassandra, ElesticSearch apod.

Dnes si povíme, jak přistupovat k SQL databázím. Už proto, že Go tyto databáze standardizuje, na rozdíl od NoSQL, kde standard není.

V první řadě, pro každou databázi potřebujeme ovladač. Pokud se budeme chtít připojit k PostgreSQL, tak si musíme stáhnout ovladač. A jelikož ovladač zasahuje do inicializace, ale sám o sobě nic nepřináší, použijeme onen magický import pro vedlejší účinky.

_ "github.com/lib/pq"

Tím pádem se ovladač může zaregistrovat a database/sql o něm ví. A tím pádem je pak možno udělat něco jako toto a připojit se k databázi:

url := "postgres://username:password@hostname/db_name"
db, _ := sql.Open("postgres", dataSourceName)

Jakmile máme připojení do databáze (v reálu počítám, si ověříte, že spojení dopadlo dobře, např. db.Ping()), můžeme začít úřadovat a pokládat dotazy.

Nejdříve si ukážeme, jak načíst celý dotaz:

rows, _ := db.Query("SELECT sloupec FROM tabulka WHERE a = $1", "b")
defer rows.Close()

sloupce := make(string, 0)
for rows.Next() {
   sloupec := ""
   err := rows.Scan(&sloupec)
   sloupce = append(sloupce, sloupec)
}

Nejdříve získáme řádky pomocí dotazu (db.Query). Následně si preventivně zaregistrujeme funkci rows.Close() jako defer, abysme za všech okolností řádky zabřely. Následně si projdeme všechny řádky a uložíme si je do slicu stringů. S tím už pak můžeme dělat cokoliv.

database/sql umožňuje načíst také jen jednu řádku pomocí db.QueryRow, ale osobně to nedoporučuji, jelikož to padá, když to nenajde ani jednu řádku. Pokud tedy chcete jednu řádku, pak doporučuji postup podobný prvnímu příkladu:

for rows.Next() {
   sloupec := ""
   err := rows.Scan(&sloupec)
   sloupce = append(sloupce, sloupec)
   break
}

Prostě po první řádce smyčku ukončíte.

V minulém díle jsme se podívali, jak testovat a demonstroval jsem tam, jak přistupovat k datům pomocí interfaců. Za tím si stojím a tím pádem nebudu opakovat kód, jak si takový interface udělat.

Mnozí z vás budou asi hned mít otázku: “Jak Go pracuju s NULL”. A je to správná otázka. Go umožňuje mít nil, tedy pointer(ukazatel) na nic, ale co když nepoužíváme ukazatele? Popravdě funkce Scan ani neumí pracovat s ukazateli a ukazatele. Pokud tedy např. máte v databázi VARCHAR, tak musíte poslat jako hodnotu k uložení ukazatel na string. Pokud je ale hodnota sloupce NULL, pak Go nahlásí chybu. Pro tyto účely je tu ovšem sql.NullString apod. (NullBool …). Vnitřně má si totiž uchovává dvě hodnoty. Value tedy string hodnoty a Valid, což je příznak, jestli byla hodnota nastavena. Pokud je false, pak byla hodnota NULL. Dá se použít jako přímá náhrada string, tedy např. jako v našem případě.

sloupce := make(sql.NullString, 0)
for rows.Next() {
   sloupec := sql.NullString{}
   err := rows.Scan(&sloupec)
   if template.Valid {
         sloupce = append(sloupce, sloupec.Value)
   }
}

Možná vás v SQL dotazu zaujalo, že je tam podmínka WHERE a = $1 a ptali jste se, co to je ono $1. Nuže je to jen zástupný znak. PostgreSQL tento znak nahradí řetězcem "b", ale v praxi to bude nejspíš nějaká proměnná. Důvod je prostý, pokud bychom začali SQL řetězce sčítat (např. pomocí ftm.Sprintf, tak bychom si akorát zadělali na SQL code injection.

Teď když víme o tom, jak si data z databáze načíst, si můžeme ukázat, jak data do databáze vložit.

if result, err := db.Exec("INSERT INTO tabulka (a) VALUES($1)", "b"); err == nil {
   fmt.Printf("Vložena %d řádka s ID  %d", result.RowsAffected(), result.LastInsertId())
}

Opět jednoduché. V příkladu předpokládám, že tabulka má ID automaticky generované.

A na závěr ještě ukázka transakcí. ACID databáze umožňují provádět několik dotazů v rámci transakcí, kdy se buď provede všechno, nebo nic. Typický příklad je ono okřídlené převod na účtu. Potřebuji v něm připsat peníze na účet příjemce a odepsat z účtu zasílatele. A buď se uloží obě změny, nebo žádná. Můžete se zeptat, proč by se neprovedli obě, inu, někdy dojde k chybě, nebo závadě (třeba nám vypnou proud, rozbije se disk apod.) a to, co se mělo provést společně se nám rozpojí. Go SQL má podporu transakcí a vypadá to následovně.

částka, zákazník1, zákazník2 := 100, 1, 2
tx, _ := db.Begin()
if _, err := tx.Exec ("UPDATE ucet SET zustatek = zustatek - $1 WHERE zakaznik = $2", částka, zákazník1); err != nil {
   tx.Rollback()
   return err
}
if _, err := tx.Exec ("UPDATE ucet SET zustatek = zustatek + $1 WHERE zakaznik = $2", částka, zákazník2); err != nil {
   tx.Rollback()
   return err
}
return tx.Commit()

Jedná se o lehký úvod, ale asi máte představu, jak taková transakce vypadá.

Tímto prozatím uzavírám kapitolu seznámení se s SQL databázemi a těším se příště.

comments powered by Disqus