Pular para o conteúdo
CrossFly é 100% open sourceSiga no GitHub
CrossFlyCrossFly
Começar

Persistência com SQLite

A forma canônica de persistir dados estruturados no CrossFly é o módulo dev.crossfly.sqlite — uma camada fina sobre a lib nativa cfsqlite (SQLite3MultipleCiphers). Substitui o antigo dev.crossfly.sql (JDBC).

Rode o banco fora da paint thread e volte à UI com Component.onUI(...) (ver Trabalho em background). SqliteDb, SqliteStmt e SqliteCursor são todos AutoCloseable → use try-with-resources.

Abrir / fechar

try (SqliteDb db = SqliteDb.open("app.db")) {
   db.exec("CREATE TABLE IF NOT EXISTS cliente(id INTEGER PRIMARY KEY, nome TEXT, idade INTEGER)");
   // … usar db …
}   // fecha sozinho

Fábricas: SqliteDb.open(String), open(String, int flags), open(String, byte[] key) (cifrado), openMemory().

CRUD parametrizado

// INSERT/UPDATE/DELETE → execute(sql, params...) retorna nº de linhas afetadas
db.execute("INSERT INTO cliente(nome, idade) VALUES(?, ?)", "Ana", 30);
db.execute("UPDATE cliente SET idade=? WHERE id=?", 31, 1);

// SELECT → query(sql, params...) devolve um cursor
try (SqliteCursor c = db.query("SELECT id, nome, idade FROM cliente WHERE idade > ?", 18)) {
   while (c.next()) {
      long id    = c.getLong(0);      // ⚠️ getters do cursor são 0-based
      String nome= c.getString(1);
      int idade  = c.getInt(2);
   }
}

// valor único:
long total = db.queryLong("SELECT COUNT(*) FROM cliente", 0);
String nome = db.queryString("SELECT nome FROM cliente WHERE id=?", 1);

⚠️ A pegadinha de índices: bind 1-based, getters 0-based

Operação Base Exemplo
bind de parâmetro (SqliteStmt) 1-based stmt.bindString(1, nome)
getter de coluna (SqliteCursor) 0-based cursor.getString(0)

É o erro mais comum do módulo — os ? começam em 1, as colunas do resultado em 0.

Transações e escrita em massa

db.transaction(() -> {                      // begin/commit automático (rollback em exceção)
   db.execute("INSERT INTO cliente(nome) VALUES(?)", "A");
   db.execute("INSERT INTO cliente(nome) VALUES(?)", "B");
});

// begin/commit/rollback manuais também existem:
db.begin(); /* … */ db.commit();

// escrita em massa (statement reusado internamente):
List<Object[]> linhas = List.of(new Object[]{"A", 1}, new Object[]{"B", 2});
db.bulkInsert("INSERT INTO cliente(nome, idade) VALUES(?, ?)", linhas);

Statement preparado reusável

try (SqliteStmt st = db.prepare("INSERT INTO cliente(nome, idade) VALUES(?, ?)")) {
   for (Cliente c : lista) {
      st.bindAll(c.nome, c.idade);   // ou bindString(1, ...).bindInt(2, ...)
      st.step();
      st.reset();                    // reaproveita o statement
   }
}

Criptografia opcional

byte[] chave = obterChave();                       // sua chave (byte[])
try (SqliteDb db = SqliteDb.open("secreto.db", chave)) {   // cifra ChaCha20-Poly1305 por padrão
   // … banco cifrado …
   db.rekey(novaChave);                            // troca a chave (ou remove passando outra)
}

Sem chave = SQLite normal, zero overhead. Cifras suportadas incluem CHACHA20 (padrão), SQLCIPHER, AES256CBC, etc. (Sqlite.Cipher).

Tabela chave/valor (substitui a antiga persistência leve)

A persistência leve (Settings.appSettings) foi removida. Para flags/tema/último login, uma tabela chave/valor resolve:

db.exec("CREATE TABLE IF NOT EXISTS kv(k TEXT PRIMARY KEY, v TEXT)");
db.execute("INSERT OR REPLACE INTO kv(k, v) VALUES(?, ?)", "tema", "escuro");
String tema = db.queryString("SELECT v FROM kv WHERE k=?", "tema");

Ver Arquivos e preferências.

Boas práticas

  • Sempre try-with-resources (db, stmt, cursor) — evita vazar handles nativos.
  • Escrita em lote → transaction(...)/bulkInsert(...) (ordens de magnitude mais rápido).
  • Banco fora da paint thread; resultados de volta com onUI(...).

Erros comuns

  • Trocar as bases: ? é 1-based, coluna do cursor é 0-based.
  • Rodar consultas na paint thread e travar a UI.
  • Esquecer de fechar cursor/statement (vaza recurso nativo).

Veja também

Esta página foi útil?