A API ZDBC (Zeos DataBase Conectivity) é um port do JDBC (Java DataBase Conectivity) 2.0 utilizada pelos componentes do Zeos DBO para prover acesso uniforme a diferentes bancos de dados. Inclusive, você pode recorrer à documentação do JDBC para sanar dúvidas quanto às interfaces e métodos da ZDBC.
Quanto aos componentes TZConnection, TZQuery, etc, eles apenas encapsulam as interfaces da ZDBC para permitir integração com o mecanismo padrão de acesso a banco de dados do Delphi (TDataSource, TDataSet e etc).
Algumas das vantagens da utilização desta API são:
- Velocidade
- Mais simples de utilizar
- Baseado em interfaces
Como nem tudo é um mar de rosas, temos também algumas desvantagens:
- Totalmente incompatível com TDataSet. Então se você um dia precisar trocar o Zeos por DBX, IBX, etc, terá uma dose extra de dor de cabeça por isso.
- Não permite ligação com componentes data-aware.
Agora imagino que você esteja se perguntando: Espera aí, como assim “não permite ligação com os componentes data-aware”? Vou ter que codificar toda a ligação entre a GUI e os dados na mão?
Calma, não é bem assim. Bom, de fato, você teria que fazer isso caso quisesse utilizar a ZDBC para criar telas. Eu recomendo utilizar esta API apenas em rotinas que precisem se comunicar com o banco de dados mas sem qualquer interação entre dados e GUI, e deixar os TDataSets para a interface com o usuário. E não se preocupe, não existe qualquer problema em misturar ambas as abordagens em um sistema.
Como disse anteriormente, a API é baseada em interfaces, e as principais são:
- IZResultSet – Representa os resultados de uma consulta. É basicamente o equivalente ao TDataSet.
- IZStatement – Um comando a ser executado no banco de dados. Pode ser um insert, um select, chamada a um stored procedure, um comando DML, etc. Não suporta parâmetros. Quando uma query é executada, um IZResultSet é retornado.
- IZPreparedStatement – Como o IZStatement, mas este permite executar comandos parametrizados.
- IZConnection – É a conexão com o banco de dados. É quem cria os statements, controla a transação(Sim, no singular, múltiplas transações não são suportadas), etc.
- IZDriver – Implementação de um driver específico para um determinado servidor de banco de dados. É quem cria as conexões, mas você normalmente não lida com ele.
- IZDriverManager – Gerencia os drivers registrados. Normalmente, é quem acessa os drivers para criar conexões para você. Só deve haver um objeto IZDriverManager na memória, o qual é acessado pela variável global DriverManager na unit ZDbcIntfs.pas
Cada driver deve prover implementações das interfaces acima (bem como de outras não citadas) específicas para o banco de dados em questão.
Agora vamos pôr as mãos na massa.
Todas as interfaces ZDBC estão na unit ZDbcIntfs.pas, então precisamos incluir esta unit aonde quisermos utilizar tais interfaces (Não nos interessa aonde estão as implementações delas).
Agora, precisamos obter uma conexão. Isso pode ser feito de duas maneiras:
Através de um TZConnection. Sim, o componente de conexão utilizado pelo código baseado em TDataSet. Como eu disse anteriormente, os componentes como TZConnection, TZQuery e etc são simplesmente uma camada adicionada sobre a ZDBC, que é quem efetivamente faz o trabalho de acesso aos dados.
Esse é o método mais utilizado por mim, pois assim meu aplicativo só precisará de uma conexão com o banco de dados tanto para os componentes baseados em TDataSet quanto para a ZDBC.
Para obter tal conexão basta conectar o TZConnection e depois acessar a sua propriedade chamada DbcConnection (caso o TZConnection esteja desconectado, esta propriedade tem o valor nil).
Outra forma de obter a conexão é requisitando-a diretamente ao DriverManager. Para isso precisamos montar uma connection string, sua sintaxe é a seguinte:
zdbc:<protocolo>://<host>[:<porta>]/<banco>[?<param1>[=<valor1>]…]
Onde “protocolo” é o nome do driver a ser utilizado. Por exemplo:
firebird-2.0
ado
mssql
postgresql
Quanto aos parâmetros, existem alguns comuns a todos os drivers, como username e password, e outros específicos de cada um. Para maiores detalhes sobre isso, consulte a documentação do Zeos.
Aqui vai um exemplo de connection string para firebird 2.0:
zdbc:firebird-2.0://localhost/MeuBanco?username=SYSDBA;password=masterkey
De posse da connection string, basta passa-la para o método GetConnection do DriverManager para obter uma implementação apropriada de IZConnection:
var
Conn: IZConnection;
begin
Conn := DriverManager.GetConnection(‘zdbc:firebird-2.0://localhost/MeuBanco?username=SYSDBA;password=masterkey’);
Agora que temos a conexão, já podemos executar algum comando no banco de dados.
Qualquer comando que queremos executar é representado por um objeto que implementa IZStatement, ou uma subinterface.
Um IZStatement representa uma instrução que é executada sem envolver parâmetros, algo como:
SELECT A, B FROM TABELA
UPDATE TABELA SET A = B + 1
INSERT INTO TABELA(A, B) VALUES (1, 2)
Uma instancia de IZStatement é obtida através do método CreateStatement de IZConnection. Desta forma:
var
Stmt: IZStatement;
begin
Stmt := Connection.CreateStatement;
Em IZStatement iremos usar, basicamente, dois métodos:
- ExecuteQuery, para executar selects, este método retorna um IZResultSet representando o conjunto de dados retornado. Irei escrever mais sobre esta interface mais adiante.
- ExecuteUpdate, para executar comandos que não retornem dados, como instruções DDL, insert, update e delete. Este método retorna um inteiro que corresponde ao número de registros afetados pelo comando executado.
Nota: Ambos os métodos acima citados recebem como parâmetro a instrução SQL a ser executada.
Para casos aonde precisamos executar uma instrução parametrizada, utilizamos o IZPreparedStatement. Ele é criado pelo método PrepareStatement de IZConnection e recebe como parâmetro a instrução a ser executada. Exemplo:
var
Stmt: IZPreparedStatement;
begin
Stmt := Connection.PrepareStatement(‘select a, b from tabela where c=?’);
Após obter o statement, precisamos alimentar os parâmetros. Isto é feito com os métodos SetXXX, onde XXX indica o tipo de dado que irá para o parâmetro. Estes métodos recebem dois parâmetros: O primeiro, um inteiro, indica o índice do parâmetro que receberá o valor, sendo que este índice é baseado em 1, ou seja, o primeiro parâmetro tem índice 1, o segundo 2 e assim por diante. Na verdade, todos os índices na ZDBC possuem 1 como primeiro elemento. O segundo parâmetro dos métodos SetXXX é o valor que será gravado no parâmetro, seu tipo corresponde ao método que você está utilizando, por exemplo, SetInt recebe um Integer neste parâmetro, SetString recebe uma String, SetFloat recebe um Double, e assim por diante. Temos também o SetNull que indica que um parâmetro tem valor indefinido, isto é, null. Ele recebe o índice do parâmetro e uma constante de TZSQLType que indica o tipo de dados do parâmetro. Exemplo:
var
Stmt: IZPreparedStatement; begin
Stmt := Connection.PrepareStatement(‘select a, b from tabela where c=? and d=?’);
Stmt.SetInt(1, 10);
Stmt.SetNull(2, stInteger);
Tendo o statement com os parâmetros definidos, podemos executa-lo. Para instruções select, devemos utilizar o método ExecuteQueryPrepared, que retorna um IZResultSet e ExecuteUpdatePrepared para as demais instruções, e retorna um inteiro indicando o número de registros afetados.
Como prometido, irei explicar um pouco sobre o IZResultSet.
Esta interface representa um conjunto de registros retornados por uma consulta. Como eu disse antes, pode ser comparado ao TDataSet.
Para navegar pelos registros, o processo é um pouco diferente do utilizado em TDataSet. Primeiramente, devemos saber que o resultset é inicialmente posicionado ANTES do primeiro registro, então você irá obter uma exceção caso tentar ler alguma coluna antes posicioná-lo em um registro (Enquanto que um TDataSet é aberto e posicionado sobre o primeiro registro). Para ir para o registro seguinte, temos o método Next. Diferentemente do seu homônimo em TDataSet, este retorna um boolean que indica se existe algum próximo registro, ou seja, em um laço que deve iterar sobre todos os registros, Next pode ser utilizado tanto para a navegação quanto para controlar a condição do laço. Seria assim:
var
RS: IZResultSet;
begin
RS := Connection.CreateStatement.ExecuteQuery(‘select * from tabela’);
while RS.Next do
// Aqui você manipula o registro.
Essa forma de navegação é bem mais interessante que a utilizada em TDataSet, na minha opinião. Achei tão melhor que até implementei uma interface que me permite navegar em um TDataSet como se fosse um IZResultSet, mas isso foge ao tema deste texto.
Para ler o conteúdo dos campos, temos os métodos IsNull, que recebe o índice do campo e retorna true ou false indicando se o campo está ou não nulo, e GetXXX, que recebe o índice do parâmetro e retorna o seu valor lido como um determinado tipo de dado (Ex, GetString para ler como String, GetInt para ler como Integer, e por aí vai).
O método GetMetaData de IZResultSet retorna um IZResultSetMetaData, que possui metadados sobre o resultset. São informações como número, nome e tipo das colunas.
Estes são os pontos básicos da utilização da API ZDBC, o necessário para conseguirmos tirar proveito dela em nossos sistemas. É claro que existem outros pontos a serem explorados, mas preferi não entrar em maiores detalhes.