JREが複数動いている環境向け。Javaプログラムで、実行しているJREのバージョンを確認する方法。

Java

複数の異なるJREがインストールされているマシン上でJavaアプリケーションを動かす場合、パスの記述方法を誤っていると意図していないJRE上でアプリケーションが動いてしまうことがあります。


アプリケーションで動作保証しているJREのバージョンは決めている事がほとんどなので、動作しているJREのバージョンをきちんと把握しておく事はとても大事です。


今回は、Javaアプリケーション内で、動作しているJREを判別する方法を紹介します。


皆さんは、開発したJavaアプリケーションを動かす場合、Javaアプリケーションを動かすサーバやパソコンにJREをインストールしているかと思います。
その際、環境変数にJREのパスを記述したり、もしくは、Javaアプリケーションの起動シェル(バッチ)にJREのパスを記述したり、といった方法をとっているのではないでしょうか。


単純に、1つのバージョンのJREをインストールし、そのサーバやパソコンで動作するアプリケーションはインストールした1つのバージョンのJRE上で動かす場合は問題ないのですが、例えば、1つのサーバやパソコン内で、あるアプリケーションはJava1.7で動かしたいが、あるアプリケーションはJava1.8で動かしたいといったパターンがまれにあります。


その場合、「本当にこのアプリ、Java1.8で動いてるっけ?」って不安になりませんか?
だって、Javaは2つ入っている訳でありまして。。。


こういう心配性な技術者向けに、Javaプログラム内で参照しているJavaのパスを取得する方法を紹介します。


複数のJREがインストールされている環境


サーバ上で10個のアプリケーションが動かす必要があり、そのうち9個はJava1.7で動かす必要があるが、1個はJava1.8で動かす必要がある、といった場面です。
この場合、ほとんどの人が以下のような方法をとるのではないか?


  • JREは、1.7と1.8の両方をインストールする。
  • 環境変数には、Java1.7のパスを定義する。
  • Java1.8で動かす必要があるアプリについてどうにかする。

環境変数にはJava1.7とJava1.8のパスを両方定義しても無意味です。
参照先パスは、先に見つかった方を採用するので、後に定義したJavaのパスは無視されます


Java1.7が採用される環境変数の記述方法

C:\Program Files\Java\jre1.7\bin;C:\Program Files\Java\jre1.8\bin

Java1.8が採用される環境変数の記述方法

C:\Program Files\Java\jre1.8\bin;C:\Program Files\Java\jre1.7\bin

2つ定義するのは無意味なので、環境変数の定義は以下のように記載するのが正解です。


正しい環境変数の定義

C:\Program Files\Java\jre1.7\bin

Java1.7で動くアプリケーションは上記でいいとして、問題はJava1.8で動かす方です。
王道としてはアプリケーションの起動シェルやバッチにJava1.8のパスを定義する方法がメジャーです。


set PATH=C:\Program Files\Java\jre1.8\bin;%PATH%

%PATH%の前にJava1.8のパスを記載するのがポイントです。


環境変数にはJava1.7のパスが定義されているので、%PATH%の後にJava1.8のパスを記載してしまうとJava1.7の方が採用されてしまいます。%PATH%の前にJava1.8のパスを定義することで、Java1.8の方が採用されます。


本当に意図したJREで動いているか?を確認


動かすアプリケーションに以下の試験プログラムを入れてみて、意図したJavaのパスを参照しているかを確認しましょう。
「System.getProperty」を使います。

public class getProperty {

    public static void main(String[] args){
        System.out.println("sun.boot.library.path=" + 
            System.getProperty("sun.boot.library.path"));
    }
}

実行結果は以下になります。

sun.boot.library.path=C:\Program Files\Java\jre1.8\bin

「sun.boot.library.path」がJavaのパスです。
これが、Java1.8になっていればOK。


Javaのプロパティ情報を出力する


Javaのパスを確認してみましたが、Javaのパス以外にもプロパティ情報が存在します。
以下のプログラムでリスト形式で確認できます。

import java.util.Properties;

public class getProperty2 {

    public static void main(String[] args){
        Properties props = System.getProperties();
        props.list(System.out);
    }
}

実行結果は以下になります。

-- listing properties --
sun.desktop=windows
awt.toolkit=sun.awt.windows.WToolkit
java.specification.version=12
sun.cpu.isalist=amd64
sun.jnu.encoding=MS932
java.class.path=.
java.vm.vendor=Oracle Corporation
sun.arch.data.model=64
user.variant=
java.vendor.url=https://java.oracle.com/
java.vm.specification.version=12
os.name=Windows 7
sun.java.launcher=SUN_STANDARD
user.country=JP
sun.boot.library.path=C:\Program Files\Java\jdk-1.8\bin
sun.java.command=getProperty
jdk.debug=release
sun.cpu.endian=little
user.home=C:\Users\user
user.language=ja
sun.stderr.encoding=ms932
java.specification.vendor=Oracle Corporation
java.version.date=2019-04-16
java.home=C:\Program Files\Java\jdk1.8
file.separator=\
java.vm.compressedOopsMode=32-bit
line.separator=
java.vm.specification.vendor=Oracle Corporation
java.specification.name=Java Platform API Specification
java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironmen
user.script=
sun.management.compiler=HotSpot 64-Bit Tiered Compilers
java.runtime.version=12.0.1+12
user.name=saku
path.separator=;
os.version=6.1
java.runtime.name=Java(TM) SE Runtime Environment
file.encoding=MS932
java.vm.name=Java HotSpot(TM) 64-Bit Server VM
java.vendor.url.bug=https://bugreport.java.com/bugreport/
java.io.tmpdir=C:\Users\Temp\
java.version=12.0.1
user.dir=C:\blog\systemProperty
os.arch=amd64
java.vm.specification.name=Java Virtual Machine Specification
sun.os.patch.level=Service Pack 1
java.library.path=C:\Program Files\Java\jre1.8\bin;...
java.vm.info=mixed mode, sharing
java.vendor=Oracle Corporation
java.vm.version=12.0.1+12
sun.io.unicode.encoding=UnicodeLittle
java.class.version=56.0

