Spring JDBC JdbcTemplate で SQL 発行

このガイドでは、Spring を使用してリレーショナルデータにアクセスするプロセスを順を追って説明します。

構築するもの

Spring の JdbcTemplate を使用して、リレーショナルデータベースに保存されているデータにアクセスするアプリケーションを構築します。

必要なもの

本ガイドの完成までの流れ

ほとんどの Spring 入門ガイドと同様に、最初から始めて各ステップを完了するか、すでに慣れている場合は基本的なセットアップステップをバイパスできます。いずれにしても、最終的に動作するコードになります。

最初から始めるには、Spring Initializr から開始に進みます。

基本をスキップするには、次の手順を実行します。

完了したときは、gs-relational-data-access/complete のコードに対して結果を確認できます。

Spring Initializr から開始

IDE を使用する場合はプロジェクト作成ウィザードを使用します。IDE を使用せずにコマンドラインなどで開発する場合は、この事前に初期化されたプロジェクトからプロジェクトを ZIP ファイルとしてダウンロードできます。このプロジェクトは、このチュートリアルの例に合うように構成されています。

プロジェクトを手動で初期化するには:

  1. IDE のメニューまたはブラウザーから Spring Initializr を開きます。アプリケーションに必要なすべての依存関係を取り込み、ほとんどのセットアップを行います。

  2. Gradle または Maven のいずれかと、使用する言語を選択してください。

  3. 依存関係をクリックし、JDBC API と H2 Database を選択します。

  4. 生成をクリックします。

  5. 結果の ZIP ファイルをダウンロードします。これは、選択して構成された Web アプリケーションのアーカイブです。

Eclipse や IntelliJ のような IDE は新規プロジェクト作成ウィザードから Spring Initializr の機能が使用できるため、手動での ZIP ファイルのダウンロードやインポートは不要です。
プロジェクトを GitHub からフォークして、IDE または他のエディターで開くこともできます。

Customer クラスを作成する

これから扱うシンプルなデータアクセスロジックは、顧客の氏名(ファーストネーム)とラストネーム(ラストネーム)を管理します。このデータをアプリケーションレベルで表現するには、以下のリストに示すように、Customer クラスを作成します。

Java
package com.example.relationaldataaccess;

public record Customer(long id, String firstName, String lastName) {

	@Override
	public String toString() {
		return String.format(
				"Customer[id=%d, firstName='%s', lastName='%s']",
				id, firstName, lastName);
	}
}
Kotlin
package com.example.relationaldataaccess

data class Customer(
    val id: Long,
    val firstName: String,
    val lastName: String
) {
    override fun toString(): String =
        "Customer[id=$id, firstName='$firstName', lastName='$lastName']"
}

データの保存と取得

Spring は、SQL リレーショナルデータベースと JDBC を簡単に操作できる JdbcTemplate というテンプレートクラスを提供します。ほとんどの JDBC コードは、リソースの取得、接続管理、例外処理、コード本来の目的とは全く関係のない一般的なエラーチェックに埋もれています。JdbcTemplate はこれらすべてを処理してくれるため、ユーザーは目の前のタスクに集中できます。次のリストは、JDBC を介してデータを保存および取得できるクラスを示しています。

Java
package com.example.relationaldataaccess;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@SpringBootApplication
public class RelationalDataAccessApplication implements CommandLineRunner {

	private static final Logger log = LoggerFactory.getLogger(RelationalDataAccessApplication.class);

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

	private final JdbcTemplate jdbcTemplate;

