java – こみなのメモ帳 / 趣味と実益のネタ帳 Tue, 05 Aug 2025 14:40:20 +0000 ja hourly 1 https://wordpress.org/?v=6.1.1 H2DBのLinked Tablesについて調べてみた /archives/1670/ /archives/1670/#respond Tue, 05 Aug 2025 14:40:20 +0000 https://www.komina.info/?p=1670 H2DBにはLinked Tablesという機能があります。外部テーブル(ほかのDB)へのテーブルリンクを作成して、あたかも H2DB 内に存在するテーブルかのように扱う機能です。

JDBCドライバで接続できるDBであれば、以下のようなコマンドで利用可能です。

CREATE LINKED TABLE LINK('org.h2.Driver', 'jdbc:h2:./test2', 'sa', 'sa', 'TEST');

社内に MySQL PostgreSQL などいろんなDBサーバが乱立しているようなときに使ったら便利そう、ということで調べてみることにしました。

環境構築

docker-composeを使ってお試し環境を作ってみました。h2db + mysql + postgresql な環境です。githubに一式を公開してあります。

https://github.com/komina77/h2db

git clone https://github.com/komina77/h2db.git
cd LinkedTables
docker-compose up -d

サンプルデータは MySQL の公式サイトにあるサンプルデータworld)を利用させてもらうことにします。これを mysqlpostgresql の両方の初期データとしてインストールすることにします。(postgresql へはそのままの文法では食わせることはできなかったので少し書き換えています)

city, country, countryLanguage の3つのテーブルから構成されるデータベースになります。

MySQLを参照してみる

実はH2DBのサーバに内蔵されているWeb管理画面はH2DB以外のDBにも接続できますので、これを利用してみたいと思います。

ホストOS上のブラウザで、http://localhost:8082/ を開き、保存済設定の中から「Generic MySQL」を選びます。ドライバクラスなどのデフォルト設定が表示されるので、JDBC URLやユーザ名、パスワード(mysql/mysql)を入力します。接続テストが通れば成功です。

PostgreSQLを参照してみる

同様に PostgreSQL のデータベースも参照することができます。

オートコンプリート機能も有効で、ちょっとしたSQLならすぐに試してみることができます。PostgreSQLの大文字小文字混在のカラムはダブルコーテーションで囲わないといけないので、逆に不便でした。

Linked Tables を定義してみる

mysqlpostgresql の準備ができたところで H2DB に接続して Linked Tables を作成してみたいと思います。

まずは mysqlworldデータベースの中の countryテーブルを my_country という名前で参照できるようにしてみます。

CREATE LINKED TABLE IF NOT EXISTS MY_COUNTRY(
'com.mysql.jdbc.Driver', 'jdbc:mysql://mysql-1:3306/world', 'mysql', 'mysql', 'country'
);

これだけで MY_COUNTRY というテーブルをローカルにあるテーブルのように参照できるようになります。

同様に、PG_CITYPG_COUNTRYLANGUAGE についてもテーブルを定義します。

CREATE LINKED TABLE IF NOT EXISTS PG_CITY(
'org.postgresql.Driver', 'jdbc:postgresql://postgres-1/world', 'postgres', 'postgres', 'city'
);

CREATE LINKED TABLE IF NOT EXISTS PG_COUNTRYLAUNGUAGE(
'org.postgresql.Driver', 'jdbc:postgresql://postgres-1/world', 'postgres', 'postgres', 'countrylanguage'
);

