20190227のJavaに関する記事は7件です。

Queue / BlockingQueue / TransferQueueのおさらいメモ

概要

Collections FrameworkのメンバーのインターフェースQueueと、そのサブインターフェースBlockingQueueTransferQueueのおさらいメモです。
特にBlockingQueueの実装クラスLinkedBlockingQueueとTransferQueueの実装クラスLinkedTransferQueueについてサンプルコードを書いて確認しました。

インタフェースBlockingQueueやTransferQueueの実装クラスを利用すると、Producer-Consumerパターンの実装が簡単になります。

BlockingQueueのJavaDocより引用

BlockingQueueの実装は、主にプロデューサとコンシューマの間のキューで使用するように設計されていますが、加えてCollectionインタフェースもサポートします。

BlockingQueueは、複数のプロデューサおよび複数のコンシューマで安全に使用できることに注意してください。

環境

  • Windows 10 Professional
  • OpenJDK 11.0.2

参考

おさらい

インタフェースQueue<E>

導入バージョン : 1.5

基本的なCollection操作に加えて、キューは追加の挿入、抽出および検査操作を提供します。これらのメソッドにはそれぞれ、2つの形式があります。1つは操作が失敗したときに例外をスローし、もう1つは特殊な値(操作に応じてnullまたはfalseのいずれか)を返します。

キュー操作メソッド(失敗時に例外をスロー)

操作 メソッドのシグネチャ 失敗時にスローする例外
挿入 boolean add​(E e) IllegalStateException
取得・削除 E remove() NoSuchElementException
取得 E element() NoSuchElementException

add
キューに要素を追加した場合はtrue、使用可能な空き領域がない場合はIllegalStateExceptionをスロー
remove
キューの先頭を取得および削除、キューが空の場合はNoSuchElementExceptionをスロー
element
キューの先頭を取得、キューが空の場合はNoSuchElementExceptionをスロー

キュー操作メソッド(特殊な値を返す)

操作 メソッドのシグネチャ 返される値
挿入 boolean offer​(E e) キューに追加できなかった場合はfalse
取得・削除 E poll() キューが空の場合はnull
取得 E peek() キューが空の場合はnull

offer
キューに要素を追加した場合はtrue、それ以外はfalse
poll
キューの先頭を取得および削除、キューが空の場合はnullを返す
peek
キューの先頭を取得、キューが空の場合はnullを返す

インタフェースBlockingQueue<E>

導入バージョン : 1.5

要素の取得時にキューが空でなくなるまで待機したり、要素の格納時にキュー内に空きが生じるまで待機する操作を追加でサポートしたりするQueueです。

ブロッキングする操作

操作 メソッドのシグネチャ
挿入 void put​(E e) throws InterruptedException
取得・削除 E take() throws InterruptedException
取得 -

put
キューに要素を追加、キューに空きが生じるまで待機、待機中に割込みが発生した場合はInterruptedExceptionをスロー
take
キューの先頭を取得および削除、要素が取得できるまで待機、待機中に割込みが発生した場合はInterruptedExceptionをスロー

タイムアウトする操作

操作 メソッドのシグネチャ
挿入 boolean offer​(E e, long timeout, TimeUnit unit) throws InterruptedException
取得・削除 E poll​(long timeout, TimeUnit unit) throws InterruptedException
取得 -

offer
キューを要素に挿入、指定された待機時間までキューに空きが生じるのを待機
poll
キューの先頭を取得および削除、指定された待機時間まで要素が利用可能になるのを待機

キューの空き容量を確認

操作 メソッドのシグネチャ
空き容量の確認 int remainingCapacity()

remainingCapacity
キューがブロックせずに受け入れることができる追加要素の数を返します。組込み制限が存在しない場合はInteger.MAX_VALUEを返します。

クラスLinkedBlockingQueue<E>

導入バージョン : 1.5

リンク・ノードに基づく、オプションで制限付きになるブロッキング・キューです。 このキューはFIFO (先入れ先出し)で要素を順序付けします。 このキューの先頭は、キューに入っていた時間がもっとも長い要素です。

クラスPriorityBlockingQueue<E>

導入バージョン : 1.5

クラスPriorityQueueと同じ順序付けルールを使用するとともにブロッキング取得オペレーションを提供する、制限なしのブロッキング・キューです。

コンストラクタ

コンストラクタにキューの順序付けを行うコンパレータを渡します。

public PriorityBlockingQueue(int initialCapacity,
                             Comparator<? super E> comparator)
public PriorityBlockingQueue(Collection<? extends E> c)