「sun.boot.library.path」も出力されていますね。

48行目です。

まとめ


いかがでしたでしょうか?
もし、“このアプリって本当に、意図したJavaみてるっけ?”と気になった時は、「System.getProperty」を使ってJavaのパスを確認した方がよいかと思います。
パスの設定が間違っていても、なかなか気が付かなかったりするので、実際にアプリケーションにデバッグブログラムをいれて確認すると安心ですね。


それではまた!



Apache Solrを使ったドキュメント検索 – Standard Highlighter

ApacheSolr

前回の記事で、Apache Solrを使って、登録・削除・検索を紹介しました。



今回は、検索結果をハイライト表示する方法を紹介します。

Apache Solrでのハイライト表示とは、検索結果として一致したキーワード前後の文字列を通知して、かつ、検索したキーワード部分を強調表示する機能になります。

本記事では、Apache Solrで最も一般的なハイライト表示である「Standard Hilighter」を使ってみます。

環境情報


環境情報は以下になります。

使用するのは、JavaとApache Solrの2つのみです。

  • Java 12.0.1
  • Apache Solr 6.4.2

スキーマ定義

ハイライト表示するためには、ハイライト表示するための準備が必要です。

Apache Solrのコンフィグである「managed-schema」に、フィールドタイプ定義とフィールド定義をおこないます。


フィールドタイプ定義

<fieldType name="text_general_content" class="solr.TextField" 
  positionIncrementGap="100" multiValued="true" 
  autoGeneratePhraseQueries="true">
  <analyzer type="index">
    <tokenizer class="solr.NGramTokenizerFactory" 
      minGramSize="1" maxGramSize="1"/>
    <filter class="solr.StopFilterFactory" 
      words="stopwords.txt" ignoreCase="true"/>
    <filter class="solr.LowerCaseFilterFactory"/>
  </analyzer>
  <analyzer type="query">
    <tokenizer class="solr.NGramTokenizerFactory" 
      minGramSize="1" maxGramSize="1"/>
    <filter class="solr.StopFilterFactory" 
      words="stopwords.txt" ignoreCase="true"/>
    <filter class="solr.SynonymFilterFactory" 
      expand="true" ignoreCase="true" synonyms="synonyms.txt"/>
    <filter class="solr.LowerCaseFilterFactory"/>
  </analyzer>
</fieldType>

フィールド定義

<field name="content" type="text_general_content" multiValued="false" indexed="true" stored="true"/>

「managed-schema」の定義を追加したら、ApacheSolrのサービスを再起動してください。


これで準備完了です。

以降に登録したドキュメントについて、ハイライト表示の文字列取得が可能になります。


Standard Highlighterのサンプルコード


「Standard Highlighter」を使った検索のサンプルコードは以下になります。

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.util.ClientUtils;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.Group;
import org.apache.solr.client.solrj.response.GroupCommand;
import org.apache.solr.client.solrj.response.GroupResponse;
import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrDocument;

public class SolrSearch {

    public static void main(String[] args){
        System.out.print("start: main\r\n");

        // Solrのインスタンス作成
        SolrClient client = new HttpSolrClient.Builder(
            "http://localhost:8983/solr/java_sample").build();

        SolrQuery solrQuery = new SolrQuery();

        // 検索結果として、文書IDを返却するよう設定
        solrQuery.setFields("id");

        // 検索結果の上限は100件
        solrQuery.setRows(100);

        try {
            StringBuilder queryString = new StringBuilder();
            String keyword = args[0];
            if (keyword.equals("")) {
                queryString.append("*");
            } else {
                String queryPhrase = "\"" + ClientUtils.escapeQueryChars(keyword) + "\"";
                queryString.append("(");
                queryString.append("content:");
                queryString.append(queryPhrase);
                queryString.append(")");
            }

            // 検索実行
            System.out.println("q=" + queryString.toString());

            solrQuery.set("hl", true);
            solrQuery.set("hl.fl", "content");
            solrQuery.set("hl.simple.pre", "<b>");
            solrQuery.set("hl.simple.post", "</b>");
            solrQuery.set("hl.fragsize", 20);
            solrQuery.set("hl.maxAnalyzedChars", 100);
            solrQuery.set("q", queryString.toString());

            QueryResponse response = client.query(solrQuery);

            // 検索結果を表示
            SolrDocumentList list = response.getResults();
            if (list == null) {
                System.out.println("文書は存在しませんでした。");
            } else {
                System.out.println(list.getNumFound() + "件ヒットしました。");

                // ハイライト情報を取得して加工
                Map<String,Map<String,List<String>>> highlighting = response.getHighlighting();
                for (SolrDocument doc : list) {
                    String id = (String) doc.getFieldValue("id");
                    Map<String, List<String>> map = highlighting.get(id);
                    List<String> contentList = map.get("content");
                    for(String val : contentList) {
                        val = val.replaceAll("[\r\n\t]", "");
                        System.out.println("val=" + val);
                    }
                }
            }
        } catch (SolrServerException e) {
            System.out.print("SolrServerException Occured!\r\n");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.print("IOException Occured!\r\n");
            e.printStackTrace();
        } finally {
            try {
                // コミットして、コネクションをクローズ
                client.commit();
                client.close();
            } catch (Exception e) {
                System.out.print("Exception Occured!\r\n");
                e.printStackTrace();
            }
        }
        System.out.print("end: main\r\n");
    }
}