どんなSQLが発行されているか(MySQL

まずは条件なしで検索。

SELECT * FROM MY_COUNTRY 
SELECT * FROM country T

次は簡単な条件を付けてみます。

SELECT * FROM MY_COUNTRY where CODE ='JPN'
SELECT * FROM country T 
WHERE CODE>='JPN' AND CODE<='JPN'

単純な文字列の等号による一致条件のつもりだったのですが不等号の範囲検索に変換されてしまいました。なにか意図があるのでしょうがとりあえずヨシとします。

どんなSQLが発行されているか(PostgreSQL

SELECT * FROM PG_CITY 
SELECT * FROM public.city T
SELECT * FROM PG_CITY 
WHERE ID =10
SELECT * FROM public.city T 
WHERE ID>=$1 AND ID<=$2
ERROR:  column "id" does not exist at character 35

おっと。エラーが出てしまいました。どうやらDDLの段階でカラム名をダブルコーテーションで囲って大文字小文字を厳密に定義していたことが原因と思われる。。

DDLを修正してもう一度やり直し。PG_CITYPG_COUNTRYLANGUAGE についてもテーブル定義をやり直します。

SELECT * FROM PG_CITY 
WHERE ID =10
SELECT * FROM public.city T 
WHERE ID>=$1 AND ID<=$2
DETAIL:  parameters: $1 = '10', $2 = '10'

無事にクエリを発行することができました。

結合したらどうなるか(異DB同士

テーブル一つに対するクエリであれば、ほぼ等価の条件式が渡されるようなので、リンクされた側のDBで適切な実行計画が適用されることになりそうです。

では、2つのDBにまたがる結合をしたらどうなるのか。やってみたいと思います。

SELECT T1.NAME, T2.LANGUAGE  FROM MY_COUNTRY  T1
INNER JOIN PG_COUNTRYLAUNGUAGE  T2
ON T2.COUNTRYCODE = T1.CODE
AND  T2.ISOFFICIAL ='T'
WHERE T1.REGION  = 'North America'
;

北アメリカの国における公用語は?、という感じの意味合いになります。まず MY_COUNTRY を北アメリカで絞り込んだのち、国コードで PG_COUNTRYLANGUAGE から公用語を得る、という結合になることを想定してみました。

MySQL

SELECT * FROM country T WHERE CODE>='ABW' AND CODE<='ABW'
SELECT * FROM country T WHERE CODE>='AFG' AND CODE<='AFG'
SELECT * FROM country T WHERE CODE>='AFG' AND CODE<='AFG'
SELECT * FROM country T WHERE CODE>='AIA' AND CODE<='AIA'
SELECT * FROM country T WHERE CODE>='ALB' AND CODE<='ALB'
SELECT * FROM country T WHERE CODE>='AND' AND CODE<='AND'
SELECT * FROM country T WHERE CODE>='ANT' AND CODE<='ANT'
SELECT * FROM country T WHERE CODE>='ANT' AND CODE<='ANT'
SELECT * FROM country T WHERE CODE>='ARE' AND CODE<='ARE'
SELECT * FROM country T WHERE CODE>='ARG' AND CODE<='ARG'
SELECT * FROM country T WHERE CODE>='ARM' AND CODE<='ARM'
:
:

PostgreSQL

SELECT * FROM public.countrylanguage T WHERE ISOFFICIAL>=$1 AND ISOFFICIAL<=$2
-- parameters: $1 = 'T', $2 = 'T'

どうやら想定と逆に、PG_COUNTRYLANGUAGE から公用語の一覧を取得したのち、対応する国の一覧を取得。クエリには北アメリカでの絞り込みは入っていないので H2DB側で一番最後に行われたようです。

実行計画を見てみる

事後になりますが H2DBでの実行計画を見てみます。

EXPLAIN
SELECT T1.NAME, T2.LANGUAGE  FROM MY_COUNTRY  T1
INNER JOIN PG_COUNTRYLAUNGUAGE  T2
ON T2.COUNTRYCODE = T1.CODE
AND  T2.ISOFFICIAL ='T'
WHERE T1.REGION  = 'North America'
;
SELECT
    "T1"."NAME",
    "T2"."LANGUAGE"
FROM "PUBLIC"."PG_COUNTRYLAUNGUAGE" "T2"
    /* PUBLIC."": ISOFFICIAL = 'T' */
    /* WHERE T2.ISOFFICIAL = 'T'
    */
INNER JOIN "PUBLIC"."MY_COUNTRY" "T1"
    /* PUBLIC."": CODE = T2.COUNTRYCODE */
    ON 1=1
WHERE ("T1"."REGION" = 'North America')
    AND (("T2"."ISOFFICIAL" = 'T')
    AND ("T2"."COUNTRYCODE" = "T1"."CODE"))

FROM句で指定していたテーブルが MY_COUNTRY から PG_COUNTRYLANGUAGE に変わっていますね。H2DB では結合順序指定するヒント句はサポートされていません。性能を高めるには希望する結合順になるまで SQLをこねくり回すしかなさそうです。

結合したらどうなるか(同DB同士

同じDBにあるテーブル同士の結合も試してみます。

EXPLAIN
SELECT T1.COUNTRYCODE ,T1.DISTRICT , T1.NAME , T2.LANGUAGE
FROM PG_CITY T1
INNER JOIN PG_COUNTRYLAUNGUAGE T2
ON T2.COUNTRYCODE =T1.COUNTRYCODE 
WHERE T1.POPULATION >7000000
AND T2.ISOFFICIAL ='T'
;
SELECT
    "T1"."COUNTRYCODE",
    "T1"."DISTRICT",
    "T1"."NAME",
    "T2"."LANGUAGE"
FROM "PUBLIC"."PG_CITY" "T1"
    /* PUBLIC."": POPULATION > 7000000 */
    /* WHERE T1.POPULATION > 7000000
    */
INNER JOIN "PUBLIC"."PG_COUNTRYLAUNGUAGE" "T2"
    /* PUBLIC."": COUNTRYCODE = T1.COUNTRYCODE */
    ON 1=1
WHERE ("T2"."COUNTRYCODE" = "T1"."COUNTRYCODE")
    AND (("T1"."POPULATION" > 7000000)
    AND ("T2"."ISOFFICIAL" = 'T'))

人口が700万人超の都市のある国の公用語の一覧を得るクエリです。PostgreSQL へは以下のクエリが送信されました。

SELECT * FROM public.city T WHERE POPULATION>=$1
-- parameters: $1 = '7000000'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'BRA', $2 = 'BRA'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'GBR', $2 = 'GBR'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'IDN', $2 = 'IDN'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'IND', $2 = 'IND'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'IND', $2 = 'IND'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'JPN', $2 = 'JPN'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'CHN', $2 = 'CHN'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'CHN', $2 = 'CHN'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'KOR', $2 = 'KOR'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'MEX', $2 = 'MEX'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'PAK', $2 = 'PAK'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'TUR', $2 = 'TUR'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'RUS', $2 = 'RUS'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'USA', $2 = 'USA'

700万人超の都市の一覧を取得したのち、国コードを使って1国ずつ PG_COUNTRYLANGUAGE を取得していますね。ISOFFICIAL については記載がないので H2DB側で絞り込みを行っているのでしょう。

同じDBにあるテーブルであっても、結合をそのDBに任せず H2DB内で行っていることが分かりました。

まとめ

  • H2DB には他のDBのテーブルをH2DB内のテーブルのように見せかける機能がある。複数のシステムのDBを透過的に扱うのに便利。
  • 外部DBのテーブル定義に依存する。カラム名に大文字小文字混在を許すようなテーブルを参照するときは失敗することがあるので、事前に調査してビューなどを介すなど工夫が必要。
  • テーブル1つに対するクエリであればテーブルを持つDB上で抽出が行われるので、性能的には問題なさそう。
  • リンクテーブル同士での結合はサポートされている。しかし、H2DBの判断で実行計画が決まってしまうので性能が出ないことがある。
  • 結合するリンクテーブルが同じDBのテーブルであっても、結合がリンク先のDBに任されない。H2DBの判断によって行われる。

異なるシステムで使われているDBのテーブルを一時的に参照するような用途では便利かもしれない。しかし、複雑なクエリで問い合わせるときは性能を出すために苦労することになりそうだ。

]]>
/archives/1670/feed/ 0
java6のzip問題 /archives/1234/ /archives/1234/#respond Thu, 05 Dec 2024 16:04:31 +0000 https://www.komina.info/?p=1234 私、仕事でいまだにjava6が生きている環境を扱っております。維持だけでなく時折補助ツールを作ったりもします。

言うてもjavaなので新しい書式を使ったり新しい機能を使おうと思わない限りは問題はほとんど起きないものです。そんな中で ZipOutputStreamで意外な原因で以下の例外が発生することを発見しました。

せっかくなのでメモしておきます。

そもそも ZipOutputStreamFileOutputStreamなどのストリームに接続してZip圧縮したバイナリを出力するものです。ZipOutputStreamオブジェクトに対して putNextEntry(ZipEntry e) メソッドでファイルやディレクトリを追加していく感じで使います。で、最後にcloseすると無事Zipアーカイブが完成するというわけです。

意外な原因とは

どこら辺が問題かというと、ZipOutputStreamオブジェクトを生成しておきながら一件も putNextEntryしないで closeすると例外が発生するという。。。

つまり中身が空っぽのZipファイルを作成できないということなのです。

えー、そんなことが想定されていなかったの?という感じでした。さすがにjava7からは修正されていますので、私のようなjava6縛りプレイを強いられている方以外は新しいjavaを使うことで回避できます。。

解決策

ではどうするか。まず、圧縮対象が見つかったタイミングで ZipOutputStreamオブジェクトを生成するようなラップクラスを作ります。(ZipOutputStreamクラス自体は継承できないので新たに包含したクラスを作成するイメージです。デザインパターンでいうところのBridgeパターンでしょうか、知らんけど)

ここでは ZipCreaterJava6 というクラスを作成しました。こんな感じです。

public class ZipCreaterJava6 {
    FileOutputStream os = null;
    ZipOutputStream zos = null;
    
    // 空ZIPバイナリ
    int[] emptyzip = { 'P', 'K', 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
    
    public ZipCreaterJava6(File zipFile) throws Exception {
        this.os = new FileOutputStream(zipFile);
    }
    
    public void putNextEntry(ZipEntry e) throws IOException {
        getZos().putNextEntry(e);
    }
    
    public void write(byte[] b, int off, int len) throws IOException {
        getZos().write(b, off, len);
    }

    void close() throws IOException {
        if (this.zos == null) {
            // Create an empty ZIP file.
            for (int e : emptyzip) {
                os.write(e);
            }
        } else {
            zos.close();
        }
        if (os != null) os.close();
    }
    
    ZipOutputStream getZos() {
        if (zos == null) zos = new ZipOutputStream(os);
        return zos;
    }
}

putNextEntry メソッド、writeメソッド、 closeメソッド を実装しますと、ZipOutputStreamっぽく使えます。 putNextEntryメソッド、writeメソッド内では getZos()経由することで初回呼び出しを検出してそのタイミングでZipOutputStreamオブジェクト生成しています。
closeメソッド内ではZipOutputStreamオブジェクトが生成されていれば close処理する。生成されてなかったら空っぽのZIPを表すバイナリ列を返すようにします。

使用サンプルは以下のようになります。

public class Main {

	public static void main(String[] args) throws Exception {
		File dir = args.length > 0 ? new File(args[0]) : null;
		if (dir != null && dir.exists() && dir.isDirectory()) {
            ZipCreaterJava6 zos = new ZipCreaterJava6(new File("archive.zip"));
            //ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("archive.zip"));
		    
    		for (File item : dir.listFiles()) {
    		    FileInputStream fis = new FileInputStream(item);
    		    ZipEntry entry = new ZipEntry(item.getName());
    		    zos.putNextEntry(entry);
    		    
    		    byte[] buffer = new byte[4096];
    		    for (int n; (n=fis.read(buffer)) != -1; ) {
                    zos.write(buffer, 0, n);
                }
    		    fis.close();
    		}
    		zos.close();
		} else {
		    System.out.println("第一引数にソースとなるディレクトリを指定してください.");
		}
	}
}

最後に

どうしてもjava6でも動くようにしたい、という奇特なケースでない限りは不要なコードはありますが、実際私のような人間も2024年の時点で存在しているわけでして。あちこちから集めた情報をもとに解決した内容をここに記しておきました。

例外処理とかは適当です。引用するときはちゃんと書きましょう。以上。

]]>
/archives/1234/feed/ 0
[play1] 単発実行用のlauncherモジュールを作りました /archives/879/ /archives/879/#respond Thu, 29 Jul 2021 09:41:43 +0000 https://www.komina.info/?p=879 以前から自分で使っていたモジュールをgithubに公開することにしました。
play1-projects/launcher at main · komina77/play1-projects (github.com)

mavenへ登録するまでは手が回っていません。launcher/dist配下のzipをコピーしてきてローカルレポジトリに置いて、依存性解決してください。設定の書き方は /samples/say-hello/conf/dependencies.yml を参考に。

何ができるか

Webアプリケーションフレームワークとして作られているplayframework1を、Webサーバを起動しないで任意のクラスを実行することができます。

$ play launcher:run yourAppDir --class=info.komina.MyJob 

play1のJPA拡張やpojo拡張などの便利な機能を享受したJavaアプリケーションを作ることができるわけです。

リソースが相互に干渉しないように設定すれば、Webアプリケーションとして稼働しつつ、コマンドラインからジョブ実行。JenkinsやJP1のようなツールにジョブの実行を任せるような運用も可能です。

作るきっかけ

playframework1のジョブ機能を利用していて運用に不満を持ったのがきっかけです。ジョブ機能は正常に動いているときは確かに便利なのですが、実行状況を把握したり、任意のジョブをリカバリのためにリランさせたりといったことは、あらかじめプログラミングされていないとできません。

そこでジョブ類をWebアプリケーションから分離できたらな、と考えコマンドラインからの実行を思い立ちました。

Groovyも使えます

最近はGroovyも良く使うので、Groovyスクリプトも利用できるようにしています。もともとplay1ではテンプレート機能のためにGroovyの実行環境が同梱されているので、それを呼び出す部分から流用しています。

そのため、テンプレートと同様にコンパイル後キャッシュされます。

ジョブの動作テスト用に使う

モジュールのドキュメントにも書きましたが、もともとあるジョブ(play.jobs.Jobを継承)にmainメソッドを追加する方法がおすすめです。mainメソッドにはジョブを起動するだけのコードを書き、メインの処理はdoJob()内に記述します。

コマンドラインからジョブだけを直接実行できるので、試行錯誤が効率的に行えます。

]]>
/archives/879/feed/ 0
[java] モジュラス10ウェイト3計算 /archives/816/ /archives/816/#respond Tue, 11 May 2021 10:11:33 +0000 https://www.komina.info/?p=816 何年か前に仕事でモジュラス10ウェイト3でチェックデジットを計算するというコードを書いた。ひょっこりコードの断片が見つかったので記事に残しておこうと思う。

