- 投稿日:2020-02-26T23:19:32+09:00
Java で文字列や値から Enum 定数を逆引きする
概要
- Java で値から Enum 定数を取得するサンプルコード
- サンプルコードは javac コマンドでビルドして java コマンドで実行する
- 動作確認環境: macOS Catalina + Java 8 (AdoptOpenJDK 1.8.0_242-b08)
Enum は Java 5 (コードネーム Tiger) から使用できる
Tiger では、Java™ プログラミング言語で列挙された型を言語的にサポートしました。列挙のもっとも簡単な形式では、C、C++、および C# の形式に似ています。
サンプルコード
package com.example.enums; /** シンプルな列挙型を定義 */ public enum MetaName { FOO, BAR, BAZ; }package com.example.enums; /** 値を1つ持つ列挙型を定義 */ public enum OneValueEnum { ONE(1), TWO(2), THREE(3); private final int number; /** * 値を指定して enum 定数を生成。 */ OneValueEnum(int number) { this.number = number; } /** * 値を返すメソッドを用意。 */ public int getNumber() { return number; } /** * 値に合致する enum 定数を返す。 */ public static OneValueEnum getByNumber(int number) { // 値から enum 定数を特定して返す処理 for (OneValueEnum value : OneValueEnum.values()) { if (value.getNumber() == number) { return value; } } return null; // 特定できない場合 } }package com.example; import com.example.enums.MetaName; import com.example.enums.OneValueEnum; public class Main { public static void main(String[] args) { testMetaName(); testOneValueEnum(); } private static void testMetaName() { try { // 自動生成される valueOf メソッドを使うと、文字列から Enum 定数を取得できる System.out.println("***** FOO *****"); MetaName foo = MetaName.valueOf("FOO"); System.out.println("foo.getClass=" + foo.getClass()); System.out.println("foo.getDeclaringClass=" + foo.getDeclaringClass()); System.out.println("foo.toString=" + foo.toString()); // 存在しない定数を取得しようとすると IllegalArgumentException が発生する System.out.println("***** bar *****"); MetaName bar = MetaName.valueOf("bar"); } catch (Exception e) { e.printStackTrace(); } } private static void testOneValueEnum() { try { // 自動生成される valueOf メソッドを使うと、文字列から Enum 定数を取得できる System.out.println("***** ONE *****"); OneValueEnum one = OneValueEnum.valueOf("ONE"); System.out.println("one.getClass=" + one.getClass()); System.out.println("one.getDeclaringClass=" + one.getDeclaringClass()); System.out.println("one.toString=" + one.toString()); System.out.println("one.getNumber=" + one.getNumber()); // 値に合致する Enum 定数を取得するためには、自前実装が必要 System.out.println("***** 1 *****"); OneValueEnum ichi = OneValueEnum.getByNumber(1); // 自前実装メソッドをコール System.out.println("ichi.getClass=" + ichi.getClass()); System.out.println("ichi.getDeclaringClass=" + ichi.getDeclaringClass()); System.out.println("ichi.toString=" + ichi.toString()); System.out.println("ichi.getNumber=" + ichi.getNumber()); // 値に合致する Enum 定数が無かったので null を返すようにしている // (自前実装なので任意の処理をすれば良い) System.out.println("***** 9 *****"); OneValueEnum kyu = OneValueEnum.getByNumber(9); System.out.println("kyu=" + kyu); } catch (Exception e) { e.printStackTrace(); } } }サンプルコードのビルド
ソースコードを配置。
$ tree . └── src └── com └── example ├── Main.java └── enums ├── MetaName.java └── OneValueEnum.javaクラスファイル出力先のディレクトリを作成。
$ mkdir destjavac コマンドでコンパイル。
$ javac -sourcepath src src/com/example/Main.java -d destクラスファイルが生成されているのを確認。
$ tree . ├── dest │ └── com │ └── example │ ├── Main.class │ └── enums │ ├── MetaName.class │ └── OneValueEnum.class └── src └── com └── example ├── Main.java └── enums ├── MetaName.java └── OneValueEnum.javaサンプルの実行結果
$ java -classpath dest com.example.Main ***** FOO ***** foo.getClass=class com.example.enums.MetaName foo.getDeclaringClass=class com.example.enums.MetaName foo.toString=FOO ***** bar ***** java.lang.IllegalArgumentException: No enum constant com.example.enums.MetaName.bar at java.lang.Enum.valueOf(Enum.java:238) at com.example.enums.MetaName.valueOf(MetaName.java:3) at com.example.Main.testMetaName(Main.java:23) at com.example.Main.main(Main.java:9) ***** ONE ***** one.getClass=class com.example.enums.OneValueEnum one.getDeclaringClass=class com.example.enums.OneValueEnum one.toString=ONE one.getNumber=1 ***** 1 ***** ichi.getClass=class com.example.enums.OneValueEnum ichi.getDeclaringClass=class com.example.enums.OneValueEnum ichi.toString=ONE ichi.getNumber=1 ***** 9 ***** kyu=null参考資料
- 投稿日:2020-02-26T22:59:40+09:00
OpenJDK 8 の java コマンドと javac コマンドのヘルプ
環境
- macOS Catalina + AdoptOpenJDK 1.8.0_242-b08
java -help
$ java -help 使用方法: java [-options] class [args...] (クラスを実行する場合) または java [-options] -jar jarfile [args...] (jarファイルを実行する場合) optionsには次のものがあります。 -d32 使用可能な場合は32ビットのデータ・モデルを使用する -d64 使用可能な場合は64ビットのデータ・モデルを使用する -server "server" VMを選択する場合 デフォルトVMはserverです, これはサーバークラスのマシンで実行しているためです。 -cp <ディレクトリおよびzip/jarファイルのクラス検索パス> -classpath <ディレクトリおよびzip/jarファイルのクラス検索パス> クラス・ファイルを検索するディレクトリ、 JARアーカイブおよびZIPアーカイブの:で区切られたリストです。 -D<name>=<value> システム・プロパティを設定する -verbose:[class|gc|jni] 詳細な出力を行う -version 製品バージョンを出力して終了する -version:<value> 警告: この機能は非推奨であり、詳細のリリースで 廃止されます。 指定したバージョンを実行に必須にする -showversion 製品バージョンを出力して続行する -jre-restrict-search | -no-jre-restrict-search 警告: この機能は非推奨であり、詳細のリリースで 廃止されます。 ユーザーのプライベートJREをバージョン検索に含める/除外する -? -help このヘルプ・メッセージを出力する -X 非標準オプションに関するヘルプを出力する -ea[:<packagename>...|:<classname>] -enableassertions[:<packagename>...|:<classname>] 指定した粒度でアサーションを有効にする -da[:<packagename>...|:<classname>] -disableassertions[:<packagename>...|:<classname>] 指定した粒度でアサーションを無効にする -esa | -enablesystemassertions システム・アサーションを有効にする -dsa | -disablesystemassertions システム・アサーションを無効にする -agentlib:<libname>[=<options>] ネイティブ・エージェント・ライブラリ<libname>をロードする。例: -agentlib:hprof -agentlib:jdwp=helpと-agentlib:hprof=helpも参照 -agentpath:<pathname>[=<options>] フルパス名でネイティブ・エージェント・ライブラリをロードする -javaagent:<jarpath>[=<options>] Javaプログラミング言語エージェントをロードする。java.lang.instrumentを参照 -splash:<imagepath> 指定したイメージでスプラッシュ画面を表示する 詳細はhttp://www.oracle.com/technetwork/java/javase/documentation/index.htmlを参照してください。javac -help
$ javac -help 使用方法: javac <options> <source files> 使用可能なオプションには次のものがあります。 -g すべてのデバッグ情報を生成する -g:none デバッグ情報を生成しない -g:{lines,vars,source} いくつかのデバッグ情報のみを生成する -nowarn 警告を発生させない -verbose コンパイラの動作についてメッセージを出力する -deprecation 非推奨のAPIが使用されているソースの場所を出力する -classpath <path> ユーザー・クラス・ファイルおよび注釈プロセッサを検索する位置を指定する -cp <path> ユーザー・クラス・ファイルおよび注釈プロセッサを検索する位置を指定する -sourcepath <path> 入力ソース・ファイルを検索する位置を指定する -bootclasspath <path> ブートストラップ・クラス・パスの場所をオーバーライドする -extdirs <dirs> インストール済拡張機能の場所をオーバーライドする -endorseddirs <dirs> 推奨規格パスの場所をオーバーライドする -proc:{none,only} 注釈処理やコンパイルを実行するかどうかを制御します。 -processor <class1>[,<class2>,<class3>...] 実行する注釈プロセッサの名前。デフォルトの検出処理をバイパス -processorpath <path> 注釈プロセッサを検索する位置を指定する -parameters メソッド・パラメータにリフレクション用のメタデータを生成します -d <directory> 生成されたクラス・ファイルを格納する位置を指定する -s <directory> 生成されたソース・ファイルを格納する場所を指定する -h <directory> 生成されたネイティブ・ヘッダー・ファイルを格納する場所を指定する -implicit:{none,class} 暗黙的に参照されるファイルについてクラス・ファイルを生成するかどうかを指定する -encoding <encoding> ソース・ファイルが使用する文字エンコーディングを指定する -source <release> 指定されたリリースとソースの互換性を保つ -target <release> 特定のVMバージョン用のクラス・ファイルを生成する -profile <profile> 使用されているAPIが指定したプロファイルで使用可能かどうかを確認します -version バージョン情報 -help 標準オプションの概要を出力する -Akey[=value] 注釈プロセッサに渡されるオプション -X 非標準オプションの概要を出力する -J<flag> <flag>を実行システムに直接渡す -Werror 警告が発生した場合にコンパイルを終了する @<filename> ファイルからの読取りオプションおよびファイル名参考資料
- 投稿日:2020-02-26T21:26:30+09:00
Top 75+ JSP Interview Questions and Answers In 2020
In this article, I am going to provide you with a lot of JSP questions that are often asked in Interviews along with their answers. Let’s move on to the Q and A tour.
click here to read more
https://www.positronx.io/top-jsp-interview-questions-and-answers/
- 投稿日:2020-02-26T21:05:16+09:00
React Native Alert Example – Show Alert in React Native App
This tutorial shows you how to easily show an alert message in React Native application using the React Native Alert API. We will also learn to create custom alert using React Native Awesome Alerts module.
click here to read more
https://www.positronx.io/react-native-alert-example-show-alert-in-react-native-app/
- 投稿日:2020-02-26T21:02:49+09:00
HTML6 is Coming – Here is a Sneak Peek
HTML, the language of the web, is one of the most well-known web technology. HTML has been in use continually for building the internet since the time it was introduced.
click here to read more
https://www.positronx.io/html6-is-coming-here-is-a-sneak-peek/
- 投稿日:2020-02-26T20:16:14+09:00
動的計画法でnCrを求める
導入
こんにちは。けちょんです。
皆さんは組み合わせの総数を出力するプログラムを書けますか?
自分は競技プロで出会ったときに書けませんでした。
恥ずかしくて復習です。方法
今回は以下の漸化式を利用して、動的計画法で導きます。
nCr = n-1Cr-1 + n-1Crちなみに、この漸化式のイメージは以下です。
n個からr個を選ぶ組み合わせは、以下の和である。 (n個から一個除いて考える) 1.追加されている1個が選択されている場合 総数はn-1個、選択する数は残りr-1個 2.追加されている1個が選択されていない場合 総数はn-1個、選択する数は残りr個図で書きたかったんですが、怠けました。
ソースは以下です。
import java.util.Scanner; public class Main { static Integer[][] dp; public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); int N = sc.nextInt(); int R = sc.nextInt(); dp = new Integer[N + 1][R + 1]; // 初期値を設定 for (int i = 0; i <= N; i++) { dp[i][1] = i; } for (int i = 0; i <= R; i++) { dp[0][i] = 0; } // "nCr = nCn-r" を使い、計算量を節約 if (R > N / 2) { R = N - R; } int res = comb(N, R); System.out.println(res); } private static int comb(int n, int r) { if (r == 0) { return 1; } // "nCr = n-1Cr-1 + n-1Cr"を使用 return getInt(n - 1, r) + getInt(n - 1, r - 1); } private static int getInt(int n, int r) { if (dp[n][r] != null) { // 与えられた(n,r)に対して、dp表から値を返す。 return dp[n][r].intValue(); } // dp表に無い場合は、combに渡し、導出する return comb(n, r); } }ちゃんと動いていそうです。
ちなみに計算量はO(n^2)らしいです。
参考:https://www.slideshare.net/chokudai/abc021動的計画法を使わないと計算量が莫大になるので注意です。
次回
次回は逆元を用いる方法を紹介します。
計算量はなんとO(n logp)とのこと
※p = 10^9+7 であり、出力値をpで割る前提
- 投稿日:2020-02-26T17:48:38+09:00
IntellijIDEA+SpringBoot+Gradle+MyBatisの設定例
属性もりもり。
IDEAはCommunityの2019.3.1
Javaのバージョンは11
SpringBootは2.2.4.RELEASE
Gradleは5.2.1
MyBatisは3.5.2
(mybatis-spring-boot-starter:2.1.0)build.gradlegroup 'com.xxx.xxx' version '0.1-SNAPSHOT' // Spring Boot ver:2.2.4 buildscript { def springBootVersion = '2.2.4.RELEASE' repositories { mavenCentral() } dependencies { classpath ("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } // Javaプラグイン apply plugin:'java' // 各種IDEの設定ファイルを出力するGradleプラグインの設定 apply plugin:'idea' apply plugin:'eclipse' // SpringBoot関連のプラグイン apply plugin:'org.springframework.boot' apply plugin:'io.spring.dependency-management' // warファイルを出力する apply plugin: 'war' // Javaバージョン def javaVersion = JavaVersion.VERSION_11 sourceCompatibility = javaVersion targetCompatibility = javaVersion [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' // 依存ライブラリ取得先リポジトリ repositories { mavenCentral() } // 依存ライブラリ dependencies { // SpringBoot compile("org.postgresql:postgresql") compile("org.springframework.boot:spring-boot-devtools") compile("org.springframework.boot:spring-boot-starter-web") compile("org.springframework.boot:spring-boot-starter-jdbc") testCompile("org.springframework.boot:spring-boot-starter-test") // JUnit testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.2.0' testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.2.0' testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.2.0' // GSON compile("com.google.code.gson:gson") // myBatis compile("org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.0") compile("ch.qos.logback:logback-classic") testCompile("org.mybatis.spring.boot:mybatis-spring-boot-starter-test:2.1.0") } test { // gradle buildの時にJUnit5を使うようにする useJUnitPlatform() // テストの並列実行スレッド数 maxParallelForks = 4 filter{ // 表に出せないやつなので伏せる } } // リリースバージョンをgradle.propertiesから取得し設定 def applicationVersion = project.properties['release.version'] version = applicationVersion def springBootApplicationName = 'hogehoge' bootJar { archiveBaseName = springBootApplicationName version = applicationVersion } bootWar { archiveBaseName = springBootApplicationName version = applicationVersion }属性もりもりで、ご参考になれば幸いです。
- 投稿日:2020-02-26T17:16:59+09:00
logbackを使ってExcelで読めるCSVファイルをログ出力する
はじめに
ユーザーから、Webアプリケーションの利用状況について分析・監査したいとの要望をいただきまして、
もともとログ出力にlogbackを使っていたのでlogbackからCSVファイルを出力させることで対応することにしました。作ったもの
- BOM付UTF-8のCSVファイルを出力します。
- Excelでは、BOMの付いていないUTF-8のCSVファイルを開くと文字化けします。
- Excelから読んだときに分かりやすくするため、ファイルの初めに固定文言のヘッダを付けます。
- ログファイルは月単位でローテーションさせます。
実現方法
Add an expression at the start of every log file using Logback - Stack Overflow を参考に作りました。
独自Appenderの作成
ファイルの先頭にBOMと固定文言を入れるAppenderを用意します。
AuthFileAppender.javapublic class AuthFileAppender extends RollingFileAppender { @Override public void openFile(String fileName) throws IOException { super.openFile(fileName); File activeFile = new File(getFile()); if (activeFile.exists() && activeFile.isFile() && activeFile.length() == 0) { // 出力対象が空ファイルである場合のみBOMとヘッダを出力する lock.lock(); try { OutputStream outputStream = super.getOutputStream(); // BOMを出力 outputStream.write(0xef); outputStream.write(0xbb); outputStream.write(0xbf); // 固定文言(CSVヘッダ)を出力 outputStream.write("操作日付,操作時刻,ユーザID,ユーザ名,操作内容,操作対象者ID・・・\n".getBytes()); if (super.isImmediateFlush()) { outputStream.flush(); } } finally { lock.unlock(); } } } }
- 月単位のログローテーションにはLogbackにあるRollingFileAppenderの仕組みをそのまま使いたかったので、RollingFileAppenderを継承したクラスとします。
- 出力対象(activeFile)について、対象ファイルが空である場合にのみBOMと固定文言(CSVヘッダ)を出力します。
- 上記のStack Overflowのページでは固定文言を外部設定するようにしていましたが、この実装ではハードコードしています。
- 操作内容,操作対象者ID・・・の箇所には、実際のコードではアプリケーション固有の項目名を設定しています。
ログ出力用クラスの作成
ログインユーザ情報の取得処理など共通化したかったのと、Loggerも共通化したかったので、クラスを用意することにしました。
AuthLogger.java@Component public class AuthLogger { /** 監査ログを出力するLogger */ public static final Logger AUTH_LOGGER = LoggerFactory.getLogger(AuthLogger.class); /** 個人を扱うService */ @Autowired protected PersonService personService; /** * 対象の個人を指定せず、監査ログを出力します。 * * @param operation 操作内容 */ public void log(String operation) { log(operation, null); } /** * 対象の個人を指定して、監査ログを出力します。 * * @param operation 操作内容 * @param personId 扱った対象者のID */ public void log(String operation, String personId) { SsoLoginData ssoLoginData = SsoLoginDataUtil.getSsoLoginData(); // ログインユーザ情報を取得 AUTH_LOGGER.trace(String.join( ",", // カンマ区切りで出力する // 操作日付、操作時刻はlogback.xmlにてレイアウトで設定するためここでは不要 ssoLoginData.getPersonId(), // ユーザID ssoLoginData.getUserName(), // ユーザ名 operation, // 操作内容 personId // ユーザ名 // 実際はここでpersonServiceを使った出力内容を設定する )); } }
- SpringBootで構築しているアプリだったので、このクラスもBean化して扱うことにしました。そのために
Component
を付けています。
- また、PersonServiceは
@Autowired
でDIしています。- Loggerの生成時には、AuthLogger.classをそのまま渡すことにしました。
- よって、logback.xmlでは、nameとしてAuthLoggerの完全修飾クラス名を指定することになります。
- 操作内容のみ指定するメソッドと、扱った対象者のIDを指定するメソッドの両方を用意しました。
- 対象者が特定されない処理では、前者によりログを出力します。
- CSVを出すのに、
String.join
を使っています。
- ダブルクォーテーションで囲いたい場合や、出力する値に改行が含まれる場合などは、opencsvなどCSVに特化したライブラリを利用することをお勧めします。
logback.xmlの設定追加
logback.xmlに、以下の設定を追加します。
logback.xml<appender name="AUTH_APPENDER" class="your.project.web.common.util.AuthFileAppender"> <file>/your/file/path.csv</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>/your/file/path.%d{yyyy-MM}.csv</fileNamePattern> </rollingPolicy> <encoder> <charset>UTF-8</charset> <pattern>%d{yyyy-MM-dd},%d{HH:mm:ss},%msg%n</pattern> </encoder> </appender> <logger name="your.project.web.common.util.AuthLogger" level="TRACE" additivity="false"> <appender-ref ref="AUTH_APPENDER" /> </logger>
- appender の設定について
- 前述のAuthFileAppenderを利用します。
/your/file/path
の箇所は、実際は${log.auth.output}
としており、アプリケーションのビルド時に稼働する環境に応じた値に置換しています。- 月単位でローテーションさせるため、rollingPolicyのfileNamePatternは
%d{yyyy-MM}
とします。- CSVの最初の2項目である操作日付と操作時刻はLogbackの機能により出すことにしました。このため、patternの最初に
%d{yyyy-MM-dd},%d{HH:mm:ss},
を設定しています。- loggerの設定について
- nameは、前述のとおりAuthLoggerの完全修飾クラス名とします。
- AuthLoggerにてLogger生成時に独自の名前を指定したならば、その名前をここでも指定すればよいです。
additivity="false"
を指定することで。AUTH_APPENDERのみが利用されるようにしています。個別実装へのログ出力の追加
今回は、SpringMVCのControllerに前述のAuthLoggerをDIして、直接logメソッドを実行することにしました。(ここでは実装は省略します。)
プロジェクトの規模や要件によってはAOPにより出力させる選択肢もあると思います。まとめと感想
Logbackにはファイルの先頭に固定文言(BOMも含めて)を入れるような機能はなかったため独自のAppenderを用意することになりましたが、比較的に簡単に実現できました。
今回は出力したログファイルをユーザが直接Excelで確認する前提だったため、上記のような手段をとりました。
要件によってはログファイルはBOMなし・ヘッダなしで出力しておき、あとからBOM・ヘッダとスクリプトなどで結合するという手段も取れると思います。
- 投稿日:2020-02-26T14:07:31+09:00
【JSP、Servlet】新規プロジェクトを立ち上げ、接続確認をする!!
新規プロジェクト立ち上げ~接続確認
今回はJSP、Servletを使ってWebアプリケーションの制作をしていきます。
動的Webプロジェクト(JSP、Servlet)の作成方法と、接続確認について説明します。接続確認がなぜ必要かというと…
アプリケーションの制作に本格的に入っていく前に、接続を確認し、しっかり動くか確かめるためです。接続確認対象は以下の通りです。
接続確認対象
- JSPからServlet呼び出し(データ渡し)
- CSS(適応されるか確認)
- JS(コンソールログに出力されるか確認)
- MySQL接続確認
プロジェクト立ち上げの後に1つ1つ見ていきましょう。
Eclipseで動的Webプロジェクトを作成
まず、動的Webプロジェクトの作成をします。
ファイルメニュー→「新規」→「動的Webプロジェクト」を選択します。
※「動的Webプロジェクト」が表示されない場合は、パースペクティブが「Java EE」が選択されていない可能性があります。
プロジェクト名をつけて、完了します。
プロジェクトができたのを確認出来たら動的Webプロジェクトをサーバーに追加し実行できるようにします。
※サーバービューにサーバーがない場合は、先にアプリケーションサーバー追加する必要があります。
使用可能のところから先ほど作成したプロジェクトを選択し→「追加」します。
構成済みの方に入れ、完了します。追加した動的Webプロジェクトを有効にするため、サーバーを起動します。
サーバービューのサーバーを選択→「右クリック」→「開始」を選択します(既に起動している場合は「再開」を選択して再起動を行います)。
これで動的Webプロジェクトは完成です。次にJSP、Servletの作成に入ります。
JSP,Servletの作成
JSPとServletを作成していきます。
JSPの作成方法と詳細については以下を参照
JSPって何?〜JSPの基礎を知ろう!!〜Servletの作成方法
ファイルメニュー→「新規」→「サーブレット」を選択します。
今回のサーブレット名はMainservletにしました。
Webアノテーションについて
@WebServlet("/URLパターン")
http://<サーバー名>/<アプリケーション名>/WebServletアノテーションの中身の文字列がURLの最後に追加される。
例(上記のプロジェクトの場合)
@WebServlet("/Mainservlet")http://localhost:8090/ConnectSample/MainservletURLパターンの中の文字列は自由に設定できる。
@WebServlet("/")例えば上のようにアノテーションの中身を変えるとそのサーブレットがエントリーポイントになる。
接続確認
JSPからServlet呼び出し(データ渡し)
index.jspのリンクからSecondservletを呼び出す
Secondservlet
サーバーで実行
リンクを押すと…
以下のような結果が出たのでJSPからServlet呼び出し完了です。CSS(適応されるか確認)
Webcontentの中にcssフォルダとexam.cssファイルを作成
index.jspに以下のリンクを貼り、cssファイルを読み込む
JS(コンソールログに出力されるか確認)
Webcontentの中にJSフォルダとexam.jsファイルを作成
ConsoleのところにConsole.logに入力したものがあれば確認OK
MySQL接続確認
最後にDB接続確認です。
MySQL接続に関してはこちらの記事を参照してください
【Java】MySQLを絶対に接続させたい!!!~JDBCドライバの取得とJARファイル位置について~これで確認は完了です。
- 投稿日:2020-02-26T08:30:19+09:00
jMeterでCSVファイルをJSONにしてリクエストする方法
1万件超のCSVデータをリクエストにしてポストしたい。
いま、ビッグデータといかないまでも、大容量データの処理を作っているんですが、
大きなデータの処理は1日にテストできる回数が限られるので、ウチでもその環境欲しいなー、jMeterで何かできないかなーと思って。
作ってみました。jMeterにはデフォルトでCSVをJSONリクエストにする機能はない
JMeter多機能で高性能なリクエスト送信アプリですが、
デフォルトで「CSVをJSONによしなに変換してくれる機能」はないです。しかし、「BeanPreProcesser」という送信時にScriptを走らせることで、
色んな制御ができる機能があります。大雑把にこんな処理をします。
・HTTPリクエストに、受取変数をセット・Bean PreProcessorを使って、JSON文字列を作り、受取変数に代入
・jMeterでHTTPリクエスト送信
JSONの送信設定をする
まずは普通にHTTPリクエストの送信設定を行います。
HTTPヘッダマネージャーを作成して、
Content-Typeをapplication/jsonにセットリクエストボディに代入用変数を作る
まず普通に、対象サーバーやパスに、ターゲットになるアドレスを記入します。
そして、リクエストに「スクリプトの結果を格納する変数」を入れます
今回は${post_data}という名前を付けましたBeanPreProcessorを作る
そして、右クリックからBeanPreProcessorを作成してJSON文字列を作ります。
BeanPreProcessorはJavaで書くので、慣れてる人は使いやすいかと、
JSONのシリアライズ、デシリアライズもjMeterにライブラリを読み込ませれば可能らしいですヾ(。>﹏<。)ノ゙✧*。処理内容は見ての通りです。
CSVを1行ごとにループしrequest.json"csvリスト":[{ "ほげほげ":"CSV1カラム目" "なんとか":"CSV2カラム目" "foo":"CSV3カラム目" } { "ほげほげ":"CSV1カラム目" "なんとか":"CSV2カラム目" "foo":"CSV3カラム目" }] ...みたいなJSONを作ります!
BeanShellPreProcessor.java//▼-----------BeanPreProcessor処理開始-------------▼ log.info("BeanShell PreProcessor start"); StringBuilder requestBody = new StringBuilder(); // JSONのスタートを作る requestBody.append("{\r\n"); // 改行もちゃんと反映されますヾ(。>﹏<。)ノ゙✧*。 // リストオブジェクトの作成開始 requestBody.append("\"requestCSV\":[\r\n"); // CSVファイルを読み込む BufferedReader reader = new BufferedReader(new FileReader(new File("C:\\csvFileForBatche\\06YAMAGAcopied.CSV"))); String line; // 1行ごとにループ boolean secondLoopFlg = false; while ((line = reader.readLine()) != null) { // 読み込んだCSVをカンマで分割 String[] params = line.split(","); if(secondLoopFlg){ requestBody.append(",\r\n"); } requestBody.append("{\r\n"); // 項目ごとにCSVパラメーターを作ります requestBody.append("\"countryPublicOrgCd\":\"").append(params[0]).append("\",\r\n"); requestBody.append("\"zipcodePare\":").append(params[1]).append(",\r\n"); requestBody.append("\"zipcode\":").append(params[2]).append(",\r\n"); requestBody.append("\"prefNameKana\":").append(params[3]).append(",\r\n"); requestBody.append("\"cityNameKana\":").append(params[4]).append(",\r\n"); requestBody.append("\"streetNameKana\":").append(params[5]).append(",\r\n"); requestBody.append("\"prefNameKanji\":").append(params[6]).append(",\r\n"); requestBody.append("\"cityNameKanji\":").append(params[7]).append(",\r\n"); requestBody.append("\"streetNameKanji\":").append(params[8]).append(",\r\n"); requestBody.append("\"var1\": \"").append(params[9]).append("\",\r\n"); requestBody.append("\"var2\": \"").append(params[10]).append("\",\r\n"); requestBody.append("\"var3\": \"").append(params[11]).append("\",\r\n"); requestBody.append("\"var4\": \"").append(params[12]).append("\",\r\n"); requestBody.append("\"var5\": \"").append(params[13]).append("\",\r\n"); requestBody.append("\"var6\": \"").append(params[14]).append("\"\r\n"); // オブジェクトを閉じる requestBody.append("}\r\n"); secondLoopFlg = true; } // JSONオブジェクトを閉じる requestBody.append("]\r\n"); requestBody.append("}\r\n"); // ファイルをクローズ reader.close(); // リクエストが用意する変数に代入する vars.put("post_data", requestBody.toString()); // ▲-----------BeanPreProcessor処理開始-------------▲ log.info("BeanShell PreProcessor start");れっつ実行ヾ(。>﹏<。)ノ゙✧*。
3万件にCSVをリクエストに送信できましたb
参考
Sending Complete JSON Data from the CSV file in one request in JMeter
https://stackoverflow.com/questions/32628934/sending-complete-json-data-from-the-csv-file-in-one-request-in-jmeterreceiving json and deserializing as List of object at spring mvc controller
https://stackoverflow.com/questions/23012841/receiving-json-and-deserializing-as-list-of-object-at-spring-mvc-controller
- 投稿日:2020-02-26T08:27:20+09:00
FileIOのDynamic DestinationのDestinationTはequals/hashCodeが必要だよ
細かすぎて伝わらないApache Beam/Dataflow選手権#1です。
FileIOって?
Apache Beamのファイル一般を扱うIOクラスです。
ドキュメントによるとS3/GCS/HDFS/ローカルのファイルを、扱う事が出来るようです。Dynamic Destinationって?
PCollectionの中身によって出力先を変える機能です。
具体的な設定はFileIO.Writeで定義されており、
- byで、入力(UserT)をグループ(DestinationT)に分類するクラスを指定
- viaで、入力(UserT)を出力(OutputT)に変換するクラスを指定
- withNamingで、グループ(DestinationT)からファイル名を決めるクラスを指定
の3つが、Dynamic Destinationの主な設定です。
DestinationTって
入力(UserT)分類し、グループ化するためのラベルとして使われるクラス(ジェネリクス)です。
境界(extends)が定義されてはいないので、特に必要なメソッドや親クラスは指定されていません。って思うじゃん‥
詰まった
やろうとしていたこと
- 数十万件のPub/Subメッセージを、Dynamic Destinationで仕分け(5種類くらい)してGCSに書き込みたい
- Dynamic Destinationなしでは、楽勝に処理出来ていた
起きたこと
- ファイルを書き出す段階で、WorkerでOut Of Memory(OOM)が起きた
- (Dynamic Destination無しと、同じインスタンス種類の場合)
- ごく一部しか結果がファイルに書き込まれなかった
- ワーカーのメモリを巨大(64GB)にすると、ゆっくり書き出される
- オートスケールにしても台数は増えない
原因
DestinationTの実装に
- equals
- hashCode
を(正しく)実装していなかったのが原因。
なぜ必要か
端的にはDestinationTがHashMapで使われているためです。
細かい流れが気になる人向けに:
Dynamic Destinationでは、以下の流れで書き込みを行います。
- 入力(UserT)をファイルに割り振るために、番号を付ける(ここらへん)
- シャードの連番とDestinationTをencodeした結果のハッシュを使っています
- encodeにはCoderのencodeメソッドが使われます
- 番号でGroupByKey(ここらへん)
- 一時ファイルに書き出す(ここらへん)
- 一時ファイルを改名(ここらへん)
下のような理由で、DestinationTがHashMapに使われています:
- 入力を連番に割り振る時(1)に、ハッシュが衝突する可能性がある
- 衝突すると、異なるDestinationがGroupByKeyでまとめられる
- そのため、一時ファイル書き出し(3)の部分でもDestinationTを計算。異なるDestinationTを異なるファイルに書き出すようにしている
- この時、DestinationTと書き出し先を管理するために、DestinationTをHashMapに突っ込んでいる
このため、DestinationTがhashCode/equalsを(正しく)実装していないと、
- 入力毎に違う書き出し先とみなされる
- つまり1ファイル1行
- 超絶細かい単位で書き出すので時間がかかる
- 出力が終わるまで、入力データが保持するので、メモリ使用量が多くなる
のではないかと推測しています。
今回学んだこと
- equals/hashCodeを実装しないでHashMapを使うと死ぬ
- Effective Java大事
- DataflowでもDumpが取れる
- Googleの人がわかりやすい資料を書いてくれています
- (がダンプしても分かるとは限らない)
- Beamのファイル書き出しは、ちょっと複雑な処理をしている
- 投稿日:2020-02-26T01:25:58+09:00
SpringBootでEhCashe2.xでキャッシュを使う
背景
Spring Bootを利用していて、キャッシュを実装したくなったので試しました。
実行環境
- Spring Boot v2.2.4.RELEASE
Spring Bootでのキャッシュ
Spring Bootではいくつかのキャッシュが利用できます。
https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/html/boot-features-caching.htmlうち、上から眺めていってEhCache2.xは導入が簡単そうに見えます。
クラスパスにehcache.xml があれば利用されるとのことなので、導入が簡単そうに見えるので、試してみます。EhCache 2.x
実装
キャッシュを利用するメソッドの呼び出し元
今回は
@SpringBootApplication
のアノテーションをつけたクラスから呼び出してみます。
@EnableCaching
のアノテーションをつけますCacheApplication.java@SpringBootApplication @EnableCaching public class CacheApplication implements CommandLineRunner { @Autowired Cache cache; private final static Logger logger = LoggerFactory.getLogger(CacheApplication.class); private final static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss"); public static void main(String[] args) { SpringApplication.run(CacheApplication.class, args); } public void run(String[] args){ this.getValue("キャッシュされる"); this.getValue("キャッシュされる");//キャッシュされる this.getValue("引数が違うのでキャッシュされない");//引数が違うのでキャッシュされない this.getValue("キャッシュされる");//引数違うのを挟んでキャッシュされる try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace(); } this.getValue("キャッシュされる");//引数が同じだけど生存時間を過ぎているのでキャッシュされない } public void getValue(String key){ logger.info(key + " Start at: " + LocalDateTime.now().format(dateTimeFormatter)); String ret = cache.getFromCache(key); //キャッシュが有効なメソッド logger.info(ret + " End at: " + LocalDateTime.now().format(dateTimeFormatter)); } }キャッシュを利用するクラス
キャッシュを有効にしたいメソッドに
@Cacheable
をつけますCache.java@Component public class Cache { @Cacheable("getCache") //このメソッドについてキャッシュを有効にする public String getFromCache(String key){ try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace(); } return "End getFromCache:" + key; } }pom.xml
pom.xmlに
spring-boot-starter-cache
とehcache
の2系を追加します。pom.xml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.3</version> </dependency>ehcache.xml
ehcache.xmlをsrc/main/resources/ 以下に作成します。
timeToLiveSeconds
を5として、キャッシュの生存時間は5秒の設定にしておきます。ehcache.xml<?xml version="1.0" encoding="UTF-8"?> <ehcache> <diskStore path="java.io.tmpdir"/> <cache name="getCache" timeToLiveSeconds="5" maxEntriesLocalHeap="0"> </cache> </ehcache>application.properties
application.propertiesには、ehcacheを利用するよう以下の設定を追加します。
利用できるキャッシュがehcacheのみならばここは不要です。spring.cache.type=ehcache実行
実行します。
実行結果はこうなりました。2020-02-29 21:29:48 - キャッシュされる Start at: 21:29:48 2020-02-29 21:29:51 - End getFromCache:キャッシュされる End at: 21:29:51 2020-02-29 21:29:51 - キャッシュされる Start at: 21:29:51 2020-02-29 21:29:51 - End getFromCache:キャッシュされる End at: 21:29:51 2020-02-29 21:29:51 - 引数が違うのでキャッシュされない Start at: 21:29:51 2020-02-29 21:29:54 - End getFromCache:引数が違うのでキャッシュされない End at: 21:29:54 2020-02-29 21:29:54 - キャッシュされる Start at: 21:29:54 2020-02-29 21:29:54 - End getFromCache:キャッシュされる End at: 21:29:54 2020-02-29 21:29:57 - キャッシュされる Start at: 21:29:57 2020-02-29 21:30:00 - End getFromCache:キャッシュされる End at: 21:30:00以下で結果を見ていきます。
一回目は3秒かかっているのに対し、二回目は一瞬なのでキャッシュが効いているようです。2020-02-29 21:29:48 - キャッシュされる Start at: 21:29:48 2020-02-29 21:29:51 - End getFromCache:キャッシュされる End at: 21:29:51 2020-02-29 21:29:51 - キャッシュされる Start at: 21:29:51 2020-02-29 21:29:51 - End getFromCache:キャッシュされる End at: 21:29:51違う引数を渡した際はキャッシュされないので、また3秒かかっています。
2020-02-29 21:29:51 - 引数が違うのでキャッシュされない Start at: 21:29:51 2020-02-29 21:29:54 - End getFromCache:引数が違うのでキャッシュされない End at: 21:29:54以下については生存時間の5秒を過ぎているので、キャッシュがされず、3秒かかっています。
2020-02-29 21:29:57 - キャッシュされる Start at: 21:29:57 2020-02-29 21:30:00 - End getFromCache:キャッシュされる End at: 21:30:00意図通りのキャッシュの挙動となりました。
所感
SpringBootとEhCache2.Xは、アノテーションと設定ファイルだけでキャッシュが実装できるので手っ取り早いと思いました。
ただ、EhCache3系がすでに利用できるのをその後知りまして、あえてEhCache2系を使うことはあまりないかもしれません。caffeineを試したら同じようにキャッシュが実装できたのでそちらも使えそうです。
以下記事が参考になりました。
https://qiita.com/yut_arrows/items/4b664acdfa852c0bd6cd