「Standard Highlighter」を使用するためのパラメータ設定は57~63行目です。

検索結果をハイライト文字列として取得しているのが76~83行目。

ハイライト文字をWEB画面上に出力することを想定して、改行とタブを削除しています。


以下のようなテキストファイルをApacheSolrに登録して、検索を試してみます。

主食と野菜をしっかり食べさせよう

このテキストファイルが登録されている状態で『野菜』というキーワードで検索してみます。

すると、以下のような文字列がハイライト文字としてApacheSolrから返却されてきます。

主食と<b>野</b><b>菜</b>をしっかり食べさせよう


これをWEBページに出力すると以下のような表示になります。

主食とをしっかり食べさせよう


検索してヒットした「野菜」という文字が強調表示されています。

これが、ハイライト表示の基本的な動きになります。


Standard Highlighter のパラメータ

サンプルプログラムで、Standard Highlighter を使用した検索をおこない、ハイライト文字を取得できます。

このハイライト表示はパラメータでチューニング可能です。

具体的には以下の部分。

    solrQuery.set("hl", true);
    solrQuery.set("hl.fl", "content");
    solrQuery.set("hl.simple.pre", "<b>");
    solrQuery.set("hl.simple.post", "</b>");
    solrQuery.set("hl.fragsize", 20);
    solrQuery.set("hl.maxAnalyzedChars", 100);

「Standard Highlighter」を使用するには、上記のパラメータを設定するだけでOKです。

各パラメータの意味合いは以下になります。


パラメータ

デフォルト

説明

hl

blank

このパラメータを「true」にする、ハイライトがオンになります。

デフォルトはブランク(オフ)です。

hl.fl

blank

ハイライトの対象となるフィールド。

複数定義する場合は、カンマ区切りで指定します。

hl.simple.pre

<em>

ハイライト表示する文字の前に挿入する文字。

hl.simple.post

</em>

ハイライト表示する文字の後に挿入する文字。

hl.fragsize

100

スニペットひとつあたりの最大文字数。

hl.maxAnalyzedChars

51200

スニペットの処理対象最大文字数。


スニペットとは、”検索キーワードを含む文書”です。

今回の例では「hl.fragsize」は20としているので、”検索キーワードを含む文書”は最大20文字です。


例えば、下のような150文字の文字列があるとします。

ああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ主食と野菜をしっかり食べさせよういいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいい

この文字に対して「野菜」を検索キーワードとして指定します。

その際の返却文字列は以下となります。20文字です。

ああああ主食とをしっかり食べさせよう

「hl.fragsize」は20なので、返却される文字列は最大20文字、ということですね。


まとめ

いかがでしたでしょうか?

「Standard Highlighter」の使い方について、ある程度わかって頂けたかと思います。


ハイライト表示は、「Standard Highlighter」以外に以下の2つがあります。

  • FastVector Highlighter
  • Postings Highlighter

次回は、「FastVector Highlighter」を使ったハイライト表示を紹介したいと思います。


それではまた!



Apache Solrを使ったドキュメント検索 – 環境構築とJavaからの実行まで

ApacheSolr

文書を管理するシステムの場合、文書の中身を検索する機能が必要だったりします。


例えば、”システムに登録されているWordファイルについて、「○△□」といった文字が存在するファイルだけ注出する”といった機能です。


でも、そういった機能をゼロから作るのは大変ですよね。

プログラムを作るとしたら、”ファイルを開いて”→ “ファイルの中身の文字を注出して”→”キーワードと一致する文字を検索して”といった作り込みが必要です。


今回は、Apache SolrをJavaから呼び出し、文書を登録、編集、削除する方法を説明していきます。


『Apache Solr』を導入すると、文書の管理(登録、編集、削除)や文書の検索をAPI形式で実行することができ、簡単に文書管理をおこなうシステムを構築することができます。

  • 文書の管理(登録、編集、削除)
  • 文書の検索
  • 文書検索結果の返却

今回は、Apache Solrの開発環境構築から、JavaからAPIを呼び出して結果を取得するまでを紹介します。


Apache Solrのインストール(サービス化)


まずは、Apache Solrをインストールします。

今回は、Apache Solrをサービス化する形でインストールします。

サービス化は、「nssm」を使用します。

nssmは、Microsoft Windows用のサービスマネージャです。

サービス化すると、バックグラウンドおよびフォアグラウンドのサービスとプロセスを管理する無料のユーティリティです。 プログラムは、失敗したサービスを自動的に再始動するように設定できます。

以下サイトからダウンロード可能です。


という訳で、環境情報としては以下になります。


  • 文書検索エンジン:Apache Solr 6.4.2
  • サービス化ツール:nssm 2.24

公式サイトからダウンロードしたApache Solrのモジュールとnssmを、任意のフォルダに配置します。

筆者は、Apache Solrを「C:\ApacheSolr」直下に、nssmを「C:\SolrInstall」に配置しました。


以下のような感じです。

ApacheSolrの配置
Solrの配置