モジュラス10 (JAN) ‐ 通信用語の基礎知識 (wdic.org)
JANの読み取りミスを防ぐために使われるチェック用数字(チェックディジット)の算出方法のこと。
また、ISBNが13桁化しISBN-13となった際、JANコードの仕様をそのまま採用したことから、チェックディジットの計算方法もモジュラス10となった。
JANのアルゴリズムは、末尾から奇数桁に3、偶数桁に1を掛けてから単純に足し算し、結果を10で割った余り(剰余)を求め、10からその剰余を引いた値をチェックディジットとして用いている。

https://www.wdic.org/w/WDIC/%e3%83%a2%e3%82%b8%e3%83%a5%e3%83%a9%e3%82%b910%20(JAN)

だらだら書きたくなかったので無理くり1行にまとめています。いわゆる自己満足なコードです。

    /**
     * チェックデジット算出. モジュラス10ウェイト3
     * @return
     */
    public static String calcCheckDigit(String x) {
        int sum = 0;
        char[] c = x.toCharArray();
        for (int i = 0; i < c.length; i++) {
            sum += (c[i] & 0xf) * (((i ^ c.length) & 1) << 1 | 1);
        }
        return DIGIT_TBL[sum % 10];
    }

    /**
     * デジット仕上げ用テーブル
     */
    static final String[] DIGIT_TBL = { "0", "9", "8", "7", "6", "5", "4", "3", "2", "1" };

