Blog sobre desenvolvimento de software (Java, muito Java!), inovação tecnológica e cotidiano do Universo de TI. Acesse notícias, tutoriais, material de cursos e eventos, código, desafios, soluções, opiniões, pensamentos, divagações, balbuciações e abobrinhas diversas. Deixe seu comentário!

terça-feira, 29 de março de 2011

Satisfação imediata com o banco de dados H2

Até hoje lembro do meu primeiro contato com bancos de dados (aproveito para avisar que os próximos parágrafos terão pouco a ver com a tecnologia do título. Se a idéia é aprender sobre o uso de H2, pule direto para ). Eu estava no segundo ano da faculdade, era bolsista e tinha todo o tempo livre do mundo, então decidi instalar e configurar um ambiente de desenvolvimento com uma versão antiga do Ubuntu e uma versão mais antiga ainda do PostgreSQL.
Foi uma experiência bastante enlouquecedora enriquecedora. Em um único sprint aprendi como trabalhar com autenticação e autorização no Linux e no PostgreSQL. Eu lembro o quanto penei para trocar a senha do usuário postgres, executar o comando psql com esse usuário, criar um novo usuário que coincidisse com o meu login do Linux no banco, criar um novo banco com o mesmo nome do usuário, criar um novo schema nesse banco para testes e assim por diante. Depois que consegui conectar no banco com o meu usuário usando o comando psql levei mais pelo menos um dia para descobrir como conectar via JDBC.
Não satisfeito com todo esse trabalho, alguns meses depois resolvi compilar e instalar na unha uma versão mais nova do banco com uma série de extensões... Foram dias brigando com o comando make (e as dependências da instalação), switches do SGBD, arquivos de configuração e coisas de dia a dia do Linux como chown, chmod, ln, configuração da variável PATH, Sys-V, etc. Quando finalmente consegui fazer tudo rodar direito eu já estava me sentindo bem familiarizado com o Linux (bem... vivendo uma ilusão na verdade, pois eu poderia ter adicionado meia dúzia de repositórios com binários que facilitariam bastante o processo).
Não preciso nem dizer que graças a minha falta de experiência tachei o PostgreSQL de complicado, acadêmico e burocrático antes mesmo de começar a usá-lo.
Um ano depois, já no mercado de trabalho, tive um contato inicial bem menos traumático com o MySQL. Um apt-get e meia dúzia de pequenas edições em arquivos de configuração depois e tudo já estava rodando em um ambiente com  Apache + PHP + phpMyAdmin + MySQL. Criei os bancos e tudo que precisava com o phpMyAdmin (tomando o cuidado de criticar injustamente o pgAdmin em toda oportunidade de comparação que tive), modelei o banco com o DBDesigner (depois conheci o MySQLWorkbench que uso até hoje) e consegui conectar via JDBC na primeira tentativa. Foi rápido e satisfatório.
Semanas depois quando descobri que as foreign keys não funcionavam (devido a engine default MyISAM) e que as constraints do tipo CHECK eram silenciosamente ignoradas, não me importei muito com o trabalho extra de alterar a engine de trocentas tabelas e reescrever o código das constraints CHECK do lado da aplicação. Em contraste passei meses implicando com coisas menores do PostgreSQL como o fato de ter que especificar mais colunas do que realmente precisava na cláusula GROUP BY, bem como o uso de DISTINCT ON, ILIKEetc.
Moral da história: Eu admito ter um peso e duas medidas para os dois mais populares SGBDs Open Source do mercado. Depois de ter desenvolvido muitas aplicações e participado da instalação e configuração de diversos outros bancos (como Oracle, DB2, SQL Server, Sybase, etc) nos mais diversos ambientes - com diferentes requisitos (e camadas de complexidade) - posso dizer que ambos os bancos são ótimos. Mesmo assim, quando a escolha cabe a mim, eu acabo usando o MySQL. E por que? Porque em um período crucial do meu aprendizado acabei tendo uma experiência mais satisfatória com ele.

