SpringBoot2+SpringSessionにてデータベースを利用する

実はいろいろと情報が錯綜していたため、動作するまでに一手間がかかりました(´・ω・)

# 目的
SpringBoot2＋SpringSessionを利用してセッション情報をデータベースで管理する

## 動作環境＋バージョン情報
以下で動作確認をしています。

* SpringBoot 2.0.5.RELEASE。
* Oracle XE
* SpringBoot-starter
    * web
    * session-core
    * session-jdbc
* HikariCP

SpringSessionJdbcに必要なライブラリ依存関係は、Appendixのmaven(pom.xml)に記載しております。バージョンを略記しているものは、SpringBoot2.0.5管理下にあるものを使います。

# 下準備

## SpringSessionでデータベース利用に必要なもの

maven(pom.xml)のとおり、最低限以下が必要です。

* spring-boot-starter
    * web
    * session-core
    * session-jdbc
* 接続プール (今回はSpringBoot2から推奨されている HikariCP を利用しています)
* 設定ファイルにデータベース接続プールの設定と、SpringSessionの設定を記載

## 利用するためのConfigクラス

`@EnableJdbcHttpSession` を宣言します。SpringBootでアプリケーションを作るときはよくConfigureクラスなどで作ります。もしアプリケーション内でJDBCの設定がない場合は、JDBCのトランザクション設定を書きます。以下はJavaConfigの例。

```java
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;

/**
 * @author A-pZ
 */
@EnableJdbcHttpSession
public class JdbcHttpSessionConfig {
	@Bean
	public PlatformTransactionManager transactionManager(DataSource dataSource) {
		return new DataSourceTransactionManager(dataSource);
	}
}
```

## JDBC設定
JDBCの設定は、Appendixの設定(application.yml) を参考にしてください。

## HTTPセッションを利用するController

では具体的に、HttpSessionを利用するControllerです。

```java
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * @author A-pZ
 */
@Controller
public class SampleController {
	@GetMapping("")
	public String start(HttpSession session) {
		session.setAttribute("sample", "sample");
		return "index";
	}
}
```

このController実行後の画面(index.html)は、特に何もしない静的HTMLのThymeleafテンプレートを出力させます（JSPでも良いでしょう）

```html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
</head>
<body>
	index.html
</body>
</html>
```

動作後、セッションを扱っているデータベースのレコードを見ると、

![spring-session-jdbc.png](https://qiita-image-store.s3.amazonaws.com/0/13268/6aadb0cf-b2f2-10e2-b920-29c546e0958f.png)

このようにセッション情報がデータベースに格納されていることがわかります。

# Appendix

## maven(pom.xml)

Oracle JDBCドライバは別途ローカルリポジトリか、ローカル環境のNexusなどのリポジトリへ登録してください。

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.github.alphapz.springsample</groupId>
	<artifactId>spring-session-sample</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>spring-session-sample</name>
	<description>spring-session-sample</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.4.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.session</groupId>
			<artifactId>spring-session-core</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>oracle.jdbc</groupId>
			<artifactId>ojdbc7</artifactId>
			<version>12.1.0.2.0</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.session</groupId>
			<artifactId>spring-session-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>com.zaxxer</groupId>
			<artifactId>HikariCP</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>
```

## 設定(application.yml)

SpringSessionJdbcを動作させるための最低限の設定です。

```yml
spring:
  session:
    timeout: 300
    jdbc:
      initialize-schema: always
      table-name: SPRING_SESSION
  datasource:
    url: jdbc:oracle:thin:@127.0.0.1:1521:xe
    username: springsession
    password: springsession
    tomcat:
      data-source: com.zaxxer.hikari.HikariDataSource
```

## 投入SQL(Oracle DB)

```SQL
CREATE TABLE SPRING_SESSION (
	PRIMARY_ID CHAR(36) NOT NULL,
	SESSION_ID CHAR(36) NOT NULL,
	CREATION_TIME NUMBER(19,0) NOT NULL,
	LAST_ACCESS_TIME NUMBER(19,0) NOT NULL,
	MAX_INACTIVE_INTERVAL NUMBER(10,0) NOT NULL,
	EXPIRY_TIME NUMBER(19,0) NOT NULL,
	PRINCIPAL_NAME VARCHAR2(100 CHAR),
	CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
);

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 VARCHAR2(200 CHAR) 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
);
```

# ハマリポイント

JDBCを利用したSpringSessionを有効化するためには、実はかなり設定する箇所が減っており、`@EnableJdbcHttpSession`で必要なSpringBean定義は、TransactionManager から DataSource を渡すだけで済みます。

実はこのDataSource設定を何らかのJavaConfigで記載したときには、Beanの優先度をつけるか、Qualifierで別名をつけないと、起動時エラーになります。なお、旧来のSpringBootで利用しているDataSource設定の記述をすると、DataSourceの設定方法が変わったため、これまた実行時エラーになります。（データソースの型が解決できない、または、ymlから読み込んだが型に変換できない、など…）