Apache Solrの”インストール”というのは不要で、マシンに配置して完了です。

nssmを使用して、配置したApache Solrをサービス化します。


それでは、nssmでのサービス化をおこないます。

DOSプロンプトで、nssmを配置したディレクトリに移動し、nssm installコマンドを実行します。



コマンドを実行するとサービスインストーラのウィンドウがひらくので、必要な情報を入力します。


  • Path:Apache Solrの起動コマンドパス
  • Startup directory:ApacheSolrの起動ディレクトリ
  • Arguments:起動パラメータ
  • Service name:サービス名

以下のように入力します。


ApacheSolrのサービス化

「Arguments」には、起動パラメータと同時に使用するポートを記載します。

筆者は、8983ポートを使用するようにしました。


全ての入力が完了したら、「Install service」ボタンを押下します。

以下のダイアログが表示されたらインストール完了です。

ApacheSolrのサービス化完了

それでは、きちんとサービス化されたかどうかを確認してみましょう。 

サービス一覧で「Apace Solr」が存在すれば、サービス化完了です。

「開始」を選択して、Apache Solrを起動してください。

ApacheSolrの起動

ステータスが「開始」になったことを確認しましょう。


きちんと起動できたかを確認するためには、ブラウザでApache SolrのWeb画面を表示します。

ブラウザで入力するURLは「http://localhost:8984」です。

以下のようにダッシュボードが表示されれば、起動成功です。

ApacheSolrのダッシュボード

コアを作成


Apache Solrの準備ができたら、Apache Solrで『コア』を作成して使用するための準備をおこないます。


『コア』とは何か?という説明は、いまは省略します。

とりあえず動かしたいので。

イメージとしては、データベースのスキーマでしょうかね?


コアの作成方法はいろいろあるみたいなのですが、筆者はDOSプロンプトでコマンドを実行する形でコアを作成します。

コアの名前は「java_sample」としました。


ApacheSolrのコア作成

コアの作成が完了したら、Apache Solrのサービスを再起動します。

再起動後にダッシュボードを確認します。

「java_sample」というコアが作成されています。


コアを選択すると、ダッシュボードにコアの情報が表示されます。

ApacheSolrでのコア作成

これで、Apache Solrの準備完了です。


文書を登録


文書の登録を、JavaからAPIを実行する形でおこなうことが可能です。

サンプルプログラムは以下となります。


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.File;

import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.common.util.ContentStreamBase;

public class SolrInsert {

    public static void main(String[] args){
        System.out.print("start: main\r\n");

        // idとファイル名をパラメータから取得
        String id = args[0];
        String fileName = args[1];

        // Solrのインスタンス作成
        SolrClient client = new HttpSolrClient.Builder(
            "http://localhost:8983/solr/java_sample").build();

        try {
            // APIの実行準備
            File file = new File(fileName);
            ContentStreamUpdateRequest update = 
                new ContentStreamUpdateRequest("/update/extract");
            update.addContentStream(
                new ContentStreamBase.FileStream(file));

            // idとファイル名をパラメータに指定
            // 既に登録済のIDを指定した場合、登録ではなく更新となる
            update.setParam("literal.id", id);
            update.setParam("literal.filename", file.getName());

            // コマンドを実行
            client.request(update);
        } catch (SolrServerException e) {
            System.out.print("SolrServerException Occured!\r\n");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.print("IOException Occured!\r\n");
            e.printStackTrace();
        } finally {
            try {
                // コミットして、コネクションをクローズ
                client.commit();
                client.close();
            } catch (Exception e) {
                System.out.print("Exception Occured!\r\n");
                e.printStackTrace();
            }
        }
        System.out.print("end: main\r\n");
    }
}

重要な部分は、強調表示している26行目から37行目の部分です。

実際にSolrに対して、「/update/extract」というAPIを呼び出して登録コマンドを実行しています。


メインとなる使用クラスは「ContentStreamUpdateRequest 」。「addContentStream」で電子ファイルの実態を指定し、setParamでパラメータを指定しています。

指定しているパラメータは2つで、IDとファイル名。


コメントにも記載していますが、同じIDを指定した場合はドキュメントの更新になります


プログラムの動作結果を、ApacheSolrのダッシュボードで確認してみます。

まだ、1つドキュメントが登録されていない状態であれば、以下のようになります。


ApacheSolrのコンソール。ドキュメント数が0。

この状態でプログラムを実行してみます。


IDとファイル名はパラメータで指定可能となっていますが、IDは101、ファイル名はtemplate.xlsxを指定して、プログラムを実行します。

プログラムの実行後にコンソール画面を確認すると、ドキュメントが登録されていることが確認できます。


ApacheSolrのドキュメント数。0から1に。

登録した結果を、もうちょっと詳しくみてみます。

Apache Solrのダッシュボードでは、ドキュメントの検索が可能です。

この検索を使って、登録したドキュメントを確認してみます。


ApacheSolrダッシュボード。

登録のときに指定したIDとファイル名のドキュメントが登録されていることがわかります。


次に「更新」をおこなってみます。

プログラムは「登録」の時のプログラムと同様です。

同じIDを指定すれば、Apache Solrが更新をおこなってくれます。

試しに、IDは登録の時に指定したIDと同じ「101」を指定し、ファイル名を「template_2.xlsx」に変更して、登録の時と同じプログラムを実行してみます。


実行した後に、Apache Solrのダッシュボードを確認すると、以下のようになります。


ApacheSolrダッシュボード。更新後。

