LoginSignup
72
78

More than 5 years have passed since last update.

jOOQ使用方法のまとめ

Last updated at Posted at 2015-09-03

概要

jOOQはHibernateやMyBatisなどと同じORMフレームワークです。
無償で使用できるOpen Source版と有償版(Express,Professional,Enterprice)があります。
有償版は商用データベース(Oracle,SQL Serverなど)も使え、Eメールによるサポートも受けられるようです。
エディションの違いはChoose your jOOQ Editionで詳しく説明されています。

この記事はjOOQを使ったアプリケーションの開発環境の準備と、公式サイトのチュートリアルやユーザーマニュアルの内容に沿って実際にjOOQを使いながらコーディング方法をまとめたものです。
今回はOpen Source版を使いました。

Open Source版のサポートデータベース

DB Version
CUBRID 8.4
Derby 10.10
Firebird 2.5
H2 1.3
HSQLDB 2.2
MariaDB 5.2
MySQL 5.5
PostgreSQL 9.0
SQLite

参照
Database Support

環境

下記の環境で動作確認を行いました。

  • Windows7 (64bit)
  • Java 1.8.0_60
  • jOOQ (Open Source) 3.6.2
  • MySQL 5.6.25
  • eclipse 4.4
  • maven 3.3.3

参考

Github

ソースコードはjooq-exampleにあります。

事前準備

データベースの作成

データベースにMySQL 5.6.25を使用します。
MySQLに今回使用するデータベースとユーザーを作成します。

  • データベース名: actor_db
  • ユーザー: actor_user / actor_pass
create database if not exists actor_db;

create user 'actor_user'@'localhost' identified by 'actor_pass';

grant all on actor_db.* to 'actor_user'@'localhost';

テーブルの作成やデータの投入はmavenプラグインを使用して行います。

アプリケーションの雛形の作成

  • アプリケーション名: jooq-example

mavenでアプリケーションの雛形を作成

> mvn archetype:generate -DgroupId=com.example.jooq -DartifactId=jooq-example -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

eclipseにインポート

> cd jooq-example
> mvn eclipse:eclipse

pom.xmlの編集

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example.jooq</groupId>
  <artifactId>jooq-example</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>jooq-example</name>
  <url>http://maven.apache.org</url>

  <properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <org.jooq.version>3.6.2</org.jooq.version>
    <mysql.connector.java.version>5.1.36</mysql.connector.java.version>
    <slf4j.version>1.7.12</slf4j.version>
    <logback.version>1.1.3</logback.version>
    <db.driver>com.mysql.jdbc.Driver</db.driver>
    <db.url>jdbc:mysql://localhost:3306/actor_db</db.url>
    <db.schema>actor_db</db.schema>
    <db.user>actor_user</db.user>
    <db.password>actor_pass</db.password>
    <db.creation.skip>false</db.creation.skip>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.jooq</groupId>
      <artifactId>jooq</artifactId>
      <version>${org.jooq.version}</version>
    </dependency>
    <dependency>
      <groupId>org.jooq</groupId>
      <artifactId>jooq-meta</artifactId>
      <version>${org.jooq.version}</version>
    </dependency>
    <dependency>
      <groupId>org.jooq</groupId>
      <artifactId>jooq-codegen</artifactId>
      <version>${org.jooq.version}</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>${mysql.connector.java.version}</version>
    </dependency>

    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2.1</version>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.4</version>
    </dependency>

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>${slf4j.version}</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>${logback.version}</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>

    <pluginManagement>
      <plugins>
        <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
        <plugin>
          <groupId>org.eclipse.m2e</groupId>
          <artifactId>lifecycle-mapping</artifactId>
          <version>1.0.0</version>
          <configuration>
            <lifecycleMappingMetadata>
              <pluginExecutions>
                <pluginExecution>
                  <pluginExecutionFilter>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>
                      sql-maven-plugin
                    </artifactId>
                    <versionRange>[1.5,)</versionRange>
                    <goals>
                      <goal>execute</goal>
                    </goals>
                  </pluginExecutionFilter>
                  <action>
                    <ignore></ignore>
                  </action>
                </pluginExecution>
                <pluginExecution>
                  <pluginExecutionFilter>
                    <groupId>org.jooq</groupId>
                    <artifactId>
                      jooq-codegen-maven
                    </artifactId>
                    <versionRange>
                      [3.6.2,)
                    </versionRange>
                    <goals>
                      <goal>generate</goal>
                    </goals>
                  </pluginExecutionFilter>
                  <action>
                    <ignore></ignore>
                  </action>
                </pluginExecution>
              </pluginExecutions>
            </lifecycleMappingMetadata>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>

    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.3</version>
        <configuration>
          <verbose>true</verbose>
          <source>${java.version}</source>
          <target>${java.version}</target>
          <encoding>${project.build.sourceEncoding}</encoding>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>versions-maven-plugin</artifactId>
        <version>2.2</version>
      </plugin>
    </plugins>

  </build>

  <profiles>
    <profile>
      <id>generate</id>
      <build>
        <plugins>

          <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>sql-maven-plugin</artifactId>
            <version>1.5</version>
            <!-- common configuration shared by all executions -->
            <configuration>
              <driver>${db.driver}</driver>
              <url>${db.url}</url>
              <username>${db.user}</username>
              <password>${db.password}</password>
              <!--all executions are ignored if -Ddb.creation.skip=true-->
              <skip>${db.creation.skip}</skip>
              <forkMode>always</forkMode>
              <autocommit>true</autocommit>
              <encoding>UTF-8</encoding>
              <orderFile>ascending</orderFile>
              <onError>abort</onError>
                <!-- sql:execute -->
<!-- 
                <srcFiles>
                  <srcFile>src/main/resources/sql/01-schema.sql</srcFile>
                  <srcFile>src/main/resources/sql/02-data.sql</srcFile>
                </srcFiles>
-->
            </configuration>
            <executions>
              <execution>
                <id>create-schema-data</id>
                <phase>generate-sources</phase>
                <goals>
                  <goal>execute</goal>
                </goals>
                <configuration>
                  <srcFiles>
                    <srcFile>src/main/resources/sql/01-schema.sql</srcFile>
                    <srcFile>src/main/resources/sql/02-data.sql</srcFile>
                  </srcFiles>
                </configuration>
              </execution>
            </executions>
            <dependencies>
              <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.connector.java.version}</version>
              </dependency>
            </dependencies>
          </plugin>

          <plugin>
            <groupId>org.jooq</groupId>
            <artifactId>jooq-codegen-maven</artifactId>
            <version>3.6.2</version>
            <!-- common configuration shared by all executions -->
            <configuration>
              <jdbc>
                <driver>${db.driver}</driver>
                <url>${db.url}</url>
                <user>${db.user}</user>
                <password>${db.password}</password>
              </jdbc>
              <generator>
                <name>org.jooq.util.DefaultGenerator</name>
                <database>
                  <name>org.jooq.util.mysql.MySQLDatabase</name>
                  <inputSchema>${db.schema}</inputSchema>
                  <includes>.*</includes>
                  <excludes></excludes>
                </database>
                <generate>
                  <relations>true</relations>
                  <records>true</records>
                  <instanceFields>true</instanceFields>
                  <pojos>false</pojos>
                  <daos>false</daos>
<!--
                  <deprecated>false</deprecated>
                  <generatedAnnotation>false</generatedAnnotation>
                  <immutablePojos>false</immutablePojos>
                  <interfaces>false</interfaces>
                  <jpaAnnotations>false</jpaAnnotations>
                  <validationAnnotations>false</validationAnnotations>
                  <globalObjectReferences>false</globalObjectReferences>
-->
                </generate>
                <target>
                  <packageName>com.example.jooq.db</packageName>
                  <directory>target/generated-sources/jooq/db</directory>
                </target>
              </generator>
            </configuration>
            <executions>
              <execution>
                <id>generate-source-code</id>
                <phase>generate-sources</phase>
                <goals>
                  <goal>generate</goal>
                </goals>
              </execution>
            </executions>
            <dependencies>
              <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.connector.java.version}</version>
              </dependency>
            </dependencies>
          </plugin>

        </plugins>
      </build>
    </profile>
  </profiles>