(c[i] & 0xf) はアスキーコードの数値化です。”0″は0x30、”1″は0x31、”9″は0x39なので下位4ビットをマスクしてます。

((i ^ c.length) & 1) は、iが末尾から奇数桁なら 1、偶数桁なら 0を返す式です。なんで”^” XORを採用したかですが、末尾から数えて奇数桁か偶数桁かどうかは文字長が奇数か偶数かで決まるのでこうした気がします。単に末尾からの桁数なので ((c.length - i) & 1) とかでもOKですよね。

(((i ^ c.length) & 1) << 1 | 1) は、3倍か1倍かの振り分けをシフト演算で実現しています。奇数桁なら 1なので 1<<1で 2進数で10|1 により 二進数で11になります。つまり 3です。
偶数桁なら 0なのでいくらシフトしてもゼロ。|1 により1 です。

デジット仕上げ用テーブルは「10からその剰余を引いた値をチェックディジット」の部分を担当しています。数値を文字にして戻り値にする関数だったので、配列を用いて数値→文字変換も同時に解決しています。

権利を主張するようなものではないので、自由に使ってもらって構わないです。責任はとれませんのでちゃんとデバッグはしてくださいね。。

]]>
/archives/816/feed/ 0
[play1] テンプレート処理結果を文字列で見たい /archives/694/ /archives/694/#respond Thu, 28 Jan 2021 08:14:16 +0000 https://www.komina.info/?p=694 play1のrenderメソッドは巧みに隠されていますが、Resultクラスを継承したランタイム例外を投げるようになっています。