	public RelationalDataAccessApplication(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	@Override
	public void run(String... strings) throws Exception {

		log.info("Creating tables");

		jdbcTemplate.execute("DROP TABLE IF EXISTS customers");
		jdbcTemplate.execute("CREATE TABLE customers(" +
				"id SERIAL, first_name VARCHAR(255), last_name VARCHAR(255))");

		// Split up the array of whole names into an array of first/last names
		List<Object[]> splitUpNames = Stream.of("John Woo", "Jeff Dean", "Josh Bloch", "Josh Long")
				.map(name -> name.split(" "))
				.collect(Collectors.toList());

		// Use a Java 8 stream to print out each tuple of the list
		splitUpNames.forEach(name -> log.info("Inserting customer record for {} {}", name[0], name[1]));

		// Use JdbcTemplate's batchUpdate operation to bulk load data
		jdbcTemplate.batchUpdate("INSERT INTO customers(first_name, last_name) VALUES (?,?)", splitUpNames);

		log.info("Querying for customer records where first_name = 'Josh':");
		jdbcTemplate.query(
				"SELECT id, first_name, last_name FROM customers WHERE first_name = ?",
				(rs, rowNum) -> new Customer(rs.getLong("id"), rs.getString("first_name"), rs.getString("last_name")), "Josh")
		.forEach(customer -> log.info(customer.toString()));
	}
}
Kotlin
package com.example.relationaldataaccess

import org.slf4j.LoggerFactory
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.query

private val log = LoggerFactory.getLogger(RelationalDataAccessApplication::class.java)

@SpringBootApplication
class RelationalDataAccessApplication(
    private val jdbcTemplate: JdbcTemplate) : CommandLineRunner {

    override fun run(vararg args: String) {
        log.info("Creating tables")

        jdbcTemplate.execute("DROP TABLE IF EXISTS customers")
        jdbcTemplate.execute(
            """
            CREATE TABLE customers(
                id SERIAL, 
                first_name VARCHAR(255), 
                last_name VARCHAR(255)
            )
        """.trimIndent())

        // Split up the array of whole names into an array of first/last names
        val splitUpNames = listOf("John Woo", "Jeff Dean", "Josh Bloch", "Josh Long")
            .map { it.split(" ").toTypedArray() }

        // Use Kotlin collection functions to print out each tuple of the list
        splitUpNames.forEach { name ->
            log.info("Inserting customer record for {} {}", name[0], name[1])
        }

        // Use JdbcTemplate's batchUpdate operation to bulk load data
        jdbcTemplate.batchUpdate("INSERT INTO customers(first_name, last_name) VALUES (?,?)", splitUpNames)

        log.info("Querying for customer records where first_name = 'Josh':")
        // Import .query() Kotlin extension that allows varargs before lambda to enable trailing lambda syntax
        jdbcTemplate.query("SELECT id, first_name, last_name FROM customers WHERE first_name = ?", "Josh")
        { rs, _ ->
            Customer(
                rs.getLong("id"),
                rs.getString("first_name"),
                rs.getString("last_name")
            )
        }
            .forEach { customer ->
                log.info(customer.toString())
            }
    }
}

fun main(args: Array<String>) {
    runApplication<RelationalDataAccessApplication>(*args)
}

@SpringBootApplication は、次のすべてを追加する便利なアノテーションです。

  • @Configuration: アプリケーションコンテキストの Bean 定義のソースとしてクラスにタグを付けます。

  • @EnableAutoConfiguration: クラスパス設定、他の Bean、さまざまなプロパティ設定に基づいて、Bean の追加を開始するよう Spring Boot に指示します。

