2016年12月19日月曜日

[H2]ロックタイムアウト時間の変更

ロックタイムアウトのデフォルトは、1000ミリ秒(1秒)となっています。
この値を変更するには、SET LOCK_TIMEOUTを使う。

接続時に値を変更する場合は、下のようにURLの後にLOCK_TIMEOUTを指定する。

jdbc:h2:test;LOCK_TIMEOUT=10000

2016年11月3日木曜日

[Jackson]jsonをMapとして読み込んだ際の数値型を変更する

jsonをMapとして読み込んだ時のデフォルトのデータ型を変更する方法。

読み込むjson

{
  "number": 123456789012,
  "decimal": 1.1
}

デフォルトの設定で読み込んだ結果

こんな結果になる。整数の場合は、読み込む内容によってIntegerに変わったりします。
result = {number=123456789012, decimal=1.1}
number = java.lang.Long
decimal = java.lang.Double

データ型を変更する

下のようにUSE_BIG_INTEGER_FOR_INTSとUSE_BIG_DECIMAL_FOR_FLOATSを有効にします。
objectMapper.enable(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS);
objectMapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);

変更後の読み込んだ結果

result = {number=123456789012, decimal=1.1}
number = java.math.BigInteger
decimal = java.math.BigDecimal

読み込みコードの全体

final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS);
objectMapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);

final TypeFactory factory = objectMapper.getTypeFactory();
final Map result =
    objectMapper.readValue(inputStream, factory.constructType(Map.class));
System.out.println("result = " + result);

outputType(result, "number");
outputType(result, "decimal");

2016年10月1日土曜日

[Jackson]Enumの値を読み書きする

Jacksonを使って、json内の値をEnumとして読み書きする方法。

Jacksoのバージョン

compile 'com.fasterxml.jackson.core:jackson-databind:2.8.3'

jsonにマッピングするオブジェクトの定義

json→Enumの変換は@JsonCreatorアノテーションを設定したstaticメソッドで行う。
Enum→jsonの変換は@JsonValueアノテーションを設定したメソッドで行う。
public class SampleBean {

    private Enum value;

    public Enum getValue() {
        return value;
    }

    public void setValue(final Enum value) {
        this.value = value;
    }
}

enum Enum {
    A(1),
    B(2),
    C(3),;

    private int value;

    Enum(final int value) {
        this.value = value;
    }

    @JsonValue
    public int toValue() {
        return value;
    }

    @JsonCreator
    public static Enum fromValue(int value) {
        return Arrays.stream(values())
              .filter(v -> v.value == value)
              .findFirst()
              .orElseThrow(() -> new IllegalArgumentException(String.valueOf(value)));
    }
}

jsonの内容

{"value": 2}

json→Beanに実装

final ObjectMapper objectMapper = new ObjectMapper();

final SampleBean bean = objectMapper.readValue("{\"value\": 2}", SampleBean.class);
System.out.println("bean.getValue() = " + bean.getValue());

final String json = objectMapper.writeValueAsString(bean);
System.out.println("json = " + json);

結果

bean.getValue() = B
json = {"value":2}

2016年8月25日木曜日

[Jackson]デシリアライズ時に発生するUnrecognizedPropertyExceptionを抑止する

Jacksonを使って、jsonをBeanにデシリアライズするときに、jsonには存在しているけどBeanに存在しない属性があるとUnrecognizedPropertyExceptionが発生する。
これを、Jacksonに対する設定で、一括で無効にする方法。

Jacksonのバージョン
compile 'com.fasterxml.jackson.core:jackson-databind:2.8.1'

入力のjson
{"name": "あいうえお"}

beanの定義
class Sample {

    private String name;

    private int age;

