概要
jOOQはHibernateやMyBatisなどと同じORMフレームワークです。
無償で使用できるOpen Source版と有償版(Express,Professional,Enterprice)があります。
有償版は商用データベース(Oracle,SQL Serverなど)も使え、Eメールによるサポートも受けられるようです。
エディションの違いは[Choose your jOOQ Edition] (http://www.jooq.org/download/)で詳しく説明されています。
この記事は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] (http://www.jooq.org/legal/licensing#databases)
環境
下記の環境で動作確認を行いました。
- Windows7 (64bit)
- Java 1.8.0_60
- jOOQ (Open Source) 3.6.2
- MySQL 5.6.25
- eclipse 4.4
- maven 3.3.3
参考
- [jOOQ in 7 easy steps] (http://www.jooq.org/doc/3.6/manual/getting-started/tutorials/jooq-in-7-steps/)
- [3.6 Documentation] (http://www.jooq.org/doc/3.6/manual-single-page/)
- [github - Java Object Oriented Querying] (https://github.com/jOOQ)
- [やっぱり SQL を書きたい派に送る jOOQ] (http://www.slideshare.net/yukung/j-ooq-shibuyajava9)
Github
ソースコードは[jooq-example] (https://github.com/rubytomato/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の編集
<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 ..."を選択します。
若しくは、
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"に指定しました。
<?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] (http://www.mojohaus.org/sql-maven-plugin/)
- [jooq-codegen-maven] (http://www.jooq.org/doc/3.6/manual-single-page/#code-generation)
sql-maven-plugin
このプラグインは[MojoHaus Project] (http://www.mojohaus.org/)で開発されており、任意のsql文を実行することができます。
jooq-codegen-maven
このプラグインはjOOQが開発しているコードジェネレータ(jooq-codegen)をmavenから使用できるようにしたものです。対象データベースのスキーマからjOOQで使用するモデルクラスやDAOクラスなどを生成することができます。
なお、コードジェネレータが生成するモデルクラスなどが有るとコーディングは楽になりますが、無くてもjOOQは使用できます。
sql-maven-plugin
sqlフォルダをsrc/main/resources下に作成し、そこへ実行する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;
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>
<packageName>com.example.jooq.db</packageName>
<directory>target/generated-sources/jooq/db</directory>
</target>
また、どのようなコードを生成するかはpluginの設定によります。
<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フォルダ下にコードが生成されていることを確認します。
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以下をビルドパスに追加します。
生成されたコード
下記のようなコードが生成されます。jOOQを使ったコーディングではこれ等のクラスを使用します。
Tables.java
このクラスは、jOOQで使用するテーブルを保持します。
jOOQを使用する各クラスで下記のようにstaticインポートするとコーディングが楽になります。
import static com.example.jooq.db.Tables.*;
/**
* 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テーブルに対応するモデルクラスです。
/**
* 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
/**
* 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として実行します。
<?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
を指定します。
設定ファイルの場所を指定します。
クラスパスに下記の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
jOOQの使い方
jOOQを使って様々な方法でSQL文を実行してみます。
チュートリアル
公式サイトの[jooq-in-7-steps] (http://www.jooq.org/doc/3.6/manual-single-page/#jooq-in-7-steps)というチュートリアルの内容をもとに使い方を確認します。
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()はテーブル結合する場合に使用する記述方法です。
Result<Record> result = create.select().from(ACTOR.join(PREFECTURE).on(PREFECTURE.ID.equal(ACTOR.BIRTHPLACE_ID))).limit(5).fetch();
下記の例のように単一テーブルの場合でも使用できます。
Result<Record> result = create.select().from(ACTOR).limit(5).fetch();
selectFrom()は単一テーブルの場合に使用する記述方法です。
戻り値の型がActorRecordになるのでフィールド値の取り出しが楽になります。
Result<ActorRecord> actorResult = create.selectFrom(ACTOR).limit(5).fetch();
Settings
SettingsクラスでSQL文の組み立て方法や実行方法をカスタマイズできます。
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文
全件取得
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()を使用します。
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というようにフィールド数によって変わります。
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
Result<Record> resultJoin1 =
create.select()
.from(ACTOR.join(PREFECTURE).on(PREFECTURE.ID.eq(ACTOR.BIRTHPLACE_ID)))
.fetch();
別の記述方法もあります。
Result<Record> resultJoin2 =
create.select()
.from(ACTOR)
.join(PREFECTURE).on(PREFECTURE.ID.eq(ACTOR.BIRTHPLACE_ID))
.fetch();
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型を任意のテーブルレコード型へマッピングすることができます。
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
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
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
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
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
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式
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
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
});
副問い合わせ
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<String> nameList =
create.select()
.from(ACTOR)
.limit(5)
.fetch(ACTOR.NAME);
nameList.stream().forEach(logger::info);
// ⇒ 丹波哲郎
// ⇒ 森田健作
// ⇒ 加藤剛
// ⇒ 島田陽子
// ⇒ 山口果林
結果をMapで受け取る
fetchMapで指定した2つのフィールドをkey,valueとして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<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
});
カーソル
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形式でフォーマットすることができます。
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] (http://www.jooq.org/doc/3.6/manual-single-page/#select-statement)
Plain SQL
直接SQL文を記述して実行することができます。
select
この例はwhere句をplainなsql文で記述しています。
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
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 = create.query("UPDATE actor SET update_at = now() WHERE actor.BLOOD = 'B'");
int numOfQuery = query.execute();
logger.info("query:{}", numOfQuery);
// ⇒ 2
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
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
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
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] (http://www.jooq.org/doc/3.6/manual-single-page/#insert-values)
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] (http://www.jooq.org/doc/3.6/manual-single-page/#update-statement)
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] (http://www.jooq.org/doc/3.6/manual-single-page/#delete-statement)
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] (http://www.jooq.org/doc/3.6/manual-single-page/#transaction-management)
メモ
sql-maven-plugin
goal
> mvn sql:execute
> mvn sql:execute -Ddb.creation.skip=false
> mvn sql:help -Ddetail=true -Dgoal=<goal-name>
[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] (http://www.jooq.org/doc/3.6/manual/code-generation/codegen-configuration/)