読者です 読者をやめる 読者になる 読者になる

Spring Data Restで@Projection

Spring Data RestでサクッとRestできるようにします。

DB

適当クエリでテーブル2つとデータを作成

create table memo_title (
  id serial primary key,
  title varchar(255) not null
);

create table memo_value (
  id serial primary key,
  value varchar(255) not null,
  title_id int not null
);

insert into memo_title(id, title) values(1, 'TESTタイトル');
insert into memo_value(id, value, title_id) values(1, 'ほげ', 1);
insert into memo_value(id, value, title_id) values(2, 'ふが', 1);

memo_value が memo_title の id を持ってて親子関係がある感じです。
(面倒なのでFKとか無しで)

コード

コードを書いていきます。
(例によってkotlinです)

MemoTitle.kt
@Entity
@Table(name="memo_title")
class MemoTitle {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = 0

    @Column(nullable = false)
    lateinit var title: String
}

@Repository
interface MemoTitleRepository : PagingAndSortingRepository<MemoTitle, Long>
MemoValue.kt
@Entity
@Table(name="memo_value")
class MemoValue {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = 0

    @Column(nullable = false)
    lateinit var value: String

    @Column(nullable = false)
    var title_id: Long = 0
}

@Repository
interface MemoValueRepository : PagingAndSortingRepository<MemoValue, Long>

こういう時に kotlin だと関連したコードが1ファイルに書けるってだけでウレシイ。。。

結果

この時点で実行すると