    @Override
    public String toString() {
        return "Sample{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Jacksonを使ったデシリアライズ処理
jsonにだけ存在する属性があった場合に例外を発生させる設定を無効化する設定を行う。
無効化する設定はDeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
final ObjectMapper mapper = new ObjectMapper();

// beanに存在しない属性があっても無視する(例外を発生させない)
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

final String content = "{\"name\": \"あいうえお\"}";
final Sample sample = mapper.readValue(content, Sample.class);

System.out.println("sample = " + sample);


結果
sample = Sample{name='null', age=0}

2016年7月20日水曜日

[JMockit]バージョン1.23からはNonStrictExpectationsではなくExpectationsを使う

バージョン1.23で非推奨になって、1.25で完全に削除されたようです。

JMockitの新しいバージョンを使う場合は、NonStrictExpectationsを使うのではなく、Expectationsを使います。


モックが返す値を設定する際にminTimesを使って、最小の呼び出し回数に0を設定する。
これで、NonStrictExpectationsと同じような動きを実現できるようになる。
        new Expectations() {{
            mockList.add("hoge");
            minTimes = 0;

            mockList.get(0);
            result = "1"; minTimes = 0;
        }};

2016年7月19日火曜日

[JMockit]VerificationsでMatcherを使って、モックに渡された値をアサートする

JMockitでモックオブジェクトのメソッドに渡されたオブジェクトをアサートしたい場合は、withArgThatを使う。

使い方

検証したいメソッドの呼び出しに対して、withArgThatを使用して、検証用のwithArgThatを指定する。

この例の場合は、単純にmockList.add("hoge")で置き換えできるが・・・
引数をキャプチャするwithCaptureを使ってたけど、こっちのほうが便利。

    @Mocked
    private List mockList;

    @Test
    public void sample() throws Exception {
        mockList.add("hoge");

        new Verifications() {{
            mockList.add(withArgThat(is("hoge")));
        }};
    }

2016年5月28日土曜日

[Oracle]アプリケーションコンテキストの作成

アプリケーションコンテキストにアプリ固有の情報を格納する方法


ネームスペースの作成

create contextを使用して、ネームスペースを作成する。
ネームスペース作成時には、名前と値を管理するためのPL/SQL(プロシージャやパッケージ)をセットで指定する。

※CREATE ANY CONTEXTシステム権限が必要

sample_ctxネームスペースを作成して、名前と値を管理するためのPL/SQLにはsample_ctx_managerを指定。
この時点では、PL/SQLが存在していなくてもOK
create context sample_ctx using sample_ctx_manager;

名前と値のペアを管理するためのPL/SQLの作成

PL/SQLの名前は、create contextを作成した時の名前にする。
dbms_session.set_contextを使ってアプリケーションコンテキストに名前と値のペアを登録できる。
本来は、ログインしたユーザの情報から、DB検索して値の設定をしたりするらしい。
CREATE OR REPLACE PROCEDURE sample_ctx_mgr
IS
BEGIN
  dbms_session.set_context(
      namespace => 'sample_ctx',
      attribute => 'sample_key',
      value     => 'sample_value'
  );
END;

PL/SQLの実行

うえで作成したPL/SQL実行することでアプリケーションコンテキストに値を設定できる。

セキュリティを高めるためには、安易にexecute権限を付与するとかじゃなくってログイントリガーとかで実現するのがよいらしい。

設定された値の確認

session_contextを参照することで値を確認できる。
07:23:44 SQL> r
  1* select * from session_context

NAMESPACE         ATTRIBUTE        VALUE
------------------------------ ------------------------------ ------------------------------
SAMPLE_CTX         SAMPLE_KEY        sample_value

sys_contextを使うと値を抜き出すことができる。
select sys_context('sample_ctx', 'sample_key') from dual;


アプリケーションコンテキストをうまく使うとセキュリティを高めることができるらしい。
(まだ詳しくわかってない。)

2016年5月27日金曜日

[Oracle]DBMS_SPACE.SPACE_USAGEを使ってブロックの使用量を調べる

このPL/SQLを実行すると結果が出力される。

PL/SQL

set serveroutput on
declare
  segment_owner varchar2(30) := '&owner';
  segment_name  varchar2(30) := '&table';
  segment_type  varchar2(30) := 'TABLE';
  unformatted_blocks   number;
  unformatted_bytes    number;
  fs1_blocks           number;
  fs1_bytes            number;
  fs2_blocks           number;
  fs2_bytes            number;
  fs3_blocks           number;
  fs3_bytes            number;
  fs4_blocks           number;
  fs4_bytes            number;
  full_blocks          number;
  full_bytes           number;
begin                  
  DBMS_SPACE.SPACE_USAGE(
    segment_owner,
    segment_name,
    segment_type,
    unformatted_blocks,
    unformatted_bytes,
    fs1_blocks,
    fs1_bytes,
    fs2_blocks,
    fs2_bytes,
    fs3_blocks,
    fs3_bytes,
    fs4_blocks,
    fs4_bytes,
    full_blocks,
    full_bytes
  );

  dbms_output.put_line('空き領域が0-25%   Blocks = ' || rpad(fs1_blocks, 15)  || ' Bytes = ' || fs1_bytes);
  dbms_output.put_line('空き領域が25-50%  Blocks = ' || rpad(fs2_blocks, 15)  || ' Bytes = ' || fs2_bytes);
  dbms_output.put_line('空き領域が50-75%  Blocks = ' || rpad(fs3_blocks, 15)  || ' Bytes = ' || fs3_bytes);
  dbms_output.put_line('空き領域が75-100% Blocks = ' || rpad(fs4_blocks, 15)  || ' Bytes = ' || fs4_bytes);
  dbms_output.put_line('一杯になったもの  Blocks = ' || rpad(full_blocks, 15)  || ' Bytes = ' || full_bytes);

end;

出力例

空き領域が0-25%   Blocks = 0               Bytes = 0
空き領域が25-50%  Blocks = 0               Bytes = 0
空き領域が50-75%  Blocks = 0               Bytes = 0
空き領域が75-100% Blocks = 22              Bytes = 180224
一杯になったもの  Blocks = 286             Bytes = 2342912

deleteでデータを削除した後の出力例

一杯になったBlockが空きが75から100のところに移動している。
deleteなので使用済みのblockはそのままのこっている
空き領域が0-25%   Blocks = 0               Bytes = 0
空き領域が25-50%  Blocks = 0               Bytes = 0
空き領域が50-75%  Blocks = 0               Bytes = 0
空き領域が75-100% Blocks = 308             Bytes = 2523136
一杯になったもの  Blocks = 0               Bytes = 0

truncate後の出力例

HWMがリセットされている。
空き領域が0-25%   Blocks = 0               Bytes = 0
空き領域が25-50%  Blocks = 0               Bytes = 0
空き領域が50-75%  Blocks = 0               Bytes = 0
空き領域が75-100% Blocks = 0               Bytes = 0
一杯になったもの  Blocks = 0               Bytes = 0

2016年5月21日土曜日

[Oracle]セキュアアプリケーションロール

セキュアアプリケーションロールを使うと、ロール作成時に指定したPL/SQLからのみそのロールを有効化することができるようになる。
また、ロールを有効化する直前に、PL/SQLのコード内でセキュリティ要件を満たしているかのチェックができるメリットがある。

セキュアアプリケーションロールの作成

セキュアアプリケーションロールは、ロール作成時にidentified using を付加することで作成できる。
なお、このタイミングでidentified usingに指定したpl/sqlが存在していなくても問題ない。

SQL> create role sample_role identified using hoge.activation_sample_role;

Role created.

ロールを有効化するためのpl/sqlを作成する

このpl/sqlでは、要件を満たす場合にのみdbms_sessionパッケージを使って、先ほど作ったロールを有効化します。
サンプルとして、接続プログラムを表す値が「SQL*Plus」の場合にロールを有効化します。
create or REPLACE PROCEDURE activation_sample_role
authid CURRENT_USER
AS
BEGIN
  if (SYS_CONTEXT('USERENV','MODULE') = 'SQL*Plus') THEN
    DBMS_SESSION.set_role('sample_role');
  END IF;
END;

ロールの付与

hrスキーマのjobsテーブルへの参照権限をロールに付与し、ロールをhogeユーザに付与する。
SQL> grant select on hr.jobs to sample_role;

Grant succeeded.

SQL> grant sample_role to hoge;

Grant succeeded.

ログインしてロールの確認をする


SQL> sho user
USER is "HOGE"

-- ログイン直後のロールの確認
-- sample_roleは付与されていない
SQL> select * from session_roles;

ROLE
--------------------------------------------------------------------------------
RESOURCE

-- ロールに与えているオブジェクト権限で参照できるテーブルへアクセスしてみるがエラーとなる。
SQL> select* from hr.jobs;
select* from hr.jobs
                *
ERROR at line 1:
ORA-00942: table or view does not exist

-- ロール有効化のPL/SQLを実行
SQL> execute activation_sample_role;

PL/SQL procedure successfully completed.

-- ロールの確認
-- 有効になっていることがわかる
SQL> select * from session_roles;

ROLE
--------------------------------------------------------------------------------
SAMPLE_ROLE

-- ロールに値ている権限で参照できるテーブルも見れるようになる。
SQL> select* from hr.jobs;

JOB_ID    JOB_TITLE          MIN_SALARY MAX_SALARY
---------- ----------------------------------- ---------- ----------
AD_PRES    President        20080      40000

2016年5月10日火曜日

[Oracle]ユーザのパスワードを強制的に有効期限切れにする

ユーザのパスワードを有効期限切れにすることで、次回のログイン時にパスワードの変更を強制できるようになる。

パスワードを有効期限切れにするには、alter userを使用するのでALTER USERシステム権限を持っているユーザで行う必要がある。

下のalter userでhrユーザのパスワードを有効期限切れに出来る。
alter user hr password expire;

アカウントの状態確認

dba_usersのaccount_statusカラムを見ることで、ユーザの状態を確認できる。
パスワードを有効期限切れに変更したHRユーザのステータスが、「EXPIRED」になっていることが確認出来る。
  1* select username, account_status from dba_users where username = 'HR'

USERNAME                       ACCOUNT_STATUS
------------------------------ --------------------------------
HR                             EXPIRED

有効期限切れ状態でのログイン

以下のように、パスワードの有効期限切れであることが表示され、パスワードの変更が強制される。
08:16:41 SQL> conn hr/password
ERROR:
ORA-28001: the password has expired


hrに対するパスワードを変更しています。
新規パスワード:
新規パスワードを再入力してください:
パスワードが変更されました。
接続されました。

2016年5月4日水曜日

[Spring Boot]ConfigurationPropertiesなクラスからメタデータを生成してpropertiesファイルで補完を有効にする

カスタムな設定値を定義した場合に、application.propertiesでIDEの補完を有効にする方法。

設定値を受け取るクラスを作成

@ConfigurationPropertiesアノテーションが設定されたクラスを作成します。
このクラスの場合、「sample.name」と「sample.hoge」という設定値を保持します。
@Component
@ConfigurationProperties(prefix = "sample")
public class SampleConfiguration {

    private String name;

    private String hoge;

    // setterとgetterは省略
}

メタ情報を生成するためのライブラリをビルドスクリプトに追加

上で作ったConfigurationPropertiesアノテーションが設定されたクラスから、メタ情報(jsonファイル)を生成するためのライブラリをビルドスクリプトに追加します。

基本的にこのリンク先通りに設定してあげます。configuration-metadata-annotation-processor

Gradleの場合は、以下の様になります。(必要な箇所のみ)
buildscript {
  repositories {
    mavenCentral()
    maven { url 'https://repo.spring.io/plugins-release' }
  }
  dependencies {
    classpath 'org.springframework.build.gradle:propdeps-plugin:0.0.7'
  }
}

configure(allprojects) {
  apply plugin: 'propdeps'
  apply plugin: 'propdeps-maven'
  apply plugin: 'propdeps-idea'
  apply plugin: 'propdeps-eclipse'
}

dependencies {
  optional "org.springframework.boot:spring-boot-configuration-processor"
}

compileJava.dependsOn(processResources)

IDEの設定でアノテーションプロセッサーを有効にする

IntelliJ IDEAの場合は、設定画面の「Annotation Processors」から有効化します。

プロジェクトをビルドする

プロジェクトをビルドすることでメタデータが生成されるようになります。
今回の場合は、以下のjsonが出力されます。
{
  "groups": [{
    "name": "sample",
    "type": "com.example.SampleConfiguration",
    "sourceType": "com.example.SampleConfiguration"
  }],
  "properties": [
    {
      "name": "sample.hoge",
      "type": "java.lang.String",
      "description": "ほげ",
      "sourceType": "com.example.SampleConfiguration"
    },
    {
      "name": "sample.name",
      "type": "java.lang.String",
      "description": "名前",
      "sourceType": "com.example.SampleConfiguration"
    }
  ],
  "hints": []
}

IDEで使ってみる

この画像のように、メタデータをもとにプロパティとして使えるものがリスト表示されます。

2016年4月26日火曜日

[Gradle]依存ライブラリのバージョンの固定化

この場合、junitのバージョンは4.11に固定化される。
どこかのライブラリが新しいバージョンを落とそうとしても、強制的に4.11になる。

configurations.all {
    resolutionStrategy { 
        force 'junit:junit:4.11'
    }
}

2016年4月21日木曜日

[Oracle]パスワードの複雑度を検証する

パスワードの複雑度の検証を行うには、デフォルト(ユーザ)プロファイルのPASSWORD_VERIFY_FUNCTIONに検証を行うファンクションを設定する。
デフォルトでは、何も設定されていないので、簡単なパスワードも普通に使える状態になっている。(例えばアカウント名と同じパスワードも設定できる)

検証ロジックは、「@$ORACLE_HOME/RDBMS/ADMIN/utlpwdmg.sql」を参考にして作成すると良い。
Oracle12cの場合は、ora12c_verify_functionに検証ロジックが書かれている。

検証用ファンクションの設定方法
ALTER PROFILE default LIMIT
PASSWORD_VERIFY_FUNCTION ora12c_verify_function;

今設定されている検証用ファンクションを確認する方法
select
  profile,
  resource_name,
  resource_type,
  limit
from
  dba_profiles
where
  profile = 'DEFAULT'
  and resource_name='PASSWORD_VERIFY_FUNCTION'

結果
PROFILE         RESOURCE_NAME                  RESOURCE_TYPE   LIMIT
--------------- ------------------------------ --------------- ---------------
DEFAULT         PASSWORD_VERIFY_FUNCTION       PASSWORD        ORA12C_VERIFY_F
                                                               UNCTION

2016年4月19日火曜日

h2のバイナリリテラル表記

バイナリリテラルは、下のように文字列リテラルの前にXをつけることで表現できる。
文字列リテラルには、16進数表記の文字列を入れる。
insert into hoge (binary) values (X'3031323334353637383930');

参考→http://stackoverflow.com/questions/9320200/inline-blob-binary-data-types-in-sql-jdbc

2016年4月7日木曜日

Java8のJavadocは自己終了要素がエラーになる

Javadoc中に自己終了要素(例えば、<p />)があると、Java8のjavadocでは下のようなエラーが出力されます。

Hoge.java:3: エラー: 自己終了要素は使用できません

調べた結果、自己終了要素は使ってはダメなようです。
例えば、<p />は<p>に置き換える必要があります。

2016年4月3日日曜日

Gradleのタスクに指定できるオプションを調べる方法

Gradleのhelpタスクで、そのタスクに指定できるオプションを見ることができる。

使い方

helpタスクのオプションのtaskに対して調べたいタスク名を指定する。
gradlew help --task タスク名

実行結果

dependencyInsightタスクのhelpを見てみた結果。
このタスクは2つのオプションを指定できることなんかがわかる。
$ ./gradlew help --task dependencyInsight
:help
Detailed task information for dependencyInsight

Path
     :dependencyInsight

Type
     DependencyInsightReportTask (org.gradle.api.tasks.diagnostics.DependencyInsightReportTask)

Options
     --configuration     Looks for the dependency in given configuration.

     --dependency     Shows the details of given dependency.

Description
     Displays the insight into a specific dependency in root project 'spring-boot-in-action'.

Group
     help

2016年3月18日金曜日

H2の1.3系では、auto_incrementなカラムを持つテーブルへのバッチinsertが使いものにならない

H2の安定版の1.3系を使うと、バッチinsert後のgetGeneratedKeysが1レコードしか返さないため、データベースで設定された値を取得できない問題がある。

getGeneratedKeysのJavadocにも下の記述があるので、完全に仕様のようです。
Return a result set that contains the last generated auto-increment key
for this connection, if there was one. If no key was generated by the
last modification statement, then an empty result set is returned.
The returned result set only contains the data for the very last row.

試した結果

試したバージョンは、1.3.176となっています。
compile 'com.h2database:h2:1.3.176'

このコードを実行すると、バッチinsertで10レコード登録しているので、it.getInt(1)が10回標準出力に出力されるはずですが、実際には1回しか出力されません。
出力される値は、Javadocにあるように最後に採番された値の「it.getInt(1) = 10」となります。
jdbcDataSource.connection.use {connection ->
  connection.createStatement().use {statement ->
    statement.execute("create table test(id bigint auto_increment, name varchar(255))")
  }

  connection.prepareStatement("insert into test (name) values (?)").use { ps ->
    for (i in 1..10) {
      ps.setString(1, "name_$i")
      ps.addBatch();
    }
    ps.executeBatch()

    ps.generatedKeys.use {
      while (it.next()) {
        println("it.getInt(1) = ${it.getInt(1)}")
      }
    }
  }
}

ちなみに、Beta版の1.4系(現時点での最新の1.4.191)にするとこの問題は解消しています。

2016年3月8日火曜日

[Jackson]Java8のDate and Time APIを使ってみる

JacksonでJava8のDate and Time APIを扱うためには、依存ライブラリにjackson-datatype-jsr310を追加し、
モジュールをObjectMapperに登録する必要があります。

このモジュールを使わなかった場合、Date and Time APIのクラスのプロパティの値が、
Jsonのプロパティとして出力されるので非常に残念な結果となります。

ライブラリの追加

Gradleの場合には、以下のようになります。
compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.7.2'

モジュールの追加

ObjectMapperのregisterModuleメソッドを使って、JavaTimeModuleを登録します。
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());

実行結果

下のコードを使ってLocalDate型の値をシリアライズします。
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
System.out.println(objectMapper.writeValueAsString(LocalDate.now()));

実行結果は、以下のようになります。年月日が配列の各要素に分解されてシリアライズされます。
[ 2016, 3, 8 ]

シリアライズ時のフォーマットを変更する

シリアライズ時のフォーマットを変更する場合にはJavaTimeModuleを使わずに
jackson-datatype-jsr310に含まれるシリアライザーを直接使います。

[Jackson]特定の型に対してカスタムなシリアライザを設定する
と同じようにSimpleMoculeを使って、任意のシリアライザーを登録します。

今回は、LocalDateを使うのでjackson-datatype-jsr310に含まれるLocalDateSerializerを登録します。
LocalDateSerializerは、シリアライズ時に行うフォーマットを指定することができるのでDateTimeFormatterを使ってフォーマットを指定ます。
final ObjectMapper objectMapper = new ObjectMapper();

SimpleModule module = new SimpleModule();

// formatを指定してLocalDateSerializerを登録する。
module.addSerializer(new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyyMMdd")));
objectMapper.registerModule(module);

System.out.println(objectMapper.writeValueAsString(LocalDate.now()));

実行すると以下のように指定したフォーマットでシリアライズされます。
"20160308"

2016年3月5日土曜日

[Jackson]特定の型に対してカスタムなシリアライザを設定する

特定の型に対して一括でカスタムなシリアライザを適用する方法。
このパターンを使用すると、シリアライズ対象のプロパティ(getter)にアノテーションでシリアライザを指定しなくてよくなります。
特定型に対して一律シリアライザを適用する場合には、もれなく実行できるメリットがあります。

カスタムなシリアライザの作り方はこちら→[Jackson]末尾のスペースを取り除いんてjson変換する

この例では、String型のプロパティに対して一律スペースをトリムするシリアライザが適用されます。
final Sample sample = new Sample();
sample.setName("aa ");
sample.setAge(100);

// 型とシリアライザのマッピングを定義
final SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(String.class, new TrimSpaceSerializer());

final ObjectMapper objectMapper = new ObjectMapper();
// ObjectMapperにシリアライザの定義を持つモジュールを追加
objectMapper.registerModule(simpleModule);
final String result = objectMapper.writeValueAsString(sample);

// カスタムなシリアライザ
private static class TrimSpaceSerializer extends JsonSerializer {
    @Override
    public void serialize(
            String value,
            JsonGenerator gen,
            SerializerProvider serializers) throws IOException, JsonProcessingException {
        gen.writeString(value.trim());
    }
}

実行すると、この結果のようにString型のプロパティに対してトリムが行われます。
{"name":"aa","age":100}

2016年3月4日金曜日

[Jackson]末尾のスペースを取り除いてjson変換する

Jacksonを使ってオブジェクトをシリアライズするときに、文字列内のスペースをトリムする方法です。
なお、使用しているJacksonのバージョンは2.7.2となります。

デフォルトでは、何も行われないので、下の実行結果のように末尾のスペースがそのままシリアライズされます。

デフォルトの場合の動作

Sample sample = new Sample();
sample.setName("aaaa   ");

StringWriter writer = new StringWriter();
objectMapper.writeValue(writer, sample);

{"name":"aaaa   "}

スペースを除去するためには、カスタムのシリアライザを作成し、アノテーションで設定する必要があります。
このページが参考になります。

http://stackoverflow.com/questions/7161638/how-do-i-use-a-custom-serializer-with-jackson


カスタムのシリアライザの実装

JsonSerializerを継承して、serializeメソッドで値を書き換えます。
シリアライズ対象の値(value)をtrimして書き込みます。
private static class TrimSpaceSerializer extends JsonSerializer<String> {

    @Override
    public void serialize(
            String value,
            JsonGenerator gen,
            SerializerProvider serializers) throws IOException, JsonProcessingException {
        gen.writeString(value.trim());
    }
}

Beanにシリアライザを設定する

カスタムシリアライザをJsonSerializeアノテーションのusing属性に設定します。
JsonSerializeは、値を変換書けたいプロパティのgetterに設定します。
private static class Sample {
    private String name;

    @JsonSerialize(using = TrimSpaceSerializer.class)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

実行結果

スペースがトリムされました。
{"name":"aaaa"}

2016年3月3日木曜日

[AngularJS1.3]One-Time Data Binding(一回だけ値をバインドして、変更は監視しない)

AngularJS1.3から、One-Time Data Bindingなるモードが増えたらしい(知らなかった)。


これは、初回の一度のみデータをバインドして、その後モデルに変更があってもバインド済みの箇所の値は変更されないらしい。
データの変更監視がなくなる分、パフォーマンスが良くなるらしい。

使い方は、下のコードのように::を付加するだけなので簡単です。

この例だと、入力欄の値が変更されても::app.nameの部分は値が変更されません。
あと、ng-repeatの部分もapp.usersに要素が増えたとしても初期表示のまま変更されません。
<p>{{ app.name }}</p>
  <p>{{ ::app.name }}</p>
  <input type="text" ng-model="app.name">

  <div ng-repeat="user in ::app.users">
    <p>{{ user.name }}</p>
  </div>

2016年3月2日水曜日

git commitで特定ファイルのみコミットする

gitでコミットするときにステージング済みのファイルから一部のファイルだけコミットする場合は、
git commit -- file listのように、コミット対象のfile listを指定する。

■コミット前のステータス
$ git status
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

        new file:   fuga.txt
        new file:   hoge.txt

■コミット
$ git commit -m 'add' -- hoge.txt
[master (root-commit) fb9d957] add
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 hoge.txt

■コミット後のステータス
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   fuga.txt

■複数ファイルの場合はスペースで区切る
$ git commit -m 'mod' -- hoge.txt piyo.txt

2016年2月19日金曜日

HibernateのColumnDefaultを使ってデフォルト値をDBへ登録する

Hibernateの独自実装のColumnDefaultアノテーションとGeneratedアノテーションを使うと、テーブルへのレコード追加時や更新時にデフォルト値を設定することができる。

ColumnDefaultアノテーション

このアノテーションに設定した値が、デフォルト値となる。
受け取れる値は文字列のみなので、数値型や日付型などのカラムでもすべて文字列で設定する必要がある。

Generatedアノテーション

このアノテーションで初期値を代入するタイミングを定義する。

Entityクラス

lastModified属性が、デフォルト値が代入されるカラムとなる。
ポイントは、Columnアノテーションのinsertableとupdatableにfalseを設定しているところ。
これを行わないと、insertやupdate時にデフォルト値が設定されなくなる。

ColumnDefaultアノテーションは、すでにデータベース上に作成されているテーブルに定義されていることを想定しているので、ここでは使用していない
@Entity
public class HogeEntity {

    @Id
    @GeneratedValue
    public Long id;

    @Column(insertable = false, updatable = false)
    @Generated(GenerationTime.ALWAYS)
    public Timestamp lastModified;

    @Override
    public String toString() {
        return "HogeEntity{" +
                "id=" + id +
                ", lastModified=" + lastModified +
                '}';
    }
}

Entityに対応したテーブル定義

lastModifiedカラムは、登録・更新時にデフォルト値がセットされる。
CREATE TABLE `HogeEntity` (
  `id` bigint(20) NOT NULL,
  `lastModified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
)

実行結果

Entityをデータベースに永続化する部分の実装例。
HogeEntity entity = new HogeEntity()
em.persist(entity)
em.flush()
System.out.println("entity = " + entity)
上のコードを実行した結果は、このようになる。
明示的にEntityに値を設定していないのに、最終更新日が登録されている。
entity = HogeEntity{id=1, lastModified=2015-12-13 01:29:35.981}

この機能は、既存のテーブルに対してHibernateを適用する際に使用する。
新規に構築する場合には、PrePersistアノテーションを使ってEntityでデフォルト値を設定するのがいいのではないかと思う。

2016年1月25日月曜日

[JUnit]ExpectedExceptionを使って例外のアサート

JUnitで提供されるExpectedExceptionを使って例外のアサートをしてみる。

ExpectedExceptionがなかった時代は、try-catchで例外補足してアサートしていたのでかなりの便利機能だと思う。
※@Testアノテーションのexpected属性だと、発生する例外のクラスしかアサートできなかったので、これは使い物にならなかった。

使い方


テストクラスのインスタンス変数で、ExpectedExceptionを宣言する。
ルールであることを示す、@Ruleアノテーションを設定する。
宣言時には、ExpectedException.noneを代入することで、例外が発生しないことを期待するようにしておく。
    @Rule
    public ExpectedException expectedException = ExpectedException.none();

例外をアサートする場合には、例外が発生することを期待するテストケースのメソッドで、アサートする内容をExpectedExceptionに設定する。
このケースだと、NullPointerExceptionが発生してそのメッセージには「error」が保持されていることを期待している。
    @Test
    public void nullPointerException() throws Exception {
        expectedException.expect(NullPointerException.class);
        expectedException.expectMessage("error");
        throw new NullPointerException("error");
    }

元例外(cause)をアサーとしたい場合は、expectCauseにMatcherを登録する。
この例では、causeの例外クラスがNullPointerExceptionであることをアサーとしている。
        expectedException.expect(CustomException.class);
        expectedException.expectCause(instanceOf(NullPointerException.class));
        throw new CustomException(new NullPointerException());

メッセージやcause以外の属性をアサートする場合には、カスタムのMatcherを作ると良い。
例えば、SQLExceptionのベンダーコード(getErrorCode)をアサートする場合には以下のようなMatcherを作る。
    public static class IsSqlErrorCode extends TypeSafeMatcher {

        private final int expectedSqlErrorCode;

        public IsSqlErrorCode(int expectedSqlErrorCode) {
            this.expectedSqlErrorCode = expectedSqlErrorCode;
        }

        public static IsSqlErrorCode isSqlErrorCode(int expectedSqlErrorCode) {
            return new IsSqlErrorCode(expectedSqlErrorCode);
        }

        @Override
        protected boolean matchesSafely(SQLException e) {
            return e.getErrorCode() == expectedSqlErrorCode;
        }

        @Override
        public void describeTo(Description description) {
            description.appendText("exception with the error code ")
                       .appendValue(expectedSqlErrorCode);
        }

        @Override
        protected void describeMismatchSafely(SQLException item, Description mismatchDescription) {
            mismatchDescription.appendText("error code was ")
                               .appendValue(item.getErrorCode());
        }
    }

Matcherの使い方は・・・
        expectedException.expect(SQLException.class);
        expectedException.expect(IsSqlErrorCode.isSqlErrorCode(1));
        throw new SQLException("sql error", null, 1);

2016年1月9日土曜日

[NIO2]を使ってZipファイルの読み込みを行う

試してみたのは、郵便番号CSV?をダウンロードしてきて、zip内のcsvデータを標準出力するもの。

NIO2では、zipファイルを仮想ファイルシスムとして扱えるので、Files.walkFileTreeでzip内のファイルだけを対象にした処理を簡単に実装できる。


// zipのダウンロード
String url = "http://www.post.japanpost.jp/zipcode/dl/oogaki/zip/10gumma.zip";
final Path zip = Paths.get("10gumma.zip");
try (InputStream stream = new URL(url).openStream()) {
    Files.copy(stream, zip, StandardCopyOption.REPLACE_EXISTING);
}

// zip内のcsvデータを標準出力に出力
try (FileSystem fileSystem = FileSystems.newFileSystem(zip, null)) {
    Files.walkFileTree(fileSystem.getPath("/"), new SimpleFileVisitor() {
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            try (BufferedReader reader = Files.newBufferedReader(file, Charset.forName("windows-31j"))) {
                reader.lines().forEach(System.out::println);
            }
            return FileVisitResult.CONTINUE;
        }
    });
}