例
mvn -P !profile
2017年11月23日木曜日
[PsotgreSQL]JDBC経由でサーバサイドでの解析結果を使わないようにする
PostgreSQLにPreparedStatementを使用して繰り返しSQLを実行すると、効率化のためにサーバサイドでの解析結果が使用されるようになる。
これによって、条件に埋め込む値が変わっても、実行計画が作り直されないので想定外の実行計画になってしまう場合があるらしい。
動きを確認するために、以下のデータを持つテーブルを使って検証してみる。
select flg, count(*) from test group by flg;
flg | count
-----+---------
0 | 1000000
1 | 1
(2 rows)
条件に固定値を指定した場合の実行計画は、"1"を指定した場合はインデックスが使われ"0"を指定した場合はフルスキャンとなる。
#EXPLAIN select * from test where flg = '1';
QUERY PLAN
----------------------------------------------------------------------
Index Scan using test_idx on test (cost=0.42..4.44 rows=1 width=17)
Index Cond: (flg = '1'::bpchar)
(2 rows)
# EXPLAIN select * from test where flg = '0';
QUERY PLAN
---------------------------------------------------------------
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17)
Filter: (flg = '0'::bpchar)
(2 rows)
これをPreparedStatementを使って実行する。
最初の10回は条件に"0"を指定しているのでフルスキャンとなり、最後実行では条件に"1"を指定しているのでインデックスが使われるはず。
try (Connection connection = dataSource.getConnection()) {
try (Statement statement = connection.createStatement()) {
statement.execute("load 'auto_explain'");
statement.execute("set auto_explain.log_min_duration = 0");
statement.execute("set auto_explain.log_analyze = true");
}
try (PreparedStatement statement = connection.prepareStatement("select * from test where flg = ?")) {
for (int i = 0; i < 10; i++) {
statement.setString(1, "0");
statement.executeQuery();
}
}
try (PreparedStatement statement = connection.prepareStatement("select * from test where flg = ?")) {
statement.setString(1, "1");
statement.executeQuery();
}
}
実際の実行結果はこのようになる。
最後のSQL実行は、固定値を指定した時とは異なりインデックスが使われずフルスキャンとなる。
デフォルトだと5回繰り返してPreparedStatementが使われるとサーバサイドの解析結果が使われるらしいけど、Filterのflg=の部分が固定値じゃなくなったのは5回目じゃないが不思議…実行計画からは判断できないのだろうか。
デフォルト値から変えたい場合は、接続プロパティのprepareThresholdを使う。
LOG: duration: 880.555 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.011..250.647 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 965.254 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.013..263.084 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 713.326 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.015..216.970 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 603.937 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.010..189.935 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 565.579 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.008..175.277 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 582.891 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.007..183.025 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 604.002 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.010..187.921 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 590.842 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.007..185.131 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 589.730 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.007..183.538 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 610.742 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.009..192.248 rows=1000000 loops=1)
Filter: (flg = ($1)::bpchar)
Rows Removed by Filter: 1
LOG: duration: 114.769 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=114.755..114.756 rows=1 loops=1)
Filter: (flg = ($1)::bpchar)
Rows Removed by Filter: 1000000
サーバサイドの解析を使わないようにするには、接続プロパティのprepareThresholdに0を指定する。
例えば、「jdbc:postgresql://localhost/db?prepareThreshold=0」みたいに指定する。
prepareThreshold=0を指定した結果は、上の結果とは異なり最後の実行がインデックススキャンとなっている。
G: duration: 813.314 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.011..248.204 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 769.790 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.013..236.008 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 663.801 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.013..198.553 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 562.532 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.009..177.537 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 599.577 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.009..188.422 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 611.355 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.008..192.423 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 626.638 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.010..197.469 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 639.909 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.012..200.366 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 590.332 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.007..187.930 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 587.067 ms plan:
Query Text: select * from test where flg = $1
Seq Scan on test (cost=0.00..18860.01 rows=1000001 width=17) (actual time=0.012..185.482 rows=1000000 loops=1)
Filter: (flg = '0'::bpchar)
Rows Removed by Filter: 1
LOG: duration: 0.018 ms plan:
Query Text: select * from test where flg = $1
Index Scan using test_idx on test (cost=0.42..4.44 rows=1 width=17) (actual time=0.014..0.014 rows=1 loops=1)
Index Cond: (flg = '1'::bpchar)
試した環境
- サーバ:9.5
- JDBC:org.postgresql:postgresql:42.1.4
ラベル:
PostgreSQL
2017年11月5日日曜日
Semantic-UIでCalendar UI
Semantic-UI-Calendarを使うと、Date Pickerが使えるようになる。
Semantic-UI-CalendarのJavaScriptとスタイルシートを読み込むようにする。
<script src="//cdnjs.cloudflare.com/ajax/libs/semantic-ui-calendar/0.0.8/calendar.min.js"
type="text/javascript"></script>
<link rel="stylesheet" type="text/css"
href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui-calendar/0.0.8/calendar.min.css"/>
日付項目を作る。
<div class="field ui calendar required">
<label>日付</label>
<div class="ui input left icon">
<i class="calendar icon"></i>
<input type="text" name="date" placeholder="日付">
</div>
</div>
JavaScriptを使用してカレンダーのタイプを設定する。
typeにdateを設定しているので、日付の項目となる。
$('.ui.calendar').calendar({
type: 'date'
})
これで、画像のような日付入力欄が作成できる。