こうした実装により、コントローラ処理での終わり方を意識することなく、バッサリ終わらせるような記述ができ、本来のコーディングに集中できるようになっています。

ということで私はrenderメソッドで例外が投げられることは知っていたので、これを捕捉すればテンプレート処理した結果を簡単に見られると思っていましたが、どうもそう簡単にはいかないようで。。

try {
    render();
} catch (RenderTemplate result) {
    Logger.info("html=%s", result.getContent());
    throw result;
}

このようなコードを書いてみたのですが、例外が捕捉されることはありませんでした。(ページはちゃんと表示される)

play1ではapp配下を自前でコンパイルしています。その際に便利になるようにコードに少しだけ変更を加えています。それを担っているのが play.classloading.enhancers.Enhancer を継承したクラス群です。

今回は play.classloading.enhancers.ControllersEnhancer クラスが働いて、play.mvc.results.Resultplay.Invoker.Suspend を継承したクラスがキャッチされると即再スローするコードを埋め込んでいるようでした。
ユーザのコードが実行される前に再スローしてしまうために捕捉に失敗しているように見えていた、と。

そんなわけで例外オブジェクトが投げられる直前の処理を抜き出せば、テンプレート処理した結果を文字列でゲットできました。

Template template = TemplateLoader.load(template("Application/index.html"));
String html = template.render(renderArgs.data);
Logger.info("html=%s", html);