Hoje tenho a grata oportunidade de treinar outros programadores. Volta e meia isso significa ajudá-los em seus primeiros passos com SQL. Por experiência própria, sei que esses primeiros contatos com o SGBD podem fazer toda a diferença na formação do aluno. Só aprenderão aqueles que fizerem exercícios, e só farão exercícios aqueles que conseguirem quebrar a barreira da instalação e configuração do ambiente.
A situação ideal seria que meus alunos pudessem criar bancos e executar todo tipo de comando SQL sem burocracia. Também é importante que usem um banco de dados confiável, com bom suporte aos padrões de SQL e que rode nos mais diversos sistemas operacionais. Se o banco puder rodar embbeded ajuda bastante, pois dessa forma é possível criar exercícios que vão direto ao ponto em projetos pré-configurados.

Conheça o H2, um banco de dados puramente implementado em Java que faz tudo isso. Em um único jar com pouco mais de 1Mb ele traz: um poderoso SGBD que pode rodar tanto em modo embbeded quanto client-server, um driver JDBC tipo 4 e várias ferramentas como uma interface de administração web com Auto Completion para SQL.
É possível fazer o download do instalador para Windows, do zip multi-plataforma, ou ainda apenas do jar do SGBD. Com o jar ou a versão zipada nenhuma instalação é necessária. Basta clicar duas vezes em h2-[versão].jar que o console do banco aparecerá no seu navegador padrão. Se o seu sistema suportar o conceito de “Área de notificação” (classe SystemTray do Java) um ícone amarelo também deve aparecer; a partir deste você pode, a qualquer momento, acessar o console.


Na tela de login você deve indicar uma URL JDBC para o banco. A parcela da URL depois do jdbc:h2: aponta para um arquivo no sistema operacional - o caminho pode ser absoluto (começando com “/”), relativo ao diretório atual (começando com “./”) ou ao diretório do usuário no SO (começando com “~/”). Também será necessário entrar com o login e senha do administrador.
Se a URL apontar para um arquivo não existente o banco será criado (verifique que um arquivo [nome do banco].db apareceu no local indicado); também será criado um usuário administrador com o login e senha informados.


Pronto, isso é tudo que precisa ser feito para criar um banco e acessá-lo em modo embbeded. Para que múltiplos processos possam acessar o mesmo banco concorrentemente basta adicionar o parâmetro AUTO_SERVER=TRUE no fim da URL. Por exemplo:
jdbc:h2:~/Projetos/DBs/h2/RH/RH;AUTO_SERVER=TRUE
É possível ainda fazer com que um dos processos aja exclusivamente como servidor e os outros exclusivamente como clientes. Clientes podem conectar em servidores remotos via TCP/IP ou SSL (veja a documentação aqui).
Uma vez conectado, a tela de administração aparecerá. No lado esquerdo você verá todos os objetos (schemas, tabelas, usuários, constraints, etc) do banco em uma Tree View. No lado direito você pode entrar com comandos SQL (contando com a funcionalidade de Auto Completion para te ajudar). O resultado dos comandos é exibido na parte inferior da tela.


Que tal um Test Drive do banco? Vamos criar um pequeno schema definido pela figura abaixo, populá-lo, e executar algumas queries de complexidade média para ver como a engine responde.


Começando pelo DDL, vamos criar as tabelas e constraints:
-- ----------------------------------------------------
-- Tabela departamento
-- ----------------------------------------------------
CREATE TABLE departamento 
(
  -- IDENTITY = BIGINT + auto incremento + PRIMARY KEY
  id IDENTITY ,
  nome VARCHAR(45) NOT NULL ,
);

-- Constraint nomeada
ALTER TABLE departamento 
ADD CONSTRAINT departamento_nome_unq UNIQUE(nome);

-- ----------------------------------------------------
-- Tabela funcionario
-- ----------------------------------------------------
CREATE TABLE funcionario 
(
  id IDENTITY ,
  nome VARCHAR(255) NOT NULL ,
  -- Valor default
  salario DECIMAL(10,2) NOT NULL DEFAULT 545.00,
  departamento_id INT NOT NULL ,
);

