【Golang】Goで画像処理: 画素勾配によるエッジ検出
画素勾配でエッジ検出
画像上に現れる物体の境界(エッジ)を検出する方法に、隣接する画素間の輝度の変化量を利用する方法があります。
この輝度の変化量が大きい部分をエッジとみなし、画像の分類や画像マッチングなどに使用できる特徴量として扱うことができます。
今回はこの方法を用いて、画像中のエッジを可視化してみました。
勾配
シンプルに輝度の勾配の大きさを求めるため、輝度勾配ベクトルのノルムを指標として使用します。
幅ピクセル、高さピクセルの画像における、画素の輝度をとしたとき( , )、画素の輝度の勾配ノルムは以下のように求めます。
,はそれぞれ水平・垂直方向の画素輝度差分を表しています。これらの二乗和平方根を輝度勾配ベクトルのノルム、すなわち勾配輝度の大きさとします。
実装
上記のエッジ検出をGoで実装しました。
画素の右隣、下隣の画素との差分を求めているので、出力する輝度勾配ノルムの画像サイズは幅、高さとなります。今回は簡単のため、末尾ピクセル(またはのピクセル)はノルム0としました。
また、あくまで勾配の大きさを可視化するにとどめ、しきい値を決定した上でのエッジ判定は行いません。
package main import ( "image" "image/color" "image/jpeg" "math" "os" ) // 勾配 type Gradient struct { dr int64 dg int64 db int64 da int64 } func main() { // 入力画像パス img, _ := jpeg.Decode(os.Stdin) // 出力画像 bounds := img.Bounds() dest := image.NewRGBA(bounds) for y := bounds.Min.Y; y < bounds.Max.Y-1; y++ { for x := bounds.Min.X; x < bounds.Max.X-1; x++ { lt := img.At(x, y) rt := img.At(x+1, y) lb := img.At(x, y+1) dx := createGradient(<, &rt) dy := createGradient(&lb, &rt) d := createGradientNormRGBA(dx, dy) dest.Set(x, y, d) } } err := jpeg.Encode(os.Stdout, dest, nil) if err != nil { panic("Failed to encode JPEG gradient image.") } } // 画素RGBAの差分を求める関数 func createGradient(c1, c2 *color.Color) *Gradient { r1, g1, b1, a1 := (*c1).RGBA() r2, g2, b2, a2 := (*c2).RGBA() return &Gradient{ int64(r2) - int64(r1), int64(g2) - int64(g1), int64(b2) - int64(b1), int64(a2) - int64(a1), } } // 画素のRGBA輝度勾配のノルムを求める関数 func createGradientNormRGBA(dx, dy *Gradient) *color.RGBA { dr := createGradientNorm(dx.dr, dy.dr) dg := createGradientNorm(dx.dg, dy.dg) db := createGradientNorm(dx.db, dy.db) da := createGradientNorm(dx.da, dy.da) return &color.RGBA{R: dr, G: dg, B: db, A: da} } // 輝度勾配のノルムを求める関数 func createGradientNorm(dx, dy int64) uint8 { d := math.Sqrt(float64(dx*dx + dy*dy)) return uint8(float64(d) / math.Pow(2, 17) * 255) }
出力画像はRGBAそれぞれの規模勾配ノルムを求めた上で、256階調のRGBA画像として出力したものとしています。 最終的な輝度はuint8に収まるように変換しているのですが、正規化のための最大値は2 ^17と決めでやっちゃってます。。。 ちゃんと正規化するなら、輝度勾配の最大値で正規化します。
結果
$ go run grad.go < gophar.jpeg > edge.jpeg
gophar.jpeg
↓
edge.jpeg
入力した画像のエッジが取れているのが見て取れます。
Gopharくんの境界はやや太めなので、境界中に輝度勾配の変化のない領域があるのがわかりますね。
参考文献
【Golang】Goで画像拡張子を変換する
goで画像処理を行う場合、image
パッケージを使用することができます。
image/jpeg
image/png
パッケージの Encode
関数で拡張子を変換します。
package main import( "os" "fmt" "bufio" "image" _ "image/jpeg" "image/png" ) func main() { // 入力画像パス. scanner := bufio.NewScanner(os.Stdin) fmt.Print("Input source image file path >>") if !scanner.Scan() { fmt.Println("Please input source image file path.") return; } srcPath := scanner.Text() // ファイルオープン file, err := os.Open(srcPath) assert(err, "Invalid image file path " + srcPath) defer file.Close() // ファイルオブジェクトを画像オブジェクトに変換 img, _, err := image.Decode(file) assert(err, "Failed to convert file to image.") // 出力画像パス. fmt.Print("Input output image file path >>") if !scanner.Scan() { fmt.Println("Please input output image file path.") return; } dstPath := scanner.Text() // 出力ファイルを生成 out, err := os.Create(dstPath) assert(err, "Failed to create destination path.") defer out.Close() // 画像ファイル出力 // jpeg.Encode(out, img, nil) png.Encode(out, img) } // errorオブジェクトをチェックし、nilの場合例外を送出 func assert(err error, msg string) { if err != nil { panic(err.Error() + ":" + msg) } }
参考文献
【Spring】Spring Cache でキャッシュ機能を利用する
Spring Cacheとは
Spring Cacheは、Springでキャッシュ機能を使用するためのライブラリです。
Spring Cacheでは抽象的なキャッシュ機能の枠組みを提供します。そのため、内部の具体的な実装を気にせずに簡単にキャッシュ機能を使うことができます。
実装はデフォルト設定のほか、既存のキャッシュライブラリを使用することができます。例えば、CaffeineやJCacheなどをSpring Cache実装として選択することができます。
Spring Cacheのアーキテクチャ
具体的なキャッシュライブラリのことは考えず、まずはSpring Cacheのアーキテクチャを眺めてみます。
このアーキテクチャの概要は、Macchinetta Framework documentationで掲載されている図がとてもわかり易いので、構成だけ引用させていただきました。
青いアイコンが、Spring Cacheで提供される部分になります。
データ読み込み時の流れ
- データを参照するモジュールは、
@Cacheable
アノテーションがついたデータ供給用メソッドを呼び出す。 - Cache AOPが提供する
@Cacheable
にキー値が渡される。キー値はデフォルトで引数値の組み合わせとなる。Cache AOPはキー値をもとにCacheManagerにキャッシュ値の有無を問い合わせる。 - CacheManagerはキー値を元にハッシュテーブルに問い合わせる。キャッシュが存在すればキャッシュ値をCache AOPに返す。
- Cache AOPはキャッシュ値を受け取り、呼び出し元に返す。キャッシュがヒットしなかった場合、通常通りデータ供給用メソッドを実行し、その結果を返す。このとき、供給用メソッドの引数をキー値として(デフォルト設定であれば)、供給用メソッドの結果がハッシュテーブルに保存される。
Spring Cacheを構成するコンポーネント
Cache AOP
@Cacheable
などCacheを有効化するためのアノテーションです。Cache機能の入り口となるインタフェースになります。この層が、キャッシュデータの使用元であるモジュールから、データがキャッシュされたものなのか、最新のデータなのかといった詳細を隠蔽します。
キャッシュ(コンピューティング) - Wikipedia)でも書かれていますが、
キャッシュは通常、隣接する層からは見えないように設計されている抽象化層です。
のように、通常キャッシュは隣接するデータ参照モジュールからはキャッシュの有無は隠蔽されます。 AOPという名がついているのはSpring AOPを使用した実装であるためで、Spring AOPはアプリケーション横断的に処理を挿入することを容易にします。この処理挿入は通常アノテーションで行われ、Spring Cacheもこのアノテーションによる処理挿入を利用しています。
CacheManager
Spring Cacheのキャッシュ処理をコントロールします。上図のキャッシュデータ引き当て処理の場合、CacheManagerはハッシュテーブルからキャッシュキーに対応するキャッシュ値を読み出し返します。CacheManagerはあくまでインタフェースであり、実装はキャッシュ実装によって変わります。
ハッシュテーブル
キャッシュ値をキーに紐付けて保存します。保存されたキャッシュがどこに格納されているのか、どのように管理されるのかはキャッシュ実装によって変わりますが、主にHashMap実装になります。
実装
では、実際にSpring Cacheを使用したキャッシュ処理を実装してみましょう。
Sprint Initializr上でSpring Web StartarとSpring Cacheを導入したプロジェクトを作成しました。言語選定はKotlinとしています。
プロジェクト内の構造は以下の通り。
└── springcachedemo ├── ServletInitializer.kt ├── SpringCacheDemoApplication.kt ├── application │ └── controller │ └── ProductController.kt ├── config │ └── cache │ ├── CacheConfig.kt │ └── MyKeyGenerator.kt └── domain ├── entity │ └── Product.kt ├── repository │ └── ProductRepository.kt └── service └── ProductService.kt
依存関係は下記の通り。
dependencies { implementation("org.springframework.boot:spring-boot-starter-cache") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-web") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") runtime ("mysql:mysql-connector-java") providedRuntime("org.springframework.boot:spring-boot-starter-tomcat") testImplementation("org.springframework.boot:spring-boot-starter-test") }
キャッシュの検証用アプリとして、商品情報を取得するAPIを実装します。
また検証のためRDB(MySQL)上にテーブル PRODUCTS
を用意し、その中に100000件のレコードを用意しました。
CREATE TABLE `PRODUCTS` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `NAME` varchar(45) COLLATE utf8mb4_general_ci NOT NULL, `PRICE` varchar(45) COLLATE utf8mb4_general_ci NOT NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB AUTO_INCREMENT=100001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
mysql> SELECT count(ID) FROM `cache-example`.PRODUCTS; +-----------+ | count(ID) | +-----------+ | 100000 | +-----------+ 1 row in set (0.01 sec)
次にコード実装に移ります。
まず、レコードをマッピングするオブジェクトのEntityクラスとして Product
を定義します。
IDはAutoIncrementとしたので @GeneratedValue
で明示的に値の自動生成を記述します。
@Entity @Table(name = "PRODUCTS") data class Product( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "ID") val id: Int?, @Column(name = "NAME") val name: String, @Column(name = "PRICE") val price: Int )
Product
テーブルを抽象化するリポジトリ ProductRepository
を用意。
@Repository interface ProductRepository : JpaRepository<Product, Int>
ProductRepository
を参照して商品情報を取得するサービスクラス ProductService
では、商品情報取得メソッド getProducts()
に@Cacheable
アノテーションを付加し、取得結果がキャッシュされるようにしています。 @Cacheable
の引数としてキャッシュを識別するラベルを付与します。
@Service class ProductService( @Autowired private val productRepository: ProductRepository ) { @Cacheable("getProducts") fun getProducts(): List<Product> = productRepository.findAll() }
商品情報取得APIを定義した ProductController
クラスを用意します。
呼び出し先の ProductService::getProducts()
はキャッシュ設定が効いているので、二回目以降の呼び出しでは処理速度が変化するはずです。
@RestController @RequestMapping("/products") class ProductController( @Autowired private val productService: ProductService ) { @GetMapping("/") fun getProducts() = productService.getProducts() }
最後に、 @SpringBootApplication
を付加したクラスに @EnableCaching
アノテーションを付加して、アプリケーションに対するSpring Cache機能を有効化します。
@SpringBootApplication @EnableCaching class SpringCacheDemoApplication fun main(args: Array<String>) { runApplication<SpringCacheDemoApplication>(*args) }
この状態でアプリケーションを起動し、以下のAPIをSpring Cache設定有り・無しの状態で3回ほど実行してそのレスポンスタイム(リクエストを送信してから、応答が返るまで、データ整形を除く)を見ます。なお、キャッシュなしの場合でも別の部分でキャッシュが効いている可能性があるため、複数回試行します。
$ curl http://localhost:8080/api/v1/products/ | jq '.[]' { "id": 1, "name": "prod0", "price": 0 } { "id": 2, "name": "prod1", "price": 100 }
キャッシュ無効
1st | 1307 ms |
2nd | 618 ms |
3nd | 451 ms |
キャッシュ有効
1st | 1277 ms |
2nd | 61 ms |
3nd | 53 ms |
キャッシュ前と比較し、キャッシュ後のレスポンスタイムは約10分の1まで短くなりました。
他の実装でも線形にこの速度短縮が適用されるわけではありませんが、十分キャッシュの効果は現れているといえるでしょう。
Spring Cacheの主な機能
@Cacheable
Cacheable (Spring Framework 5.1.9.RELEASE API)
メソッド(またはクラス内のすべてのメソッド)を呼び出した結果をキャッシュできることを示す注釈。推奨メソッドが呼び出されるたびに、キャッシュ動作が適用され、指定された引数に対してメソッドが既に呼び出されているかどうかがチェックされます。賢明なデフォルトでは、単にメソッドのパラメーターを使用してキーを計算しますが、SpEL式はkey()属性を介して提供するか、カスタム KeyGenerator実装でデフォルトの実装を置き換えることができます(keyGenerator()を参照)。
(Cacheable (Spring Framework 5.1.9.RELEASE API) より)
@Cacheableをメソッドに付加することで、その返戻値の結果をキャッシュすることができます。キャッシュストレージや格納データ構造は後述するキャッシュ実装に任せます。
@Cacheable("getProducts") fun getProducts(): List<Product> = productRepository.findAll()
キャッシュキー
Spring Cacheでは、キャッシュの結果に対し一意のキーを設定します。
このキーの値はデフォルトではキャッシュの引数を元に生成されます。
ただし、引数 key
にSpEL式と呼ばれる形式で独自のキーを設定することもできます。
@Cacheable(value = ["getProduct"], key = "'products/' + #id") fun getProduct(id: Int): Product = productRepository.findById(id).orElseThrow()
上記の例では、引数 key
に 'products/' という文字列と、引数である id
を結合した文字列を設定した例となっています。
keyに渡す文字列内では、静的文字列はシングルクォートで囲む必要があります(これをしないと、変数として扱われてしまいます)。
KeyGenerator
@Cacheableデフォルトキーの生成規則をカスタマイズできます。
KeyGenerator
インタフェースを実装したクラスを定義し、メソッド generate
内でどのようなキーを生成するかを定義します。
class MyKeyGenerator : KeyGenerator { override fun generate(target: Any, method: Method, vararg params: Any?): Any { return target.javaClass.simpleName + "/" + method.name + "/" + params.joinToString() } }
生成したKeyGeneratorはBeanとして登録し、 @Cacheable
の引数 keyGenerator
に設定したBean名を渡せば自前のKeyGeneratorが適用されます。
@Configurationclass CacheConfig : CachingConfigurerSupport() { @Bean("MyKeyGenerator") override fun keyGenerator(): KeyGenerator? = MyKeyGenerator() }
@Service class ProductService(@Autowired private val productRepository: ProductRepository) { @Cacheable("getProducts", keyGenerator = "MyKeyGenerator") fun getProductsWithMyKeyCache(): List<Product> = productRepository.findAll() }
CacheManager
Springのキャッシュ機能を管理するクラスです。キャッシュの引当処理と、キャッシュに紐づくキーの取得機能を提供します。CacheManager実装では、キャッシュの有効期限などより細かい機能を提供します。
CacheManagerを用意せずとも @Cacheable
アノテーションでキャッシュ機能を使うことができますが、@Cacheable
の引数にCacheManagerを渡すことで、引数に渡したCacheManagerの管理の元、キャッシュ機能を使用することができます。
以下の例では、 SimpleCacheManager
クラスをインスタンス化し、それを明示的に @Cacheable
に引き渡しています。
@Configurationclass CacheConfig : CachingConfigurerSupport() { @Bean("MyCacheManager") override fun cacheManager(): CacheManager? = SimpleCacheManager() }
先程の KeyGenerator
同様、定義した CacheManager
Beanの名称を @Cacheable
アノテーション引数に渡します。
@Service class ProductService(@Autowired private val productRepository: ProductRepository) { @Cacheable("getProducts", cacheManager = "MyCacheManager") fun getProducts(): List<Product> = productRepository.findAll() }
上記の SimpleCacheManager
はデフォルトで使用されるCacheManager実装ですが、このキャッシュマネージャを独自にカスタマイズしたり、キャッシュライブラリの実装に基づいたCacheManagerも使用することができます。
@CachePut
キャッシュの値を更新します。
@Cacheable
で指定したラベル値を指定し、それを更新するメソッドに付与します。
CachePut(Spring Framework 5.1.9.RELEASE API)
メソッド(またはクラスのすべてのメソッド)がcache put操作をトリガーすることを示す注釈 。 @Cacheable注釈とは対照的に、この注釈は推奨されるメソッドをスキップさせません。むしろ、常にメソッドが呼び出され、その結果が関連付けられたキャッシュに保存されます。Java8のOptional戻り値の型は自動的に処理され、そのコンテンツはキャッシュに保存されます(存在する場合)。
キャッシュは @CachePut
を付与したメソッドの戻り値によって更新され、引き続き更新後の値がキャッシュされます。
@CachePut("getProduct", key = "'products/' + #product.id") fun putProduct(product: Product) = productRepository.save(product)
@CacheEvict
付加したメソッドを呼び出すと、指定したキーに紐づくキャッシュを削除します。
主に不要なキャッシュを能動的に削除したい場合に使用します。
例えば、外部からキャッシュを削除するAPIを定義し、 @CacheEvict
を付加したServiceメソッドを呼び出していつでも外からキャッシュ値を削除する・・・といったことができます。
@CacheEvict("getProduct", key = "'products/' + #id") fun deleteProducts(id: Int) = productRepository.deleteAll()
参考文献
- Spring Cache
- 4.2. キャッシュの抽象化(Cache Abstraction) — Macchinetta Framework オンライン版クラウド拡張 Development Guideline 1.1.1.RELEASE documentation
- キャッシュ(コンピューティング) - Wikipedia#The_difference_between_buffer_and_cache)
- Cacheable (Spring Framework 5.1.9.RELEASE API)
- CachePut(Spring Framework 5.1.9.RELEASE API)
- Spring Cache - Creating a Custom KeyGenerator | Baeldung
- 6. Spring Expression Language (SpEL)
【Spring】JSONリクエストのBoolean型以外のパラメータを暗黙的にBooleanに変換させない
Spring2ではJSONリクエスト/レスポンスボディのマッピングの際、Boolean型メンバに非Booleanの値をマッピングしようとしたときに正常に処理を終えてしまいます。
例えば、"1" や "true" という文字列を渡してもtrueに変換され、 "0" や "false" 文字列を渡すとfalseに変換されるといった塩梅です。
これは fasterxml/Jacksonのデフォルト仕様で 、このマッピングを防ぐにはSpringで使用されるObjectMapperの設定を修正する必要があります。
環境
- Kotlin: 1.3.21
- Spring: 2.1.4 RELEASE
- JDK : OpenJDK11.0.2
ソースはKotlinです。
Deserializerを定義
JSONリクエストパラメータをKotlin内オブジェクトにマッピングする際の規則を明示的に記述する Deserializer
クラスを定義します。
BooleanDeserializer
class BooleanDeserializer : JsonDeserializer<Boolean>() { // JSONパラメータをBooleanにマッピングしようとすると呼ばれる override fun deserialize(jsonParser: JsonParser, ctxt: DeserializationContext): Boolean { return jsonParser.booleanValue // booleanValue : booleanでない値のときに参照されるとJsonParseError } }
deserialize
メソッドはJSONパラメータをBooleanにマッピングしようとすると呼ばれます。このとき、 booleanでない値をマッピングしようとするとJsonParseErrorが呼ばれるので、制御したい場合は適宜ハンドリングしてください。
DeserializerをObjectMapperに適用する
次に作成したDeserializerをSpringが使用するObjectMapperに設定し、前述の変換規則が適用されるようにします。
JsonConfig
@Configuration class JsonConfig { @Bean fun objectMapperBuilder(): Jackson2ObjectMapperBuilder { val builder = Jackson2ObjectMapperBuilder() return builder } @Bean fun objectMapper(): ObjectMapper { val objectMapper = objectMapperBuilder().build<ObjectMapper>() objectMapper.registerModule(createModule()) return objectMapper } private fun createModule(): Module { val module = SimpleModule() // boolean型以外を暗黙的に許可しない module.addDeserializer(Boolean::class.java, BooleanDeserializer()) return module } }
Springで使用される ObjectMapper
及び ObjectMapperBuilder
を、@Bean
宣言したメソッドで上書きしています。
さらに createModule()
内で BooleanDecelializer
を登録した Module
オブジェクトを生成し、それを ObjectMapper
に登録しました。
【Cloud Foundry】Cloud Foundryアプリケーションのインスタンスを明示的に指定してHTTPリクエストを送る
Cloud Foundryインスタンスを複数立ち上げたときに、特定のインスタンスにアクセスするやり方のメモです。
X-CF-APP-INSTANCE
ヘッダーでインスタンスを指定
Cloud Foundry上にデプロイしたアプリケーションにアクセスするとき、 X-CF-APP-INSTANCE
ヘッダーをつけることでアクセスするインスタンスを指定することができます。
$ curl --request GET --url http://localhost:8080/api/v1/hoges/1 --header 'X-CF-APP-INSTANCE: 6aa4cf0f-1dd4-29b6-af62-cc21bc23df10:1'
X-CF-APP-INSTANCE
の値は {アプリのGUID}:{インスタンスインデックス}
のフォーマットで指定します。上記例では、インデックスの採番順序で2番目(インデックスは0開始)のアプリケーションインスタンスへアクセスしています。
ちなみに、アプリのGUIDは cf app {アプリ名} --guid
で取得できます。
$ cf app hoge-app --guid 6aa4cf0f-1dd4-29b6-af62-cc21bc23df10
また、インスタンスのインデックスはインスタンス内環境変数 $CF_INSTANCE_INDEX
で取得可能です。
参考文献
【Spring】Spring Sessionでセッション情報をDb2上に保存する
Spring でのセッション
SpringではJava ServletのHttpSessionの仕組みを利用することができます。ServletのHttpSessionはSpringフレームワークの上に構築されたアプリケーション上でそのまま利用できるほか、Springの各種ライブラリによってラッピングされた形でも利用することができます。
アプリケーションサーバを複数台構成とした場合のセッション
特に設定を行わなかった場合、Servletのセッションはメモリ上で管理されます。もし複数台のサーバや複数のクラウド上インスタンスにアプリケーションがデプロイされていると、各インスタンス間で個別のメモリ領域を持つことになり、互いにセッションの情報が共有されません。そのため、例えばあるインスタンス上アプリケーションでログインに成功したユーザが、別のインスタンス上アプリにアクセスすると未ログイン状態として扱われてしまう・・・といったことが起こります。
このような問題に対する解決策はいくつかあるのですが、今回はDB上にセッション情報を保存する方法を紹介します。
適用方法
今回はDBMSとしてDb2を使用します。 またSpringアプリケーションのビルドツールとしてGradleを利用し、 すでにDBMSへの接続は完了しているものとします。
Spring Sessionの導入
セッションの管理をDB上で行えるようにします。
build.gradle
に下記パッケージを追加します。
dependencies { .... implementation('org.springframework.boot:spring-boot-starter-web') implementation('org.springframework.boot:spring-boot-starter-data-jpa') implementation("org.springframework.session:spring-session-core") implementation("org.springframework.session:spring-session-jdbc") .... }
Configuration Beanクラスの定義
@EnableJdbcHttpSession
アノテーションを付加した Config
クラスを定義します。これにより、JDBC経由でDB上でセッションを管理できるようになります。
import javax.sql.DataSource; import org.springframework.context.annotation.Bean; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession; import org.springframework.transaction.PlatformTransactionManager; /** * * <p> * セッション情報をJDBC経由でDB上で管理するためのConfiguration * </p> * */ @EnableJdbcHttpSession public class JdbcHttpSessionConfig { @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }
Spring設定ファイルの変更
application.properties
に下記項目を追加します。
# Session Setting in DB server.session.jdbc.initialize-schema=always server.session.jdbc.table-name=SPRING_SESSION
application.yml
の場合は以下。
server: sesion: jdbc: initialize-schema: always table-name: SPRING_SESSION
2019/06/02追記
application.yml
application.properties
なしでも動作します。
テーブル名をデフォルトの SPRING_SESSION
から変更したい場合 @EnableJdbcHttpSession
のプロパティ tableName
に値を渡します。
@EnableJdbcHttpSession(tableName = "APP_SESSION")
テーブルの追加
設定は上記で完了なのですが、このままではセッションを保存できません。 既存のDBにセッション情報を追加するテーブルを定義する必要があります。
Spring SessionのGitHubリポジトリにDBMS別のテーブルCREATE用SQLがおいてあるので、こちらを活用しましょう。
ex. Db2の場合(少し編集してあります):
CREATE TABLE SPRING_SESSION ( PRIMARY_ID CHAR(36) NOT NULL, SESSION_ID CHAR(36) NOT NULL, CREATION_TIME BIGINT NOT NULL, LAST_ACCESS_TIME BIGINT NOT NULL, MAX_INACTIVE_INTERVAL INT NOT NULL, EXPIRY_TIME BIGINT NOT NULL, PRINCIPAL_NAME VARCHAR(100), CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID) ) ORGANIZE BY ROW; CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID); CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME); CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME); CREATE TABLE SPRING_SESSION_ATTRIBUTES ( SESSION_PRIMARY_ID CHAR(36) NOT NULL, ATTRIBUTE_NAME VARCHAR(200) NOT NULL, ATTRIBUTE_BYTES BLOB NOT NULL, CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME), CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE ) ORGANIZE BY ROW;
Db2は列指向*1を定義する機能を持ち、一部データ分析用DBではこの機能がデフォルトになっていたりします(IBM Cloud Db2 Warehouseなど)。この方針のままだと、BLOB
型定義をサポートしていない仕様のため*2、上記SQLでは元のSQLに明示的に ORGANIZE BY ROW
を指定し、メジャーなRDBMS同様の行指向操作がされるようにしています。
2019/06/02追記
@EnableJdbcHttpSession
にてテーブル名を変更した場合、上記SQLでもテーブル名を変更します。
これで、DB上にセッションが保持されるはずです!
参考文献
【Grafana】Grafanaでアラート時通知を設定する
Grafanaで可視化対象になっている監視サーバが障害を検知したら、Grafana側でメールやWebhookなどの通知を行うための設定メモです。
Grafanaでメール通知を設定する
ダッシュボード左上のGrafanaアイコンをクリックし、プルダウンから「Alerting」->「Notification channels」をクリック。
「+New Channel」をクリック。
「Name」に設定のタイトルを任意に入力。
「Description」には設定内容の概要を任意に入力。
「Type」はEmailを選択。
「Email ID」に送信したいメールアドレスを入力してください。
「Send Test」を押すと試しに「Email ID」に指定したメールアドレス宛にメールを送ることができます。
メールが受け取れることが確認できたら「Save」をクリック。簡単。
監視対象メトリクスと通知設定を紐付ける
メールの設定が完了したら、今度はメール送信のトリガーとなるアラートを設定します。
アラートを設定したいメトリクスのグラフを選択肢、タイトルをクリックします。クリックして表示されるEditボタンをクリックします。
出現するGraph設定用ボードの「Alert」タブを開き、「Create Alert」をクリック。
「Alert Config」でアラートのタイトル・しきい値を設定します。
「Conditions」ではアラート条件を指定します。「WHEN」にはグラフ中のどのタイミングでの値を参照するか決定します。デフォルトは last()
で、グラフの最右端の値を参照します。「or」句で他の条件を指定しています。
「IS BELOW」の部分ではメトリクス上で参照する値のしきい値を設定しています。「IS BELOW」であれば参照値がしきい値を下回ったときに、「IS ABOVE」であれば参照値がしきい値を上回った場合にアラートを行います。
下のプルダウンでは、値が未定・nullのとき/実行時エラー・タイムアウトのときにアラートを行うかどうかを設定できます。
これで設定完了です。