省略していますが、テンプレート内で params session などのオブジェクトを参照している場合は、renderArgs.dataにそれらも追加する必要があります。

詳しくは play.mvc.Controller.renderTemplate(String, Map) の実装を見てみてください。

]]>
/archives/694/feed/ 0
[gcp][java]datastoreへデータを保存する /archives/687/ /archives/687/#respond Wed, 20 Jan 2021 05:58:39 +0000 https://www.komina.info/?p=687 今更需要があるかわかりませんがメモ。
Mavenレポジトリから必要なライブラリをゲットして参照しておきます。

Google Cloud Datastore
Java idiomatic client for Google Cloud Datastore.

Maven Repository: com.google.cloud » google-cloud-datastore (mvnrepository.com)

さらに下記にあるように環境変数GOOGLE_APPLICATION_CREDENTIALSに認証情報の書かれたjsonファイルへのパスを設定しておきます。こうしておくとcloud上のdatastoreへ直接アクセスできるようになります。(エミュレータについてはここでは書きません)

Authenticating with this module

google-cloud-node/authentication.md at master · googleapis/google-cloud-node (github.com)

Google Cloud Platformのデータストアの管理ページがあるので、そこで結果がすぐに確認できます。更新ボタン(グルっとした矢印のアイコン)があるので、そこをクリックして表示データをリフレッシュしましょう。

