Java JDBC Metadata
Inspecione bancos de dados e conjuntos de resultados em tempo de execução no Java com DatabaseMetaData e ResultSetMetaData.
Metadata são dados sobre os dados — a estrutura de um conjunto de resultados e as capacidades do banco de dados, e não as próprias linhas. O JDBC expõe duas interfaces de metadata, e elas respondem a perguntas diferentes. Elas alimentam ferramentas genéricas: impressoras de linhas, navegadores de esquemas, ORMs e scripts de migração que precisam funcionar sem conhecimento fixo das tabelas.
Este capítulo aborda as duas interfaces — ResultSetMetaData (as colunas que uma consulta retornou) e DatabaseMetaData (o banco de dados e o driver em si) — como ler códigos de tipo SQL e um exemplo executável que constrói um dicionário de códigos de tipo. Pressupõe que você já sabe como abrir uma conexão JDBC e iterar um ResultSet.
ResultSetMetaData — descrevendo as colunas de uma consulta
Um ResultSet carrega as linhas; seu ResultSetMetaData carrega as descrições das colunas. Chame rs.getMetaData() para descobrir quais colunas uma consulta retornou: quantas, seus nomes e seus tipos SQL. É assim que uma grade genérica renderiza qualquer consulta:
ResultSetMetaData md = rs.getMetaData();
int n = md.getColumnCount();
for (int i = 1; i <= n; i++) { // 1-based, of course
System.out.println(md.getColumnLabel(i)
+ " : " + md.getColumnTypeName(i)
+ " (java.sql.Types " + md.getColumnType(i) + ")");
}Os índices de coluna são baseados em 1, não em 0 — getColumnLabel(0) lança uma exceção. Os métodos mais úteis:
| Método | Retorna |
|---|---|
getColumnCount() | número de colunas no resultado |
getColumnLabel(i) | nome de exibição, respeitando qualquer alias AS |
getColumnName(i) | nome da coluna subjacente, ignorando aliases |
getColumnType(i) | o código int de java.sql.Types (ex.: 4 para INTEGER) |
getColumnTypeName(i) | o nome do tipo do fornecedor (ex.: INT4 no PostgreSQL) |
isNullable(i) | columnNoNulls, columnNullable ou columnNullableUnknown |
getPrecision(i) / getScale(i) | tamanho e dígitos decimais, útil para colunas NUMERIC |
Prefira getColumnLabel a getColumnName ao exibir cabeçalhos: ele respeita SELECT total AS revenue, portanto o cabeçalho exibirá revenue.
DatabaseMetaData — descrevendo o banco de dados
Chame conn.getMetaData() para obter informações sobre o banco de dados e o driver em si: nome e versão do produto, funcionalidades suportadas e o catálogo de tabelas, colunas, chaves e índices.
DatabaseMetaData dbmd = conn.getMetaData();
System.out.println(dbmd.getDatabaseProductName() + " " + dbmd.getDatabaseProductVersion());
System.out.println("supports transactions: " + dbmd.supportsTransactions());
// the schema catalog comes back as ResultSets you read normally
try (ResultSet tables = dbmd.getTables(null, null, "%", new String[]{"TABLE"})) {
while (tables.next()) System.out.println(tables.getString("TABLE_NAME"));
}Observe que getTables, getColumns e métodos semelhantes retornam ResultSets — o catálogo é consultado com a mesma API de cursor que qualquer outro dado. Cada linha de getColumns possui rótulos de coluna bem conhecidos que você lê pelo nome:
// columns of the "users" table, in any catalog/schema
try (ResultSet cols = dbmd.getColumns(null, null, "users", "%")) {
while (cols.next()) {
System.out.println(cols.getString("COLUMN_NAME")
+ " " + cols.getString("TYPE_NAME")
+ (cols.getInt("NULLABLE") == DatabaseMetaData.columnNoNulls ? " NOT NULL" : ""));
}
}Os quatro argumentos para getTables/getColumns são catalog, schemaPattern, um padrão de nome e (para getTables) os tipos de tabela. null significa "não filtrar", e % é o curinga SQL para "qualquer nome". Companheiros comuns são getPrimaryKeys, getImportedKeys (chaves estrangeiras) e getIndexInfo.
Quando usar cada interface
- Use
ResultSetMetaDataquando você tiver um resultado de consulta e precisar descrever suas colunas — um visualizador de grade, um exportador de CSV, um mapeador de objetos. - Use
DatabaseMetaDataquando precisar de informações sobre o banco de dados antes de consultá-lo — detecção de recursos (ele suporta atualizações em lote? savepoints?), descoberta de esquema para uma ferramenta de migração, ou ramificação de SQL comgetDatabaseProductName().
Mapeando códigos de tipo para nomes
getColumnType(i) retorna um int — uma das constantes de java.sql.Types como INTEGER (4), VARCHAR (12) ou TIMESTAMP (93). O int bruto é difícil de ler e de usar em um switch, portanto um leitor genérico constrói um dicionário de código para nome uma vez (refletindo sobre java.sql.Types) e o reutiliza para cada conjunto de resultados. O mesmo código também indica qual getter tipado chamar — getInt, getString, getTimestamp — ao mapear colunas para campos, da forma como um mapeador orientado por PreparedStatement faz.
Um exemplo prático: um dicionário de códigos de tipo e um relatório de colunas
Este programa constrói o dicionário int→nome para cada constante de java.sql.Types, depois o usa para descrever um hipotético resultado de três colunas da maneira que ResultSetMetaData faria — e lista os tipos de perguntas que DatabaseMetaData responde — sem um banco de dados ativo.
O que aprender com a execução:
- Há 39 códigos
java.sql.Typespadrão, e o programa constrói o mapa completo de código→nome refletindo sobre a classeTypes. Um leitor genérico real constrói este dicionário uma vez e o reutiliza para cada conjunto de resultados. ResultSetMetaData.getColumnType(i)retorna um desses códigosint; o dicionário converte o código4emINTEGER,12emVARCHAR,93emTIMESTAMP. Essa consulta é exatamente o que permite que uma ferramenta renderize qualquer consulta sem conhecer suas colunas antecipadamente.- O relatório por coluna — nome mais tipo — é o que
getColumnLabel/getColumnTypefornecem para uma consulta real. É a base de visualizadores de grade, exportadores de CSV e ORMs que mapeiam colunas para campos. DatabaseMetaDataresponde a uma classe diferente de perguntas: não "o que esta consulta retornou", mas "o que este banco de dados pode fazer" — seu nome de produto, versão do driver, suporte a recursos e catálogo de tabelas.- Crucialmente, os métodos de catálogo (
getTables,getColumns) retornamResultSets, portanto você lê a estrutura do banco de dados com o mesmo loop de cursor que usa para dados. Metadata não é uma API especial — são dados sobre dados, entregues da mesma forma.
java.sql.Types (39 aqui) pode variar ligeiramente entre versões do JDK à medida que novas constantes são adicionadas. Construa o dicionário por reflexão, como acima, em vez de fixar a contagem no código.Próximos passos
- JDBC ResultSet — a API de cursor que tanto
getMetaData()quanto os métodos de catálogo retornam. - JDBC Connection — onde
getMetaData()na conexão se origina. - JDBC PreparedStatement — alimente o mapeamento de colunas orientado por metadata com vinculação segura de parâmetros.
- JDBC Transactions —
supportsTransactions()e suporte a savepoints são reportados porDatabaseMetaData.