Persistência de Dados em Java com jOOQ (alternativa ao Hibernate, MyBatis, JDBC)

Olá pessoal,

Começando uma série de posts aqui no blog para falar sobre o jOOQ, um framework java de persistência de dados que pode ser usado como uma alternativa ao Hibernate, MyBatis, JDBC, SpringData, etc.

No post de hoje vou apresentar esse framework a vocês, prós e contras e como fazer o setup de um projeto HelloWorld que vamos usar como base nos próximos posts.

jooq-logo-black

O jOOQ ganhou bastante espaço nos últimos tempos, principalmente em conferências na gringa. Um dos motivos é essa frase que encontramos logo na página inicial do framework:

jOOQ gera código Java a partir de seu banco de dados e permite que você construa consultas SQL typesafe através de sua API fluente.

Ou seja, ao contrário de outros frameworks, onde você configura a partir de classes Java, o jOOQ é focado totalmente no SQL, fazendo a configuração das classes para você. Vocês irão ver que apenas de setup inicial, quase não vamos escrever código.

O jOOQ é open source, e essa versão oferece suporte a banco de dados que também são open source. Se você quiser usar o jOOQ com algum banco de dados comercial, é preciso pagar (justo; e do ponto de vista corporativo, a licença tem um preço muito bom - afinal, desenvolvedor também precisa pagar contas né? rs).

Para fazer setup do nosso projeto, vamos precisar:

Também é possível usar o Maven para criar o projeto (vou criar o projeto com os jars mesmo, caso alguém queria executar o exemplo e ainda não use Maven).

Os passos que vamos executar nesse tutorial são:

  1. Criação do banco de dados de exemplo
  2. Criação do projeto Java
  3. Geração das classes com o jOOQ
  4. Criação de uma classe Main para testar a conexão com o banco de dados
  5. Codificar uma query usando jOOQ DSL
  6. Iterar os resultados

1 - Criação do banco de dados de exemplo

Vou usar um banco de dados pronto para esse exemplo. É o banco World disponibilizado pelo MySQL mesmo - pode fazer download de qualquer versão abaixo - eu fiz do InnoDB.

jooq-loiane-01

Faça o Download e instal no seu MySQL. Basta executar os scripts que vem dentro do zip. O meu banco ficou assim:

jooq-loiane-02

2 - Criação do projeto Java

Crie um projeto Java (Java Project mesmo, o mais simples) - estou usando o Eclipse - e crie uma pasta chamada lib para colocar os jars - e não esqueça de adicionar os jars ao build path do projeto:

jooq-loiane-03

Maven

Se preferir, pode gerar o projeto maven e usar o seguintes artefatos no seu pom.xml:

<dependency>
  <groupId>org.jooq</groupId>
  <artifactId>jooq</artifactId>
  <version>3.2.2</version>
</dependency>
<dependency>
  <groupId>org.jooq</groupId>
  <artifactId>jooq-meta</artifactId>
  <version>3.2.2</version>
</dependency>
<dependency>
  <groupId>org.jooq</groupId>
  <artifactId>jooq-codegen</artifactId>
  <version>3.2.2</version>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.28</version>
</dependency>

3 - Geração das classes com o jOOQ

O jOOQ é totalmente focado no SQL. Por este motivo, não criamos classes Java e mapeamos ao banco de dados como geralmente fazemos quando utilizamos Hibernate, JPA, MyBatis, etc.

É o jOOQ que gera o código java que precisamos para "mapear" o banco de dados. E é bem simples. Basta criar um arquivo XML para isso que fica assim - arquivo library.xml que é criado dentro da pasta src do projeto:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<configuration xmlns="http://www.jooq.org/xsd/jooq-codegen-3.2.0.xsd">
  <!-- Configure the database connection here -->
  <jdbc>
    <driver>com.mysql.jdbc.Driver</driver>
    <url>jdbc:mysql://localhost:3306/world</url>
    <user>root</user>
    <password>root</password>
  </jdbc>

  <generator>
    <!-- The default code generator. You can override this one, to generate your own code style
         Defaults to org.jooq.util.DefaultGenerator -->
    <name>org.jooq.util.DefaultGenerator</name>

    <database>
      <!-- The database type. The format here is:
           org.util.[database].[database]Database -->
      <name>org.jooq.util.mysql.MySQLDatabase</name>

      <!-- The database schema (or in the absence of schema support, in your RDBMS this
           can be the owner, user, database name) to be generated -->
      <inputSchema>world</inputSchema>

      <!-- All elements that are generated from your schema
           (A Java regular expression. Use the pipe to separate several expressions)
           Watch out for case-sensitivity. Depending on your database, this might be important! -->
      <includes>.*</includes>

      <!-- All elements that are excluded from your schema
           (A Java regular expression. Use the pipe to separate several expressions).
           Excludes match before includes -->
      <excludes></excludes>
    </database>

    <target>
      <!-- The destination package of your generated classes (within the destination directory) -->
      <packageName>com.loiane.mysql.generatedclasses</packageName>

      <!-- The destination directory of your generated classes -->
      <directory>/Users/loiane/Documents/workspace/jOOQ-examples/src</directory>
    </target>
  </generator>