MemoTitle
> curl http://localhost:9000/api/memoTitles
{
  "_embedded" : {
    "memoTitles" : [ {
      "title" : "TESTタイトル",
      "_links" : {
        "self" : {
          "href" : "http://localhost:9000/api/memoTitles/1"
        },
        "memoTitle" : {
          "href" : "http://localhost:9000/api/memoTitles/1"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:9000/api/memoTitles"
    },
    "profile" : {
      "href" : "http://localhost:9000/api/profile/memoTitles"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 1,
    "totalPages" : 1,
    "number" : 0
  }
}
MemoValue
> curl http://localhost:9000/api/memoValues
{
  "_embedded" : {
    "memoValues" : [ {
      "id" : 1,
      "value" : "ほげ",
      "title_id" : 1,
      "_links" : {
        "self" : {
          "href" : "http://localhost:9000/api/memoValues/1"
        },
        "memoValue" : {
          "href" : "http://localhost:9000/api/memoValues/1"
        },
        "title" : {
          "href" : "http://localhost:9000/api/memoValues/1/title"
        }
      }
    }, {
      "id" : 2,
      "value" : "ふが",
      "title_id" : 1,
      "_links" : {
        "self" : {
          "href" : "http://localhost:9000/api/memoValues/2"
        },
        "memoValue" : {
          "href" : "http://localhost:9000/api/memoValues/2"
        },
        "title" : {
          "href" : "http://localhost:9000/api/memoValues/2/title"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:9000/api/memoValues"
    },
    "profile" : {
      "href" : "http://localhost:9000/api/profile/memoValues"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 2,
    "totalPages" : 1,
    "number" : 0
  }
}

改変

MemoValueをGETしたときにtitleも欲しかったりします。
なのでMemoValueにProjectionを入れます。

MemoValue.kt
@Entity
@Table(name="memo_value")
class MemoValue {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = 0

    @Column(nullable = false)
    lateinit var value: String

    @Column(nullable = false)
    var title_id: Long = 0

    @ManyToOne(targetEntity = MemoTitle::class, fetch = FetchType.EAGER)
    @JoinColumn(name = "title_id", insertable = false, updatable = false, nullable = false)
    lateinit var title: MemoTitle
}

@Projection(types = arrayOf(MemoValue::class))
interface MemoValueProjection {
    var id: Long
    var value: String
    var title: MemoTitle
}

@RepositoryRestResource(excerptProjection = MemoValueProjection::class)
interface MemoValueRepository : PagingAndSortingRepository<MemoValue, Long>

@Entity が FROM
@Projection が SELECT
@Repository が WHERE(今回はfindByxxとか書いてないけど)
SQL発行結果を返してくれてると思うとわかりやすいです。
jpaddl-autoとか使ってたらMemoTitle側にも@OneToManyいるかも。)

改変後結果

MemoValue
> curl http://localhost:9000/api/memoValues
{
  "_embedded" : {
    "memoValues" : [ {
      "id" : 1,
      "value" : "ほげ",
      "title" : {
        "title" : "TESTタイトル"
      },
      "_links" : {
        "self" : {
          "href" : "http://localhost:9000/api/memoValues/1"
        },
        "memoValue" : {
          "href" : "http://localhost:9000/api/memoValues/1{?projection}",
          "templated" : true
        },
        "title" : {
          "href" : "http://localhost:9000/api/memoValues/1/title"
        }
      }
    }, {
      "id" : 2,
      "value" : "ふが",
      "title" : {
        "title" : "TESTタイトル"
      },
      "_links" : {
        "self" : {
          "href" : "http://localhost:9000/api/memoValues/2"
        },
        "memoValue" : {
          "href" : "http://localhost:9000/api/memoValues/2{?projection}",
          "templated" : true
        },
        "title" : {
          "href" : "http://localhost:9000/api/memoValues/2/title"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:9000/api/memoValues"
    },
    "profile" : {
      "href" : "http://localhost:9000/api/profile/memoValues"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 2,
    "totalPages" : 1,
    "number" : 0
  }
}

無事にアソシエーション先の情報も取れました。
逆に親から子を取る時は子のサイズがわからないので個別にLazyで。

これだけの記述で必要な情報のGETに加えてPOSTやらPUTやらPATCHでのINSERTやDELETEやUPDATEなんかもルーティングされてるとかステキですね。

いじょ

libgdxで複数のInputProcessorを扱う

例えば、UIのレイヤの入力とは別にゲームのレイヤでも入力を取りたい場合、InputMultiplexerを使うといいみたい

自分が今回やりたかったケースは、スマホゲームによくある、ボタン系をそのままに後ろのマップをスクロールできるような感じ

UIをStageで定義していて、StageはInputAdapterを継承している

これに対して後ろのレイヤをタッチ操作でスクロールする処理を施そうと思い、
GestureDetectorを使おうと考えた

しかしこちらもInputAdapterを継承している為、UIの入力との同時設定が直にはできない
こんな時に、Gdx.input.inputProcessorへ複数を登録する為に使えるのがInputMultiplexer

InputMultiplexer自体の使い方は単純で、インスタンス化して上位レイヤから順にInputProcessorを登録していくだけ

以下、サンプルコード(kotlin)

val im = InputMultiplexer()
// uiのステージを先に登録
im.addProcessor(ui.stage)
im.addProcessor(GestureDetector(CameraScroller()))
Gdx.input.inputProcessor = im

// pan操作でcameraを動かすだけのリスナー
private inner class CameraScroller : GestureDetector.GestureListener {
    override fun pan(x: Float, y: Float, deltaX: Float, deltaY: Float): Boolean {
        camera.translate(-deltaX, deltaY)
        return true
    }
    override fun panStop(x: Float, y: Float, pointer: Int, button: Int): Boolean = true

    override fun pinch(initialPointer1: Vector2?, initialPointer2: Vector2?, pointer1: Vector2?, pointer2: Vector2?): Boolean = false
    override fun pinchStop() {}
    override fun longPress(x: Float, y: Float): Boolean = false
    override fun fling(velocityX: Float, velocityY: Float, button: Int): Boolean = false
    override fun touchDown(x: Float, y: Float, pointer: Int, button: Int): Boolean = false
    override fun zoom(initialDistance: Float, distance: Float): Boolean = false
    override fun tap(x: Float, y: Float, count: Int, button: Int): Boolean = false
}

// あんまりオーバーライドしないならアダプター版もあるヨ
private inner class CameraScroller : GestureDetector.GestureAdapter() {
    override fun pan(x: Float, y: Float, deltaX: Float, deltaY: Float): Boolean {
        camera.translate(-deltaX, deltaY)
        return true
    }
    override fun panStop(x: Float, y: Float, pointer: Int, button: Int): Boolean = true
}

登録されたInputProcessorは登録順で呼び出される
同じイベントが順次発火するようにしていた場合に重要になるのが各イベントの戻り値で、先に実行されたInputProcessorがtrueを返すと後のInputProcessorのイベントが呼び出されなくなるっぽい
いわゆるstopPropagation(cancelBubble)みたいな使い方ができるわけだ
この辺はInputMultiplexerのソースを見るとわかりやすいかも

IQOSの調子が悪いから交換してもらった話

半年ほど前に購入したIQOSの調子が悪い
具体的には

  • チャージャーの爪がヒビ入ってて蓋を閉じてもすぐ開いちゃう
    → 後日折れてパカパカになった
  • 朝イチに限り、満充電のハズのホルダーの電源を入れると緑点滅
    → 赤点灯になって未充電扱いされる

蓋パカはヘアゴムで留めて、
赤点灯はそこから再充電すれば数秒で充電完了になって使えるようになるので、
すごく焦っていてすぐにでも新しいのが欲しい!ってわけではないけれど、
言うても不便ではあるし、保証期間1年らしいのでオペレータに電話してみた

同じ症状の人の為に少し補足
赤点灯になる場面てタイミング的に吸う時なので、
ヒートスティックを刺した後=チャージャーに入れても蓋が閉じれないと思います
そういうときはチャージャーに入れて上からグッと押し込む感じで押さえると充電が始まってくれます
この症状の時のホルダーはほとんど満充電に近いので、
数秒程度その状態で押さえていればチャージャー側の点滅が点灯になって
またホルダーが使えるようになります


以下は記憶を頼りに記述しているので若干脚色されているかもしれません

とりあえずIQOS製品ページにあるお問い合わせ電話番号に電話してみる
長々とダイアルキーの説明を聞かされ、
しかも自分が該当するものがよくわからんので、
それっぽいのを選択

電話先: オペレータにお繋ぎします

電話お問い合わせあるあるだけど、ここの待ちが長かった・・・
待つこと数十分、やっとこオペレータさんに繋がりました

ぼく: IQOSの調子が悪いんですが・・・
オペ: どのような症状でしょうか?

ぼく: チャージャーの爪がヒビ割れていて、うまく閉まらないんです
オペ: かしこまりました。WEB登録はされていますか?

ぼく: はい
オペ: そうしましたら登録情報を確認致しますので、
本体横のシールに記載されているシリアルナンバーの右下4桁を教えてください

ぼく: XXXXです
オペ: ○○様でお間違いありませんか?

ぼく: はい
オペ: 保証期間内ですので、交換とさせていただきます

ぼく: それと、朝方だけなんですけど、
充電済のホルダーの電源入れると赤点灯になって使えなくなるんです

オペ: 朝方ですと冷え込んでいる為、
ホルダーが既定の温度まで上がらないことがありまして、
既定の温度まで上昇しない場合にそのような状態になる製品仕様となっております

ぼく: はあ
オペ: こちらからは、ホルダーの方の電源を入れる前に
手で握るなどして温めてあげることで
改善されるというようにご案内させていただいております

ぼく: (人力!?)
オペ: 念のためホルダーの方も交換致しますので、
ホルダーのキャップを外していただき、
その下に記載されているシリアルナンバーの右下4桁を教えてください

ぼく: XXXXです
オペ: かしこまりました
只今配送の方が混雑致しておりまして、
XX日(たしか三日後くらいを言われた)のお届けになります

ぼく: じゃあXX日でお願いします
配送先はWEB登録されているXXXXでお間違えありませんか?

ぼく: 大丈夫です




というわけで交換してもらったのでした
爪の脆さもさることながら、赤点灯に関しては仕様、
しかも回避手段が人肌で温めるとはなんとも
ガジェットとしてまだまだ未成熟なのだなと痛感しました

もはや普通のタバコには戻れない身になってしまったので、
製品が良くなっていくことに期待したいところです