アラカン"BOKU"のITな日常

文系システムエンジニアの”BOKU”が勉強したこと、経験したこと、日々思うこと。

SQLライクなORマッパ。JOOQの使い方1回目。利用環境構築と動作確認。STS3.9.0+SpringBoot2.0

SpringBootの標準なので、DBアクセスにはJPAを使ってます。

 

でも、自分としては、S2JDBCみたいにSQLライクな書き方ができる方が好きなので、そういうのはないかなと思ってました。

 

そしたら、JOOQを使えば、かなりSQLライクに見通しのいい記述ができるらしいという情報がありました。

 

早速試してみます。

 

とりあえず、JPAはそのまま残して、併用しながら評価していく感じで使いたいと思ってます。

 

今回は、評価用にログインユーザテーブル(user)と、エンティティクラスを用意し、それを操作するサービスクラスを作って、JUNITで動かしてテストするというところまでやってみます。

 

まずは、JOOQの組み込みです。

 

pom.xmlに以下を追加するだけです。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jooq</artifactId>
</dependency>

 

ただ、これだけでは不十分です。

 

JOOQで処理を書くためには、対象のDBとマッピングされた、独自のフォーマットのエンティティクラスが必要です。

 

手で書くには面倒すぎるフォーマットです。

 

だから、DBからテーブル定義情報を読み込んで、それを作ってくれる自動生成ツールが必要です。

 

そのツール(Code-genarator等)は、pom.xmlの指定だけでははいりませんので、本家からOPENSOURCE版をダウンロードします。 

www.jooq.org

 

ダウンロードしたZIPファイルはどこかに解凍しておきます。

 

次にeclipseで作業用のプロジェクトを作成します。

 

このプロジェクトは、JOOQのCodeGeneratorで使うだけのものなので、ごく普通のJavaプロジェクトで良いです。

f:id:arakan_no_boku:20170729214554j:plain

 

名前は、「jooqgen」とでもしておきます。(何でもいいです)

 

次に設定用のXMLファイルを作成します。

 

以下をコピーして、赤字の部分を自分の環境にあわせて書き換えて「library.xml」という名前で保存してください。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<configuration>
<jdbc>
<driver>com.mysql.jdbc.Driver</driver>
<url>jdbc:mysql://localhost:3306/sampledb</url>
<user>sample</user>
<password>sample</password>

<properties>
<property><key>user</key><value>sample</value></property>
<property><key>password</key><value>sample</value></property>
</properties>
</jdbc>

<generator>
<database>
<name>org.jooq.util.mysql.MySQLDatabase</name>

<includes>.*</includes>

<excludes>
UNUSED_TABLE 
| PREFIX_.* 

| SECRET_SCHEMA\.SECRET_TABLE 
| SECRET_ROUTINE 
</excludes>

<inputSchema>sampledb</inputSchema>
</database>

<generate>

</generate>

<target>
<packageName>jp.boku.model.jooq</packageName>
<directory>C:\sts\workspace\jooqgen\src</directory>
</target>
</generator>
</configuration> 

 

上記は、MySQLMariaDBも同じ)専用です。

 

packageNameは、生成されたJAVAクラスを利用するプロジェクトのパッケージ構成にあわせてください。

 

directoryは、JAVAクラスを生成するフォルダのパスです。

 

ここに生成されたクラスファイルが書き出されます。

 

このXMLができれば、コマンドラインで実行することもできますが、せっかくSTSを使っているので、eclipseの機能をつかって実行できるようにします。

 

まず先程作成した「library.xml」を、JAVAプロジェクトのsrcフォルダ直下に置きます。

 

次に、外部JARの取り込みをします。

f:id:arakan_no_boku:20170729223309j:plain

 

必要なjarを先程ダウンロードしたフォルダを取り込み、あわせて、利用予定のプロジェクトからMySQLコネクタを取り込みます。

f:id:arakan_no_boku:20170729223335j:plain

 

JOOKで必要なファイルは以下の3つです。

  • jooq-3.9.5.jar,
  • jooq-meta-3.9.5.jar
  • jooq-codegen-3.9.5.jar

あと、MySQL-connector-java-5.1.4.jar があればいいです。

 

log4jももってくれば、ログ出力にも対応してくれますけど、今回はコンソールで見れたら困らないのでしてません。

 

今度はeclipseの実行構成を行います。

f:id:arakan_no_boku:20170729223635j:plain

 

STSはデフォルトでSpringBootアプリケーションの実行構成ダイアログが立ち上がる場合がありますが、そのときは、Javaアプリケーションを選択して新規ボタンを押します。

 

適当な名前をつけて、メインタブで、プロジェクト名とメイン・クラスを設定します。

f:id:arakan_no_boku:20170729224930j:plain

 

メインクラスは、かならず org.jooq.util.GenarationTool を検索して設定します。

 

引数タブを開いて、「/library.xml」を入力します。

f:id:arakan_no_boku:20170729225214j:plain

 

これで、適用を押して保存してから、実行します。

 

繰り返し実行する場合は、JAVAアプリケーションで、GenarationToolを選んで行うことになります。

 

エラーがなければ、指定したフォルダに以下のようにクラスが生成されます。(リフレッシュしないと表示されませんので注意を)

f:id:arakan_no_boku:20170729232901j:plain

 

この生成されたクラスを、SpringBootのプロジェクトにコピーすればテーブル定義データの用意はできました。

 

簡単な処理を書いて、JUNITでテストしてみます。

 

