Spring Bootでデータベースへのアクセスを試してみた

# 背景・目的

以前、[Sprint Bootの環境を構築したときのメモ](https://qiita.com/zumax/items/8effa97f338dd0224b22)では、Spring Bootの環境を構築しました。
今回は、Spring Bootからデータベースへのアクセスを試してみます。

# まとめ
下記に特徴を記載いたします

|特徴 |説明|
|:--|:--|
|Spring Data JPA|JPA(Java Persistence API)ベース リポジトリを簡単に開発できる<BR><BR>データアクセステクノロジーを使ってSpring アプリケーションの構築が簡単にできる<BR><BR>目的は、データアクセスレイヤの実装を減らし改善すること<BR><BR>開発者は、様々な技術を使い、リポジトリインタフェイスを記述することで、Springは自動で接続できる|
|JPA|Java Persistence APIの略。<BR><BR>オブジェクトリレーションマッピングのためのPOJO 永続モデルを提供している<BR><BR>EJBソフトウェアコンポーネントに限定されない<BR>Webアプリケーション、アプリケーションクライアント、Java EE、Java SEなどでも利用できる|
|JPAの主要コンポーネント|・Object Releational Mapping<BR>・Entity Manager API<BR>・Transaction<BR>・JPQL|

# 概要
[Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa)と、[gs-accessing-data-jpa](https://github.com/spring-guides/gs-accessing-data-jpa/tree/main)を基に整理します。

## Spring Data JPAとは？
そもそも、Spring Data JPAとは何でしょうか。[Spring Data JPA](https://spring.io/projects/spring-data-jpa)を基に整理します。

> Spring Data JPA, part of the larger Spring Data family, makes it easy to easily implement JPA-based (Java Persistence API) repositories. It makes it easier to build Spring-powered applications that use data access technologies.

- Spring Data familyの一部
- JPA(Java Persistence API)ベース リポジトリを簡単に開発できる
- データアクセステクノロジーを使ってSpring アプリケーションの構築が簡単にできる

> Implementing a data access layer for an application can be quite cumbersome. Too much boilerplate code has to be written to execute the simplest queries. Add things like pagination, auditing, and other often-needed options, and you end up lost.

- アプリケーションのデータアクセスレイヤーを実装するのは面倒
- 単純なクエリを実行するために大量の定型コードを書く必要がある
- ページネイション、監査、その他必須のオプションなど、大変（途方に暮れる）

> Spring Data JPA aims to significantly improve the implementation of data access layers by reducing the effort to the amount that’s actually needed. As a developer you write your repository interfaces using any number of techniques, and Spring will wire it up for you automatically. You can even use custom finders or query by example and Spring will write the query for you!


- Spring Data JPAの目的は、データアクセスレイヤの実装を減らし改善すること
- 開発者は、様々な技術を使い、リポジトリインタフェイスを記述することで、Springは自動で接続できる
- カスタムファインダーを使用したり、クエリ例により、Springがクエリを記述する

### バージョン
[バージョン](https://spring.io/projects/spring-data-jpa#support)ページによると、2024/7/13現在では、3.3が最新のようです。


## JPAとは？
[Java Persistence API](https://www.oracle.com/java/technologies/persistence-jsp.html)をページを基に整理します。

> The Java Persistence API provides a POJO persistence model for object-relational mapping. The Java Persistence API was developed by the EJB 3.0 software expert group as part of JSR 220, but its use is not limited to EJB software components. It can also be used directly by web applications and application clients, and even outside the Java EE platform, for example, in Java SE applications. See JSR 220.

- Java Persistence API（以降、JPAと書きます。）は、オブジェクトリレーションマッピングのためのPOJO 永続モデルを提供している
- JPAは、EJB3.0のエキスパートグループより、JSR220の一部として開発されたが、EJBソフトウェアコンポーネントに限定されない
- Webアプリケーション、アプリケーションクライアント、Java EE、Java SEなどでも利用できる


2011年頃の資料と思われ、若干古そうですが、[Java Persistence API の詳細ついて](https://www.oracle.com/technetwork/jp/ondemand/java/jpa20-1843289-ja.pdf)という、Oracle社の資料が分かりやすかったので、以降は、こちらを基に整理します。

### JPAの主要コンポーネント
- Object Releational Mapping
    - ORM
    - JavaオブジェクトとDBテーブルのマッピング
- Entity Manager API
    - Entityのクラウド操作を実行
- Transaction
    - 複数を処理を単一処理としてまとめる。いわゆるトランザクション
- JPQL
    - Java Presistence Query Language
    - オブジェクト指向のクエリ



# 実践
[Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa)を基に試します。

## 前提
下記を前提としています。
1. Mac
1. VSCode
1. Java 17 
1. Maven 3.9.8

## プロジェクトの作成
1. VSCodeを起動します
1. 表示>コマンドパレットをクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/010d3a56-0a80-5afd-bdd4-372aba142418.png)
1. 下記を選択します
    - Spring Initializrを選択し、3.3.1
    - Java
1. GroupIdを指定します
1. ArtifactIdを指定します
1. Package TypeにJarを指定します
1. Javaバージョンに17を指定します
1. DependencyにSpring Webを指定します
1. 作成後、Add Workspaceをクリックします
1. 追加されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/b9846c8c-674e-51ea-80fc-4d13a24838cd.png)

## pom.xmlの修正
1. dependenciesに下記を追加します
    ```
    <dependency>
        <groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    	<scope>test</scope>
    </dependency>
    ```
## エンティティを定義する
1. Customer クラスを追加します
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/982d3656-de72-cf63-ad1b-c10d1b694225.png)

1. 下記のとおり追加します
    ```
    package com.example.data.jpa;
    
    import jakarta.persistence.Entity;
    import jakarta.persistence.GeneratedValue;
    import jakarta.persistence.GenerationType;
    import jakarta.persistence.Id;
    
    @Entity
    public class Customer {
      @Id
      @GeneratedValue(strategy = GenerationType.AUTO)
      private Long id;
      private String firstName;
      private String lastName;
    
      protected Customer() {}
    
      public Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
      }
    
      @Override
      public String toString() {
        return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id, firstName, lastName);
      }
    
      public Long getId() {
        return id;
      }
    
      public void setId(Long id) {
        this.id = id;
      }
    
      public String getFirstName() {
        return firstName;
      }
    
      public void setFirstName(String firstName) {
        this.firstName = firstName;
      }
    
      public String getLastName() {
        return lastName;
      }
    
      public void setLastName(String lastName) {
        this.lastName = lastName;
      }
    
    }
    ```

    - `@Entity`
        - JPAエンティティとして認識される
    - `@Id`
        - idプロパティに設定されており、これがオブジェクトIDとして認識される
    - `@GeneratedValue`
        - IDが自動的に生成される

## シンプルなクエリを作成する
Spring Data JPAは、RDBにデータを保存することに重点を置いている。
実行時に、リポジトリI/Fからリポジトリ実装を自動的に作成できる。

1. 下記のとおり、CustomerRepositoryインタフェイスを作成します
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/f39a670d-cdb0-c073-3aa6-d1f2d4334fe5.png)

1. 下記のコードを追加します
    ```
    package com.example.data.jpa;
    
    import org.springframework.data.repository.CrudRepository;
    import java.util.List;
    
    public interface CustomerRepository extends CrudRepository<Customer, Long>{
      List<Customer> findByLastName(String lastName);
    
      Customer findById(long id);
    
    }
    ```
## アプリケーションクラスを作成する
1. 下記を追加します
    ```
    package com.example.data.jpa;
    
    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.context.annotation.Bean;
    
    @SpringBootApplication
    public class JpaApplication {
    
    	private static final Logger log = LoggerFactory.getLogger(JpaApplication.class);
    
    	public static void main(String[] args) {
    		SpringApplication.run(JpaApplication.class, args);
    	}
    
    	@Bean
    	public CommandLineRunner demo(CustomerRepository repository){
    		return(args)-> {
    			// save a few customers
    			repository.save(new Customer("Jack", "Bauer"));
    			repository.save(new Customer("Chloe", "O'Brian"));
    			repository.save(new Customer("Kim", "Bauer"));
    			repository.save(new Customer("David", "Palmer"));
    			repository.save(new Customer("Michelle", "Dessler"));
    
    			// fetch all customers
    			log.info("Customers found with findAll():");
    			log.info("-------------------------------");
    			repository.findAll().forEach(customer -> {
    				log.info(customer.toString());
    			});
    			log.info("");
    
    			// fetch an individual customer by ID
    			Customer customer = repository.findById(1L);
    			log.info("Customer found with findById(1L):");
    			log.info("--------------------------------");
    			log.info(customer.toString());
    			log.info("");
    
    			// fetch customers by last name
    			log.info("Customer found with findByLastName('Bauer'):");
    			log.info("--------------------------------------------");
    			repository.findByLastName("Bauer").forEach(bauer -> {
    				log.info(bauer.toString());
    			});
    			log.info("");
    		};
    	}
    }
    ```

## JARをビルドする
1. 下記のコマンドによりビルドします
    ```
    $ mvn clean install
    $ ls -l target/jpa-0.0.1-SNAPSHOT.jar
    -rw-r--r--  1 XXXX  XXX  47716176  7 13 17:01 target/jpa-0.0.1-SNAPSHOT.jar
    $ 
    ```
1. 下記のコマンドで実行します
    ```
    $ java -jar target/jpa-0.0.1-SNAPSHOT.jar
    
      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
    
     :: Spring Boot ::                (v3.3.1)
    
    2024-07-13T17:03:27.197+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : Starting JpaApplication v0.0.1-SNAPSHOT using Java 17.0.11 with PID 7825 (/Users/XXXXX/git/spring/jpa/target/jpa-0.0.1-SNAPSHOT.jar started by XXXX in /Users/XXXX/git/spring/jpa)
    2024-07-13T17:03:27.202+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : No active profile set, falling back to 1 default profile: "default"
    2024-07-13T17:03:28.063+09:00  INFO 7825 --- [jpa] [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
    2024-07-13T17:03:28.142+09:00  INFO 7825 --- [jpa] [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 66 ms. Found 1 JPA repository interface.
    2024-07-13T17:03:28.776+09:00  INFO 7825 --- [jpa] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
    2024-07-13T17:03:28.790+09:00  INFO 7825 --- [jpa] [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
    2024-07-13T17:03:28.790+09:00  INFO 7825 --- [jpa] [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.25]
    2024-07-13T17:03:28.854+09:00  INFO 7825 --- [jpa] [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
    2024-07-13T17:03:28.856+09:00  INFO 7825 --- [jpa] [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1536 ms
    2024-07-13T17:03:29.004+09:00  INFO 7825 --- [jpa] [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
    2024-07-13T17:03:29.332+09:00  INFO 7825 --- [jpa] [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:b43ea3f2-01c5-457a-9ffd-20e2cab76853 user=SA
    2024-07-13T17:03:29.333+09:00  INFO 7825 --- [jpa] [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
    2024-07-13T17:03:29.408+09:00  INFO 7825 --- [jpa] [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
    2024-07-13T17:03:29.507+09:00  INFO 7825 --- [jpa] [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 6.5.2.Final
    2024-07-13T17:03:29.564+09:00  INFO 7825 --- [jpa] [           main] o.h.c.internal.RegionFactoryInitiator    : HHH000026: Second-level cache disabled
    2024-07-13T17:03:29.986+09:00  INFO 7825 --- [jpa] [           main] o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
    2024-07-13T17:03:30.901+09:00  INFO 7825 --- [jpa] [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
    2024-07-13T17:03:30.946+09:00  INFO 7825 --- [jpa] [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
    2024-07-13T17:03:31.306+09:00  WARN 7825 --- [jpa] [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
    2024-07-13T17:03:31.647+09:00  INFO 7825 --- [jpa] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
    2024-07-13T17:03:31.667+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : Started JpaApplication in 5.098 seconds (process running for 5.595)
    2024-07-13T17:03:31.772+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : Customers found with findAll():
    2024-07-13T17:03:31.772+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : -------------------------------
    2024-07-13T17:03:31.883+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : Customer[id=1, firstName='Jack', lastName='Bauer']
    2024-07-13T17:03:31.884+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : Customer[id=2, firstName='Chloe', lastName='O'Brian']
    2024-07-13T17:03:31.884+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : Customer[id=3, firstName='Kim', lastName='Bauer']
    2024-07-13T17:03:31.884+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : Customer[id=4, firstName='David', lastName='Palmer']
    2024-07-13T17:03:31.884+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : Customer[id=5, firstName='Michelle', lastName='Dessler']
    2024-07-13T17:03:31.884+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : 
    2024-07-13T17:03:31.896+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : Customer found with findById(1L):
    2024-07-13T17:03:31.896+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : --------------------------------
    2024-07-13T17:03:31.896+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : Customer[id=1, firstName='Jack', lastName='Bauer']
    2024-07-13T17:03:31.896+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : 
    2024-07-13T17:03:31.896+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : Customer found with findByLastName('Bauer'):
    2024-07-13T17:03:31.896+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : --------------------------------------------
    2024-07-13T17:03:31.951+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : Customer[id=1, firstName='Jack', lastName='Bauer']
    2024-07-13T17:03:31.951+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : Customer[id=3, firstName='Kim', lastName='Bauer']
    2024-07-13T17:03:31.951+09:00  INFO 7825 --- [jpa] [           main] com.example.data.jpa.JpaApplication      : 
    ```

    - findoAllで、saveにより登録したCustomerのすべてを見つける
    - findByIdでは、CustomerRepositoryのメソッドを呼び出す
    - findByLastNameでLastNameをキーに探し出す


# 考察
Spring Data JPA により、オブジェクトを簡単にデータベースに保存することができました。次回以降も継続して試してみます。


# 参考

https://qiita.com/zumax/items/8effa97f338dd0224b22

https://spring.io/guides/gs/accessing-data-jpa#initial

https://github.com/spring-guides/gs-accessing-data-jpa/tree/main

https://www.oracle.com/java/technologies/javaee/java-persistence-api-faq.html

https://www.oracle.com/technetwork/jp/ondemand/java/jpa20-1843289-ja.pdf

https://ja.wikipedia.org/wiki/Java_Persistence_API

https://download.oracle.com/otn-pub/jcp/persistence-2_2-mrel-spec/JavaPersistence.pdf?AuthParam=1720831323_b80959b510caebcf8f2b283555f528d3