-- Foreign Keys não poderiam faltar 
ALTER TABLE funcionario 
ADD CONSTRAINT funcionario_departamento_fk 
FOREIGN KEY (departamento_id) REFERENCES departamento (id);

-- Funciona!
ALTER TABLE funcionario
ADD CONSTRAINT funcionario_salario_chk CHECK (salario >= 545.00);

-- ----------------------------------------------------
-- Tabela bonus
-- ----------------------------------------------------
CREATE TABLE bonus 
(
  id IDENTITY ,
  valor DECIMAL(10,2) NOT NULL DEFAULT 0.00 ,
  -- Que tal usar uma função?
  data DATE NOT NULL DEFAULT CURRENT_DATE ,
  funcionario_id BIGINT NOT NULL ,
);

ALTER TABLE bonus 
ADD CONSTRAINT bonus_funcionario_fk
FOREIGN KEY (funcionario_id) REFERENCES funcionario (id);
 
-- Precisa de índices? Sem problemas
CREATE INDEX bonus_data_idx ON bonus(data);
Bem direto né? E agora inserts de múltiplas linhas a la MySQL:
INSERT INTO departamento (nome) 
VALUES ('Administrativo'),  
       ('TI'), 
       ('Vendas');     

INSERT INTO funcionario (nome, salario, departamento_id)  
VALUES ('Auxiliar Administrativo', 2000.00, 1),
       ('GP I', 5500.00, 1),
       ('GP II', 7000.00, 1),
       ('Programador I', 3000.00, 2),
       ('Programador II', 6000.00, 2),
       ('Programador III', 10000.00, 2),
       ('Vendedor I', 1500.00, 3),
       ('Vendedor II', 4500.00, 3);
   
INSERT INTO bonus (valor, data, funcionario_id)
VALUES (15000.00, '2010-03-20', 7),
       (15000.00, '2010-07-03', 7),
       (20000.00, '2009-09-09', 8),
       (60000.00, '2010-10-06', 8),
       (45000.00, '2010-11-15', 8);
Veja que todas as constraints estão sendo respeitadas (tente inserir um funcionário que ganhe menos de R$545,00 ou atribuir um valor inválido em um campo que tenha uma foreign key e você se deparará com um exceção).
Mas e as queries? Vamos resolver um primeiro exercício: Determinar, para cada departamento, a quantidade de funcionários e o salário médio.
   SELECT 
         d.nome AS `Departamento`, 
         COUNT(f.id) AS `Numero de Funcionários`, 
         ROUND(AVG(f.salario),2) AS `Média Salarial`
    FROM  
         departamento d 
         LEFT OUTER JOIN funcionario f ON (d.id = f.departamento_id)
GROUP BY
         d.id
ORDER BY
         d.nome;
Perfeito. Essa query usa de tudo um pouco, alias, group by, order by, joins e funções (veja a documentação). Repare que temos o melhor de dois mundos: um banco que não exige a repetição de colunas funcionalmente dependentes na cláusula group by e respeita as constraints do tipo CHECK.
Mas que tal complicar um pouco mais? A definição do exercício número dois é: Encontrar todos os vendedores que ganharam mais de R$10.000,00 em bônus nos últimos doze meses; determinar o total de bônus recebidos por cada vendedor no perído e ordená-los de acordo (começando com o vendedor com a maior soma de bônus e terminando com o vendedor com a menor).
SET @MESES = 12; 
SET @BONUS = 10000.00;

  SELECT 
         f.id,
         f.nome AS `Vendedor`,
         SUM(b.valor) AS `Bonus` 
    FROM
         funcionario f 
         INNER JOIN departamento d ON (f.departamento_id = d.id)  
         INNER JOIN bonus b ON (f.id = b.funcionario_id)
   WHERE
         d.nome = 'Vendas'
         AND b.data >= DATEADD('MONTH', -@MESES, CURRENT_DATE)