  • @ComponentScan: com.example.relationaldataaccess パッケージの他のコンポーネント、構成、サービスを探すように Spring に指示します。この場合、何もありません。

main() メソッドは、Spring Boot の SpringApplication.run() メソッドを使用してアプリケーションを起動します。

Spring Boot は H2(インメモリリレーショナルデータベースエンジン)をサポートし、自動的に接続を作成します。spring-jdbc を使用しているため、Spring Boot は自動的に JdbcTemplate を作成します。@Autowired JdbcTemplate フィールドは自動的にロードし、使用可能にします。

この Application クラスは Spring Boot の CommandLineRunner を実装します。これは、アプリケーションコンテキストがロードされた後に run() メソッドを実行することを意味します。

最初に、JdbcTemplate の execute メソッドを使用して DDL をインストールします。

次に、文字列のリストを取得し、Java のストリームまたは Kotlin のコレクション関数を使用して、firstname/lastname のペアに分割します。

次に、JdbcTemplate の batchUpdate メソッドを使用して、新しく作成したテーブルにいくつかのレコードをインストールします。メソッド呼び出しの最初の引数はクエリ文字列です。最後の引数(Object インスタンスの配列)は、? 文字があるクエリに代入される変数を保持します。

単一挿入ステートメントの場合、JdbcTemplate の insert メソッドが適切です。ただし、複数の挿入の場合、batchUpdate を使用することをお勧めします。
引数に ? を使用して、変数をバインドするよう JDBC に指示することにより、SQL インジェクション攻撃 [Wikipedia] (英語) を回避します。

最後に、query メソッドを使用して、条件に一致するレコードをテーブルで検索します。再び ? 引数を使用してクエリのパラメーターを作成し、呼び出しを行うときに実際の値を渡します。最後の引数は、各結果行を新しい Customer オブジェクトに変換するために使用される Java 8 ラムダです。

ラムダは、Spring の RowMapper (Java ラムダまたはレシーバー付きの Kotlin 関数リテラル) などの単一メソッドインターフェースに適切にマップされます。

実行可能 JAR を構築する

コマンドラインから Gradle または Maven を使用してアプリケーションを実行できます。必要なすべての依存関係、クラス、リソースを含む単一の実行可能 JAR ファイルを構築して実行することもできます。実行可能な jar を構築すると、開発ライフサイクル全体、さまざまな環境などで、アプリケーションとしてサービスを簡単に提供、バージョン管理、デプロイできます。

Gradle を使用する場合、./gradlew bootRun を使用してアプリケーションを実行できます。または、次のように、./gradlew build を使用して JAR ファイルをビルドしてから、JAR ファイルを実行できます。

java -jar build/libs/gs-relational-data-access-0.0.1-SNAPSHOT.jar

Maven を使用する場合、./mvnw spring-boot:run を使用してアプリケーションを実行できます。または、次のように、./mvnw clean package で JAR ファイルをビルドしてから、JAR ファイルを実行できます。

java -jar target/gs-relational-data-access-0.0.1-SNAPSHOT.jar

次の出力が表示されます。

2019-09-26 13:46:58.561  INFO 47569 --- [           main] c.e.r.RelationalDataAccessApplication    : Creating tables
2019-09-26 13:46:58.564  INFO 47569 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2019-09-26 13:46:58.708  INFO 47569 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2019-09-26 13:46:58.809  INFO 47569 --- [           main] c.e.r.RelationalDataAccessApplication    : Inserting customer record for John Woo
2019-09-26 13:46:58.810  INFO 47569 --- [           main] c.e.r.RelationalDataAccessApplication    : Inserting customer record for Jeff Dean
2019-09-26 13:46:58.810  INFO 47569 --- [           main] c.e.r.RelationalDataAccessApplication    : Inserting customer record for Josh Bloch
2019-09-26 13:46:58.810  INFO 47569 --- [           main] c.e.r.RelationalDataAccessApplication    : Inserting customer record for Josh Long
2019-09-26 13:46:58.825  INFO 47569 --- [           main] c.e.r.RelationalDataAccessApplication    : Querying for customer records where first_name = 'Josh':
2019-09-26 13:46:58.835  INFO 47569 --- [           main] c.e.r.RelationalDataAccessApplication    : Customer[id=3, firstName='Josh', lastName='Bloch']
2019-09-26 13:46:58.835  INFO 47569 --- [           main] c.e.r.RelationalDataAccessApplication    : Customer[id=4, firstName='Josh', lastName='Long']

要約

おめでとう! Spring を使用して、単純な JDBC クライアントを開発しました。

Spring Boot には、接続プールを構成およびカスタマイズするための多くの機能があります。たとえば、メモリ内データベースではなく外部データベースに接続するためです。詳細については、リファレンスガイド (英語) を参照してください。

関連事項

次のガイドも役立つかもしれません:

新しいガイドを作成したり、既存のガイドに貢献したいですか? 投稿ガイドラインを参照してください [GitHub] (英語) 。

すべてのガイドは、コード用の ASLv2 ライセンス、およびドキュメント用の帰属表示、NoDerivatives クリエイティブコモンズライセンス (英語) でリリースされています。

コードを入手する