なおデフォルトのフォーマットは画像のような形になる。

フォーマットの変更は、JavaScriptを使って行う。例えば、yyyy/mm/ddにしたい場合は、このようにする。
$('.ui.calendar').calendar({
type: 'date',
formatter: {
date: function (date) {
var day = ('0' + date.getDate()).slice(-2);
var month = ('0' + (date.getMonth() + 1)).slice(-2);
var year = date.getFullYear();
return year + '/' + month + '/' + day;
}
}
})
これで、フォーマットがyyyy/mm/dd形式になる。
2017年10月22日日曜日
AssertJ Assertions Generatorを使ってクラス専用のアサーションを生成する
使い方や生成対象などは、AssertJ Assertions Generatorに書かれています。
MavenやGradleプラグインやshを使うパターンがあるみたいですが、Gradleプラグインを使った方法を試してみます。
build.gradle
Gradleプラグインの使い方は、Githubに書かれています。
自動生成したクラスは、置き場所を変えたほうが分かりやすいのでsrc-gen配下に出力しています。
plugins {
id "com.github.opengl-BOBO.assertjGen" version "1.1.8"
}
// assertj-gen
ext {
assertjGenPreConfig = [
'assertjGenerator': 'org.assertj:assertj-assertions-generator:2.1.0',
'sourceSets': ['main]
]
}
sourceSets.test.java.srcDirs += 'src-gen/test/java'
assertjGen {
// test.Hogeがアサーションを生成するクラス。
// パッケージ指定もできる
classOrPackageNames = ['test.Hoge']
outputDir = 'src-gen/test/java'
}
生成対象のクラス
package test;
public class Hoge {
public final int hoge;
public Hoge(final int hoge) {
this.hoge = hoge;
}
public String getFuga() {
return "fuga";
}
}
アサーションの生成
assertjGenを使用してアサーションを生成します。
$ ./gradlew assertjGen
> Task :assertjGen
07:22:13.289 INFO o.a.a.g.c.AssertionGeneratorLauncher - Generating assertions for classes [class test.Hoge]
07:22:13.310 INFO o.a.a.g.c.AssertionGeneratorLauncher - Generating assertions for class : test.Hoge
07:22:13.326 INFO o.a.a.g.c.AssertionGeneratorLauncher - Generated Hoge assertions file -> /home/hoge/IdeaProjects/test-pj/src-gen/test/java/./test/HogeAssert.java
生成されたクラス
下の画像のようなアサーションクラスが生成されます。
生成されたクラスを使用する
@Test
void test() {
final Hoge hoge = new Hoge(100);
HogeAssert.assertThat(hoge)
.hasHoge(10);
}
テスト結果もわかりやすく出力されます。
Expecting hoge of:
<test.Hoge@3c72f59f>
to be:
<10>
but was:
<100>
ラベル:
assertj
2017年10月14日土曜日
[Oracle]データベースバッファキャッシュを明示的に破棄してみる
データベースバッファキャッシュは下のSQLで明示的に破棄できる。
alter system flush buffer_cache;
破棄されたことを確認する
破棄前の統計情報
SQL> select * from person where id = 1;
Elapsed: 00:00:00.01
Execution Plan
----------------------------------------------------------
Plan hash value: 1167617095
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 17 | 3 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| PERSON | 1 | 17 | 3 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | SYS_C0013811 | 1 | | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("ID"=1)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
4 consistent gets
0 physical reads
0 redo size
469 bytes sent via SQL*Net to client
540 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
データベースバッファキャッシュを破棄する。
SQL> alter system flush buffer_cache;
System altered.
同じ問い合わせを実行するとphysical readsが増えているので、データファイルからブロックが読み取られていることが確認できる。
SQL> select * from person where id=1;
Elapsed: 00:00:00.01
Execution Plan
----------------------------------------------------------
Plan hash value: 1167617095
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 17 | 3 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| PERSON | 1 | 17 | 3 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | SYS_C0013811 | 1 | | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("ID"=1)
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
4 consistent gets
24 physical reads
0 redo size
469 bytes sent via SQL*Net to client
540 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
ラベル:
Oracle
2017年9月29日金曜日
GradleでOracle Maven RepositoryからJDBCドライバを取得する方法
$HOME/.gradle/gradle.propertiesにOTNのアカウント情報を設定
OTNの自分のアカウント情報を設定する。
otn_user=hoge
otn_password=fuga
build.gradleにOracle Maven Repositoryの情報を追加
mavenリポジトリの設定とともに、credentialsを使用してリポジトリにアクセスするためのユーザ名とパスワードを指定する。
ユーザ名とパスワードは、gradle.propertiesに設定したものを参照する。
maven {
url "https://www.oracle.com/content/secure/maven/content"
credentials {
username otn_user
password otn_password
}
}
JDBCドライバの追加
使いたいバージョンを設定する。
compile 'com.oracle.jdbc:ojdbc8:12.2.0.1'
dependenciesで確認
参照できました。(UCPも落ちてくるのか)
\--- com.oracle.jdbc:ojdbc8:12.2.0.1
\--- com.oracle.jdbc:ucp:12.2.0.1
\--- com.oracle.jdbc:ojdbc8:12.2.0.1 (*)
2017年9月24日日曜日
sqlplusでプラガブルデータベースに接続する
sqlplus ユーザ名@プラガブルデータベース名/パスワードで接続する。
例
sqlplus hoge@pdb1/hoge@orcl
ラベル:
Oracle12
2017年9月4日月曜日
Spring BootのthymeleafでDate and Time api
Spring BootのThymeleafでDate and Time apiを使う手順
thymeleaf-extras-java8timeを依存に追加
gradleであれば下のように依存を追加する。
Thymeleaf 3.0以上の場合は、3系のバージョンを指定する。
compile 'org.thymeleaf.extras:thymeleaf-extras-java8time:2.1.0.RELEASE'
thymeleafでfomatなどを行う
temporals.formatを使ってフォーマットを行う。
<span th:text="${#temporals.format(nowDateTime, 'yyyy/MM/dd HH:mm:ss')}"></span>
その他
昔は、Java8TimeDialectをBeanとして登録しないとダメだったようですが、下のコミットの対応によりthymeleaf-extras-java8timeを依存に追加するだけでviewで利用できるようになったようです。
https://github.com/spring-projects/spring-boot/commit/c68ffa5ba059781ab2c0fb21eed7a541036e837d
ラベル:
Spring Boot,
thymeleaf
2017年8月30日水曜日
thymeleafでdata-から始まる属性に値を設定する
th:httrを使う。
<div th:attr="data-sample=${sample.name}"></div>
複数ある場合はカンマで区切って列挙する。
<div th:attr="data-sample=${sample.name},data-sample2=${sample.name2}"></div>
ラベル:
thymeleaf
2017年8月18日金曜日
maven-surefire-pluginでテスト実行時のjavaを指定する
pomの定義
jmvパラメータにテスト実行時に使用するjavaを指定します。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<jvm>/opt/java/jdk1.6.0_45/bin/java</jvm>
</configuration>
</plugin>
動作確認用のテストクラス
public class HogeTest {
@Test
public void name() throws Exception {
System.out.println("System.getProperty(\"java.version\") = " + System.getProperty("java.version"));
}
}
実行結果
指定したバージョンでテストが実行されていることが確認できます。
$ export JAVA_HOME=/opt/java/jdk1.8.0_141
$ mvn test
********** 省略 **********
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running HogeTest
System.getProperty("java.version") = 1.6.0_45
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.955 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
ラベル:
maven-surefire-plugin,
mvn
2017年8月4日金曜日
Java9でJMockit
Java9でのJMockitについて
build.gradleの定義
dependencies {
testCompile 'org.jmockit:jmockit:1.33'
testCompile 'junit:junit:4.12'
}
何も設定をせずにテストを実行すると
jmockitのライブラリを-javaagentに設定するか、システムプロパティにjdk.attach.allowAttachSelfを設定してください例外が発生します。
Caused by: java.lang.IllegalStateException: Running on JDK 9 requires
-javaagent:/jmockit-1.n.jar or -Djdk.attach.allowAttachSelf
テスト実行時のシステムプロパティを追加する
Gradleのテストタスク実行時に、上の例外で指定されたシステムプロパティを設定します。
test {
jvmArgs "-Djdk.attach.allowAttachSelf"
}
システムプロパティ追加後にテストを実行
このようにテストが正常に実行されます。
➜ java9 ./gradlew clean test
BUILD SUCCESSFUL in 2s
3 actionable tasks: 3 executed
gradle実行時にワーニングログが出力される場合
Gradle実行時にこのようなワーニングが出力される場合があります。
その場合は、Gradleのバージョンを4.1以上(現時点では4.1-rc-2が最新)にします。
➜ java9 ./gradlew clean test
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.gradle.internal.reflect.JavaMethod
WARNING: Please consider reporting this to the maintainers of org.gradle.internal.reflect.JavaMethod
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
2017年7月29日土曜日
SQL Maven Pluginを使ってデータベースのセットアップ
SQL Maven Pluginを使うことで、Mavenからデータベースのセットアップを行うことができます。
pomにpluginの定義を行う
sql-maven-pluginの追加と、接続したいデータベースに対応したJDBCドライバをdependenciesに追加します。
configurationには、接続先データベースの情報と実行したいSQLファイルのパスを設定します。
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>sql-maven-plugin</artifactId>
<version>1.5</version>
<dependencies>
<!-- specify the dependent jdbc driver here -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.1.3</version>
</dependency>
</dependencies>
<configuration>
<driver>org.postgresql.Driver</driver>
<url>jdbc:postgresql://localhost:5432/test_db</url>
<username>test</username>
<password>test</password>
<srcFiles>
<srcFile>src/test/resources/test.sql</srcFile>
</srcFiles>
</configuration>
</plugin>
</plugins>
</build>
実行結果
実行するとSQLフィルが実行されたことなどが標準出力に出力されます。
$ mvn sql:execute
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building sql-maven-plugin 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- sql-maven-plugin:1.5:execute (default-cli) @ sql-maven-plugin ---
[INFO] Executing file: /tmp/test.838893334sql
[INFO] 1 of 1 SQL statements executed successfully
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.428 s
[INFO] Finished at: 2017-07-29T06:59:17+09:00
[INFO] Final Memory: 9M/238M
[INFO] ------------------------------------------------------------------------
ラベル:
mvn
2017年7月15日土曜日
Tomcat EmbeddedでAccessLogValveを適用してアクセスログを出力する
Tomcat EmbeddedでTomcatのアクセスログを出力する方法
META-INF/context.xmlを作成してAccessLogValveを適用する
context.xmlを用いて、下のようにAccessLogValveを適用する
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="access" suffix=".log" pattern="%h %l %u %t "%r" %s %b" />
</Context>
Tomcat Embeddedを使ってTomcatを起動する
final Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
final String docBase = new File("src/main/webapp").getAbsolutePath();
final StandardContext context = (StandardContext) tomcat.addWebapp("/", docBase);
final File additionWebInfClasses = new File("build/classes/main");
final StandardRoot resources = new StandardRoot(context);
resources.addPreResources(
new DirResourceSet(resources, "/WEB-INF/classes",
additionWebInfClasses.getAbsolutePath(), "/"));
context.setResources(resources);
tomcat.start();
tomcat.getServer().await();
結果
カレントディレクトリ配下のtomcat.8080/logs配下にアクセスログが出力されるようになる。
➜ tomcat-embedded-example cat tomcat.8080/logs/access.2017-07-15.log
127.0.0.1 - - [15/Jul/2017:15:36:39 +0900] "GET / HTTP/1.1" 200 89
127.0.0.1 - - [15/Jul/2017:15:36:43 +0900] "GET /hoge HTTP/1.1" 404 1002
ラベル:
tomcat,
tomcat 8.0.45,
tomcat8
2017年6月16日金曜日
[Tomcat]StuckThreadDetectionValveを使って遅延した処理の情報をログに出力する
StuckThreadDetectionValveを使うことで、処理遅延が発生したスレッドを検知することができる。
使い方は下の通り
設定を追加する
conf/server.xmlにStuckThreadDetectionValveを使用するように設定をする。
thresholdを使って、どれぐらい遅延したら検知するかを設定する。
デフォルトは600秒となっている。下のように60と設定すると60秒となる。
ログの結果
単純に一定時間スリープするServletで試すと下のようなログが出力される。
16-Jun-2017 21:14:37.445 警告 [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.valves.StuckThreadDetectionValve.notifyStuckThreadDetected Thread [http-nio-8080-exec-6] (id=[29]) has been active for [62,731] milliseconds (since [17/06/16 21:13]) to serve the same request for [http://localhost:8080/hoge] and may be stuck (configured threshold for this StuckThreadDetectionValve is [60] seconds). There is/are [1] thread(s) in total that are monitored by this Valve and may be stuck.
java.lang.Throwable
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at hoge.HogeServlet.doGet(HogeServlet.kt:11)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
その後処理が完了すると、下のようなログも出力される
16-Jun-2017 21:15:37.465 警告 [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.valves.StuckThreadDetectionValve.notifyStuckThreadCompleted Thread [http-nio-8080-exec-6] (id=[29]) was previously reported to be stuck but has completed. It was active for approximately [120,031] milliseconds.
これで、処理遅延などが発生した場合に自動的にスレッドダンプが取得できる。
障害時にスレッドダンプとってません問題も回避できそうな気もする。
2017年5月31日水曜日
AssertJ-DBを使ってデータベースの値をアサート
AssertJ-DBを使ってデータベースのテーブルの値などをアサートする方法
assertj-dbを依存に追加する
testCompile 'org.assertj:assertj-db:1.2.0'
テーブルの内容
ID,NAME
1,name
2,name_2
シンプルにテーブルの内容をアサートする例
Tableクラスを使うことでテーブルの内容全体に対してアサートを行なうことができます。
final OracleDataSource source = new OracleDataSource();
// 接続先情報は省略
final Table.Order order = Table.Order.asc("id");
final Table table = new Table(source, "test_table", new Table.Order[] {order});
assertThat(table)
.column("id")
.value().isEqualTo(1L)
.value().isEqualTo(2L)
.column("name")
.value().isEqualTo("name")
.value().isEqualTo("name_2");
SQLの結果に対してアサート
Requestクラスを使ってSQLを実行し、その取得内容をアサートすることができます。
final Request request = new Request(source, "select name from test_table where id = 2");
assertThat(request).column(0).value().isEqualTo("name_2");
テーブルの変更内容をアサートする
Changesクラスを使うことで、開始ポイントと終了ポイントを定義しその間の変更内容をアサートすることができます。
この例では、1レコードの変更が正しいことをアサートしています。
final Table table = new Table(source, "test_table");
final Changes changes = new Changes(table);
changes.setStartPointNow();
try (final Connection connection = source.getConnection();
final PreparedStatement statement = connection.prepareStatement(
"update test_table set name = ? where id = 2")) {
statement.setString(1, "へんこうご");
statement.execute();
}
changes.setEndPointNow();
assertThat(changes)
.hasNumberOfChanges(1) // 1レコード変更されている
.ofModification().hasNumberOfChanges(1) // 変更内容は更新
.change()
.columnAmongTheModifiedOnes()
.hasColumnName("name")
.hasValues("name_2", "へんこうご"); // name_2からへんこうごに変更されていること
ラベル:
assertj-db
2017年4月13日木曜日
文字列からLocalDateを作る方法
DateTimeFormatterを使って文字列をパースする。
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
final LocalDate localDate = LocalDate.parse("2017/04/13", formatter);
ラベル:
Java8
2017年1月11日水曜日
[JMockit]特定のテストでだけモックを定義する
JMockitで特定のテストでモックを使う方法
使用バージョン
org.jmockit
jmockit
1.30
test
サンプルコード
この例では、initialCapacityを受け取るコンストラクタとaddメソッドにモック実装を定義しています。
new MockUp<ArrayList<String>>() {
@Mock void $init(int initialCapacity) {
System.out.println("initialCapacity = " + initialCapacity);
}
@Mock boolean add(String s) {
System.out.println("s = " + s);
return true;
}
};
final List<String> strings = new ArrayList<>(10);
strings.add("hoge");
実行結果
initialCapacity = 10
s = hoge
ラベル:
JMockit
登録:
投稿 (Atom)