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

27 Jan 2014
11 mins read

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>

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! :)