</project>
  • プラグインのexecute要素がエラーになる場合

Plugin execution not covered by lifecycle configuration ...というエラーが発生する場合は

pom.xmlの編集画面上でエラーが発生しているexecutionにマウスカーソルを当てるクイックfixメニューが表示されるので、そのなかから"Permanently mark goal compili in pom.xml as ignored in ..."を選択します。

plugin_execution_not_coverd.png

若しくは、

eclipseの"Windows" -> "Preferences" -> "Maven" -> "Errors/Warnings"の設定画面で、"Plugin execution not coverd by lifecycle configuration"を"Error"から"Warning"または"Ignore"へ変えることで無視できます。

resourcesフォルダの作成

resourcesフォルダをsrc/main下に作成します。

作成したらプロジェクトに反映させます。
* "Build Path" -> "Configure Build Path" -> "Java Buld Path" -> "Source"タブを選択する。
* "Add Folder"ボタンをクリック -> 作成した"resources"フォルダにチェックを入れる。

logback.xmlの作成

src/main/resourcesフォルダ内にlogback.xmlを作成します。
ログの出力先フォルダを"D:/logs"に指定しました。

logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

  <property name="LOG_DIR" value="D:/logs" />

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{yyyy-MMM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{35} - %msg %n</pattern>
    </encoder>
  </appender>
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
     <file>${LOG_DIR}/jooq-example.log</file>
     <encoder>
       <charset>UTF-8</charset>
       <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] - %msg %n</pattern>
     </encoder>
  </appender>

  <logger name="com.example" level="DEBUG" />
  <logger name="org.jooq" level="INFO" />

  <root>
    <appender-ref ref="STDOUT" />
    <appender-ref ref="FILE" />
  </root>
</configuration>

Maven plugin

下記の2つのプラグインを使用してみました。
どちらのプラグインもjOOQを使用する上で必須ではありませんが、jOOQ関連の情報を見ていたところ使用している例が幾つかあったので使用感を確かめる目的も兼ねて使ってみました。

sql-maven-plugin

このプラグインはMojoHaus Projectで開発されており、任意のsql文を実行することができます。

jooq-codegen-maven

このプラグインはjOOQが開発しているコードジェネレータ(jooq-codegen)をmavenから使用できるようにしたものです。対象データベースのスキーマからjOOQで使用するモデルクラスやDAOクラスなどを生成することができます。
なお、コードジェネレータが生成するモデルクラスなどが有るとコーディングは楽になりますが、無くてもjOOQは使用できます。

sql-maven-plugin

sqlフォルダをsrc/main/resources下に作成し、そこへ実行するsqlファイルを用意します

01-schema.sql
drop table if exists actor;
drop table if exists prefecture;

create table if not exists actor (
  id int not null auto_increment,
  name varchar(30) not null,
  height smallint,
  blood varchar(2),
  birthday date,
  birthplace_id smallint,
  update_at timestamp(6) not null default current_timestamp(6) on update current_timestamp(6),
  primary key (id)
) engine = INNODB;


create table if not exists prefecture (
  id smallint not null,
  name varchar(6) not null,
  primary key (id)
) engine = INNODB;
02-data.sql
insert into actor (name, height, blood, birthday, birthplace_id) values
('丹波哲郎', 175, 'O',  '1922-07-17', 13),
('森田健作', 175, 'O',  '1949-12-16', 13),
('加藤剛',   173, null, '1938-02-04', 22),
('島田陽子', 171, 'O',  '1953-05-17',  43),
('山口果林', null, null,'1947-05-10',  13),
('佐分利信', null, null, '1909-02-12', 1),
('緒形拳',   173, 'B',   '1937-07-20', 13),
('松山政路', 167, 'A',   '1947-05-21', 13),
('加藤嘉',   null, null, '1913-01-12', 13),
('菅井きん', 155, 'B',   '1926-02-28', 13),
('笠智衆',   null, null, '1904-05-13', 43),
('殿山泰司', null, null, '1915-10-17', 28),
('渥美清',   173,  'B',  '1928-03-10', 13);

insert into prefecture (id, name) values
(1,'北海道'),(2,'青森県'),(3,'岩手県'),(4,'宮城県'),(5,'秋田県'),(6,'山形県'),(7,'福島県'),
(8,'茨城県'),(9,'栃木県'),(10,'群馬県'),(11,'埼玉県'),(12,'千葉県'),(13,'東京都'),(14,'神奈川県'),
(15,'新潟県'),(16,'富山県'),(17,'石川県'),(18,'福井県'),(19,'山梨県'),(20,'長野県'),(21,'岐阜県'),
(22,'静岡県'),(23,'愛知県'),(24,'三重県'),(25,'滋賀県'),
(26,'京都府'),(27,'大阪府'),(28,'兵庫県'),(29,'奈良県'),(30,'和歌山県'),
(31,'鳥取県'),(32,'島根県'),(33,'岡山県'),(34,'広島県'),(35,'山口県'),
(36,'徳島県'),(37,'香川県'),(38,'愛媛県'),(39,'高知県'),
(40,'福岡県'),(41,'佐賀県'),(42,'長崎県'),(43,'熊本県'),(44,'大分県'),(45,'宮崎県'),(46,'鹿児島県'),(47,'沖縄県');
jooq-codegen-maven

jooq-codegen-mavenの方は特に準備は必要ありません。

コードはpom.xmlで設定した場所へ生成されます。
この例ではtarget以下を指定しています。

target
<target>
  <packageName>com.example.jooq.db</packageName>
  <directory>target/generated-sources/jooq/db</directory>
</target>

また、どのようなコードを生成するかはpluginの設定によります。

generate
<generate>
  <relations>true</relations>
  <deprecated>true</deprecated>
  <records>true</records>
<!--
  <instanceFields>true</instanceFields>
  <generatedAnnotation>false</generatedAnnotation>
  <pojos>false</pojos>
  <immutablePojos>false</immutablePojos>
  <interfaces>false</interfaces>
  <daos>false</daos>
  <jpaAnnotations>false</jpaAnnotations>
  <validationAnnotations>false</validationAnnotations>
  <globalObjectReferences>true</globalObjectReferences>
-->
</generate>
key defalut description
relations true Primary key / foreign key relations should be generated and used.
deprecated true Generate deprecated code for backwards compatibility
instanceFields true Do not reuse this property. It is deprecated as of jOOQ 3.3.0
generatedAnnotation true Generate the javax.annotation.Generated annotation to indicate jOOQ version used for source code.
records true Generate jOOQ Record classes for type-safe querying. You can turn this off, if you don't need "active records" for CRUD
pojos false Generate POJOs in addition to Record classes for usage of the ResultQuery.fetchInto(Class) API
immutablePojos false Generate immutable POJOs for usage of the ResultQuery.fetchInto(Class) API
interfaces false Generate interfaces that will be implemented by records and/or pojos.
daos false Generate DAOs in addition to POJO classes
jpaAnnotations false Annotate POJOs and Records with JPA annotations for increased compatibility and better integration with JPA/Hibernate, etc
validationAnnotations false Annotate POJOs and Records with JSR-303 validation annotations
globalObjectReferences true Allow to turn off the generation of global object references,
fluentSetters false Generate fluent setters
pluginによるテーブルの作成とコードの生成

ここまで準備ができたらmvnコマンドでテーブルの作成とコードの生成を行います。
プロジェクトのルートディレクトリでコマンドプロンプトを開き下記のコマンドを実行します。

> mvn clean generate-sources -Pgenerate

テーブルとデータの確認

データベースにテーブルとデータが作成されていることを確認します。

生成されたコードの確認

target/generated-sourcesフォルダ下にコードが生成されていることを確認します。

tree
generated-sources
 ├─annotations
 └─jooq
     └─db
         └─com
             └─example
                 └─jooq
                     └─db
                         │  ActorDb.java
                         │  Keys.java
                         │  Tables.java
                         │
                         └─tables
                             │  Actor.java
                             │  Prefecture.java
                             │
                             └─records
                                     ActorRecord.java
                                     PrefectureRecord.java

生成されたコードをプロジェクトで使用できるように、db以下をビルドパスに追加します。