検索結果のドキュメント数は変わっておらず(numFoundが1)、id=101のドキュメントファイル名が、「template.xlsx」から「template_2.xlsx」に変わっています。

つまり、「登録」ではなく「更新」をおこなったことがわかります。


文書を全部削除


文書の削除もAPIから可能です。

まずは、全削除のAPIを作成して実行してみます。


削除するまえに、ApacheSolrにドキュメントを3つ登録してみました。

ApacheSolrのダッシュボードで、ドキュメントが3つ登録されていることを確認しておきます。


ApacheSolrダッシュボード。

ドキュメントが3つ登録されていることが確認できました。

この状態で、ドキュメント全削除のプログラムを実行します。


サンプルプログラムは以下です。

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.File;

import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.common.util.ContentStreamBase;

public class SolrAllDelete {

    public static void main(String[] args){
        System.out.print("start: main\r\n");

        // Solrのインスタンス作成
        SolrClient client = new HttpSolrClient.Builder(
            "http://localhost:8983/solr/java_sample").build();

        try {
            // APIを実行
            String deleteQuery = "*:*";
            client.deleteByQuery(deleteQuery);
        } catch (SolrServerException e) {
            System.out.print("SolrServerException Occured!\r\n");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.print("IOException Occured!\r\n");
            e.printStackTrace();
        } finally {
            try {
                // コミットして、コネクションをクローズ
                client.commit();
                client.close();
            } catch (Exception e) {
                System.out.print("Exception Occured!\r\n");
                e.printStackTrace();
            }
        }
        System.out.print("end: main\r\n");
    }
}

実際の削除処理をおこなっている部分は、23行目と24行目になります。。

「SolrClient」クラスの「deleteByQuery」メソッドを使用して削除をおこなっています。

クエリパラメータに「*:*」を指定すると全削除、になります。


ApacheSolrのダッシュボードを確認するとドキュメント数が0になっており、全削除されていることがわかります。


ApacheSolrのダッシュボード。全削除後。

文書を一部削除


文書を全削除する方法を紹介しましたが、実際のシステムではあまりニーズはないかと思います。

あるとすれば、全削除ではなく指定したドキュメントのみを削除、ですかね。


指定したドキュメントのみを削除することも可能です。

サンプルプログラムは以下になります。


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.File;

import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.common.util.ContentStreamBase;

public class SolrOneDelete {

    public static void main(String[] args){
        System.out.print("start: main\r\n");

        // Solrのインスタンス作成
        SolrClient client = new HttpSolrClient.Builder(
            "http://localhost:8983/solr/java_sample").build();

        try {
            // APIを実行
            String deleteQuery = "id:(102)";
            client.deleteByQuery(deleteQuery);
        } catch (SolrServerException e) {
            System.out.print("SolrServerException Occured!\r\n");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.print("IOException Occured!\r\n");
            e.printStackTrace();
        } finally {
            try {
                // コミットして、コネクションをクローズ
                client.commit();
                client.close();
            } catch (Exception e) {
                System.out.print("Exception Occured!\r\n");
                e.printStackTrace();
            }
        }
        System.out.print("end: main\r\n");
    }
}

idが102のドキュメントを削除しています。

解り易いように、IDをハードコーディングしていますが、23行目と24行目です。


複数のドキュメントを削除する場合は、ID指定をORで繋いで指定すればよいです。

    String deleteQuery = "id:(201 OR 202)";
    client.deleteByQuery(deleteQuery);

文書を検索


次に文書の検索です。

ApacheSolrの肝となる部分ですね。

今回は「とりあえず動かす編」ということで、ファイル名での検索をおこないます。


本来であれば、登録したドキュメントの中身に対する検索をおこないたいところですが、それについてはまた次回、という事で。

今回は、とりあえず動かしちゃいましょう!


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.util.ClientUtils;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.Group;
import org.apache.solr.client.solrj.response.GroupCommand;
import org.apache.solr.client.solrj.response.GroupResponse;
import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrDocument;

public class SolrSearch {

    public static void main(String[] args){
        System.out.print("start: main\r\n");

        // Solrのインスタンス作成
        SolrClient client = new HttpSolrClient.Builder(
            "http://localhost:8983/solr/java_sample").build();

        SolrQuery solrQuery = new SolrQuery();

        // 検索結果として、文書IDを返却するよう設定
        solrQuery.setFields("id");

        // 検索結果の上限は100件
        solrQuery.setRows(100);

        try {
            StringBuilder queryString = new StringBuilder();
            String keyword = args[0];
            if (keyword.equals("")) {
                queryString.append("*");
            } else {
                String queryPhrase = "\"" + 
                    ClientUtils.escapeQueryChars(keyword) + "\"";
                queryString.append("(");
                queryString.append("filename:");
                queryString.append("*" + queryPhrase + "*");
                queryString.append(")");
            }

            // 検索実行
            solrQuery.set("q", queryString.toString());
            QueryResponse response = client.query(solrQuery);

            // 検索結果を表示
            SolrDocumentList list = response.getResults();
            if (list == null) {
                System.out.println("文書は存在しませんでした。");
            } else {
                System.out.println(list.getNumFound() + 
                    "件ヒットしました。");
                for (SolrDocument doc : list) {
                    System.out.println(doc.get("id"));
                }
            }
        } catch (SolrServerException e) {
            System.out.print("SolrServerException Occured!\r\n");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.print("IOException Occured!\r\n");
            e.printStackTrace();
        } finally {
            try {
                // コミットして、コネクションをクローズ
                client.commit();
                client.close();
            } catch (Exception e) {
                System.out.print("Exception Occured!\r\n");
                e.printStackTrace();
            }
        }
        System.out.print("end: main\r\n");
    }
}

