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でデフォルト値を設定するのがいいのではないかと思う。