buildpath.png

生成されたコード

下記のようなコードが生成されます。jOOQを使ったコーディングではこれ等のクラスを使用します。

Tables.java

このクラスは、jOOQで使用するテーブルを保持します。
jOOQを使用する各クラスで下記のようにstaticインポートするとコーディングが楽になります。

import static com.example.jooq.db.Tables.*;
Tables.java
/**
 * This class is generated by jOOQ
 */
package com.example.jooq.db;


import com.example.jooq.db.tables.Actor;
import com.example.jooq.db.tables.Prefecture;

import javax.annotation.Generated;


/**
 * Convenience access to all tables in actor_db
 */
@Generated(
    value = {
        "http://www.jooq.org",
        "jOOQ version:3.6.2"
    },
    comments = "This class is generated by jOOQ"
)
@SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class Tables {

    /**
     * The table actor_db.actor
     */
    public static final Actor ACTOR = com.example.jooq.db.tables.Actor.ACTOR;

    /**
     * The table actor_db.prefecture
     */
    public static final Prefecture PREFECTURE = com.example.jooq.db.tables.Prefecture.PREFECTURE;
}

Actor.java

actorテーブルに対応するモデルクラスです。

Actor.java
/**
 * This class is generated by jOOQ
 */
package com.example.jooq.db.tables;


import com.example.jooq.db.ActorDb;
import com.example.jooq.db.Keys;
import com.example.jooq.db.tables.records.ActorRecord;

import java.sql.Date;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.List;

import javax.annotation.Generated;

import org.jooq.Field;
import org.jooq.Identity;
import org.jooq.Table;
import org.jooq.TableField;
import org.jooq.UniqueKey;
import org.jooq.impl.TableImpl;


/**
 * This class is generated by jOOQ.
 */