</configuration>
  • Nas linhas 4-9 temos os dados para fazer a conexão com o banco de dados.
  • Na linha 19 temos qual é o banco de dados que vamos nos conectar. No nosso caso é o MySQL.
  • Na linha 23 temos o nome do schema que queremos nos conectar.
  • Na linha 38 qual é o pacote onde queremos que as classes sejam geradas.
  • E na linha 41 qual é o caminho completo da pasta src do projeto.

Para gerar as classes é possível executar por linha de comando, mas vamos fazer no eclipse mesmo. Para isso, selecione o projeto e vá em Run Configurations:

Selecione o projeto e a classe principal como mostra abaixo:

jooq-loiane-04

E adicione o library.xml como parâmetro:

jooq-loiane-05

Clique em Run - e veja o output no console. Será algo similar a isso:

Jan 23, 2014 11:15:43 AM org.jooq.tools.JooqLogger info
INFO: Initialising properties  : /library.xml
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: License parameters
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: ----------------------------------------------------------
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   Thank you for using jOOQ and jOOQ's code generator
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: Database parameters
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: ----------------------------------------------------------
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   dialect                : MYSQL
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   target dir             : /Users/loiane/Documents/workspace/jOOQ-examples/src
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   target package         : com.loiane.mysql.generatedclasses
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   includes               : [.*]
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   excludes               : []
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   includeExcludeColumns  : false
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: ----------------------------------------------------------
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: DefaultGenerator parameters
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: ----------------------------------------------------------
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   strategy               : class org.jooq.util.DefaultGeneratorStrategy
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   deprecated             : true
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   generated annotation   : true
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   JPA annotations        : false
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   validation annotations : false
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   instance fields        : true
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   records                : true
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   pojos                  : false
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   immutable pojos        : false
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   interfaces             : false
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   daos                   : false
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   relations              : true
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:   global references      : true
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: ----------------------------------------------------------
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO:
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: Generation remarks
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: ----------------------------------------------------------
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: ----------------------------------------------------------
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: Emptying                 : /Users/loiane/Documents/workspace/jOOQ-examples/src/com/loiane/mysql/generatedclasses
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: Generating schemata      : Total: 1
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: Generating schema        : World.java
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: ----------------------------------------------------------
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: Sequences fetched        : 0 (0 included, 0 excluded)
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: Tables fetched           : 3 (3 included, 0 excluded)
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: UDTs fetched             : 0 (0 included, 0 excluded)
Jan 23, 2014 11:15:44 AM org.jooq.tools.JooqLogger info
INFO: Generating tables
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Generating table         : City.java [input=City, output=City, pk=KEY_City_PRIMARY]
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: ARRAYs fetched           : 0 (0 included, 0 excluded)
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Enums fetched            : 2 (2 included, 0 excluded)
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Generating table         : Country.java [input=Country, output=Country, pk=KEY_Country_PRIMARY]
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Generating table         : Countrylanguage.java [input=CountryLanguage, output=CountryLanguage, pk=KEY_CountryLanguage_PRIMARY]
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Tables generated         : Total: 1.862s
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Generating table references
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Table refs generated     : Total: 1.864s, +1.34ms
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Generating Keys
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Keys generated           : Total: 1.868s, +4.122ms
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Generating records
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Generating record        : CityRecord.java
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Generating record        : CountryRecord.java
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Generating record        : CountrylanguageRecord.java
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Table records generated  : Total: 1.903s, +35.639ms
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Generating ENUMs
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Generating ENUM          : CountryContinent.java
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Generating ENUM          : CountrylanguageIsofficial.java
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Enums generated          : Total: 1.907s, +3.571ms
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Routines fetched         : 0 (0 included, 0 excluded)
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: Packages fetched         : 0 (0 included, 0 excluded)
Jan 23, 2014 11:15:46 AM org.jooq.tools.JooqLogger info
INFO: GENERATION FINISHED!     : Total: 1.911s, +3.693ms

E pronto! Faça o refresh do projeto e verá que as classes foram geradas!

jooq-loiane-06

No XML!

Se você assim como eu não é muito fã de XML, também é possível fazer a geração das classes acima sem usar XML!

Para isso, vamos criar uma classe com um método main (ponto de entrada):

package com.loiane.config;