インタフェースTransferQueue<E>

導入バージョン : 1.7

コンシューマが要素を受け取るまでプロデューサが待機するBlockingQueue。

ブロッキングする操作

操作 メソッドのシグネチャ
挿入 void transfer​(E e) throws InterruptedException

transfer
要素の受信を待機しているコンシューマがあれば直ちに転送、それ以外の場合はコンシューマに受信されるまで待機

タイムアウトしない操作

操作 メソッドのシグネチャ
挿入 boolean tryTransfer​(E e)

tryTransfer
要素の受信を待機しているコンシューマがあれば直ちに転送、それ以外の場合はfalseを返す

タイムアウトする操作

操作 メソッドのシグネチャ
挿入 boolean tryTransfer​(E e, long timeout, TimeUnit unit) throws InterruptedException

tryTransfer
要素の受信を待機しているコンシューマがあれば直ちに転送、それ以外の場合は指定された待機時間までコンシューマに受信されるのを待機、指定された待機時間が経過した場合はfalseを返す

待機するコンシューマを確認

操作 メソッドのシグネチャ
待機コンシューマの確認 boolean hasWaitingConsumer()
待機コンシューマの確認 int getWaitingConsumerCount()

hasWaitingConsumer
BlockingQueue.takeまたはpollで要素の受信を待機しているコンシューマが1つ以上あればtrueを返す
getWaitingConsumerCount
BlockingQueue.takeまたはpollで要素の受信を待機しているコンシューマの推定数を返す

クラスLinkedTransferQueue<E>

導入バージョン : 1.7

リンク・ノードに基づく、制限なしのTransferQueueです。 このキューは、指定された任意のプロデューサに関して、FIFO (先入れ先出し)で要素を順序付けします。 キューの先頭は、特定のプロデューサに関して、もっとも長い時間キューに入っていた要素です。

サンプルコード

LinkedBlockingQueueのサンプルコード

このサンプルは1プロデューサーがBlockingQueueに定期的に要素(このサンプルではInteger型の連番)を追加し、3コンシューマが定期的にBlockingQueueから要素を取り出すというものです。
無期限に実行するのではなくプロデューサーがキューに100回要素を追加したら終了するようになっています。