@Generated(
    value = {
        "http://www.jooq.org",
        "jOOQ version:3.6.2"
    },
    comments = "This class is generated by jOOQ"
)
@SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class Actor extends TableImpl<ActorRecord> {

    private static final long serialVersionUID = -1073473535;

    /**
     * The reference instance of <code>actor_db.actor</code>
     */
    public static final Actor ACTOR = new Actor();

    /**
     * The class holding records for this type
     */
    @Override
    public Class<ActorRecord> getRecordType() {
        return ActorRecord.class;
    }

    /**
     * The column <code>actor_db.actor.id</code>.
     */
    public final TableField<ActorRecord, Integer> ID = createField("id", org.jooq.impl.SQLDataType.INTEGER.nullable(false), this, "");

    /**
     * The column <code>actor_db.actor.name</code>.
     */
    public final TableField<ActorRecord, String> NAME = createField("name", org.jooq.impl.SQLDataType.VARCHAR.length(30).nullable(false), this, "");

    /**
     * The column <code>actor_db.actor.height</code>.
     */
    public final TableField<ActorRecord, Short> HEIGHT = createField("height", org.jooq.impl.SQLDataType.SMALLINT, this, "");

    /**
     * The column <code>actor_db.actor.blood</code>.
     */
    public final TableField<ActorRecord, String> BLOOD = createField("blood", org.jooq.impl.SQLDataType.VARCHAR.length(2), this, "");

    /**
     * The column <code>actor_db.actor.birthday</code>.
     */
    public final TableField<ActorRecord, Date> BIRTHDAY = createField("birthday", org.jooq.impl.SQLDataType.DATE, this, "");

    /**
     * The column <code>actor_db.actor.birthplace_id</code>.
     */
    public final TableField<ActorRecord, Short> BIRTHPLACE_ID = createField("birthplace_id", org.jooq.impl.SQLDataType.SMALLINT, this, "");

    /**
     * The column <code>actor_db.actor.update_at</code>.
     */
    public final TableField<ActorRecord, Timestamp> UPDATE_AT = createField("update_at", org.jooq.impl.SQLDataType.TIMESTAMP.nullable(false).defaulted(true), this, "");

    /**
     * Create a <code>actor_db.actor</code> table reference
     */
    public Actor() {
        this("actor", null);
    }

    /**
     * Create an aliased <code>actor_db.actor</code> table reference
     */
    public Actor(String alias) {
        this(alias, ACTOR);
    }

    private Actor(String alias, Table<ActorRecord> aliased) {
        this(alias, aliased, null);
    }

    private Actor(String alias, Table<ActorRecord> aliased, Field<?>[] parameters) {
        super(alias, ActorDb.ACTOR_DB, aliased, parameters, "");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Identity<ActorRecord, Integer> getIdentity() {
        return Keys.IDENTITY_ACTOR;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public UniqueKey<ActorRecord> getPrimaryKey() {
        return Keys.KEY_ACTOR_PRIMARY;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<UniqueKey<ActorRecord>> getKeys() {
        return Arrays.<UniqueKey<ActorRecord>>asList(Keys.KEY_ACTOR_PRIMARY);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Actor as(String alias) {
        return new Actor(alias, this);
    }

    /**
     * Rename this table
     */
    public Actor rename(String name) {
        return new Actor(name, null);
    }
}

ActorRecord.java

ActorRecord.java
/**
 * This class is generated by jOOQ
 */
package com.example.jooq.db.tables.records;


import com.example.jooq.db.tables.Actor;

import java.sql.Date;
import java.sql.Timestamp;

import javax.annotation.Generated;

import org.jooq.Field;
import org.jooq.Record1;
import org.jooq.Record7;
import org.jooq.Row7;
import org.jooq.impl.UpdatableRecordImpl;


/**
 * This class is generated by jOOQ.
 */
@Generated(
    value = {
        "http://www.jooq.org",
        "jOOQ version:3.6.2"
    },
    comments = "This class is generated by jOOQ"
)
@SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class ActorRecord extends UpdatableRecordImpl<ActorRecord> implements Record7<Integer, String, Short, String, Date, Short, Timestamp> {

    private static final long serialVersionUID = -672781441;

    /**
     * Setter for <code>actor_db.actor.id</code>.
     */
    public void setId(Integer value) {
        setValue(0, value);
    }

    /**
     * Getter for <code>actor_db.actor.id</code>.
     */
    public Integer getId() {
        return (Integer) getValue(0);
    }

    /**
     * Setter for <code>actor_db.actor.name</code>.
     */
    public void setName(String value) {
        setValue(1, value);
    }

    /**
     * Getter for <code>actor_db.actor.name</code>.
     */
    public String getName() {
        return (String) getValue(1);
    }

    /**
     * Setter for <code>actor_db.actor.height</code>.
     */
    public void setHeight(Short value) {
        setValue(2, value);
    }

    /**
     * Getter for <code>actor_db.actor.height</code>.
     */
    public Short getHeight() {
        return (Short) getValue(2);
    }

    /**
     * Setter for <code>actor_db.actor.blood</code>.
     */
    public void setBlood(String value) {
        setValue(3, value);
    }

    /**
     * Getter for <code>actor_db.actor.blood</code>.
     */
    public String getBlood() {
        return (String) getValue(3);
    }

    /**
     * Setter for <code>actor_db.actor.birthday</code>.
     */
    public void setBirthday(Date value) {
        setValue(4, value);
    }

    /**
     * Getter for <code>actor_db.actor.birthday</code>.
     */
    public Date getBirthday() {
        return (Date) getValue(4);
    }

    /**
     * Setter for <code>actor_db.actor.birthplace_id</code>.
     */
    public void setBirthplaceId(Short value) {
        setValue(5, value);
    }

    /**
     * Getter for <code>actor_db.actor.birthplace_id</code>.
     */
    public Short getBirthplaceId() {
        return (Short) getValue(5);
    }

    /**
     * Setter for <code>actor_db.actor.update_at</code>.
     */
    public void setUpdateAt(Timestamp value) {
        setValue(6, value);
    }

    /**
     * Getter for <code>actor_db.actor.update_at</code>.
     */
    public Timestamp getUpdateAt() {
        return (Timestamp) getValue(6);
    }

    // -------------------------------------------------------------------------
    // Primary key information
    // -------------------------------------------------------------------------

    /**
     * {@inheritDoc}
     */
    @Override
    public Record1<Integer> key() {
        return (Record1) super.key();
    }

    // -------------------------------------------------------------------------
    // Record7 type implementation
    // -------------------------------------------------------------------------

    /**
     * {@inheritDoc}
     */
    @Override
    public Row7<Integer, String, Short, String, Date, Short, Timestamp> fieldsRow() {
        return (Row7) super.fieldsRow();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Row7<Integer, String, Short, String, Date, Short, Timestamp> valuesRow() {
        return (Row7) super.valuesRow();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Field<Integer> field1() {
        return Actor.ACTOR.ID;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Field<String> field2() {
        return Actor.ACTOR.NAME;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Field<Short> field3() {
        return Actor.ACTOR.HEIGHT;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Field<String> field4() {
        return Actor.ACTOR.BLOOD;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Field<Date> field5() {
        return Actor.ACTOR.BIRTHDAY;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Field<Short> field6() {
        return Actor.ACTOR.BIRTHPLACE_ID;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Field<Timestamp> field7() {
        return Actor.ACTOR.UPDATE_AT;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Integer value1() {
        return getId();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String value2() {
        return getName();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Short value3() {
        return getHeight();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String value4() {
        return getBlood();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Date value5() {
        return getBirthday();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Short value6() {
        return getBirthplaceId();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Timestamp value7() {
        return getUpdateAt();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ActorRecord value1(Integer value) {
        setId(value);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ActorRecord value2(String value) {
        setName(value);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ActorRecord value3(Short value) {
        setHeight(value);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ActorRecord value4(String value) {
        setBlood(value);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ActorRecord value5(Date value) {
        setBirthday(value);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ActorRecord value6(Short value) {
        setBirthplaceId(value);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ActorRecord value7(Timestamp value) {
        setUpdateAt(value);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ActorRecord values(Integer value1, String value2, Short value3, String value4, Date value5, Short value6, Timestamp value7) {
        value1(value1);
        value2(value2);
        value3(value3);
        value4(value4);
        value5(value5);
        value6(value6);
        value7(value7);
        return this;
    }

    // -------------------------------------------------------------------------
    // Constructors
    // -------------------------------------------------------------------------

    /**
     * Create a detached ActorRecord
     */
    public ActorRecord() {
        super(Actor.ACTOR);
    }

    /**
     * Create a detached, initialised ActorRecord
     */
    public ActorRecord(Integer id, String name, Short height, String blood, Date birthday, Short birthplaceId, Timestamp updateAt) {
        super(Actor.ACTOR);

        setValue(0, id);
        setValue(1, name);
        setValue(2, height);
        setValue(3, blood);
        setValue(4, birthday);
        setValue(5, birthplaceId);
        setValue(6, updateAt);
    }
}
pluginを使わない方法

sql-maven-plugin

プラグインを使用しない場合は、sqlを直接実行してテーブルやデータを作成してもjOOQの使用に問題ありません。

jooq-codegen-maven

プラグインを使用しない場合は、jOOQコードジェネレータをそのまま使用する方法があります。
設定ファイル(jooq-config.xml)を用意し、Java Applicationとして実行します。

jooq-config.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<configuration>
  <!-- Configure the database connection here -->
  <jdbc>
    <driver>com.mysql.jdbc.Driver</driver>
    <url>jdbc:mysql://localhost:3306/actor_db</url>
    <user>actor_user</user>
    <password>actor_pass</password>
  </jdbc>

  <generator>

    <database>

      <!-- The database dialect from jooq-meta. Available dialects are
           named org.util.[database].[database]Database.

           Natively supported values are:

               org.jooq.util.ase.ASEDatabase
               org.jooq.util.cubrid.CUBRIDDatabase
               org.jooq.util.db2.DB2Database
               org.jooq.util.derby.DerbyDatabase
               org.jooq.util.firebird.FirebirdDatabase
               org.jooq.util.h2.H2Database
               org.jooq.util.hsqldb.HSQLDBDatabase
               org.jooq.util.informix.InformixDatabase
               org.jooq.util.ingres.IngresDatabase
               org.jooq.util.mariadb.MariaDBDatabase
               org.jooq.util.mysql.MySQLDatabase
               org.jooq.util.oracle.OracleDatabase
               org.jooq.util.postgres.PostgresDatabase
               org.jooq.util.sqlite.SQLiteDatabase
               org.jooq.util.sqlserver.SQLServerDatabase
               org.jooq.util.sybase.SybaseDatabase

           This value can be used to reverse-engineer generic JDBC DatabaseMetaData (e.g. for MS Access)

               org.jooq.util.jdbc.JDBCDatabase

           This value can be used to reverse-engineer standard jOOQ-meta XML formats

               org.jooq.util.xml.XMLDatabase

           You can also provide your own org.jooq.util.Database implementation
           here, if your database is currently not supported -->
      <name>org.jooq.util.mysql.MySQLDatabase</name>

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

           You can create case-insensitive regular expressions using this syntax: (?i:expr)

           Whitespace is ignored and comments are possible.
           -->
      <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>

      <!-- The schema that is used locally as a source for meta information.
           This could be your development schema or the production schema, etc
           This cannot be combined with the schemata element.

           If left empty, jOOQ will generate all available schemata. See the
           manual's next section to learn how to generate several schemata -->
      <inputSchema>actor_db</inputSchema>
    </database>

    <generate>
      <!-- Generation flags: See advanced configuration properties -->
      <relations>true</relations>
      <records>true</records>
      <instanceFields>true</instanceFields>
      <pojos>false</pojos>
      <daos>false</daos>
    </generate>

    <target>
      <!-- The destination package of your generated classes (within the
           destination directory)

           jOOQ may append the schema name to this package if generating multiple schemas,
           e.g. org.jooq.your.packagename.schema1
                org.jooq.your.packagename.schema2 -->
      <packageName>com.example.jooq.db</packageName>
      <!-- The destination directory of your generated classes -->
      <directory>target/generated-sources/jooq/db</directory>
    </target>
  </generator>
</configuration>

Main classにorg.jooq.util.GenerationToolを指定します。
main.png

設定ファイルの場所を指定します。

args.png

クラスパスに下記のjarが通っていることが必要です。
mavenで依存関係を管理しているので、Meven Dependenciesが設定されていることを確認します。

  • jooq-3.6.2.jar
  • jooq-meta-3.6.2.jar
  • jooq-codegen-3.6.2.jar
  • mysql-connector-java-5.1.36.jar

classpath.png

jOOQの使い方

jOOQを使って様々な方法でSQL文を実行してみます。

チュートリアル

公式サイトのjooq-in-7-stepsというチュートリアルの内容をもとに使い方を確認します。

JooqIn7Steps.java
package com.example.jooq;

import static com.example.jooq.db.Tables.*;
import static org.jooq.impl.DSL.*;

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

import org.jooq.DSLContext;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.SQLDialect;
import org.jooq.conf.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.example.jooq.db.tables.records.ActorRecord;

public class JooqIn7Steps {
  private static final Logger logger = LoggerFactory.getLogger(JooqIn7Steps.class);

  public static void main(String[] args) {

    String userName = "actor_user";
    String password = "actor_pass";
    String url = "jdbc:mysql://localhost:3306/actor_db";

    try (Connection conn = DriverManager.getConnection(url, userName, password)) {

      Settings settings = new Settings();
      settings.setExecuteLogging(true);
      settings.withRenderFormatted(true);
      settings.withRenderSchema(false);

      DSLContext create = using(conn, SQLDialect.MYSQL, settings);

      /*
       * select
       */
      Result<Record> result =
          create.select()
                .from(ACTOR)
                .limit(5)
                .fetch();

      for (Record r : result) {
        Integer id = r.getValue(ACTOR.ID);
        String name = r.getValue(ACTOR.NAME);
        Short height = r.getValue(ACTOR.HEIGHT);
        String blood = r.getValue(ACTOR.BLOOD);
        logger.info("id:{} name:{} height:{} blood:{}", id, name, height, blood);
        // ⇒ id:1 name:丹波哲郎 height:175 blood:O
        // ⇒ id:2 name:森田健作 height:175 blood:O
        // ⇒ id:3 name:加藤剛 height:173 blood:null
        // ⇒ id:4 name:島田陽子 height:171 blood:O
        // ⇒ id:5 name:山口果林 height:null blood:null
      }

      /*
       * join
       */
      Result<Record> resultJoin =
          create.select()
                .from(ACTOR.join(PREFECTURE).on(PREFECTURE.ID.equal(ACTOR.BIRTHPLACE_ID)))
                .limit(5)
                .fetch();

      for (Record r : resultJoin) {
        Integer id = r.getValue(ACTOR.ID);
        String name = r.getValue(ACTOR.NAME);
        Short height = r.getValue(ACTOR.HEIGHT);
        String blood = r.getValue(ACTOR.BLOOD);
        String prefname = r.getValue(PREFECTURE.NAME);
        logger.info("id:{} name:{} height:{} blood:{} pref:{}", id, name, height, blood, prefname);
        // ⇒ id:1 name:丹波哲郎 height:175 blood:O pref:東京都
        // ⇒ id:2 name:森田健作 height:175 blood:O pref:東京都
        // ⇒ id:3 name:加藤剛 height:173 blood:null pref:静岡県
        // ⇒ id:4 name:島田陽子 height:171 blood:O pref:熊本県
        // ⇒ id:5 name:山口果林 height:null blood:null pref:東京都
      }

      /*
       * select from
       */
      Result<ActorRecord> actorResult =
          create.selectFrom(ACTOR)
                .limit(5)
                .fetch();

      for (ActorRecord r : actorResult) {
        logger.info("id:{} name:{} height:{} blood:{}", r.getId(), r.getName(), r.getHeight(), r.getBlood());
        // ⇒ id:1 name:丹波哲郎 height:175 blood:O
        // ⇒ id:2 name:森田健作 height:175 blood:O
        // ⇒ id:3 name:加藤剛 height:173 blood:null
        // ⇒ id:4 name:島田陽子 height:171 blood:O
        // ⇒ id:5 name:山口果林 height:null blood:null
      }

    } catch (Exception e) {
      e.printStackTrace();
    }

  }

}

このようにsql文に近い形でコーディングすることができます。
ちなみにselect().from(ACTOR)selectFrom(ACTOR)の違いは下記の通りです。

from()はテーブル結合する場合に使用する記述方法です。

from
Result<Record> result = create.select().from(ACTOR.join(PREFECTURE).on(PREFECTURE.ID.equal(ACTOR.BIRTHPLACE_ID))).limit(5).fetch();

下記の例のように単一テーブルの場合でも使用できます。

from
Result<Record> result = create.select().from(ACTOR).limit(5).fetch();

selectFrom()は単一テーブルの場合に使用する記述方法です。
戻り値の型がActorRecordになるのでフィールド値の取り出しが楽になります。

selectFrom
Result<ActorRecord> actorResult = create.selectFrom(ACTOR).limit(5).fetch();

Settings

SettingsクラスでSQL文の組み立て方法や実行方法をカスタマイズできます。

Settings
Settings settings = new Settings();
settings.setExecuteLogging(true);  // jOOQのログ出力を行うか
settings.withRenderFormatted(true); // SQL文の出力を見易い形にフォーマットするか
settings.withRenderSchema(false); // SQL文にスキーマを出力するか
DSLContext create = using(conn, SQLDialect.MYSQL, settings);

デフォルトの設定を使う場合

DSLContext create = using(conn, SQLDialect.MYSQL);
key default description
renderSchema true Whether any schema name should be rendered at all.
renderMapping Configure render mapping for runtime schema / table rewriting in generated SQL.
renderNameStyle QUOTED Whether rendered schema, table, column names, etc should be quoted in rendered SQL, or transformed in any other way.
renderKeywordStyle LOWER Whether SQL keywords should be rendered with upper or lower case.
renderFormatted false Whether rendered SQL should be pretty-printed.
paramType INDEXED Whether rendered bind values should be rendered
statementType PREPARED_STATEMENT The type of statement that is to be executed.
executeLogging true When set to true, this will add jOOQ's default logging ExecuteListeners
executeWithOptimisticLocking false Whether store() and delete() methods should be executed with optimistic locking.
attachRecords true Whether fetched records should be attached to the fetching configuration.
updatablePrimaryKeys false Whether primary key values are deemed to be "updatable" in jOOQ

SQL文実行方法の確認

select文

全件取得

select
Result<Record> result =
    create.select()
          .from(ACTOR)
          .fetch();

result.stream().forEach(r->{
  logger.info("select, id:{} name:{} height:{} blood:{}", r.getValue(ACTOR.ID), r.getValue(ACTOR.NAME), r.getValue(ACTOR.BLOOD), r.getValue(ACTOR.HEIGHT));
  // ⇒ id:1 name:丹波哲郎 height:O blood:175
  // ⇒ id:2 name:森田健作 height:O blood:175
  // ⇒ id:3 name:加藤剛 height:null blood:173
  // ⇒ id:4 name:島田陽子 height:O blood:171
  // ⇒ id:5 name:山口果林 height:null blood:null
  // ⇒ id:6 name:佐分利信 height:null blood:null
  // ⇒ id:7 name:緒形拳 height:B blood:173
  // ⇒ id:8 name:松山政路 height:A blood:167
  // ⇒ id:9 name:加藤嘉 height:null blood:null
  // ⇒ id:10 name:菅井きん height:B blood:155
  // ⇒ id:11 name:笠智衆 height:null blood:null
  // ⇒ id:12 name:殿山泰司 height:null blood:null
  // ⇒ id:13 name:渥美清 height:A blood:173
});

実際に発行されるsql文を確認するには、fetch()の代わりにgetSQL()を使用します。

select
String sql = create.select().from(ACTOR).getSQL();

logger.info("sql:{}", sql);
// ⇒ select
// ⇒   `actor`.`id`,
// ⇒   `actor`.`name`,
// ⇒   `actor`.`height`,
// ⇒   `actor`.`blood`,
// ⇒   `actor`.`birthday`,
// ⇒   `actor`.`birthplace_id`,
// ⇒   `actor`.`update_at`
// ⇒ from `actor`

任意のフィールドを取得したい場合はselect()に取得するフィールドを列挙します。
この場合、戻り値の型はRecord4というようにフィールド数によって変わります。

select
Result<Record4<Integer, String, Short, String>> resultField =
    create.select(ACTOR.ID, ACTOR.NAME, ACTOR.HEIGHT, ACTOR.BLOOD)
          .from(ACTOR)
          .limit(5)
          .fetch();

resultField.stream().forEach(r->{
  logger.info("id:{} name:{} height:{} blood:{}", r.getValue(ACTOR.ID), r.getValue(ACTOR.NAME), r.getValue(ACTOR.HEIGHT), r.getValue(ACTOR.BLOOD));
  // ⇒ id:1 name:丹波哲郎 height:175 blood:O
  // ⇒ id:2 name:森田健作 height:175 blood:O
  // ⇒ id:3 name:加藤剛 height:173 blood:null
  // ⇒ id:4 name:島田陽子 height:171 blood:O
  // ⇒ id:5 name:山口果林 height:null blood:null
});

join

join
Result<Record> resultJoin1 =
    create.select()
          .from(ACTOR.join(PREFECTURE).on(PREFECTURE.ID.eq(ACTOR.BIRTHPLACE_ID)))
          .fetch();

別の記述方法もあります。

join
Result<Record> resultJoin2 =
    create.select()
          .from(ACTOR)
          .join(PREFECTURE).on(PREFECTURE.ID.eq(ACTOR.BIRTHPLACE_ID))
          .fetch();

where

where
Record result4 =
    create.select()
          .from(ACTOR)
          .join(PREFECTURE).on(ACTOR.BIRTHPLACE_ID.eq(PREFECTURE.ID))
          .where(ACTOR.ID.eq(1))
          .and(ACTOR.BLOOD.eq("O"))
          .fetchOne();

logger.info("id:{} name:{} height:{} blood:{}", result4.getValue(ACTOR.ID), result4.getValue(ACTOR.NAME), result4.getValue(ACTOR.HEIGHT), result4.getValue(ACTOR.BLOOD));
// ⇒ id:1 name:丹波哲郎 height:175 blood:O
logger.info("id:{} name:{}", result4.getValue(PREFECTURE.ID), result4.getValue(PREFECTURE.NAME));
// ⇒ id:13 name:東京都

Record型を任意のテーブルレコード型へマッピングすることができます。

mapping
ActorRecord actor = result4.into(ACTOR);

logger.info("id:{} name:{} height:{} blood:{}", actor.getId(), actor.getName(), actor.getHeight(), actor.getBlood());
// ⇒ id:1 name:丹波哲郎 height:175 blood:O

PrefectureRecord pref = result4.into(PREFECTURE);

logger.info("id:{} name:{}", pref.getId(), pref.getName());
// ⇒ id:13 name:東京都

like

like
Result<Record> resultLike =
    create.select()
          .from(ACTOR)
          .join(PREFECTURE).on(ACTOR.BIRTHPLACE_ID.eq(PREFECTURE.ID))
          .where(ACTOR.NAME.like("%山%"))
          .fetch();

resultLike.stream().forEach(r->{
  logger.info("like, id:{} name:{} height:{} blood:{}", r.getValue(ACTOR.ID), r.getValue(ACTOR.NAME), r.getValue(ACTOR.HEIGHT), r.getValue(ACTOR.BLOOD));
  // ⇒ id:5 name:山口果林 height:null blood:null
  // ⇒ id:8 name:松山政路 height:167 blood:A
  // ⇒ id:12 name:殿山泰司 height:null blood:null
});

is not null

isNotNull
Result<Record> resultIsNotNull =
    create.select()
          .from(ACTOR)
          .join(PREFECTURE).on(ACTOR.BIRTHPLACE_ID.eq(PREFECTURE.ID))
          .where(ACTOR.BLOOD.isNotNull())
          .fetch();

resultIsNotNull.stream().forEach(r->{
  logger.info("is not null, id:{} name:{} height:{} blood:{}", r.getValue(ACTOR.ID), r.getValue(ACTOR.NAME), r.getValue(ACTOR.HEIGHT), r.getValue(ACTOR.BLOOD));
  // ⇒ id:1 name:丹波哲郎 height:175 blood:O
  // ⇒ id:2 name:森田健作 height:175 blood:O
  // ⇒ id:4 name:島田陽子 height:171 blood:O
  // ⇒ id:7 name:緒形拳 height:173 blood:B
  // ⇒ id:8 name:松山政路 height:167 blood:A
  // ⇒ id:10 name:菅井きん height:155 blood:B
  // ⇒ id:13 name:渥美清 height:173 blood:A
});

group by

groupby
Result<Record2<String, Integer>> resultGroupBy =
    create.select(ACTOR.BLOOD, count())
          .from(ACTOR)
          .groupBy(ACTOR.BLOOD)
          .fetch();

resultGroupBy.stream().forEach(r->{
  logger.info("groupby, blood:{} count:{}", r.getValue(0), r.getValue(1));
  // ⇒ blood:null count:6
  // ⇒ blood:A count:2
  // ⇒ blood:B count:2
  // ⇒ blood:O count:3
});

having

having
Result<Record2<String, Integer>> resultHaving =
    create.select(ACTOR.BLOOD, count())
          .from(ACTOR)
          .groupBy(ACTOR.BLOOD)
          .having(count().eq(3))
          .fetch();

resultHaving.stream().forEach(r->{
  logger.info("having, blood:{} count:{}", r.getValue(0), r.getValue(1));
  // ⇒ blood:O count:3
});

order by

orderby
Result<Record> resultOrderby =
    create.select()
          .from(ACTOR)
          .orderBy(ACTOR.BIRTHDAY.asc().nullsLast())
          .limit(5)
          .fetch();

resultOrderby.stream().forEach(r->{
  logger.info("orderby, id:{} name:{} birthday:{}", r.getValue(ACTOR.ID), r.getValue(ACTOR.NAME), r.getValue(ACTOR.BIRTHDAY));
  // ⇒ id:11 name:笠智衆 birthday:1904-05-13
  // ⇒ id:6 name:佐分利信 birthday:1909-02-12
  // ⇒ id:9 name:加藤嘉 birthday:1913-01-12
  // ⇒ id:12 name:殿山泰司 birthday:1915-10-17
  // ⇒ id:1 name:丹波哲郎 birthday:1922-07-17
});

CASE式

case
Result<Record4<Integer, String, Short, String>> resultCase =
    create.select(ACTOR.ID, ACTOR.NAME, ACTOR.HEIGHT, decode().when(ACTOR.BLOOD.isNull(),"unknown").otherwise(ACTOR.BLOOD).as("blood"))
          .from(ACTOR)
          .fetch();

resultCase.stream().forEach(r->{
  logger.info("case, id:{} name:{} height:{} blood:{}", r.getValue(ACTOR.ID), r.getValue(ACTOR.NAME), r.getValue(ACTOR.HEIGHT), r.getValue("blood"));
  // ⇒ id:1 name:丹波哲郎 height:175 blood:O
  // ⇒ id:2 name:森田健作 height:175 blood:O
  // ⇒ id:3 name:加藤剛 height:173 blood:unknown
  // ⇒ id:4 name:島田陽子 height:171 blood:O
  // ⇒ id:5 name:山口果林 height:null blood:unknown
  // ⇒ id:6 name:佐分利信 height:null blood:unknown
  // ⇒ id:7 name:緒形拳 height:173 blood:B
  // ⇒ id:8 name:松山政路 height:167 blood:A
  // ⇒ id:9 name:加藤嘉 height:null blood:unknown
  // ⇒ id:10 name:菅井きん height:155 blood:B
  // ⇒ id:11 name:笠智衆 height:null blood:unknown
  // ⇒ id:12 name:殿山泰司 height:null blood:unknown
  // ⇒ id:13 name:渥美清 height:173 blood:A
});

union

union
Result<ActorRecord> resultUnion =
    create.selectFrom(ACTOR).where(ACTOR.ID.eq(1))
          .unionAll(
           selectFrom(ACTOR).where(ACTOR.ID.eq(2)))
          .fetch();

resultUnion.stream().forEach(r->{
  logger.info("union, id:{} name:{} birthday:{}", r.getValue(ACTOR.ID), r.getValue(ACTOR.NAME), r.getValue(ACTOR.BIRTHDAY));
  // ⇒ id:1 name:丹波哲郎 birthday:1922-07-17
  // ⇒ id:2 name:森田健作 birthday:1949-12-16
});

副問い合わせ

nested
Result<Record> nestedSelect =
    create.select()
          .from(ACTOR)
          .where(ACTOR.BIRTHPLACE_ID.in(
              create.select(PREFECTURE.ID)
                    .from(PREFECTURE)
                    .where(PREFECTURE.ID.in((short)12,(short)13,(short)14))))
          .fetch();

nestedSelect.stream().forEach(r->{
  logger.info("select, id:{} name:{} height:{} blood:{} birthplace_id:{}", r.getValue(ACTOR.ID), r.getValue(ACTOR.NAME), r.getValue(ACTOR.HEIGHT), r.getValue(ACTOR.BLOOD), r.getValue(ACTOR.BIRTHPLACE_ID));
  // ⇒ id:1 name:丹波哲郎 height:175 blood:O birthplace_id:13
  // ⇒ id:2 name:森田健作 height:175 blood:O birthplace_id:13
  // ⇒ id:5 name:山口果林 height:null blood:null birthplace_id:13
  // ⇒ id:7 name:緒形拳 height:173 blood:B birthplace_id:13
  // ⇒ id:8 name:松山政路 height:167 blood:A birthplace_id:13
  // ⇒ id:9 name:加藤嘉 height:null blood:null birthplace_id:13
  // ⇒ id:10 name:菅井きん height:155 blood:B birthplace_id:13
  // ⇒ id:13 name:渥美清 height:173 blood:A birthplace_id:13
});

結果をListで受け取る

list
List<String> nameList =
    create.select()
          .from(ACTOR)
          .limit(5)
          .fetch(ACTOR.NAME);

nameList.stream().forEach(logger::info);
// ⇒ 丹波哲郎
// ⇒ 森田健作
// ⇒ 加藤剛
// ⇒ 島田陽子
// ⇒ 山口果林

結果をMapで受け取る

fetchMapで指定した2つのフィールドをkey,valueとしてMapに格納します。

map
Map<Integer, String> idMap =
    create.select()
          .from(ACTOR)
          .limit(5)
          .fetchMap(ACTOR.ID, ACTOR.NAME);

idMap.forEach((key,value)->{
  logger.info("id:{} name:{}", key, value);
  // ⇒ id:1 name:丹波哲郎
  // ⇒ id:2 name:森田健作
  // ⇒ id:3 name:加藤剛
  // ⇒ id:4 name:島田陽子
  // ⇒ id:5 name:山口果林
});

intoMapで指定したフィールドをkey、テーブルレコード型がvalueになります。

map
Map<Integer, ActorRecord> recordMap =
    create.selectFrom(ACTOR)
          .limit(5)
          .fetch()
          .intoMap(ACTOR.ID);

recordMap.forEach((key,value)->{
  logger.info("id:{} name:{} height:{} blood:{}", key, value.getName(), value.getHeight(), value.getBlood());
  // ⇒ id:1 name:丹波哲郎 height:175 blood:O
  // ⇒ id:2 name:森田健作 height:175 blood:O
  // ⇒ id:3 name:加藤剛 height:173 blood:null
  // ⇒ id:4 name:島田陽子 height:171 blood:O
  // ⇒ id:5 name:山口果林 height:null blood:null
});

カーソル

cusor
Cursor<ActorRecord> cursor = null;
try {
  cursor = create.selectFrom(ACTOR).fetchLazy();
  while (cursor.hasNext()) {
    ActorRecord r = cursor.fetchOne();

    logger.info("union, id:{} name:{} birthday:{}", r.getValue(ACTOR.ID), r.getValue(ACTOR.NAME), r.getValue(ACTOR.BIRTHDAY));
    // ⇒ id:1 name:丹波哲郎 birthday:1922-07-17
    // ⇒ id:2 name:森田健作 birthday:1949-12-16
    // ⇒ id:3 name:加藤剛 birthday:1938-02-04
    // ⇒ id:4 name:島田陽子 birthday:1953-05-17
    // ⇒ id:5 name:山口果林 birthday:1947-05-10
  }
} finally {
  if (cursor != null) {
    cursor.close();
  }
}

json

json以外にもcsv,xml,html形式でフォーマットすることができます。

json
String json =
    create.select()
          .from(ACTOR)
          .limit(3)
          .fetch()
          .formatJSON();

logger.info("json:{}", json);
// ⇒{"fields":[{"name":"id","type":"INTEGER"},{"name":"name","type":"VARCHAR"},{"name":"height","type":"SMALLINT"},{"name":"blood","type":"VARCHAR"},{"name":"birthday","type":"DATE"},{"name":"birthplace_id","type":"SMALLINT"},{"name":"update_at","type":"TIMESTAMP"}],"records":[[1,"丹波哲郎",175,"O","1922-07-17",13,"2015-09-03 00:05:55.754675"],[2,"森田健作",175,"O","1949-12-16",13,"2015-09-03 00:05:55.754675"],[3,"加藤剛",173,null,"1938-02-04",22,"2015-09-03 00:05:55.754675"]]}

参考
4.3.3. The SELECT statement

Plain SQL

直接SQL文を記述して実行することができます。

select

この例はwhere句をplainなsql文で記述しています。

select
Result<Record> result =
    create.select()
          .from(ACTOR)
          .where("ACTOR.BLOOD = ?", "O")
          .fetch();

result.stream().forEach(r->{
  logger.info("select, id:{} name:{} height:{} blood:{}", r.getValue(ACTOR.ID), r.getValue(ACTOR.NAME), r.getValue(ACTOR.HEIGHT), r.getValue(ACTOR.BLOOD));
  // ⇒ id:1 name:丹波哲郎 height:175 blood:O
  // ⇒ id:2 name:森田健作 height:175 blood:O
  // ⇒ id:4 name:島田陽子 height:171 blood:O
});

ResultQuery

sql
ResultQuery<Record> resultQuery =
  create.resultQuery("SELECT * FROM actor WHERE actor.blood = 'B' LIMIT 5");
Result<Record> query = resultQuery.fetch();

query.stream().forEach(r->{
  logger.info("select, id:{} name:{} height:{} blood:{}", r.getValue(ACTOR.ID), r.getValue(ACTOR.NAME), r.getValue(ACTOR.HEIGHT), r.getValue(ACTOR.BLOOD));
  // ⇒ id:7 name:緒形拳 height:173 blood:B
  // ⇒ id:10 name:菅井きん height:155 blood:B
});

Query

query
Query query = create.query("UPDATE actor SET update_at = now() WHERE actor.BLOOD = 'B'");
int numOfQuery = query.execute();

logger.info("query:{}", numOfQuery);
// ⇒ 2

insert文

insertにもいくつかの方法があります。

insert

insert
int numOfInsert =
    create.insertInto(ACTOR, ACTOR.ID, ACTOR.NAME, ACTOR.HEIGHT, ACTOR.BLOOD, ACTOR.BIRTHDAY, ACTOR.BIRTHPLACE_ID)
          .values(14, "春川ますみ", null, "AB", DateParser.parseSQL("1935-11-10"), Short.valueOf("9"))
          .execute();

logger.info("insert:{}", numOfInsert);
// ⇒ insert:1

set

set
int numOfSet =
    create.insertInto(ACTOR)
          .set(ACTOR.ID, 15)
          .set(ACTOR.NAME, "村松英子")
          .set(ACTOR.HEIGHT, Short.valueOf("162"))
          .set(ACTOR.BLOOD, "B")
          .set(ACTOR.BIRTHDAY, DateParser.parseSQL("1938-03-31"))
          .set(ACTOR.BIRTHPLACE_ID, Short.valueOf("13"))
          .execute();

logger.info("set:{}", numOfSet);
// ⇒ set:1

returning

returning
ActorRecord actor =
    create.insertInto(ACTOR, ACTOR.ID, ACTOR.NAME, ACTOR.HEIGHT, ACTOR.BLOOD, ACTOR.BIRTHDAY, ACTOR.BIRTHPLACE_ID)
          .values(16, "野村昭子", Short.valueOf("157"), "A", DateParser.parseSQL("1927-01-02"), Short.valueOf("13"))
          .returning(ACTOR.ID, ACTOR.UPDATE_AT)
          .fetchOne();

logger.info("returning, id:{} updateAt:{}", actor.getId(), actor.getUpdateAt());
// ⇒ id:16 updateAt:2015-09-03 18:05:49.145236

batch

batch
int[] numOfBatch =
    create.batch(
      create.insertInto(ACTOR, ACTOR.ID, ACTOR.NAME).values(17,"穂積隆信"),
      create.insertInto(ACTOR, ACTOR.ID, ACTOR.NAME).values(18,"信欣三"),
      create.insertInto(ACTOR, ACTOR.ID, ACTOR.NAME).values(19,"浜村純")
    )
    .execute();

    for (int i=0; i<numOfBatch.length; i++) {
      logger.info("status:{}", i);
      // ⇒ status[0]:1
      // ⇒ status[1]:1
      // ⇒ status[2]:1
    }

参考
4.3.4.1. INSERT .. VALUES

update文

update

update
int numOfUpdate =
    create.update(ACTOR)
          .set(ACTOR.UPDATE_AT, new Timestamp(new Date().getTime()))
          .where(ACTOR.BLOOD.eq("O"))
          .execute();

logger.info("update:{}", numOfUpdate);
// ⇒ update:3

参考
4.3.5. The UPDATE statement

delete文

delete

delete
int numOfDelete =
    create.delete(ACTOR)
          .where(ACTOR.ID.greaterOrEqual(14))
          .execute();

logger.info("delete:{}", numOfDelete);
// ⇒ delete:6

参考
4.3.6. The DELETE statement

Transaction

transaction
package com.example.jooq;

import static com.example.jooq.db.Tables.*;
import static org.jooq.impl.DSL.*;

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

import org.jooq.DSLContext;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.SQLDialect;
import org.jooq.conf.Settings;
import org.jooq.exception.DataAccessException;
import org.jooq.impl.DSL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TransactionSample {
  private static final Logger logger = LoggerFactory.getLogger(TransactionSample.class);

  public static void main(String[] args) {

    String userName = "actor_user";
    String password = "actor_pass";
    String url = "jdbc:mysql://localhost:3306/actor_db";

    try (Connection conn = DriverManager.getConnection(url, userName, password)) {

      Settings settings = new Settings();
      settings.setExecuteLogging(true);
      settings.withRenderFormatted(true);

      DSLContext create = using(conn, SQLDialect.MYSQL, settings);

      try {

        create.transaction(transactional->{
          DSLContext ctx = DSL.using(transactional);

          ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.NAME, ACTOR.BIRTHDAY, ACTOR.BIRTHPLACE_ID)
             .values(20, "森三平太", DateParser.parseSQL("1927-11-15"), Short.valueOf("6"))
             .execute();

          ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.NAME, ACTOR.BIRTHDAY, ACTOR.BIRTHPLACE_ID)
             .values(21, "山谷初男", DateParser.parseSQL("1933-12-19"), Short.valueOf("5"))
             .execute();

          ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.NAME, ACTOR.BIRTHDAY, ACTOR.BIRTHPLACE_ID)
             .values(22, "加藤健一", DateParser.parseSQL("1945-10-31"), Short.valueOf("22"))
             .execute();
        });

      } catch (DataAccessException e) {
        logger.error("{}", e.getLocalizedMessage());
      }

      try {

        create.transaction(transactional->{
          DSLContext ctx = DSL.using(transactional);

          ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.NAME, ACTOR.BIRTHDAY, ACTOR.BIRTHPLACE_ID)
             .values(23, "松本克平", DateParser.parseSQL("1905-04-25"), Short.valueOf("20"))
             .execute();

          ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.NAME, ACTOR.BIRTHDAY, ACTOR.BIRTHPLACE_ID)
             .values(24, "山崎満", DateParser.parseSQL("1933-05-14"), Short.valueOf("1"))
             .execute();

          // 重複データ
          ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.NAME)
             .values(20, "森三平太")
             .execute();
        });

      } catch (DataAccessException e) {
        logger.error("{}", e.getLocalizedMessage());
        // ⇒ org.jooq.exception.DataAccessException: SQL [insert into `actor_db`.`actor` (
        // ⇒   `id`,
        // ⇒   `name`
        // ⇒ )
        // ⇒ values (
        // ⇒   ?,
        // ⇒   ?
        // ⇒ )]; Duplicate entry '20' for key 'PRIMARY'
      }

      Result<Record> result =
          create.select()
                .from(ACTOR)
                .where(ACTOR.ID.greaterOrEqual(20))
                .fetch();

      result.stream().forEach(r->{
        logger.info("select, id:{} name:{} height:{} blood:{}", r.getValue(ACTOR.ID), r.getValue(ACTOR.NAME), r.getValue(ACTOR.BLOOD), r.getValue(ACTOR.HEIGHT));
        // ⇒ id:20 name:森三平太 height:null blood:null
        // ⇒ id:21 name:山谷初男 height:null blood:null
        // ⇒ id:22 name:加藤健一 height:null blood:null
      });

    } catch (Exception e) {
      e.printStackTrace();
    }

  }
}

この例では1つ目のトランザクションは成功するので3件のインサートはコミットされます。
2つ目のトランザクションは3番目のinsertが例外を発生させるため、このトランザクション内のインサートはすべてロールバックされます。

結果として1つ目のトランザクションでインサートした3件だけがデータベースに登録されます。
ちなみにDataAccessExceptionは非検査例外なので通常はキャッチしません。

参考
5.14. Transaction management

メモ

sql-maven-plugin

goal

> mvn sql:execute
> mvn sql:execute -Ddb.creation.skip=false
> mvn sql:help -Ddetail=true -Dgoal=<goal-name>
help
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building jooq-example 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- sql-maven-plugin:1.5:help (default-cli) @ jooq-example ---
[INFO] org.codehaus.mojo:sql-maven-plugin:1.5

SQL Maven Plugin
  Execute SQL Statements

sql:execute
  Executes SQL against a database.

  Available parameters:

    autocommit (Default: false)
      Set to true to execute none-transactional SQL.
      Expression: ${autocommit}

    delimiter (Default: ;)
      Set the delimiter that separates SQL statements.
      Expression: ${delimiter}

    delimiterType (Default: normal)
      The delimiter type takes two values - 'normal' and 'row'. Normal means
      that any occurrence of the delimiter terminate the SQL command whereas
      with row, only a line containing just the delimiter is recognized as the
      end of the command.

      For example, set this to 'go' and delimiterType to 'row' for Sybase ASE or
      MS SQL Server.
      Expression: ${delimiterType}

    driver
      Database driver classname.
      Required: Yes
      Expression: ${driver}

    driverProperties
      Additional key=value pairs separated by comma to be passed into JDBC
      driver.
      Expression: ${driverProperties}

    enableAnonymousPassword (Default: false)
      Ignore the password and use anonymous access. This may be useful for
      databases like MySQL which do not allow empty password parameters in the
      connection initialization.

    enableBlockMode
      Deprecated. Use delimiterType instead.

      When true, the whole SQL content in sqlCommand, srcFiles and fileset are
      sent directly to JDBC in one SQL statement. This option is for executing
      database stored procedures/functions.
      Expression: ${enableBlockMode}

    enableFiltering (Default: false)
      Set to true if you want to filter the srcFiles using system-, user- and
      project properties
      Expression: ${enableFiltering}

    encoding (Default: ${project.build.sourceEncoding})
      Encoding to use when reading SQL statements from a file.
      Expression: ${encoding}

    escapeProcessing (Default: true)
      Argument to Statement.setEscapeProcessing If you want the driver to use
      regular SQL syntax then set this to false.
      Expression: ${escapeProcessing}

    fileset
      File(s) containing SQL statements to load. Only use a Fileset if you want
      to use ant-like filepatterns, otherwise use srcFiles. The order is based
      on a matching occurrence while scanning the directory (not the order of
      includes!).

    forceMojoExecution (Default: false)
      Setting this parameter to true will force the execution of this mojo, even
      if it would get skipped usually.
      Required: Yes
      Expression: ${forceOpenJpaExecution}

    keepFormat (Default: false)
      Keep the format of an SQL block.
      Expression: ${keepFormat}

    onError (Default: abort)
      Action to perform if an error is found. Possible values are abort and
      continue.
      Expression: ${onError}

    orderFile
      Set the order in which the SQL files will be executed. Possible values are
      ascending and descending. Any other value means that no sorting will be
      performed. Refers to fileset and srcFiles
      Expression: ${orderFile}

    outputDelimiter (Default: ,)
      The delimiter used to separate fields in the output when using
      printResultSet.

    outputFile
      Dump the SQL execution's output to a file.
      Default value is: System.out.

    password
      Database password. If not given, it will be looked up through
      settings.xml's server with ${settingsKey} as key.
      Expression: ${password}

    printResultSet (Default: false)
      Print SQL results.
      Expression: ${printResultSet}

    settingsKey
      Server's id in settings.xml to look up username and password. Defaults to
      ${url} if not given.
      Expression: ${settingsKey}

    skip (Default: false)
      When true, skip the execution.

    skipOnConnectionError (Default: false)
      Skip execution when there is an error obtaining a connection. This is a
      special case to support databases, such as embedded Derby, that can
      shutdown the database via the URL (i.e. shutdown=true).
      Expression: ${skipOnConnectionError}

    sqlCommand
      SQL input commands separated by ${delimiter}.
      Expression: ${sqlCommand}

    srcFiles
      List of files containing SQL statements to load.

    url
      Database URL.
      Required: Yes
      Expression: ${url}

    username
      Database username. If not given, it will be looked up through
      settings.xml's server with ${settingsKey} as key.
      Expression: ${username}

jooq-codegen-maven

goal

> mvn jooq-codegen:generate

Configure jOOQ's code generator

72
78
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
72
78