Spring Boot で Spring JDBC を使う

## 概要
- Spring Boot + Spring JDBC で動作するサンプルコードを試す
- データベースには H2 Database を使用する

## H2 Database を Spring Framework で使う際のデフォルトの設定情報

デフォルトの設定を application.properties に書き出すと以下のようになる。

```applicatin.properties
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.initialization-mode=embedded
```

この設定内容は自動で適用されるため application.properties に記述する必要は無い。この設定値ではない値を使いたいときには application.properties に記述する必要がある。

デフォルト設定情報は以下の Spring JDBC のソースコードなどに記述されている。

[spring\-framework/H2EmbeddedDatabaseConfigurer\.java at v5\.2\.0\.RC1 · spring\-projects/spring\-framework · GitHub](https://github.com/spring-projects/spring-framework/blob/v5.2.0.RC1/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/H2EmbeddedDatabaseConfigurer.java)

```java
ClassUtils.forName("org.h2.Driver", H2EmbeddedDatabaseConfigurer.class.getClassLoader()));
```

```java
@Override
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
	properties.setDriverClass(this.driverClass);
	properties.setUrl(String.format("jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false", databaseName));
	properties.setUsername("sa");
	properties.setPassword("");
}
```

[spring\-framework/EmbeddedDatabaseFactory\.java at v5\.2\.0\.RC1 · spring\-projects/spring\-framework · GitHub](https://github.com/spring-projects/spring-framework/blob/v5.2.0.RC1/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactory.java)

```java
public static final String DEFAULT_DATABASE_NAME = "testdb";
```

application.properties の設定項目は [Spring Boot Reference Documentation - Appendices - Appendix A: Common application properties](https://docs.spring.io/spring-boot/docs/2.2.0.M5/reference/html/appendix.html#appendix) にまとまっている。

## schema.sql と data.sql

schema.sql と data.sql は Spring Boot 起動時に実行される SQL 文を記述したファイル。

- schema.sql には DDL (Data Definition Language: データ定義言語) テーブル作成などの SQL 文を記述する
- data.sql には DML (Data Manipulation Language: データ操作言語) レコード追加などの SQL 文を記述する

schema-h2.sql や data-h2.sql のようにデータベースの種類によって実行される SQL 文を変えることも可能。

[“How\-to” Guides - 10. Database Initialization](https://docs.spring.io/spring-boot/docs/2.2.0.M5/reference/html/howto.html#howto-database-initialization)

> Spring Boot can automatically create the schema (DDL scripts) of your DataSource and initialize it (DML scripts). It loads SQL from the standard root classpath locations: schema.sql and data.sql, respectively. In addition, Spring Boot processes the schema-\${platform}.sql and data-\${platform}.sql files (if present), where platform is the value of spring.datasource.platform. This allows you to switch to database-specific scripts if necessary. For example, you might choose to set it to the vendor name of the database (hsqldb, h2, oracle, mysql, postgresql, and so on).

applicatin.properties に記述することで読み込む SQL ファイルを変更することも可能。

```applicatin.properties
spring.datasource.schema=classpath:foo-schema.sql
spring.datasource.data=classpath:bar-data.sql
```

## サンプルコード

### ソースコード一覧

- pom.xml : Maven のビルド設定用ファイル
- .java : Java のソースコードファイル
- .sql : 起動時に実行されるSQL文を記述したファイル

```
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           ├── Person.java
        │           ├── SampleController.java
        │           └── SampleService.java
        └── resources
            ├── data.sql
            └── schema.sql
```

### pom.xml

Maven のビルド設定用ファイル。

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

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.0.M5</version>
    <relativePath/>
  </parent>

  <groupId>com.example</groupId>
  <artifactId>spring-jdbc-sample</artifactId>
  <version>0.0.1</version>
  <name>spring-jdbc-sample</name>
  <description>Spring JDBC sample</description>

  <properties>
    <java.version>11</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring JDBC を使う -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <!-- H2 Database を使う -->
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <scope>runtime</scope>
    </dependency>
  </dependencies>

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

  <repositories>
    <repository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
    </repository>
  </repositories>
  <pluginRepositories>
    <pluginRepository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
    </pluginRepository>
  </pluginRepositories>

</project>
```

### SampleController.java

HTTP リクエストを受けるためのクラス。

```java
package com.example;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@SpringBootApplication
@RestController
public class SampleController {

  public static void main(String[] args) {
    SpringApplication.run(SampleController.class, args);
  }

  @Autowired
  private SampleService service;

  // 指定した name のデータを追加する
  @RequestMapping("/add/{name}")
  public List<Person> index(@ModelAttribute Person person) {
    service.save(person);
    return service.findAll();
  }
}
```

### SampleService.java

Spring JDBC の JdbcTemplate クラスを使ってデータベースを操作するクラス。

```java
package com.example;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class SampleService {

  @Autowired
  private JdbcTemplate jdbcTemplate;

  // データの一覧を返す
  public List<Person> findAll() {
    // 実行する SQL を組み立てて実行
    String query = "SELECT * from person";
    List<Person> persons = jdbcTemplate.query(query, new BeanPropertyRowMapper<>(Person.class));
    return persons;
  }

  // データを追加する
  public Person save(Person person) {
    // 実行する SQL を組み立てる
    SqlParameterSource param = new BeanPropertySqlParameterSource(person);
    SimpleJdbcInsert insert =
      new SimpleJdbcInsert(jdbcTemplate)
        .withTableName("person")
        .usingGeneratedKeyColumns("id");
    // SQL を実行して、AUTO_INCREMENT の値を取得する
    Number key = insert.executeAndReturnKey(param);
    person.setId(key.longValue());
    System.out.println("Add: " + person);
    return person;
  }
}
```

### Person.java

データベースの1レコードを表現するクラス。

```java
package com.example;

public class Person {

  private Long id; // AUTO_INCREMENT で ID を付与
  private String name;

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

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

  @Override
  public String toString() {
    return "Person(id=" + id + ", name=" + name + ")";
  }
}
```

### schema.sql

起動時に実行される SQL 文を記述したファイル。

```sql
CREATE TABLE IF NOT EXISTS person (
  id   INTEGER      NOT NULL AUTO_INCREMENT,
  name VARCHAR(256) NOT NULL,
  PRIMARY KEY (id)
);
```

### data.sql

起動時に実行される SQL 文を記述したファイル。

```sql
INSERT INTO person (name) VALUES('Alice');
```

## ビルドと Spring Boot の起動

mvn コマンドでビルドして JAR ファイルを生成。

```
$ mvn package
```

java コマンドで Spring Boot を起動。

```
$ java -jar target/spring-jdbc-sample-0.0.1.jar
```

## 起動した Spring Boot にアクセスする

データベースにデータが追加される様子がわかる。

```
$ curl http://localhost:8080/add/Bob
[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]
```

```
$ curl http://localhost:8080/add/Cindy
[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"},{"id":3,"name":"Cindy"}]
```

## 今回の動作確認環境
- OpenJDK 11.0.2
- Spring Boot 2.2.0.M5
- Spring JDBC 5.2.0.RC1
- H2 Database 1.4.199

## 参考資料
- [Spring Boot Features - 10. Working with SQL Databases](https://docs.spring.io/spring-boot/docs/2.2.0.M5/reference/html/spring-boot-features.html#boot-features-sql)
- [“How\-to” Guides - 10. Database Initialization](https://docs.spring.io/spring-boot/docs/2.2.0.M5/reference/html/howto.html#howto-database-initialization)
- [Spring Boot Reference Documentation - Appendices - Appendix A: Common application properties](https://docs.spring.io/spring-boot/docs/2.2.0.M5/reference/html/appendix.html#appendix)
- [Spring Framework Documentation Version 5.2.0.RC1 - Data Access - 3. Data Access with JDBC](https://docs.spring.io/spring/docs/5.2.0.RC1/spring-framework-reference/data-access.html#jdbc)
- [spring\-framework/spring\-jdbc at v5\.2\.0\.RC1 · spring\-projects/spring\-framework · GitHub](https://github.com/spring-projects/spring-framework/tree/v5.2.0.RC1/spring-jdbc)