まず、35行目と36行目で、返却するフィールドを定義しています。

このサンプルプログラムでは、「id」を返却するようにしています


検索条件の設定部分は、47行目から51行目部分です。

サンプルプログラムでは、ファイル名を格納している「filename」フィールドに対して検索をおこなうようにしています

あと、検索ワードはエスケープしています。


まとめ

いかがでしたでしょうか?


紹介した方法でApache Solrを構築してサンプルプログラムを実行すれば、といあえず一連の動作は動きます。

しかし、ApacheSolrの醍醐味はやっぱり検索です。

ApacheSolrの検索には、いろいろなオプションが存在しますので、次回の記事でそこら辺を紹介していきます。


それではまた!



Javaでエクセルを操作したい!POIの環境構築からセルの背景色を設定して出力まで

ApachePOI

こんにちは。

さくさくTECHブロガーの「さく」です。


Webアプリケーションの開発では、“○○○一覧画面の内容をエクセルに出力する”といった場面がよく出てきます。

さらに、“○○○の条件に一致したらセルを赤くして!”といったユーザからのリクエストもありえますね。


Javaからエクセルを操作するための方法として、POIを使ったエクセル操作がとても便利です。

ライブラリが豊富で、ある程度の要件ついては、ほぼ対応することができます。


という訳で今回は、POIを使ってJavaでエクセル操作をおこなう方法を紹介します。


Apache POI

Javaでエクセルを扱うといえば。。。『Apache POI』しかないでしょう!

Apache POIは、Java用のエクセル操作ライブラリになります。

エクセルファイルの読み書き、セルを指定して文字列を出力、セル背景色を変更、といった様々な機能をもっています。

Apache POIの詳細については公式ページを参照ください。

ライブラリ本体(JARファイル)も、公式ページからダウンロードできます。



環境を構築

Apache POIを組み込んだJavaのバッチプログラムを動かすための環境を構築します。

今回、サンプルプログラムを作成した実行した環境としては以下になります。

  • OS:Windows 7
  • Java:Java1.8.0_40
  • Apache POI:poi 4.1.0

Javaのメインクラスを作って、必要なライブラリをダウンロードしてクラスパスを通して。。。といった具合で環境は作れるのですが、筆者はいくつかつまずきました。

ビルドはすんなり通ったのですが、実行しようとしても「NoClassFound」が発生してしまう、などですね。

結果、うまくいったのですが、ビルドする際に必要なJARと実行する際に必要なJARを記載しておきます。


ビルドする際に必要なJAR

  • poi-4.1.0.jar
  • poi-ooxml-4.1.0.jar
  • poi-ooxml-schemas-4.1.0.jar
  • poi-scratchpad-4.1.0.jar

実行する際に必要なJAR

  • poi-4.1.0.jar
  • poi-ooxml-4.1.0.jar
  • poi-ooxml-schemas-4.1.0.jar
  • poi-scratchpad-4.1.0.jar
  • xmlbeans-3.1.0.jar
  • commons-collections4-4.3.jar
  • commons-compress-1.18.jar

サンプルコードを実行するためには、上記のJARをクラスパスに設定する必要があります。


サンプルコードと解説

では実際にプログラムを作成して実行してみます。

以下を実行するプログラムを作成しました。


  1. テンプレートファイルである「template.xlsx」を読み込み。
  2. 3つのセルに文字列を書き込み。同時にそれぞれのセルの背景色設定。
  3. 作成したエクセルファイルを「out.xlsx」として保存。

簡単なプログラムですが、セルに色を付ける方法は理解頂けると。

プログラムは以下になります。


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.CellValue;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.usermodel.IndexedColors;

public class PoiColor {

    public static void main(String[] args){
        System.out.print("start: main\r\n");

        Workbook tempbook = null;
        try {
            //--- テンプレートファイルをひらいて、シートを指定 --//
            Path tempPath = Paths.get("./template.xlsx");
            InputStream inSt = Files.newInputStream(tempPath);
            tempbook = new XSSFWorkbook(inSt);
            Sheet sheet = tempbook.getSheet("Sheet1");

            //--- A1セルへの書き込み --//
            //--- セル背景色は栗色 --//
            Row row = sheet.createRow(0);
            Cell cell = row.createCell(0);
            cell.setCellValue("あああ");
            CellStyle styleA1 = tempbook.createCellStyle();
            styleA1.setFillPattern(
                FillPatternType.SOLID_FOREGROUND);
            styleA1.setFillForegroundColor(
                IndexedColors.MAROON.getIndex());
            cell.setCellStyle(styleA1);

            //--- A2セルへの書き込み --//
            //--- セル背景色は青 --//
            row = sheet.createRow(1);
            cell = row.createCell(0);
            cell.setCellValue("いいい");
            CellStyle styleA2 = tempbook.createCellStyle();
            styleA2.setFillPattern(
                FillPatternType.SOLID_FOREGROUND);
            styleA2.setFillForegroundColor(
                IndexedColors.BLUE.getIndex());
            cell.setCellStyle(styleA2);

            //--- A3セルへの書き込み --//
            //--- セル背景色は緑 --//
            row = sheet.createRow(2);
            cell = row.createCell(0);
            cell.setCellValue("ううう");
            CellStyle styleA3 = tempbook.createCellStyle();
            styleA3.setFillPattern(
                FillPatternType.SOLID_FOREGROUND);
            styleA3.setFillForegroundColor(
                IndexedColors.GREEN.getIndex());
            cell.setCellStyle(styleA3);

            //--- out.xlsxとしてファイル出力 --//
            Path outPath = Paths.get("./out.xlsx");
            OutputStream outSt = Files.newOutputStream(outPath);
            tempbook.write(outSt);
        } catch (IOException e) {
            System.out.print("入出力例外が発生!");
        } finally {
            //--- テンプレートをクローズ --//
            try {
                if (tempbook != null) {
                    tempbook.close();
                }
            } catch (IOException e) {
                System.out.print("終了処理で入出力例外が発生!");
            }
        }
        System.out.print("end: main\r\n");
    }
}