下記コードはIDを自動採番してデータを保存する例です。
IncompleteKeyというのを作るのがポイント。”gcp-test2″というのがテーブル名みたいなものです。
Datastoreでは各列に暗黙インデックスが作成されていて、1500bytes超の文字列の場合はそれが作成できないためにエンティティの登録に失敗します。そこでsetExcludeFromIndexesにてインデックスから除外指定することで登録できるようになります。代償としてその列を条件にした抽出はヒットしなくなります。

import com.google.cloud.datastore.Datastore;
import com.google.cloud.datastore.DatastoreOptions;
import com.google.cloud.datastore.Entity;
import com.google.cloud.datastore.FullEntity;
import com.google.cloud.datastore.IncompleteKey;

Datastore ds = DatastoreOptions.getDefaultInstance().getService();
IncompleteKey key = ds.newKeyFactory().setKind("gcp-test2").newKey();
FullEntity<IncompleteKey> e = Entity.newBuilder().setKey(key)
        .set("name", "komi")
        .set("age", 99)
        .set("text", StringValue.newBuilder("aaaa").setExcludeFromIndexes(true).build())        .build();
ds.put(e);
]]>
/archives/687/feed/ 0
[java][play1]eclipseからの実行でエラー発生 /archives/621/ /archives/621/#respond Fri, 08 Jan 2021 09:27:12 +0000 https://www.komina.info/?p=621 久しぶりにplay1でプログラムを組んでいます。play2がメインストリームなので保守程度にしかメンテされていないようで。。

いつものように play deps して play ec でeclispeプロジェクトへ変換。
eclipseへプロジェクトをインポートし、eclipse/xxx.launch を右クリックして実行したところ、下記のようなエラーが発生しました。

-Djava.endorsed.dirs=C:\Tools\play\play-1.5.3/framework/endorsed is not supported. Endorsed standards and standalone APIs
in modular form will be supported via the concept of upgradeable modules.

ちなみに playは1.5.3、javaはOpenJDKの11.0.9.1を使用しています。

エラーメッセージで検索してみると Apache Tomcat での事例を中心にヒット。
とりあえずそのオプションを消したら動いた、的な感じだったのでそれにならうことにしました。

VM引数から下記の記述部分を削除してみたところ、たしかに解決。

-Djava.endorsed.dirs="C:\Tools\play\play-1.5.3/framework/endorsed"

釈然としませんがJava9以降は不要なオプションのようです。

推奨標準優先メカニズムの削除
java.endorsed.dirsシステム・プロパティおよびlib/endorsedディレクトリは存在しなくなりました。このいずれかが検出された場合、javacコンパイラおよびjava起動ツールは終了します。
JDK 9では、アップグレード可能なモジュールを使用するか、またはJARファイルをクラス・パスに指定します。

Java Platform, Standard Edition Oracle JDK 9移行ガイド, リリース9

っていうか、そもそも play-1.5.3/framework/endorsed なんでフォルダ存在しないですね。

2010年ころのplay1.0系とかでも、このオプションは付いていますが、肝心の /framework/endorsed というフォルダは存在しませんでした。試行錯誤で記述したものの消すタイミングを逸していつまでも残ってしまった系なのかも??とか邪推。

eclipsify用のリソースが置かれている /play-1.5.3/resources/eclipse の、debug.launch と test.launch がテンプレートです。
毎回修正するのが面倒なのでこのファイルを修正するのが良いと思います。

]]>
/archives/621/feed/ 0
[java][play1]依存性解決でnot foundエラー /archives/612/ /archives/612/#respond Fri, 08 Jan 2021 08:27:07 +0000 https://www.komina.info/?p=612 久しぶりにplay1でdepsによって依存性解決しようとしたところ、エラー。
--verboseオプションを付けて再実行したところ、

java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty

という例外が出ている模様。

検索してみるとstackoverflowの記事で、

btw this happens e.g. when switching from OracleJDK8 to OpenJDK8 since OpenJDK comes with an empty trust store. Copying the one from OpenJDK11 fixes the problem – light_303 Nov 13 ’18 at 9:38