GROUP BY
         f.id 
  HAVING
         `Bonus` >= @BONUS
ORDER BY
         `Bonus` DESC; 
Novamente tudo no lugar. Aí está nossa cláusula having, múltiplos joins e até mesmo o uso de variáveis. Deixo para o leitor brincar com subqueries, UNION, MINUS, EXCEPT, INTERSECT, etc.
O H2 têm uma quantidade de funcionalidades que deixa muitos outros bancos conhecidos no chinelo. Saindo do básico (views, sequences, triggers, stored procedures, controle de transações, controle de usuários, roles, análise de planos de execução , etc) algumas features interessantes que lembro de cabeça são o modo in memory (com ótima performance, excelente para caches), a habilidade de emular outros databases, queries recursivas, suporte a índices do tipo full text (nativos ou integrados ao Lucene) e suporte básico a clusterização.
Os veteranos de Java que sentem calafrios quando escutam a combinação de palavras embedded database podem ficar tranquilos. Uso o H2 já há alguns anos e nunca tive sequer um banco corrompido. Cheguei a fazer testes em que bati forte no banco com o JMeter para estressar um aplicativo web e observei um curva de degradação de performance bem saudável. O controle transacional é confiável (inclusive quando operações do H2 participam de transações XA), e, no geral, o otimizador de queries faz um bom trabalho (apesar de algumas limitações).
Bom para não escrever um artigo muito tendencioso, informo que o H2  não deve ser considerado um substituto para databases tradicionais em ambiente de produção. Segue um link do site oficial que fala sobre algumas de suas limitações (diga-se de passagem, 4TB de armazenamento é um limite bastante generoso). Também é necessário dizer que por padrão o banco usa table locks, o que reduz bastante a performance quando uma tabela sofre muitos comandos de escrita concorrentes (porém, já existe suporte experimental a MVCC).
Comprou a idéia? Não custa experimentar, é grátis.
Ah! Já que estamos falando de dinheiro, quem está por trás desse banco é o Thomas Müller, o mesmo criador do HSQLDB. Esse segundo banco você já deve ter usado mesmo que não saiba... Por padrão ele é incluído nos softwares Mathematica, Open Office Base, Liferay, JBoss, Jira, Hibernate e JForum, além, é claro, do super renomado software de contabilidade Open Source que escrevi na faculdade, o AAS hehehe). Você encontra um dotão “Donate” do PayPal na página do H2.

4 comentários:

  1. Parabéns pela explicação amigo!
    Não é primeira vez que passo por aqui em busca de informações sobre o H2! Sou novato em programação + já comprei a ideia desse banco de dados(quero criar aplicativos meus 100% bd embarcados). Tenho um software pronto para apresentar no meu TCC e preciso de uma justificativa convincente para utilização do H2 junto à banca. Fiz uma ligação do H2 com caminho relativo, onde posso "transportar" o software para qualquer equipamento (pc, note, pendrive).Muitooo legal!

    ResponderExcluir
  2. Obrigado pelo comentário.
    Realmente, conectar com um caminho relativo e mover seu projeto com o banco para qualquer lugar (inclusive entre SOs diferentes) é bem bacana.
    Quanto a sua banca de TCC, eu usaria como argumento o fato desse SGBD, mesmo com apenas 1 Mb, obedecer princípios ACID e implementar o padrão SQL/ISO (incluindo boa parte das features opcionais das especificações recentes).

    ResponderExcluir
  3. Valeu pela dica Anthony! Parece não haver referência bibliográfica sobre o H2, é um pena! Eu iria bater nessa tecla mesmo: de ser um SGBD e cumprir o ACID. Dá uma olhadinha no meu projeto: http://www.youtube.com/watch?v=SCzRklqI8T8
    Abraço!

    ResponderExcluir
  4. Ficou bem legal a demonstração, parabéns. Boa sorte com o TCC e futuros clientes :).

    ResponderExcluir