このプログラムを実行すると、以下のエクセル(out.xlsx)が出力されます。

ApachePOIを使用したエクセルの出力

では、ポイントを説明していきます。

エクセルの読み込み・書き込み

Apahce POIは「XSSFWorkbook」というクラスでエクセルファイル(ブック)を管理します。

このファイルにInputStreamで読み込んだエクセルファイルを食わせてあげるだけですね。


    Path tempPath = Paths.get("./template.xlsx");
    InputStream inSt = Files.newInputStream(tempPath);
    tempbook = new XSSFWorkbook(inSt);
    Sheet sheet = tempbook.getSheet("Sheet1");

一番最後の「getSheet」は、対象となるシートを指定しています。

“Sheet1をカレントにしている”という感じですね。

セルの指定と文字列書き込み

行(row)と列(colum)を指定する形でセルを特定し、文字列出力などの操作をおこないます。

普段使っているエクセルでは、行は1始まりの番号で、列はAから始まるアルファベットですが、Apache POIは両方とも0オリジンの数値指定です。


    //--- A1セルへの書き込み --//
    //--- セル背景色は栗色 --//
    Row row = sheet.createRow(0);
    Cell cell = row.createCell(0);
    cell.setCellValue("あああ");

上はサンプルコードの一部抜粋ですが、このプログラムで、エクセルのA1セルに”あああ”という文字列を出力しています。

色の指定(スタイル指定)

セルに対する色指定は、スタイル指定という方法で可能です。

サンプルコードでは背景色を設定しています。


    CellStyle styleA1 = tempbook.createCellStyle();
    styleA1.setFillPattern(FillPatternType.SOLID_FOREGROUND);
    styleA1.setFillForegroundColor(IndexedColors.MAROON.getIndex());
    cell.setCellStyle(styleA1);

背景色の設定は、『塗り潰しパターン』と『色』を指定することで設定可能です。

『塗り潰しパターン』は、CellStyle クラスのsetFillPatternメソッドで変更可能です。

設定する値は、FillPatternTypeクラスに列挙型として定義されているようです。


NO_FILL

SOLID_FOREGROUND

FINE_DOTS

ALT_BARS

SPARSE_DOT

THICK_HORZ_BANDS

THICK_VERT_BANDS

THICK_BACKWARD_DIAG

THICK_FORWARD_DIAG

BIG_SPOTS

BRICKS

THIN_HORZ_BANDS

THIN_VERT_BANDS

THIN_BACKWARD_DIAG

THIN_FORWARD_DIAG

SQUARES

DIAMONDS

LESS_DOTS

LEAST_DOTS


試しに塗りつぶしパターンを「DIAMONDS」にしてみます。

そうすると、ちょっと出力結果が変わってきます。



格子上の塗りつぶしになりました。

これってつまり、エクセルのセルの書式設定で変更できるやつ、ですね。


FillPatternTypeをDIAMONDに指定

『色』は、CellStyle クラスのsetFillForegroundColorメソッドで変更可能です。

色については、「IndexedColors」に定義されている列挙型で定義されている色のインデックスを指定します。


AQUA

AUTOMATIC

BLACK

BLACK1

BLUE

BLUE_GREY

BLUE1

BRIGHT_GREEN

BRIGHT_GREEN1

BROWN

CORAL

CORNFLOWER_BLUE

DARK_BLUE

DARK_GREEN

DARK_RED

DARK_TEAL

DARK_YELLOW

GOLD

GREEN

GREY_25_PERCENT

GREY_40_PERCENT

GREY_50_PERCENT

GREY_80_PERCENT

INDIGO

LAVENDER

LEMON_CHIFFON

LIGHT_BLUE

LIGHT_CORNFLOWER_BLUE

LIGHT_GREEN

LIGHT_ORANGE

LIGHT_TURQUOISE

LIGHT_TURQUOISE1

LIGHT_YELLOW

LIME

MAROON

OLIVE_GREEN

ORANGE

ORCHID

PALE_BLUE

PINK

PINK1

PLUM

REDRED1

ROSE

ROYAL_BLUE

SEA_GREEN

SKY_BLUE

TAN

TEAL

TURQUOISE

TURQUOISE1

VIOLET

WHITE

WHITE1

YELLOW

YELLOW1

 


どんな色かは、名前からだいたいで察してもらえればと。

まとめ

いかがでしたでしょうか?

Apache POIの基本的な使い方と、セルの色設定方法についてわかって頂いたかと思います。



まとめ
  • Javaでエクセルを操作するためには、Apache POIをつかうべき!
  • Apache POIを使えば、エクセルへの文字列出力だけではなく、色設定も簡単におこなうことができる

それではまた!



Javaの文字列連結は速度を考慮して、「+」ではなく「StringBuffer」を使うべき

Java

今回は、一般的に言われているJavaの文字列連結について紹介します。