また、この記事の主題とは直接関係ありませんが、プロデューサー・コンシューマの実装にはScheduledExecutorServiceを利用しています。

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class BlockingDemo {

    private final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);
    private final ScheduledExecutorService producer = Executors.newSingleThreadScheduledExecutor();
    private final ScheduledExecutorService consumer = Executors.newScheduledThreadPool(3);

    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSS");

    public static void main(String[] args) throws Exception {
        BlockingDemo demo = new BlockingDemo();
        demo.execute();
    }

    void execute() throws Exception {
        println("main start");

        CountDownLatch doneSignal = new CountDownLatch(100);

        // 2秒後に起動、以後1秒間隔で実行
        println("create producer task");
        producer.scheduleAtFixedRate(new ProducerTask(queue, doneSignal), 2, 1, TimeUnit.SECONDS);

        println("create consumer task");
        consumer.scheduleAtFixedRate(new ConsumerTask(queue, "1"), 20, 2, TimeUnit.SECONDS);
        consumer.scheduleAtFixedRate(new ConsumerTask(queue, "2"), 20, 3, TimeUnit.SECONDS);
        consumer.scheduleAtFixedRate(new ConsumerTask(queue, "3"), 20, 3, TimeUnit.SECONDS);

        doneSignal.await();

        shutdown(producer);
        shutdown(consumer);

        println("main end");
    }

    class ProducerTask implements Runnable {
        private final BlockingQueue<Integer> queue;
        private final CountDownLatch doneSignal;
        private final AtomicInteger counter = new AtomicInteger(0);
        private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSS");

        ProducerTask(BlockingQueue<Integer> queue, CountDownLatch doneSignal) {
            this.queue = queue;
            this.doneSignal = doneSignal;
        }

        @Override
        public void run() {
            try {
                Integer e = Integer.valueOf(counter.incrementAndGet());
                queue.put(e);
                System.out.println(String.format("[%s] producer -> [%3d]", formatter.format(LocalDateTime.now()), e));
                doneSignal.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    class ConsumerTask implements Runnable {
        private final BlockingQueue<Integer> queue;
        private final String name;
        private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSS");

        ConsumerTask(BlockingQueue<Integer> queue, String name) {
            this.queue = queue;
            this.name = name;
        }

        @Override
        public void run() {
            try {
                //Integer e = queue.take();
                Integer e = queue.poll(1, TimeUnit.SECONDS);
                if (e != null) {
                    System.out.println(String.format("[%s]             [%3d] <- consumer(%s)", formatter.format(LocalDateTime.now()), e, name));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    void shutdown(ScheduledExecutorService service) {
        println("shutdown start");

        // Disable new tasks from being submitted
        service.shutdown();
        try {
            // Wait a while for existing tasks to terminate
            if (!service.awaitTermination(60, TimeUnit.SECONDS)) {
                // Cancel currently executing tasks
                service.shutdownNow();
                // Wait a while for tasks to respond to being cancelled
                if (!service.awaitTermination(60, TimeUnit.SECONDS)) {
                    println("Pool did not terminate");
                }
            }
        } catch (InterruptedException e) {
            System.err.println(e);
            // (Re-)Cancel if current thread also interrupted
            service.shutdownNow();
            // Preserve interrupt status
            Thread.currentThread().interrupt();
        }

        println("shutdown end");
    }

    void println(String message) {
        System.out.println(String.format("[%s] %s", formatter.format(LocalDateTime.now()), message));
    }

}

コンストラクタ

コンストラクタでキューの容量を決めることができます。
このサンプルではキューに追加できる要素の最大数を10に設定しています。

private final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);

容量を明示しないときはInteger.MAX_VALUEが指定されたのと同じになります。

private final BlockingQueue<String> queue = new LinkedBlockingQueue<String>();

キューに要素を挿入する

キューに空きができるまで無期限にブロッキングするputメソッドを利用しています。
putメソッドのブロッキング中に割り込みが入るとInterruptedExceptionが発生します。

try {
    Integer e = Integer.valueOf(counter.incrementAndGet());
    queue.put(e);
    doneSignal.countDown();
} catch (InterruptedException e) {
    e.printStackTrace();
}

キューから要素を取り出す

キューから要素を取り出せるまで1秒間待機するpollメソッドを利用しています。コメントアウトしていますが、takeメソッドを使うと取り出せるまで無期限にブロッキングします。

try {
    //Integer e = queue.take();
    Integer e = queue.poll(1, TimeUnit.SECONDS);
    if (e != null) {
        // do something
    }
} catch (InterruptedException e) {
    e.printStackTrace();
}

LinkedTransferQueueのサンプルコード

このサンプルは1プロデューサーがTransferQueueに定期的に要素(このサンプルではInteger型の連番)を追加し、3コンシューマが定期的にTransferQueueから要素を取り出すというものです。
このサンプルもプロデューサーがキューに100回要素を追加したら終了するようになっています。

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TransferQueue;
import java.util.concurrent.atomic.AtomicInteger;

public class TransferDemo {

    private final TransferQueue<Integer> queue = new LinkedTransferQueue<>();
    private final ScheduledExecutorService producer = Executors.newSingleThreadScheduledExecutor();
    private final ScheduledExecutorService consumer = Executors.newScheduledThreadPool(3);

    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSS");

    public static void main(String[] args) throws Exception {
        TransferDemo demo = new TransferDemo();
        demo.execute();
    }

    void execute() throws Exception {
        println("main start");

        CountDownLatch doneSignal = new CountDownLatch(100);

        println("create producer task");
        producer.scheduleAtFixedRate(new ProducerTask(queue, doneSignal), 2, 1, TimeUnit.SECONDS);

        println("create consumer task");
        consumer.scheduleAtFixedRate(new ConsumerTask(queue, "1"), 10, 3, TimeUnit.SECONDS);
        consumer.scheduleAtFixedRate(new ConsumerTask(queue, "2"), 10, 3, TimeUnit.SECONDS);
        consumer.scheduleAtFixedRate(new ConsumerTask(queue, "3"), 10, 3, TimeUnit.SECONDS);

        doneSignal.await();

        shutdown(producer);
        shutdown(consumer);

        println("main stop");
    }

    class ProducerTask implements Runnable {
        private final TransferQueue<Integer> queue;
        private final CountDownLatch doneSignal;
        private final AtomicInteger counter = new AtomicInteger(0);
        private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSS");

        ProducerTask(TransferQueue<Integer> queue, CountDownLatch doneSignal) {
            this.queue = queue;
            this.doneSignal = doneSignal;
        }

        @Override
        public void run() {
            try {
                Integer e = Integer.valueOf(counter.incrementAndGet());
                queue.transfer(e);
                System.out.println(String.format("[%s] producer -> [%3d]", formatter.format(LocalDateTime.now()), e));
                doneSignal.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    class ConsumerTask implements Runnable {
        private final TransferQueue<Integer> queue;
        private final String name;
        private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSS");

        ConsumerTask(TransferQueue<Integer> queue, String name) {
            this.queue = queue;
            this.name = name;
        }

        @Override
        public void run() {
            try {
                // Integer e = queue.take();
                Integer e = queue.poll(1, TimeUnit.SECONDS);
                if (e != null) {
                    System.out.println(String.format("[%s]             [%3d] <- consumer(%s)", formatter.format(LocalDateTime.now()), e, name));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    void shutdown(ScheduledExecutorService service) {
        // Disable new tasks from being submitted
        service.shutdown();
        try {
            // Wait a while for existing tasks to terminate
            if (!service.awaitTermination(60, TimeUnit.SECONDS)) {
                // Cancel currently executing tasks
                service.shutdownNow();
                // Wait a while for tasks to respond to being cancelled
                if (!service.awaitTermination(60, TimeUnit.SECONDS)) {
                    System.err.println("Pool did not terminate");
                }
            }
        } catch (InterruptedException e) {
            System.err.println(e);
            // (Re-)Cancel if current thread also interrupted
            service.shutdownNow();
            // Preserve interrupt status
            Thread.currentThread().interrupt();
        }
        System.out.println("shutdown");
    }

    void println(String message) {
        System.out.println(String.format("[%s] %s", formatter.format(LocalDateTime.now()), message));
    }

}

コンストラクタ

容量に制限はありません。

private final TransferQueue<Integer> queue = new LinkedTransferQueue<>();

キューに要素を挿入する

要素がコンシューマに受信されるまで無期限にブロッキングするtransferメソッドを利用しています。
transferメソッドのブロッキング中に割り込みが入るとInterruptedExceptionが発生します。

try {
    Integer e = Integer.valueOf(counter.incrementAndGet());
    queue.transfer(e);
    doneSignal.countDown();
} catch (InterruptedException e) {
    e.printStackTrace();
}

キューから要素を取り出す

キューから要素を取り出せるまで1秒間待機するpollメソッドを利用しています。コメントアウトしていますが、takeメソッドを使うと取り出せるまで無期限にブロッキングします。

try {
    // Integer e = queue.take();
    Integer e = queue.poll(1, TimeUnit.SECONDS);
    if (e != null) {
        // do something
    }
} catch (InterruptedException e) {
    e.printStackTrace();
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaでOCR(画像から文字認識)

やること

OSSのtess4jを利用して画像からテキストを取得する

Maven

mvnrepositoryからPOM.xmlへコピペ

<dependency>
    <groupId>net.sourceforge.tess4j</groupId>
    <artifactId>tess4j</artifactId>
    <version>4.3.1</version>
</dependency>

tess4j-4.3.1.jarがDLされる
キャプチャ.PNG

Maven使えない場合はここから

日本語認識ファイル

GitHubのリポジトリから日本語の認識ファイル(jpn.traineddata)を取得

ソース

OcrTrial.java
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import net.sourceforge.tess4j.ITesseract;
import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;

public class OcrTrial {
    public static void main(String[] args) throws IOException, TesseractException {
        // 画像を読み込む
        File file = new File("C:\\work\\INPUT.JPG");
        BufferedImage img = ImageIO.read(file);

        ITesseract tesseract = new Tesseract();
        tesseract.setDatapath("C:\\work"); // 言語ファイル(jpn.traineddata))の場所を指定
        tesseract.setLanguage("jpn"); // 解析言語は「日本語」を指定

        // 解析
        String str = tesseract.doOCR(img);

        // 結果
        System.out.println(str);
    }
}

INPUTにした画像ファイル

INPUT.JPG

出力された結果

キャプチャ.JPG

まとめ

間違いはここくらい
〇(ピクトグラム)
× ( ピ ビ ク ト グ ラ ム )

はっきり文字として判別できる画像であれば認識率は高そう

次回

  • 色々な画像を試してみる
  • グレースケール
  • Class Tesseractの関数を理解し使ってみる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java Swingの使い方

目次 Swing Java

・私のブログに書いているSwingの使い方です。

・入門の位置づけですが、JTableのセルレンダラ、セルエディタくらいの内容はあります。

・みなさんの参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DBFlute 外だしsql使用せず、テーブル結合方法

はじめに

皆さん、はじめまして。本連載を担当する呉(WU)と申します。

代表的な3つのO/Rマッピングツール(iBATIS、Torque、Hibernate)がありますが、
今回はO/RマッピングとしてのDBFluteを紹介させていただきます。
※O/Rマッピングとは、「オブジェクト」と「リレーショナルデータベース」をマッピング(対応付け)することです。

なぜ、DBFluteの外だしSQLの利用をなるべく避けたい?
以下2点の理由:
①SQL文がソースコードの様々な場所に記述されていると、修正漏れや修正間違いといった問題を常に意識しながら開発しなければなりません。
②O/Rマッピングは実装時のデータベース操作にかかわる煩雑な作業を軽減し、「ミスマッチ」を解決してくれます。

課題背景

DBFluteのホームページがありましたが、知識がバラバラに点在し、現場の仕事を利用するためには、設定ファイル(additionalforeignkey.dfprop)とJavaソースの書き方を整理しなければならないです。

テーブル結合方法説明

この記事ではDBFlute-1.0.5NとJAVA7を使っています。

DBFluteの基本仕組み

DBFluteのO/Rマッピングの大まかな仕組み
(1)DBFluteにおける自動生成
DBFluteは、自動生成されたクラスがあって初めて動作するO/Rマッピングです。
(2)JAVA実装
  Behavior 全てのDBアクセスの処理を司る
  ConditionBean 検索条件を組み立てる
詳細は実装マニュアルをご参照ください。

2つ以上のテーブル間が外部キーも紐付けていない、業務上が結合したい場合には以下の方法で対応します。

例:

利用テーブル

TableA TableB TableC
A_column_1 B_column_1 C_column_1
A_column_2 B_column_1 C_column_1

※上記はTableAとTableBとTableCがすべて外部キーが紐付けていないことが想定する。

 下記の設定ファイルはdbflute/XXX/dfprop/additionalforeignkey.dfpropを指すこと。


1、期待実行結果SQL(TableAとTableB結合):

SELECT *
FROM TableA A
LEFT JOIN TableB B
ON A.A_column_1 = B.B_column_1

設定ファイル:

; FK_TableA_TO_TableB_BY_ID = map:{
; localTableName = TableA ; foreignTableName = TableB
; localColumnName = A_column_1 ; foreignColumnName = B_column_1
; fixedSuffix = ByID
}

JAVA:
//TableA情報の取得
TableACB cb = tableABhv.newMyConditionBean();
cb.setupSelect_TableBById();


2、期待実行結果SQL(TableA・TableBとTableB・TableC結合):

SELECT *
FROM TableA A
INNER JOIN TableB B
ON A.A_column_1 = B.B_column_1
INNER JOIN TableC C
ON B.B_column_1 = C.C_column_1

設定ファイル:

; FK_TableB_TO_TableC_BY_ID = map:{
; localTableName = TableB ; foreignTableName = TableC
; localColumnName = B_column_1 ; foreignColumnName = C_column_1
; fixedSuffix = ById
}

; FK_TableA_TO_TableB_BY_ID = map:{
; localTableName = TableA ; foreignTableName = TableB
; localColumnName = A_column_1 ; foreignColumnName = B_column_1
; fixedSuffix = ById
}

JAVA:
//TableA情報の取得
TableACB cb = tableABhv.newMyConditionBean();
cb.setupSelect_TableBById();
cb.setupSelect_TableBById().join();
cb.setupSelect_TableBById().withTableCBId();
cb.setupSelect_TableBById().withTableCBId().join();


3、期待実行結果SQL(TableA・TableBとTableB・TableCとTableA・TableC結合):

SELECT *
FROM TableA A
LEFT JOIN TableB B
ON A.A_column_1 = B.B_column_1
LEFT JOIN TableC C
ON B.B_column_1 = C.C_column_1
AND A.A_column_2 = C.C_column_2

設定ファイル:

; FK_TableA_TO_TableB_BY_ID = map:{
; localTableName = TableA ; foreignTableName = TableB
; localColumnName = A_column_1 ; foreignColumnName = B_column_1
; fixedSuffix = ById
}

; FK_TableA_TO_TableC_BY_ID = map:{
; localTableName = TableA ; foreignTableName = TableC
; localColumnName = A_column_2 ; foreignColumnName = C_column_2
; fixedCondition = $$foreignAlias$$.C_column_1 = $$over($localTable.tableBById)$$.B_column_1
; fixedSuffix = ById
}

JAVA:
//TableA情報の取得
TableACB cb = tableABhv.newMyConditionBean();
cb.setupSelect_TableBById();
cb.setupSelect_TableCById();


4、実行結果SQL(fixedConditionで相関サブクエリ):

SELECT *
FROM TableA A
INNER JOIN TableB B
ON A.A_column_1 = B.B_column_1
INNER JOIN TableC C
ON B.B_column_1 = C.C_column_1
AND A.A_column_2 = C.C_column_2
INNER JOIN
(SELECT TC.C_column_1
,MAX(CAST(TC.C_column_2 AS INT)) AS VER
FROM TableC TC
GROUP BY
TC.C_column_1
TC.C_column_2
) C_0
ON C_0.C_column_1 = C.C_column_1

設定ファイル:

; FK_TableA_TO_TableB_BY_ID = map:{
; localTableName = TableA ; foreignTableName = TableB
; localColumnName = A_column_1 ; foreignColumnName = B_column_1
; fixedSuffix = ById
}

; FK_TableA_TO_TableC_BY_ID = map:{
; localTableName = TableA ; foreignTableName = TableC
; localColumnName = A_column_2 ; foreignColumnName = C_column_2
; fixedCondition = $$foreignAlias$$.C_column_1 = $$over($localTable.tableBById)$$.B_column_1
INNER JOIN
(SELECT TC.C_column_1
,MAX(CAST(TC.C_column_2 AS INT)) AS VER
FROM TableC TC
GROUP BY
TC.C_column_1
TC.C_column_2
) C_0
ON C_0.C_column_1 = $$foreignAlias$$.C_column_1
; fixedSuffix = ById
}

JAVA:

//TableA情報の取得
TableACB cb = tableABhv.newMyConditionBean();
cb.setupSelect_TableBById();
cb.setupSelect_TableBById().join();
cb.setupSelect_TableCById();
cb.setupSelect_TableCById().join();


設定ファイルの用語説明

上記に表したマックは以下の意味合いで、理解した上で活用していくと思います。

$$foreignAlias$$:参照先テーブルのエリアス
$$localAlias$$:参照元テーブルのエリアス
$$over([テーブル名].[リレーション名])$$:別リレーションのテーブルのエリアス
$$over($localTable.[リレーション名])$$:LocalテーブルのForeignテーブル
$$over($foreignTable.[リレーション名])$$:ForeignテーブルのForeignテーブル


まとめ

BDfluteの設定ファイルとJAVAソースをペアーで組み合わせると、外だしsql使用せず、すべてのテーブル結合が可能です。
今回の説明は現場よく利用している複数テーブルの結合する場合に対応方法ですが、実際にはまたいろいろ細かいのノウハウと思っていますが、次回はJAVA側の実装するノウハウを説明します。


参考資料

~現場指向O/Rマッパー~
http://dbflute.seasar.org/

additionalForeignKeyMapとは?
http://dbflute.seasar.org/ja/manual/reference/dfprop/additionalforeignkey/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Git BashでSDKMAN!を使う

TL;DR

Git for Windows に付属しているGit Bash(MINGW64)やMSYS2などを利用する際は、以下を .bashrc などに追加することでSDKMAN!の利用が可能になります。

.bashrc
export MSYS=winsymlinks:lnk

インストール自体は以下の通りなので割愛。

https://sdkman.io/install

蛇足

なぜSDKMAN!を使おうとしたのか

JDK、様々なベンダーやコミュニティがビルドし、提供されるようになってきているので、「気軽に切り替えられるようにしておきたい」と思うと思います。(思いますよね?)

上記のようなことを行う場合、JDKなどのバージョンを管理するツールが必要になってきます。
JVM系ではSDKMAN!というツールがあるので、これを利用します。

SDKMAN!を利用してインストールできるSDK(と書かれているので表記。言語、ツール、アプリケーションフレームワークなど様々なものがインストール可能)は以下を参照してください。

Git BashでSDKMAN!を利用する際の課題

以下を見るとGit Bashでふつうに利用できそうなのですが、JDKのダウンロードは出来るものの、デフォルトに設定しようとするとエラーになってしまいます。

https://sdkman.io/install

$ sdk default java 8.0.202.hs-adpt
SDKMAN: No update needed. Using existing candidates cache: ant,asciidoctorj,bpipe,ceylon,crash,cuba,cxf,gaiden,glide,gradle,grails,groovy,groovyserv,infrastructor,java,jbake,kotlin,kscript,lazybones,leiningen,maven,micronaut,sbt,scala,spark,springboot,sshoogr,vertx,visualvm
Not refreshing version cache now...
Validate java 8.0.202.hs-adpt for MINGW64_NT-10.0: valid
Validation URL: https://api.sdkman.io/2/candidates/validate/java/8.0.202.hs-adpt/mingw64_nt-10.0
rm: cannot remove '/c/Users/tenten0213/.sdkman/candidates/java/current': Is a directory
ln: failed to create symbolic link '/c/Users/tenten0213/.sdkman/candidates/java/current/8.0.202.hs-adpt': File exists

↑はデバッグモードを有効にしています。( ~/.sdkman/etc/configsdkman_debug_modetrue に)

コードを見てみると、以下の rm -f している箇所でエラーになっているようです。

https://github.com/sdkman/sdkman-cli/blob/b63ee15c8885b89ea09dee4f965fbd48167398d1/src/main/bash/sdkman-path-helpers.sh#L85

なぜエラーになるのかというと、冒頭の設定をしていない場合、 ln -s ではシンボリックリンクは作成されず、コピーが作成されます。シンボリックリンクではなくディレクトリになるので、 rm -f でエラーになってしまいます。

…ということで、冒頭に戻り MSYS=winsymlinks:lnk とすることでコピーではなくシンボリックリンク(Windowsのショートカット)が作成されるようになり、無事利用できるようになりました。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】Arrays.asList()で注意すべき点

はじめに

久々にJavaを使った時にArrays.asList()で少々嵌ったので、Arrays.asList()の注意点について備忘録として書き残しておきます。

Arrays.asList()とは

  • 配列を操作するためのユーティリティクラスであるArraysクラスで定義されているメソッド。
  • ざっくり言うと「引数で指定した配列をリストとして返す」というメソッド。
String[] words = {"abc","def","ghi"};
List<String> list = Arrays.asList(words);

Arrays.asList()の注意点

1. 戻り値のリストの長さが固定

指定された配列に連動する固定サイズのリストを返します。

JavaのAPIには、上記のように書かれています。
つまり、asListメソッドの戻り値がリストであっても、リストの長さが固定されているということです。

例えば、asListメソッドの戻り値に対してaddメソッドやremoveメソッドで要素数を変更しようとすると、コンパイルエラーは発生しませんが、実行時にjava.lang.UnsupportedOperationExceptionが発生します。

この点についてはAPIにも書かれているので、きちんとAPIを確認しておけば問題なくクリアできると思います。

// 固定長のリストに対する要素の追加
static void add() {
    List<Integer> fixedSizeList = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    // 実行時にUnsupportedOperationExceptionが発生する。
    fixedSizeList.add(11);
}

// 固定長のリストに対する要素の削除
static void remove() {
    List<Integer> fixedSizeList = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    // 実行時にUnsupportedOperationExceptionが発生する。
    fixedSizeList.remove(5);
}

「リストの長さが固定されている」という問題については、以下のように「asListメソッドで生成したリストを、ArrayListのコンストラクタに指定する」とすることで回避できます。

リストから配列への変換はList#toArray()を使えば一発で変換できますが、配列から可変長のリストへの変換はどうしても2段階になってしまうようです。

// Arrays.asList()で作成したリストを、可変長(ミュータブル)のリストに変換する。
static void add2() {
    List<Integer> fixedSizeList = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    List<Integer> list = new ArrayList<>(fixedSizeList);
    // 実行時にエラーが発生しない。
    list.add(11);
}

2. 引数にプリミティブ型の配列を指定すると想定外の挙動をする

Arrays.asList()の引数にプリミティブ型の配列を指定すると、戻り値がList<ラッパークラス型>とはなりません。
例えば、Arrays.asList()の引数にint[]を指定すると、戻り値がList<int[]>となってしまいます。

これはAPIには一切書かれていなかったので、最初はコンパイルが通らずに悩みましたが、結果的には「仕様通りの動き」ということが分かって安心しました。

この問題を回避するには、「ラッパークラス型の配列を引数として指定する」などの方法があるそうですが、わざわざラッパークラス型に変換しておくのも変な感じがします。

static void create1() {
    // 参照型の配列だと、参照型のListが想定通りに返されますが...
    String[] words = {"abc","def","ghi"};
    List<String> list1 = Arrays.asList(words);
}

static void create2() {
    // プリミティブ型の配列をasList()の引数にすると、「配列がリスト中の1要素となる」ため、要素数1のリストが返されます。
    // list2.size()は1となります。
    int[] numbers = {1,2,3,4,5,6,7,8,9,10};
    List<int[]> list2 = Arrays.asList(numbers);
    System.out.println("list2の長さ:" + list2.size());
}

static void create3() {
    // 可変長の引数をasList()に渡すと、引数が整数値(=int)であっても、想定通りにリストが生成されます。
    // list3.size()は10となります。
    List<Integer> list3 = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    System.out.println("list3の長さ:" + list3.size());
}

まとめ

  1. Arrays.asList()で得られるリストは、固定長のリストとなる。
  2. Arrays.asList()の引数にプリミティブ型の配列を指定すると、配列の要素が展開されず、配列そのものが戻り値のリストの1要素となる。
    • 特にリストに変換する必要が無ければ、そのままプリミティブ型の配列として使った方が良いかもしれません...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ことりんと一緒 Springもね - 8. リポジトリ層 - 補足:H2 Database

概要 / 説明

ことりんと一緒 Springもね - 8. リポジトリ層 では、リポジトリ層を設けて、データの永続化処理を抽象化して外部の永続化領域への操作を透過的に実施できるようにしました。

今回のケースでは、外部の永続化領域はインメモリでアプリケーションに組み込みで動作する H2 Database Engine を利用していました。

この H2 Database Engine には、データベース情報を確認するためのコンソールアプリケーションが組み込まれています。そちらを利用してデータベースの状態確認を行ってみます。

前提 / 環境

ランタイムバージョン

  • Kotlin : 1.3.21
  • SpringBoot : 2.1.1.RELEASE

Spring Dependencies

  • Web
  • JDBC
  • JPA
  • H2
  • Actuator

開発環境

  • OS : Mac
  • IDE : IntelliJ IDEA
  • Build : Gradle

手順 / 解説

application.yml の設定

SpringBoot の application.yml に以下のようにH2データベースが使用できるように構成情報を定義します。

データソース

Springアプリケーションからデータベースに接続するためのデータソース定義を以下のように実施します。

spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:app;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE
    username: guest
    password: guest
構成要素 内容
driver-class-name H2DBのドライバーライブラリ
org.h2.Driver
url データベース接続URL(※フォーマットについて後述)
jdbc:h2:mem:app;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE
username データベース接続ユーザ名
password データベース接続パスワード
H2データベース接続URLフォーマット

H2データベースは、インメモリで動作するアプリケーション組み込み型の使用や、リモートサーバとして稼働するサーバモード、またファイルシステムにデータを書き出す動作など様々な動作モードがとれます。それぞれの動作モードに合わせた接続URLフォーマットとなります。

動作モード URLフォーマット サンプル
組み込みモード:インメモリ(プライベート) jdbc:h2:mem: jdbc:h2:mem:
組み込みモード:インメモリ(Named) jdbc:h2:mem:<DB名> jdbc:h2:mem:app
組み込みモード:ローカルファイル jdbc:h2:[file:][<ファイルパス>]<DB名> jdbc:h2:file:/data/sample
インメモリDBのドロップ防止 DB_CLOSE_DELAY=-1 jdbc:h2:mem:app;DB_CLOSE_DELAY=-1
サーバモード:TCP接続 jdbc:h2:tcp://<サーバアドレス>[:<ポート>]/[<ファイルパス>] jdbc:h2:tcp://localhost:8084/data/sample
サーバモード:TLS接続 jdbc:h2:ssl://<サーバアドレス>[:<ポート>]/[<ファイルパス>] jdbc:h2:ssl://localhost/mem:test
VM終了時のDB接続切断 DB_CLOSE_ON_EXIT=TRUE jdbc:h2:mem:app;DB_CLOSE_ON_EXIT=TRUE

H2データベース

H2 データベースのコンソール画面を表示させるには以下の設定を行います。

spring:
  h2:
    console:
      enabled: true

H2 データベースのコンソール画面の表示

アプリケーションを起動した後、H2 データベースコンソール画面にアクセスしてみます。

$ ./gradlew bootRun

> Task :bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.1.RELEASE)
:
:
:
2019-02-26 16:05:13.358  INFO 7073 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 15 endpoint(s) beneath base path '/admin'
2019-02-26 16:05:13.457  INFO 7073 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-02-26 16:05:13.460  INFO 7073 --- [           main] i.p.s.simple.SimpleApplicationKt         : Started SimpleApplicationKt in 5.577 seconds (JVM running for 5.998)
<==========---> 83% EXECUTING [2m 46s]
> :bootRun
  • http://localhost:[設定したポート番号]/h2-console へアクセスし、H2 データベースの接続ユーザ/パスワードを入力して接続を行います。

h2-console-login.png

H2-console.png

まとめ / 振り返り

SpringBoot と組み合わせて手軽に利用できるH2データベースを操作してみました。
データベースの機能自体に依存しないアプリケーション開発の場面では、インストール作業が不要で、簡単に使えるH2データベースは利用できる場面がいろいろとあるのではないでしょうか。

今回のソース

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む