Archive for November 2011
Getting started with jOOQ: A Tutorial
Introduction
I accidentally stumbled onto jOOQ a few days ago while doing a lot of research on Hibernate. Funny how things work, isn’t it? For those of you that aren’t familiar with it, jOOQ is a different approach to the over-ORMing of Java persistence. Rather than try to map database tables to Java classes and abstract away the SQL underneath, jOOQ assumes you want low level control over the SQL queries you execute, and provides a mostly typesafe interface for executing queries. I don’t have anything against simple ORMs, but it’s good to have the right tool for the right job. From the jOOQ homepage:</p?
Instead of this SQL query:
SELECT * FROM BOOK WHERE PUBLISHED_IN = 2011 ORDER BY TITLE
You would execute this Java code:
create.selectFrom(BOOK)
.where(PUBLISHED_IN.equal(2011))
.orderBy(TITLE)
Why a Java interface? Type safety, for one. Programmatically using jOOQ’s DSL has some advantages over writing SQL queries by hand, such as IDE support and compile time checking of some things.
The idea interested me and I dug in. Unfortunately, the jOOQ site’s documentation, while fairly comprehensive, DO NOT PROVIDE AN END TO END “GETTING STARTED” PAGE!!! This means that if you want to learn jOOQ, you’ll have to jump to the chapter about Meta model code generation, then jump to the DSL, then jump to jOOQ classes section. It’s a bit of a mess for new users. Google search also didn’t turn up many useful results, so I figured I’d whip up a quick “Getting started” guide. We’re going to go over the following steps:
Preparation: Download jOOQ and your SQL driver
Step 1: Create a SQL database and a table
Step 2: Generate classes
Step 3. Write a main class and establish MySQL connection
Step 4: Write a query using jOOQ’s DSL
Step 5: Iterate over results
Step 6: Profit!
Ready? Let’s get started.
Getting our hands dirty
Preparation: Download jOOQ and your SQL driver
If you haven’t already downloaded them, download jOOQ:
http://sourceforge.net/projects/jooq/files/
For this example, we’ll be using MySQL. If you haven’t already downloaded MySQL Connector/J, download it here:
http://dev.mysql.com/downloads/connector/j/
Stash these somewhere where you can get to them later.
Step 1: Create a SQL database and a table
We’re going to create a database called “guestbook” and a corresponding “posts” table. Connect to MySQL via your command line client and type the following:
create database guestbook; CREATE TABLE `posts` ( `id` bigint(20) NOT NULL, `body` varchar(255) DEFAULT NULL, `timestamp` datetime DEFAULT NULL, `title` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) );
(I copied and pasted the create table statement from a “show create table” command)
Step 2: Generate classes
In this step, we’re going to use jOOQ’s command line tools to generate classes that map to the Posts table we just created. The official docs are here.
I’m going to augment the command line steps a bit. The easiest way to generate a schema is to copy the jOOQ jar files (there should be 3) and the MySQL Connector jar file to a temporary directory. Create a properties file. I’ve created a file called guestbook.properties that looks like this:
#Configure the database connection here jdbc.Driver=com.mysql.jdbc.Driver jdbc.URL=jdbc:mysql://localhost:3306/guestbook jdbc.Schema=guestbook jdbc.User=ikai jdbc.Password= #The default code generator. You can override this one, to generate your own code style #Defaults to org.jooq.util.DefaultGenerator generator=org.jooq.util.DefaultGenerator #The database type. The format here is: #generator.database=org.util.[database].[database]Database generator.database=org.jooq.util.mysql.MySQLDatabase #All elements that are generated from your schema (several Java regular expressions, separated by comma) #Watch out for case-sensitivity. Depending on your database, this might be important! generator.database.includes=.* #All elements that are excluded from your schema (several Java regular expressions, separated by comma). Excludes match before includes generator.database.excludes= #Primary key / foreign key relations should be generated and used. #This will be a prerequisite for various advanced features #Defaults to false generator.generate.relations=true #Generate deprecated code for backwards compatibility #Defaults to true generator.generate.deprecated=false #The destination package of your generated classes (within the destination directory) generator.target.package=test.generated #The destination directory of your generated classes generator.target.directory=/Users/ikai/workspace/MySQLTest/src
One thing that wasn’t clear from jOOQ’s docs is the value of jdbc.Schema: it should be your database name. Since our database name is “guestbook”, that’s what we put. Replace the username with whatever user has the appropriate privileges: in my local dev database, my user has what is effectively root access to everything without a password. You’ll want to look at the other values and replace as necessary. Here are the two interesting properties:
generator.target.package – set this to the parent package you want to create for the generated classes. My setting of test.generated will cause the test.generated.Posts and test.generated.PostsRecord to be created
generator.target.directory – the directory to output to. Worst case scenario you can just copy the files to the package.
Once you have the JAR files and guestbook.properties in your temp directory, type this:
java -classpath jooq-1.6.8.jar:jooq-meta-1.6.8.jar:jooq-codegen-1.6.8.jar:mysql-connector-java-5.1.18-bin.jar:. org.jooq.util.GenerationTool /jooq.properties
Note the prefix slash before jooq.properies. Even though it’s in our working directory, we need to prepend a slash.
Replace the filenames with your filenames. In this example, I’m using jOOQ 1.6.8. If everything has worked, you should see this in your console output:
Nov 1, 2011 7:25:06 PM org.jooq.impl.JooqLogger info INFO: Initialising properties : /jooq.properties Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Database parameters Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: ---------------------------------------------------------- Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: dialect : MYSQL Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: schema : guestbook Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: target dir : /Users/ikai/Documents/workspace/MySQLTest/src Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: target package : test.generated Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: ---------------------------------------------------------- Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Emptying : /Users/ikai/workspace/MySQLTest/src/test/generated Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Generating classes in : /Users/ikai/workspace/MySQLTest/src/test/generated Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Generating schema : Guestbook.java Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Generating factory : GuestbookFactory.java Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Schema generated : Total: 122.18ms Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Sequences fetched : 0 (0 included, 0 excluded) Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Masterdata tables fetched: 0 (0 included, 0 excluded) Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Tables fetched : 5 (5 included, 0 excluded) Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Generating tables : /Users/ikai/workspace/MySQLTest/src/test/generated/tables Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: ARRAYs fetched : 0 (0 included, 0 excluded) Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Enums fetched : 0 (0 included, 0 excluded) Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: UDTs fetched : 0 (0 included, 0 excluded) Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Generating table : Posts.java Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Tables generated : Total: 680.464ms, +558.284ms Nov 1, 2011 7:25:07 PM org.jooq.impl.JooqLogger info INFO: Generating Keys : /Users/ikai/workspace/MySQLTest/src/test/generated/tables Nov 1, 2011 7:25:08 PM org.jooq.impl.JooqLogger info INFO: Keys generated : Total: 718.621ms, +38.157ms Nov 1, 2011 7:25:08 PM org.jooq.impl.JooqLogger info INFO: Generating records : /Users/ikai/workspace/MySQLTest/src/test/generated/tables/records Nov 1, 2011 7:25:08 PM org.jooq.impl.JooqLogger info INFO: Generating record : PostsRecord.java Nov 1, 2011 7:25:08 PM org.jooq.impl.JooqLogger info INFO: Table records generated : Total: 782.545ms, +63.924ms Nov 1, 2011 7:25:08 PM org.jooq.impl.JooqLogger info INFO: Routines fetched : 0 (0 included, 0 excluded) Nov 1, 2011 7:25:08 PM org.jooq.impl.JooqLogger info INFO: Packages fetched : 0 (0 included, 0 excluded) Nov 1, 2011 7:25:08 PM org.jooq.impl.JooqLogger info INFO: GENERATION FINISHED! : Total: 791.688ms, +9.143ms
Step 3. Write a main class and establish MySQL connection
Let’s just write a vanilla main class in the project containing the generated classes:
public class Main {
public static void main(String[] args) {
Connection conn = null;
String userName = "ikai";
String password = "";
String url = "jdbc:mysql://localhost:3306/guestbook";
try {
Class.forName("com.mysql.jdbc.Driver").newInstance();
conn = DriverManager.getConnection(url, userName, password);
conn.close();
} catch (Exception e) {
// You'll probably want to handle the exceptions in a real app
// Don't ever do this silence catch(Exception e) thing. I've seen this in
// live code and it is horrendous.
e.printStackTrace();
}
}
}
This is pretty standard code for establishing a MySQL connection.
Step 4: Write a query using jOOQ’s DSL
Let’s add a simple query:
GuestbookFactory create = new GuestbookFactory(conn); Result result = create.select().from(Posts.POSTS).fetch();
We need to first get an instance of GuestbookFactory so we can write a simple SELECT query. We pass an instance of the MySQL connection to GuestbookFactory. Note that factory doesn’t close the connection. We’ll have to do that ourselves.
We then use jOOQ’s DSL to return an instance of Result. We’ll be using this result in the next step.
Step 5: Iterate over results
After the line where we retrieve the results, let’s iterate over the results and print out the data:
for (Record r : result) {
Long id = r.getValueAsLong(Posts.ID);
String title = r.getValueAsString(Posts.TITLE);
String description = r.getValueAsString(Posts.BODY);
System.out.println("ID: " + id + " title: " + title + " desciption: " + description);
}
The full program should now look like this:
package test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import org.jooq.Record;
import org.jooq.Result;
import test.generated.GuestbookFactory;
import test.generated.tables.Posts;
public class Main {
/**
* @param args
*/
public static void main(String[] args) {
Connection conn = null;
String userName = "ikai";
String password = "";
String url = "jdbc:mysql://localhost:3306/guestbook";
try {
Class.forName("com.mysql.jdbc.Driver").newInstance();
conn = DriverManager.getConnection(url, userName, password);
GuestbookFactory create = new GuestbookFactory(conn);
Result result = create.select().from(Posts.POSTS).fetch();
for (Record r : result) {
Long id = r.getValueAsLong(Posts.ID);
String title = r.getValueAsString(Posts.TITLE);
String description = r.getValueAsString(Posts.BODY);
System.out.println("ID: " + id + " title: " + title + " desciption: " + description);
}
conn.close();
} catch (Exception e) {
// You'll probably want to handle the exceptions in a real app
// Don't ever do this silence catch(Exception e) thing. I've seen this in
// live code and it is horrendous.
e.printStackTrace();
}
}
}
Step 6: Profit!
Get a job and go to work like the rest of us.
Conclusion
I haven’t explored the more advanced bits of jOOQ, but, at least judging from the docs, it looks like there’s a lot of meat there. I’m hoping this guide makes it easier for new users to dive in.
- ikai
Currently listening: Sweat – Snoop Dogg vs David Guetta