僕は都内でSIerをやっているのですが、新人時代のJava研修で教わったことがあります。


 

文字列連結する場合は「+」演算子を使わずに「StringBuffer」を使え!

 


Java研修の時は”へー。そーなんだ。”くらいでしか考えていませんでした。

理由は性能(スピード)だと教えてもらったかと思いますが、ちゃんと理解できず。。。です。

「百聞を一見し如かず」ということで、実際に検証してみようと思います。


環境情報

実際にJavaのプログラムを作って、僕のローカルマシンで動かす形で検証します。

ローカルマシンのスペックは以下になります。

  • OS:Windows7 Professional
  • CPU:Inter Pentium 2.3GHz
  • メモリ:4.00 GB
  • Java:1.7.0_02

あんまりいいパソコン使ってないんですよね。

でもまあ、検証するには十分な環境かと思います。

「+」演算子で文字列をする

まずは、「+」演算子で文字列を連結した場合のプログラムを作ります。

実際に作成したプログラムは以下となります。

“add+数字”の文字列追加を5万回繰り返すプログラムです。

全て、「+」演算子で文字列を連結しています。

import java.text.SimpleDateFormat;
import java.util.Date;

public class PlusConnect {
     public static void main(String[] args){
        System.out.print("start\r\n");

        //-- 日付フォーマット作成 --//
        SimpleDateFormat fmt = 
            new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
        String str = "";

        //-- 文字列の連携を指定回数だけ実行 --//
        for (int lc = 0; lc &amp;amp;lt; 50000; lc++) {
            str = str + "add" + Integer.toString(lc);

            //-- 一定の間隔でログ出力 --//
            if (lc % 10000 == 0) {
                Date now = new Date();
                System.out.print(fmt.format(now) + 
                ":" + Integer.toString(lc) + 
                "回目のループ終了\r\n");
            }
        }

        //-- 文字列長を表示して終了 --//
        System.out.print(
            "文字列長=" + Integer.toString(str.length()));
        System.out.print("end\r\n");
     }
}

上記プログラムの実行結果は以下になります。

hoge>java -Xms256M -Xmx256m PlusConnect
start
2019/04/28 21:43:53.487:0回目のループ終了
2019/04/28 21:43:55.066:10000回目のループ終了
2019/04/28 21:43:59.256:20000回目のループ終了
2019/04/28 21:44:05.239:30000回目のループ終了
2019/04/28 21:44:12.774:40000回目のループ終了
文字列長=388890
end

コメントが解り辛くて申し訳ないです。

「0回目のループ終了」が開始日時で、「40000回目のループ終了」が終了日時です。

約19秒経過していることがわかります。


「StringBuffer」で文字列を連結する

次に、「StringBuffer」で文字列を連結した場合のプログラムを作ります。

実際に作成したプログラムは以下となります。

「StringBuffer」の「append」を使用して文字列を連結するプログラムです。

「+」演算子と同様に、文字列追加を5万回繰り返すプログラムです。

import java.text.SimpleDateFormat;
import java.util.Date;

public class BufferConnect {
    public static void main(String[] args){
        System.out.print("start\r\n");

        //-- 日付フォーマット作成 --//
        SimpleDateFormat fmt = 
            new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
        StringBuffer buf = new  StringBuffer();

        //-- 文字列の連携を指定回数だけ実行 --//
       	for (int lc = 0; lc <= 50000; lc++) {
            buf = buf.append("add");
            buf = buf.append(Integer.toString(lc));

            //-- 一定の間隔でログ出力 --//
            if (lc % 10000 == 0) {
                Date now = new Date();
                System.out.print(fmt.format(now) + ":" + 
                    Integer.toString(lc) + "回目のループ終了\r\n");
            }
        }

        //-- 文字列長を表示して終了 --//
        System.out.print("文字列長=" + 
            Integer.toString(buf.length()) + "\r\n");
        System.out.print("end\r\n");
    }
}

上記プログラムの実行結果は以下になります。

hoge>java -Xms256M -Xmx256m BufferConnect
start
2019/04/28 21:55:55.539:0回目のループ終了
2019/04/28 21:55:55.620:10000回目のループ終了
2019/04/28 21:55:55.651:20000回目のループ終了
2019/04/28 21:55:55.658:30000回目のループ終了
2019/04/28 21:55:55.664:40000回目のループ終了
文字列長=388890
end

こちらは、1秒もかかっておらず約0.5秒ですね。


結果

性能では以下の結果となりました。


  • 「+」演算子で文字列を連結する=19秒
  • 「StringBuffer」で文字列を連結する=0.5秒

38倍の差です。

あきらかに「StringBuffer」を使用した方が性能がよいということがわかります。

コーディングの手間を考えると+演算子の方が簡単なので、ついつい手を抜いてしまうんですが、ある程度の数の文字列を連結する場合は、StringBuffrerを使用するべきです。


しかし上記は、ループを5万回繰り返すといった極端な検証の結果となります。

単純な文字列結合では、気にする程度の差は出なかったりします。


まとめ

やっぱり、Java研修で教えてもらった内容は間違っていなかったという結論になりました。


まとめ
  • 性能を考えると、確実にStringBufferを使った方がいい。
  • +演算子とStringBufferは、約40倍の差が出る(場合がある)。
  • “ループ内で文字列結合する場合はStringBufferを使う!”と認識しておけば問題なさそう!

という訳で、皆さん、文字列連結する場合は面倒くさがらずにStringBufferを使うようにしましょう!


それではまた!