https://stackoverflow.com/questions/6784463/error-trustanchors-parameter-must-be-non-empty

というコメントを発見。

OpenJDK8trust storeが空なので、OpenJDK11からコピーして来いよ。ということらしいのでさっそく試してみる。

C:\Tools\bin\openjdk-11.0.9.1_1\lib\security

にあるファイル cacerts

C:\Tools\bin\java-se-8u41-ri\jre\lib\security

へコピー。
※オリジナルのcacertsは事前に名前を変えておきました。

無事に事なきを得ることができました。ありがとうインターネット、ありがとうstack overflow。

]]>
/archives/612/feed/ 0
hibernateのL1キャッシュを無効にする /archives/417/ /archives/417/#respond Tue, 13 Oct 2020 21:00:36 +0000 https://www.komina.info/?p=417
【注意】
この記事は過去に別サイトで執筆したものを再掲載したものです。
内容が古くなっていて現在では適用できない場合や、もっと良い方法が出来ている場合があります。
 

https://stackoverflow.com/questions/3827704/how-to-disable-hibernate-caching

このやりとりを見ると、、、

  • hibernateにはL1キャッシュとL2キャッシュがある。
  • L2キャッシュはデフォルトでOFFになっている。
  • L1キャッシュはOFFにはできない。

つまり、L1キャッシュは無効にできないということらしい。

無効にはできないが任意のオブジェクトをキャッシュから追い出すこと(セッションのevictメソッド)はできるので、リードの前に実施することでキャッシュからリードしてしまう事象は回避できそう。

    seesion.evict(yourObject);

ちなみに、L2キャッシュやクエリキャッシュについては以下のオプションが指定できる。

hibernate.cache.use.query_cache = true or false
hibernate.cache.use_second_level_cache = true or false

クエリキャッシュは単体では有効にできない。L2キャッシュを有効にした上で使用する。

]]>
/archives/417/feed/ 0
GroovyでMavenで単一の配布可能なjar作成 (2) /archives/470/ /archives/470/#respond Mon, 12 Oct 2020 08:04:26 +0000 https://www.komina.info/?p=470 前回で配布可能なjar作成については解決したかと思ったのですが、またまた問題が発生。解決に至れましたのでメモしておきます。

そもそも、こういった依存するライブラリをすべて含めた配布可能な単一のjarのことをfat-jarと呼ぶそうです。検索するときは fatjar で調べると有用な情報が見つかります。

さてどんな問題が起きたかなんですが、mavenレポジトリに置いてないようなライブラリ(ojdbc.jar、マイナーなjar、個人的なjarなど)の場合、dependenciesには以下のように <scope>system</scope> とし、配置場所を<systemPath>タグでダイレクトに指定します。(プロジェクトディレクトリを参照すると警告が出ちゃうけど気にせずに)

Mavenでローカルjarを追加する

https://qiita.com/inuDrill/items/bd2843e812016a3a900b
        <dependency>
            <groupId>com.oracle.jdbc</groupId>
            <artifactId>ojdbc</artifactId>
            <version>1</version>
            <scope>system</scope>
            <systemPath>${basedir}/lib/ojdbc6_g.jar</systemPath>
        </dependency>

これでコンパイルしたりする分には十分なのですが、これらのライブラリはjar-with-dependenciesの対象外となっているため、このままではfatjarは作れません。

そこで maven-dependency-plugin というプラグイン定義を追加します。

            <!-- https://maven.apache.org/plugins/maven-dependency-plugin/usage.html -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>unpack-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/classes</outputDirectory>
                            <includeArtifactIds>ojdbc</includeArtifactIds>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

こうすることで、systemライブラリがclasses配下に展開され、まとめてjar作成されるようになります。

参考

Maven Dependency Plugin で system スコープの jar ファイルもひとつにまとめる

https://qiita.com/niwasawa/items/766ba558678ac9bb85aa

Apache Maven Dependency Plugin – Usage

https://maven.apache.org/plugins/maven-dependency-plugin/usage.html

https://www.komina.info/archives/482
次へ
]]>
/archives/470/feed/ 0