Como conectar e fazer CRUD em um banco PostgreSQL

Quando desenvolvemos programas que vão trabalhar com dados, sem sombra de dúvida uma das coisas mais importantes é armazenar esses dados com segurança e robustez.

Nesse post, vou mostrar como conectar e executar um CRUD utilizando um banco de dados PostgreSQL.

Antes de começar a escrever código, vamos fazer download do drive do postgres para Go com o comando go get github.com/lib/pq.

Ok, agora podemos começar.

Para ficar um pouco mais próximo da realidade, vou separar todas as operações em funções, incluindo a conexão com o banco.

E por falar em conexão com o banco, vamos começar justamente por ela.

import (
  "database/sql"

  _ "github.com/lib/pq"
)

func OpenConn() (*sql.DB, error) {
    db, err := sql.Open("postgres", "host=localhost port=5432 user=aprenda password=golang dbname=blog sslmode=disable")
    if err != nil {
        panic(err)
    }

    err = db.Ping()

    return db, err
}

Essa função é bem simples, e sua única função é abrir uma conexão com o banco de dados e retorna-lá para que as outras funções do CRUD possam realizar suas transações.

Um ponto que vale a pena se atentar aqui é o underline antes do import do drive do postgres. Como nós não utilizamos o package de forma direta, esse underline é necessário para que o compilador do Go não gere um erro ao tentar fazer o build da aplicação.

Dando continuidade, vamos criar as funções para CRUD usando como base uma struct de categoria.

type Categoria struct {
    ID   int64
    Nome string
}

Para começar gerir os dados, vamos iniciar com a função responsável por adicionar um novo registro no banco de dados.

func Insert(c Categoria) (id int64, err error) {
    conn, err := db.OpenConn()
    if err != nil {
        return
    }
    defer conn.Close()

    sql := `INSERT INTO categorias (nome) VALUES ($1) RETURNING id`

    err = conn.QueryRow(sql, c.Nome).Scan(&id)

    return id
}

Base para todas as funções que vamos escrever, iniciamos a função abrindo uma nova conexão com o banco de dados, e caso não haja nenhum erro, colocamos um defer para o fechamento da conexão.

Na sequência, preparamos o SQL que queremos executar com as variáveis que serão substituídas na função QueryRow. Utilizamos a função QueryRow pois nosso SQL irá retornar o ID que foi gerado após o insert.

Se a execução da nossa query não fosse retornar nada, o ideal seria utilizar a função Exec. Caso ela fosse retornar mais do que 1 linha, o melhor seria a função Query.

Agora que já conseguimos adicionar itens no nosso banco de dados, vamos criar duas funções, sendo a primeira para obter um registro especifico e a segundo para obter todos os registros da tabela.

// categoria especifica
func Get(id int64) (c Categoria, err error) {
    conn, err := db.OpenConn()
    if err != nil {
        return
    }
    defer conn.Close()

    row := conn.QueryRow(`SELECT * FROM categorias WHERE id=$1`, id)

    err = row.Scan(&c.ID, &c.Nome)

    return
}

// todas as categorias
func GetAll() (sc []Categoria, err error) {
    conn, err := db.OpenConn()
    if err != nil {
        return
    }
    defer conn.Close()

    rows, err := db.Query(`SELECT * FROM categorias`)
    if err != nil {
        return
    }
    defer rows.Close()

    for rows.Next() {
        var c Categoria

        err = row.Scan(&c.ID, &c.Nome)
        if err != nil {
            continue
        }

        sc = append(sc, c)
    }

    return
}

Como explicado antes, podemos ver como acima a clara diferença entre as funções Query e QueryRow.

Seguindo para o “U” do nosso CRUD, vamos implementar a função responsável por atualizar os registros em nossa tabela.

Como não queremos que a execução nos retorne algum registro do banco de dados, para realizar o update nós vamos utilizar a função Exec.

func Update(id int64, c Categoria) (int64, error) {
    conn, err := db.OpenConn()
    if err != nil {
        return
    }
    defer conn.Close()

    res, err := db.Exec(`UPDATE categorias SET nome=$2 WHERE id=$1`, id, c.Nome)
    if err != nil {
        return err
    }

    return res.RowsAffected()
}

Se você der uma olhada com atenção, vai notar que no nosso SQL, o nome recebe o segundo parâmetro e o id o primeiro.

A função RowsAffected, chamada no final, irá retornar o número de registros alterados e se houve algum erro.

É importante retornar essas informações para que possamos validar, além do erro, se o número de registros alterados condiz com a quantidade que era esperada. Essa simples validação pode revelar uma inconsistência em nossa tabela.

Para finalizar, vamos implementar a função delete.

func Delete(id int64) (int64, error) {
    conn, err := db.OpenConn()
    if err != nil {
        return
    }
    defer conn.Close()

    res, err := db.Exec(`DELETE FROM users WHERE id=$1`, id)
    if err != nil {
        return err
    }

    return res.RowsAffected()
}

Aqui, retornar o RowsAffected tem o mesmo propósito que no update, validar se o número de registros removidos é igual o esperado.

Como removemos com base em ID, receber a informação que 2 registros foram removidos mostra claramente que existe algum problema em nossa tabela.

Com isso fechamos nosso CRUD e nosso post.

Espero que tenha sido útil para você. Qualquer dúvida é só deixar ai nos comentário.

Até a próxima!


Subscreva

Fique por dentro de tudo o que acontece no mundo Go.

Deixe uma resposta