JOOQの構文を詳しく調べるのは、次回にして、今回は以下だけやってみます。

  • USERテーブルにユーザIDとパスワードを指定して挿入する。
  • USERテーブルから指定ユーザIDのレコードを取得し、CSVフォーマットで出力する。
  • USERテーブルでユーザIDを指定してレコードを削除する。 

 

サービスクラス(今回はUserJooqServiceという名前にしました)を作成します。

 

まず大枠から。

@Component
public class UserJooqService implements JooqService {
     @Autowired
     protected DSLContext jooq;

}

 

DSLContextクラスが、jooq本体なのでDIしておきます。

 

JppqServiceというinterfaceをimplementsしてますが、今のところ中身が空なので、別になくても大丈夫です。

 

@Componentは忘れずにつけてください。

 

これを忘れるとJUNIT実行時に「UnsatisfiedDependencyException」になりますので。

 

この中に処理を追加していきます。

 

まず、codeGenerationで生成したTableクラスをstaticインポートします。

import static jp.boku.model.jooq.Tables.USER;

 

JOOQは、このようにしてテーブル情報をstaticインポートして、定数のようにテーブル名やカラム名を指定します。

 

この辺、かなり独特ですね。

 

名前付けルールは、基本物理名がそのまま大文字になってます。

 

userテーブルなら、USER。

 

userテーブルのuidカラムは、USER.UID。

 

こんな感じで指定するわけです。

 

USERテーブルにユーザIDとパスワードを指定して挿入する処理。

@Transactional
public void createUser(final String uid,final String pass){
    // 引数の空白&NULLチェック
    if(!StringUtils.isEmpty(uid) && !StringUtils.isEmpty(pass)){

         // パスワード文字列を暗号化する。
         PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String encriptedPass = passwordEncoder.encode(pass);
        // すでに同じキーのレコードがあるかをチェックする
        boolean judge = jooq
             .selectFrom(USER)
             .where(USER.UID.eq(uid))
             .fetch()
             .isEmpty();
        // 空(レコードがない)場合はINSERTを実行する
        if(judge){
             jooq.insertInto(USER, USER.UID, USER.PASSWORD, USER.START_DATE, USER.END_DATE, USER.UPDATED, USER.INFO)
            .values(uid, encriptedPass, null, null, Timestamp.valueOf(LocalDateTime.now()), "Test")
            .execute();
     }
}

 

@Transactionalをつけておくと、例外発生時にロールバックします。

 

後は、コメントの通りです。

 

JOOQの構文が、SQLライクなので、何をやっているかはSQLがわかる人なら一目瞭然だというのが、よくわかります。

 

プラス、isEmpty()みたいな、痒いところに手が届く感じのメソッドがあるのが、なかなか好印象です。

 

ちょっと補足すると、「USER.UID.eq(uid)」の部分で、候補としては大量に「equals」もでてくるんですが、eqでないと警告になります。

 

ご注意を。

 

USERテーブルから指定ユーザIDのレコードを取得し、CSVフォーマットで出力する

public String fetchCsvFormat(final String uid){

// 引数が空白かNULLでないことをチェックする

     if(!StringUtils.isEmpty(uid)){

          // SELECTしてCSVフォーマットで出力する
          String csv = jooq
               .selectFrom(USER)
               .where(USER.UID.eq(uid))
               .fetch()
               .formatCSV();
          return csv;
     }else{
          return "";
    }
}

 

まんまですね。 

USERテーブルでユーザIDを指定してレコードを削除する

@Transactional
public void deleteUser(final String uid){

     boolean judge = true;

     // 引数が空白&NULLでないことをチェック

     if(!StringUtils.isEmpty(uid)){
          // レコードの存在チェック

          judge = jooq
             .selectFrom(USER)
             .where(USER.UID.eq(uid))
             .fetch()
             .isEmpty();
           // レコードがある場合はDELETEする
           if(!judge){
             jooq.deleteFrom(USER).where(USER.UID.eq(uid)).execute();
         }
    }
}

 

これもまんまですね。

 

例外がでるとロールバックしてしまうので、事前のチェックはきちんとした方がいいです。

 

これでServiceクラスができたので、それをテストするJUNITクラスを作成していきます。

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestUserService {

     @Autowired
      UserJooqService us;

      @Test
      public void testCreateUser() {
         us.createUser("sampleUser", "samplePassword");
          String res = us.fetchCsvFormat("sampleUser");
          assertFalse(StringUtils.isEmpty(res));
          us.deleteUser("sampleUser");
     }

}

 

さきほど、作成したメソッドを並べているだけですから、特に解説はしません。

 

注意すべき点としては、アノテーションのつけ忘れですね。

 

上記のアノテーションのどれかひとつでも忘れると、テストが例外を発生しておちます。

 

特に、@SpringBootTestのつけわすれは、「UnsatisfiedDependencyException」という例外になるんですが、これは様々な原因で、同じ例外がでるみたいで、知らないと、ちょっとはまります。

 

注意してください。

 

実行してみて、Greenでした。OKです。

 

CSVもこんな感じで出力されてます。

sampleUser,$2a$10$ZNeFqhEpOq7qVLkhJS.KUORqsPGNKonz/FomoTQhCb.Cpt2HHRM9O,"","",2017-07-30 16:03:34.0,Test

 

楽ですね。

 

とりあえず、使い所はありそうなので、JOOQもちょこちょこ使って記事にしていこうかなと思います。

 

JOOQ関連記事

arakan-pgm-ai.hatenablog.com

 

f:id:arakan_no_boku:20170725215801j:plain