import org.jooq.util.GenerationTool;
import org.jooq.util.jaxb.Configuration;
import org.jooq.util.jaxb.Database;
import org.jooq.util.jaxb.Generator;
import org.jooq.util.jaxb.Jdbc;
import org.jooq.util.jaxb.Target;

public class JOOQConfig {

	public static void main(String[] args) throws Exception {
		Configuration configuration = new Configuration()
		    .withJdbc(new Jdbc()
		        .withDriver("com.mysql.jdbc.Driver")
		        .withUrl("jdbc:mysql://localhost:3306/world")
		        .withUser("root")
		        .withPassword("root"))
		    .withGenerator(new Generator()
		        .withName("org.jooq.util.DefaultGenerator")
		        .withDatabase(new Database()
		            .withName("org.jooq.util.mysql.MySQLDatabase")
		            .withIncludes(".*")
		            .withExcludes("")
		            .withInputSchema("world"))
		        .withTarget(new Target()
		            .withPackageName("com.loiane.mysql.generatedclasses")
		            .withDirectory("/Users/loiane/Documents/workspace/jOOQ-examples/src")));

		GenerationTool.main(configuration);
	}

}

E se executarmos essa classe, o resultado será o mesmo que usando o library.xml:

jooq-loiane-07

4 - Criação de uma classe Main para testar a conexão com o banco de dados

O próximo passo agora é criar uma classe para testarmos a conexão. Para deixar mais simples, não vou usar JUnit ou alguma biblioteca de testes. Vou criar uma classe bem simples para testar:

package com.loiane.test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class TestConnection {

	public static void main(String[] args) {
		Connection conn = null;

		String userName = "root";
		String password = "root";
		String url = "jdbc:mysql://localhost:3306/world";

		try {
			Class.forName("com.mysql.jdbc.Driver").newInstance();
			conn = DriverManager.getConnection(url, userName, password);

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException ignore) {
				}
			}
		}

	}
}

5 - Codificar uma query usando jOOQ DSL

O próximo passo é buscar os dados no banco. Para isso vamos usar o jOOQ DSL:

package com.loiane.test;

import static com.loiane.mysql.generatedclasses.tables.Country.COUNTRY;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import org.jooq.DSLContext;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.SQLDialect;
import org.jooq.impl.DSL;

public class TestConnection {

	public static void main(String[] args) {
		Connection conn = null;

		String userName = "root";
		String password = "root";
		String url = "jdbc:mysql://localhost:3306/world";

		try {
			Class.forName("com.mysql.jdbc.Driver").newInstance();
			conn = DriverManager.getConnection(url, userName, password);

			//buscando dados no banco
            DSLContext create = DSL.using(conn, SQLDialect.MYSQL);
            Result<Record> result = create.select().from(COUNTRY).fetch();

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException ignore) {
				}
			}
		}

	}
}

6 - Iterar os resultados

E por último, vamos fazer um loop para ver se o resultado é o mesmo esperado:

package com.loiane.test;

import static com.loiane.mysql.generatedclasses.tables.Country.COUNTRY;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import org.jooq.DSLContext;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.SQLDialect;
import org.jooq.impl.DSL;

public class TestConnection {

	public static void main(String[] args) {
		Connection conn = null;

		String userName = "root";
		String password = "root";
		String url = "jdbc:mysql://localhost:3306/world";

		try {
			Class.forName("com.mysql.jdbc.Driver").newInstance();
			conn = DriverManager.getConnection(url, userName, password);

			//buscando dados no banco
			DSLContext create = DSL.using(conn, SQLDialect.MYSQL);
			Result<Record> result = create.select().from(COUNTRY).fetch();

			//iterando os resultados
			for (Record r : result) {
				String code = r.getValue(COUNTRY.CODE);
				String name = r.getValue(COUNTRY.NAME);

				System.out.println("Codigo: " + code + " name: " + name);
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException ignore) {
				}
			}
		}

	}
}

E se executarmos a classe acima o resultado será:

Codigo: ABW name: Aruba
Codigo: AFG name: Afghanistan
Codigo: AGO name: Angola
Codigo: AIA name: Anguilla
Codigo: ALB name: Albania
Codigo: AND name: Andorra
Codigo: ANT name: Netherlands Antilles
Codigo: ARE name: United Arab Emirates
Codigo: ARG name: Argentina
Codigo: ARM name: Armenia
Codigo: ASM name: American Samoa
Codigo: ATA name: Antarctica
Codigo: ATF name: French Southern territories
Codigo: ATG name: Antigua and Barbuda
Codigo: AUS name: Australia
Codigo: AUT name: Austria
Codigo: AZE name: Azerbaijan
...

Prontinho!

Download do Código Fonte

Código completo disponível em: https://github.com/loiane/jOOQ-examples

Referência: http://www.jooq.org/doc/3.2/manual-single-page/

Até a próxima! :)