Skip to main content


Criando um banco SQLite


Vamos usar SQLite com Dart puro, usando o pacote sqlite3. Ele é um pacote usado para acessar SQLite a partir de aplicações Dart, e funciona bem para aprender a base antes de entrar em Flutter, sqflite, drift ou banco remoto como PostgreSQL.


A ideia será criar um banco chamado infra.db, uma tabela chamada servidores e alguns registros de servidores. Esse exemplo é simples de propósito, porque o foco agora é entender o fluxo básico. Primeiro, crie um projeto Dart:

dart create banco_sqlite
cd banco_sqlite
dart pub add sqlite3

Agora, substitua o conteúdo de bin/banco_sqlite.dart por este código:

import 'package:sqlite3/sqlite3.dart';

void main() {
final db = sqlite3.open('infra.db');

db.execute('''
CREATE TABLE IF NOT EXISTS servidores (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nome TEXT NOT NULL,
ip TEXT NOT NULL,
status TEXT NOT NULL
);
''');

final insert = db.prepare('''
INSERT INTO servidores (nome, ip, status)
VALUES (?, ?, ?);
''');

insert.execute(['web01', '192.168.0.10', 'ativo']);
insert.execute(['db01', '192.168.0.20', 'ativo']);
insert.execute(['old01', '192.168.0.30', 'inativo']);

insert.dispose();

final servidores = db.select('''
SELECT id, nome, ip, status
FROM servidores;
''');

for (final servidor in servidores) {
print(
'ID: ${servidor['id']} | '
'Nome: ${servidor['nome']} | '
'IP: ${servidor['ip']} | '
'Status: ${servidor['status']}',
);
}

db.dispose();
}

Para executar, faça:

dart run

Esse programa cria o arquivo infra.db no diretório do projeto. Se o arquivo já existir, o Dart abre o banco existente. Se não existir, o SQLite cria o arquivo automaticamente. Agora explicando o código.


A linha abaixo importa o pacote sqlite3, que é usado para abrir o banco SQLite e executar comandos SQL.

import 'package:sqlite3/sqlite3.dart';

A linha abaixo abre o banco infra.db. A variável db representa a conexão com o banco, então ela será usada para criar tabela, inserir dados e consultar registros.

final db = sqlite3.open('infra.db');

Agora vamos falar do db.execute:

db.execute('''
CREATE TABLE IF NOT EXISTS servidores (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nome TEXT NOT NULL,
ip TEXT NOT NULL,
status TEXT NOT NULL
);
''');

Ele serve para enviar um comando SQL para o SQLite. O execute é usado para comandos que não precisam retornar dados, como criar tabela ou alterar estrutura.


O SQL CREATE TABLE IF NOT EXISTS servidores cria a tabela servidores apenas se ela ainda não existir. Isso evita erro quando o programa roda mais de uma vez, porque sem o IF NOT EXISTS o SQLite tentaria criar uma tabela que já existe.


A coluna id é do tipo INTEGER, que representa número inteiro. O PRIMARY KEY define essa coluna como chave primária, ou seja, o identificador único de cada servidor. O AUTOINCREMENT faz o banco gerar o próximo número automaticamente.


As colunas nome, ip e status usam TEXT, porque guardam texto. O NOT NULL impede que esses campos fiquem vazios, já que um servidor sem nome, sem IP ou sem status não faria muito sentido nesse cadastro.


A linha abaixo é bastante importante:

final insert = db.prepare('''
INSERT INTO servidores (nome, ip, status)
VALUES (?, ?, ?);
''');

Aqui criamos um comando. O prepare é usado quando o SQL tem valores que serão enviados depois. Os sinais ? são espaços reservados, e isso é melhor do que montar SQL juntando texto manualmente, porque evita erro com aspas e também reduz risco de SQL injection.


Agora vem a parte que inserimos dados no banco, para isso, usamos o execute e colocamos os valores nos lugares dos ?. O primeiro valor vai para nome, o segundo para ip e o terceiro para status.

insert.execute(['web01', '192.168.0.10', 'ativo']);


CRUD básico


O CRUD é o nome usado para as quatro operações básicas feitas em dados. Ele vem de Create, Read, Update e Delete. Para manter o exemplo prático, vamos continuar usando a tabela servidores.



Create


O Create é a operação usada para criar um novo registro no banco. No nosso caso, criar significa cadastrar um servidor.

final insert = db.prepare('''
INSERT INTO servidores (nome, ip, status)
VALUES (?, ?, ?);
''');

insert.execute(['app01', '192.168.0.40', 'ativo']);

insert.dispose();

O SQL usado aqui é:

INSERT INTO servidores (nome, ip, status)
VALUES (?, ?, ?);

O INSERT INTO servidores informa que o registro será criado dentro da tabela servidores. As colunas nome, ip e status indicam quais campos serão preenchidos. O VALUES (?, ?, ?) deixa os valores para serem enviados pelo Dart, e isso é melhor do que colocar os dados diretamente dentro da string SQL.



Read


O Read é a operação usada para ler dados do banco. Pode ser uma consulta geral, buscando todos os servidores, ou uma consulta filtrada, buscando apenas um servidor específico.


Consulta buscando todos:

final servidores = db.select('''
SELECT id, nome, ip, status
FROM servidores;
''');

for (final servidor in servidores) {
print('${servidor['id']} - ${servidor['nome']} - ${servidor['ip']}');
}

O SQL usado foi:

SELECT id, nome, ip, status
FROM servidores;

O SELECT indica que queremos consultar dados. Depois dele aparecem as colunas que devem ser retornadas. O FROM servidores indica a tabela de origem.


Consulta buscando por status:

final ativos = db.select('''
SELECT id, nome, ip, status
FROM servidores
WHERE status = ?;
''', ['ativo']);

for (final servidor in ativos) {
print('${servidor['nome']} está ativo');
}

O SQL usado foi:

SELECT id, nome, ip, status
FROM servidores
WHERE status = ?;

O WHERE é usado para filtrar os registros. Nesse caso, o banco só retorna linhas onde a coluna status tem o valor informado pelo Dart.


No código Dart acima, estamos buscando apenas servidores ativos, isso fica fácil de identificar por causa desta parte da query WHERE status = ?;. Além dela, depois do que é fornecido ao Select é passo o valor ['ativo'].



Update


O Update é a operação usada para alterar um registro existente. Normalmente a atualização deve usar o id, porque ele identifica exatamente qual linha será alterada.

final update = db.prepare('''
UPDATE servidores
SET status = ?
WHERE id = ?;
''');

update.execute(['manutencao', 1]);

update.dispose();

O SQL usado aqui é:

UPDATE servidores
SET status = ?
WHERE id = ?;

O UPDATE servidores informa que a tabela servidores será alterada. O SET status = ? define que a coluna status receberá um novo valor. O WHERE id = ? limita a alteração a um servidor específico.


Essa parte do WHERE é muito importante. Sem ela, o banco poderia alterar todos os servidores da tabela.



Delete


O Delete é a operação usada para remover um registro do banco. Assim como no update, o ideal é apagar usando o id.

final delete = db.prepare('''
DELETE FROM servidores
WHERE id = ?;
''');

delete.execute([3]);

delete.dispose();

O SQL usado foi:

DELETE FROM servidores
WHERE id = ?;

O DELETE FROM servidores informa que registros serão apagados da tabela servidores. O WHERE id = ? limita a remoção ao servidor com aquele id.


Sem WHERE, o comando apagaria todos os registros da tabela. Por isso, em comandos de alteração ou remoção, o WHERE precisa ser tratado com bastante cuidado.



Exemplo completo com CRUD


Agora juntando tudo em um único exemplo executável:

import 'package:sqlite3/sqlite3.dart';

void main() {
final db = sqlite3.open('infra.db');

db.execute('''
CREATE TABLE IF NOT EXISTS servidores (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nome TEXT NOT NULL,
ip TEXT NOT NULL,
status TEXT NOT NULL
);
''');

db.execute('DELETE FROM servidores;');

criarServidor(db, 'web01', '192.168.0.10', 'ativo');
criarServidor(db, 'db01', '192.168.0.20', 'ativo');
criarServidor(db, 'old01', '192.168.0.30', 'inativo');

print('Servidores cadastrados:');
listarServidores(db);

print('\nServidores ativos:');
listarServidoresPorStatus(db, 'ativo');

atualizarStatusServidor(db, 1, 'manutencao');

print('\nDepois de atualizar o servidor 1:');
listarServidores(db);

apagarServidor(db, 3);

print('\nDepois de apagar o servidor 3:');
listarServidores(db);

db.dispose();
}

void criarServidor(Database db, String nome, String ip, String status) {
final insert = db.prepare('''
INSERT INTO servidores (nome, ip, status)
VALUES (?, ?, ?);
''');

insert.execute([nome, ip, status]);
insert.dispose();
}

void listarServidores(Database db) {
final servidores = db.select('''
SELECT id, nome, ip, status
FROM servidores
ORDER BY id;
''');

for (final servidor in servidores) {
print(
'ID: ${servidor['id']} | '
'Nome: ${servidor['nome']} | '
'IP: ${servidor['ip']} | '
'Status: ${servidor['status']}',
);
}
}

void listarServidoresPorStatus(Database db, String status) {
final servidores = db.select('''
SELECT id, nome, ip, status
FROM servidores
WHERE status = ?
ORDER BY id;
''', [status]);

for (final servidor in servidores) {
print(
'ID: ${servidor['id']} | '
'Nome: ${servidor['nome']} | '
'IP: ${servidor['ip']} | '
'Status: ${servidor['status']}',
);
}
}

void atualizarStatusServidor(Database db, int id, String novoStatus) {
final update = db.prepare('''
UPDATE servidores
SET status = ?
WHERE id = ?;
''');

update.execute([novoStatus, id]);
update.dispose();
}

void apagarServidor(Database db, int id) {
final delete = db.prepare('''
DELETE FROM servidores
WHERE id = ?;
''');

delete.execute([id]);
delete.dispose();
}

No começo do exemplo existe esta linha:

db.execute('DELETE FROM servidores;');

Ela apaga os registros antigos antes de inserir os dados de teste. Isso foi colocado apenas para o exemplo ficar previsível toda vez que rodar. Em uma aplicação real, normalmente isso não ficaria no código, porque apagaria os dados do usuário ou do sistema.


A função criarServidor representa o Create. Ela recebe a conexão com o banco e os dados do servidor, monta o comando INSERT e salva o registro.


A função listarServidores representa o Read. Ela faz um SELECT, percorre os resultados e imprime cada servidor.


A função listarServidoresPorStatus também é Read, mas com filtro. Ela usa WHERE status = ? para buscar apenas servidores com um status específico.


A função atualizarStatusServidor representa o Update. Ela muda o status de um servidor usando o id, porque o id identifica exatamente qual linha deve ser alterada.


A função apagarServidor representa o Delete. Ela remove um servidor pelo id, evitando apagar outras linhas sem querer.