SpringBootを使ってホットペッパーAPIを呼び出すアプリを作ってみた

# はじめに
Springについて初めて学び、実践的取り組みとして「飲み会での幹事支援アプリ」というWebアプリケーションを開発しました。
このアプリでは、飲み会に適した店舗の情報を取得することができ、その情報の取得には「ホットペッパーAPI」を利用しています。
この記事では、ホットペッパーAPIを使用して店舗情報を取得する方法や、その際に直面した問題について詳しく紹介していきたいと思います。

# ホットペッパーAPIとは？
ホットペッパーAPIとは、**飲食店の基本情報、メニュー、クーポン情報などのデータを取得することができる飲食店検索サービス**です。
このサービスは、リクルートが無料で提供しており、誰でも簡単に使用することができます。

https://webservice.recruit.co.jp/

## 新規登録方法
ホットペッパーAPIを使用するには、新規登録を行います。
新規登録を行うには、[ホームページ](https://webservice.recruit.co.jp/)にある新規登録ボタンを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3933656/f9650ada-1346-1752-6bc0-f2384046ece8.png)

新規登録ボタンを押すと以下のような画面になり、メールアドレスで登録を行います。
登録が完了すると個別のAPIキーが割り当てられ、このAPIキーを用いることでホットペッパーAPIの使用が可能なります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3933656/1f02ac23-8654-e4d4-1eb0-ea5957bea10c.png)


## 使用方法
ホットペッパーAPIにはベースとなるリクエストURLが提供されています。
そのURLのクエリパラメータに[上記で取得したAPIキー](#新規登録方法)と任意のパラメータを加えることで情報の取得ができるようになります。
```:ベースURL
http://webservice.recruit.co.jp/hotpepper/gourmet/v1/
```
また、任意のパラメータは、[ホームページのグルメサーチAPI](https://webservice.recruit.co.jp/doc/hotpepper/reference.html)に記載されています。
全部で63個ものパラメータが提供されており、この中から自分が検索したい条件に合ったパラメータを組み合わせます。

:::note
いずれか最低一つ選択する必要があります。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3933656/2ee5d0ee-3209-d874-4a62-25dfb0b99eaf.png)

サンプルとして、東京にあるお店（大エリアコード=Z011）を検索したいときのURLは以下のようになります。
```:サンプル
http://webservice.recruit.co.jp/hotpepper/gourmet/v1/?key=[APIキー]&large_area=Z011
```
このURLをたたくと以下のように表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3933656/c4b81198-7d01-963d-2742-22294a9b681f.png)

このように東京にある店舗の情報を取得することができました。また、レスポンスの形式はデフォルトではxmlなのですが、`format=json`をクエリパラメータに加えることでjson形式で受け取ることもできます。
```:json形式でのレスポンス
http://webservice.recruit.co.jp/hotpepper/gourmet/v1/?key=[APIキー]&large_area=Z011&format=json
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3933656/00d77781-64e6-d2e1-8edb-362d498606a2.png)

# SpringBootを使ってホットペッパーAPIをたたいてみる
ここまでで、ホットペッパーAPIの使用方法を簡単に説明しました。
次は実際にSpring Bootを用いて、上記のように作成したURLをたたき、店舗の情報を取得する方法を説明していきます。

## 実装したアプリの構造
今回作成したアプリはAPI通信を行っているため、Restfulインターフェースに基づいて設計されています。
また、Spring Bootは3層アーキテクチャを採用しており、Repository層でホットペッパーAPIを呼び出す仕組みになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3933656/d8f167c7-1c0c-a78f-1b5e-c1801ffa99d6.png)

## アプリで行うこと
今回のアプリでは、「ユーザからGETリクエストが発生されたらホットペッパーAPIを用いて横浜にあるすべての居酒屋情報を取得し、取得した一覧情報の中から必要な情報をピックアップしてユーザに返す」ということを行います。
そこで、この一連の流れを行うAPIを作成しました。（エンドポイントは`/shop`。）

今回のAPIでは、リクエストで受け取るパラメータはなしと設定し、GETリクエストを受け取るとホットペッパーAPIを用いて自動で横浜の居酒屋情報のみを返す仕組みになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3933656/59bcf164-f5ec-df4b-7284-96e0801992ef.png)

今回設計したAPIの詳細は以下の通りです。
| メソッド | エンドポイント | パラメータ | 説明 |
|:--|:--|:--| :-- |
| GET | /shop | なし | 横浜にある居酒屋の「店舗ID、店舗名、店舗の住所、予算、店舗URL、営業時間」をリストで返すAPI |

また、APIのレスポンス例は以下の通りです。

```json:Response：ShopOutput
[
  {
    "shopId": "J999999999",
    "shopName": "居酒屋 ホットペッパー",
    "shopAddress": "神奈川県横浜市...",
    "budgetName": "～1000",
    "url": "店舗URL",
    "open": "月～金／11：30～14：00"
  }
]
```

## アプリが実行するホットペッパーAPIのIF
上記で説明したように、今回のアプリでは横浜の居酒屋情報のみを取得します。
そこで、ホットペッパーAPIのクエリパラメータに条件にあうパラメータを加えていきます。
具体的に、今回は以下の5つを使用しました。
| パラメータ | 項目名 | 説明 | 値 |
|:--|:--|:--|:--|
| format | レスポンス形式 | レスポンスをXMLかJSONかを指定。 | xmlまたはjson<br>初期値:xml|
| key | APIキー | APIを利用するために[新規登録方法](#新規登録方法)で取得したキー。 |   |
| genre | お店ジャンルコード | 絞り込みたいお店のジャンル。指定できるコードについては[ホットペッパーAPI公式ページ](https://webservice.recruit.co.jp/doc/hotpepper/reference.html)に記載されているジャンルマスタAPIを参照。 | （例）G001 |
| lat | 緯度 | ある地点からの範囲内のお店の検索を行う場合の緯度。 | （例）35.465981 |
| lng | 経度 | ある地点からの範囲内のお店の検索を行う場合の経度。 | （例）139.622062 |

formatを用いることでjson形式のレスポンスにし、genreで居酒屋に、lat、lngで場所を横浜に設定しています。

:::note
**場所の指定に緯度経度を用いた理由**
場所の指定はエリアコードを用いることも可能だが、エリアコードの指定範囲が公式ページ内に記載されていないため緯度経度を使用して場所を特定しました。
また、緯度経度での場所指定ではデフォルトでrangeクエリが指定されており、特定の場所から半径1000m以内を示しています。指定範囲は変えることもできます。
| パラメータ | 項目名 | 説明 | 値 |
|:--|:--|:--|:--|
| range | 検索範囲 | ある地点からの範囲内のお店の検索を行う場合の範囲を5段階で指定可能。 | 1: 300m、2: 500m、<br>3: 1000m (初期値)、4: 2000m、5: 3000m<br>(例) 1 |
:::

これらをのパラメータを使用した横浜の居酒屋情報を取得するURLは以下のようになります。
```:URL
https://webservice.recruit.co.jp/hotpepper/gourmet/v1/?format=json&key=[APIキー]&genre=G001&lat=35.465981&lng=139.622062
```
実際にこのURLをたたいた結果の一部はこのようになります。
```json:取得例
{
  "results": {
    "api_version": "1.30",
    "results_available": 310,
    "results_returned": "1",
    "results_start": 1,
    "shop": [
      {
        "id": "J000243924",
        "name": "鹿児島黒豚専門店 黒ぶたや ルミネ横浜店",
        "logo_image": "https://imgfp.hotp.jp/IMGH/54/31/P022575431/P022575431_69.jpg",
        "name_kana": "かごしまくろぶたせんもんてん　くろぶたや　るみねよこはま",
        "address": "神奈川県横浜市西区高島２‐１６‐１ルミネ横浜店７Ｆ",
        "station_name": "横浜",
        "ktai_coupon": 0,
        "large_service_area": {
          "code": "SS10",
          "name": "関東"
        },
        "service_area": {
          "code": "SA12",
          "name": "神奈川"
        },
        "large_area": {
          "code": "Z012",
          "name": "神奈川"
        },
        "middle_area": {
          "code": "Y135",
          "name": "横浜"
        },
        "small_area": {
          "code": "X270",
          "name": "横浜駅"
        },
        "lat": 35.4661179661,
        "lng": 139.6230811444,
        "genre": {
          "name": "居酒屋",
          "catch": "横浜駅◆鹿児島県産の六白黒ぶたの専門店",
          "code": "G001"
        },
        "sub_genre": {
          "name": "和食",
          "code": "G004"
        },
        "budget": {
          "code": "B002",
          "name": "2001～3000円",
          "average": "3500円(通常平均)1500円(ランチ平均)"
        },
        "catch": "黒ぶたやのこだわり丼もの しゃぶしゃぶセット！",
        "capacity": 58,
        "access": "JR横浜駅直結!!ルミネの7階です★雨にぬれる心配もありません♪",
        "mobile_access": "JR横浜駅東口徒歩1分 JR横浜駅直結!!ﾙﾐﾈの7階",
        "urls": {
          "pc": "https://www.hotpepper.jp/strJ000243924/?vos=nhppalsa000016"
        },
        "photo": {
          "pc": {
            "l": "https://imgfp.hotp.jp/IMGH/95/96/P042279596/P042279596_238.jpg",
            "m": "https://imgfp.hotp.jp/IMGH/95/96/P042279596/P042279596_168.jpg",
            "s": "https://imgfp.hotp.jp/IMGH/95/96/P042279596/P042279596_58_s.jpg"
          },
          "mobile": {
            "l": "https://imgfp.hotp.jp/IMGH/95/96/P042279596/P042279596_168.jpg",
            "s": "https://imgfp.hotp.jp/IMGH/95/96/P042279596/P042279596_100.jpg"
          }
        },
        "open": "月～日、祝日、祝前日: 11:00～22:30 （料理L.O. 21:30 ドリンクL.O. 22:00）",
        "close": "不定休(ルミネ横浜店に準ずる)",
        "party_capacity": 30,
        "other_memo": "ご要望などございましたらご相談ください",
        "shop_detail_memo": "当日、宴会承ります！ご予約時間は17時～19時の間で承ります。※リニューアルにより内装が画像と異なります",
        "budget_memo": "チャージ(お通し代)　なし",
        "wedding": "ご相談ください",
        "course": "あり",
        "free_drink": "あり ：焼酎・フルーツサワー・ソフトドリンクなど、老若男女お楽しみ頂けるドリンクメニューをご用意♪",
        "free_food": "なし ：鹿児島県産の六白黒ぶたのしゃぶしゃぶ食べ放題をご用意いたします。",
        "private_room": "なし ：貸切ご利用も可能です。ご宴会にご利用ください。",
        "horigotatsu": "なし ：掘りごたつ",
        "tatami": "なし ：座敷あり",
        "card": "利用可",
        "non_smoking": "全面禁煙",
        "charter": "貸切可 ：貸切承ります！お問い合わせください。",
        "parking": "あり ：ルミネ駐車場、東口",
        "barrier_free": "あり ：バリアフリーあり",
        "show": "なし",
        "karaoke": "なし",
        "band": "不可",
        "tv": "なし",
        "lunch": "あり",
        "midnight": "営業していない",
        "english": "あり",
        "pet": "不可",
        "child": "お子様連れOK ：片側ソファのお席がございます。",
        "wifi": "あり",
        "coupon_urls": {
          "pc": "https://www.hotpepper.jp/strJ000243924/map/?vos=nhppalsa000016",
          "sp": "https://www.hotpepper.jp/strJ000243924/scoupon/?vos=nhppalsa000016"
        }
      }
    ]
  }
}

```

## 実装例
上記を踏まえて、Spring Bootを用いてホットペッパーAPIを呼び出すコードを紹介していきます。

### Controller層
```java:ShopController.java
package com.example.backend.controller;

import com.example.backend.exception.ShopNotFoundException;
import com.example.backend.output.ShopOutput;
import com.example.backend.service.ShopService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@CrossOrigin
@RestController
@RequestMapping("/shop")
public class ShopController {

    private final ShopService shopService;

    @Autowired
    public ShopController(ShopService shopService) {
        this.shopService = shopService;
    }

    /**
     * GET /shop : 店舗の取得
     * 横浜の居酒屋情報一覧を返すAPI
     *
     * @return successful operation (status code 200)
     *         or Invalid status value (status code 400)
     *         or Shop not found (status code 404)
     */
    @GetMapping()
    public ResponseEntity<List<ShopOutput>> getFindShops() {
        List<ShopOutput> shopOutputs = shopService.findFindShops();
        if (shopOutputs.isEmpty()) {
            throw new ShopNotFoundException(); // 店舗が一つもない場合の例外処理
        }
        return ResponseEntity.ok()
                .body(shopOutputs);
    }
```
Controller層は主にクライアントから受け取った通信をService層に与える橋渡しのような役割を担っています。

### Service層

```java:ShopService.java
package com.example.backend.service;

import com.example.backend.exception.ShopNotFoundException;
import com.example.backend.output.*;
import com.example.backend.repository.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Transactional
@Service
public class ShopService {

    private final HotPepperRepository hotPepperRepository;

    @Autowired
    public ShopService(HotPepperRepository hotPepperRepository) {
        this.hotPepperRepository = hotPepperRepository;
    }

    /**
     *店舗一覧をホットペッパーAPIをもちいて返す処理。
     *
     * @return List<ShopOutput>　すべての横浜の店舗
     */
    public List<ShopOutput> findFindShops() {
        // ジャンルを検索用のコードに変更
        String genre = "G001";  // 居酒屋の検索コード

        // 検索場所を検索用コードに変更
        // 横浜駅の緯度経度
        String lat = "35.465981";
        String lng = "139.622062";

        // ホットペッパーAPIを用いて店舗の検索
        Map<String, Object> hotPepperMap = hotPepperRepository.findShop(genre, lat, lng);
        Map<String, Object> results = (Map<String, Object>) hotPepperMap.get("results");

        // 店舗情報が取得できなかった場合
        if((int) results.get("results_available") == 0) {
            throw new ShopNotFoundException();
        }

        // 必要情報を取得
        List<ShopOutput> shopOutputs = new ArrayList<>();

        List<Map<String, Object>> shops = (List<Map<String, Object>>) results.get("shop");

        for(Map<String, Object> shop : shops) {

            ShopOutput shopOutput = new ShopOutput();

            shopOutput.setShopId((String) shop.get("id"));
            shopOutput.setShopName((String) shop.get("name"));
            shopOutput.setShopAddress((String) shop.get("address"));
            Map<String, Object> budgetMap = (Map<String, Object>) shop.get("budget");
            shopOutput.setBudgetName((String) budgetMap.get("name"));
            Map<String, Object> urls = (Map<String, Object>) shop.get("urls");
            shopOutput.setUrl((String) urls.get("pc"));
            shopOutput.setOpen((String) shop.get("open"));

            shopOutputs.add(shopOutput);
        }

        return shopOutputs;
    }
```
Service層では、データの処理を行っています。
まず、前処理を行い、Repository層に処理後のデータを渡しています。
そして、Repository層から受け取った値を展開し、必要な値のみをピックアップしています。


### Repository層
```java:HotPepperRepository.java
package com.example.backend.repository;

import com.example.backend.exception.ShopNotFoundException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.Map;

@Repository
public class HotPepperRepository {

    @Value("${hotpepper.api.key}")
    private String hotPepperApiKey;
    private final String baseUrl = "https://webservice.recruit.co.jp/hotpepper/gourmet/v1/?format=json";


    private final RestTemplate restTemplate;

    @Autowired
    public HotPepperRepository(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }
    
    /**
     * ホットペッパーAPIを用いて店舗情報を取得する
     *
     * @param genre お店のジャンル
     * @param lat 緯度
     * @param lng 経度
     *
     * @return Map<String, Object>　店舗情報一覧
     */
    public Map<String, Object> findShop(String genre, String lat, String lng) {

        try {
            ObjectMapper objectMapper = new ObjectMapper();

            // 検索用のURLの作成
            String url = String.format("%s&key=%s&genre=%s&lat=%s&lng=%s", baseUrl, hotPepperApiKey, genre, lat, lng);
            // ホットペッパーAPIを用いて情報を取得
            String hotPepperString = restTemplate.getForObject(url, String.class);
            Map<String, Object> hotPepperMap = objectMapper.readValue(hotPepperString, Map.class);
            return hotPepperMap;
        } catch (JsonProcessingException e) {
            throw new ShopNotFoundException();
        } catch (Exception e) {
            e.printStackTrace();
            throw new ShopNotFoundException();
        }
    }
}
```
Repository層でホットペッパーAPIを用いた情報の取得を行っています。
**ホットペッパーAPI（外部API）をSpring Boot上でたたくには`restTemplate`を用います。**
```java
String hotPepperString = restTemplate.getForObject(url, String.class);
```
ここでは、`restTemplate`の`getForObject`を使用し、引数に`String.class`を使用することでホットペッパーAPIから取得した情報をString型で受け取っています。

※また、詳細な説明は省くのですが、`restTemplate`でGETメソッドを実装するには`getForObject`や`getForEntity`、`exchange`を用いる方法があります。

String型で受け取った情報は`ObjectMapper`を用いてMap型に変更し、Service層に返しています。

# ホットペッパーAPIをたたく際に生じた問題
ここからは`restTemplate`を用いてホットペッパーAPIをたたいた際に生じた問題とその解決方法を記載していきます。

## 問題① プロキシの設定
今回、実行環境の制約があり、以下のようなホットペッパーAPIから値が持ってこれないというエラーが発生しました。
```shell:エラー文
エラー: リソースへのアクセスに失敗しました。 I/O error on GET request for "https://webservice.recruit.co.jp/hotpepper/gourmet/v1/": webservice.recruit.co.jp
org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://webservice.recruit.co.jp/hotpepper/gourmet/v1/": webservice.recruit.co.jp
    at org.springframework.web.client.RestTemplate.createResourceAccessException(RestTemplate.java:915)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:895)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:790)
    at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:431)
    ...
Caused by: java.net.UnknownHostException: webservice.recruit.co.jp
    at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:567)
    ...
```
この問題に対し、**`restTemplate`をカスタムしてプロキシ設定を追加**することで解決しました。
この時のコードは以下のようになります。
```java:MyRestTemplateCustomizer.java

package com.example.backend.config;
 
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.core5.http.HttpHost;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
 
 
public class MyRestTemplateCustomizer implements RestTemplateCustomizer {
 
    @Override
    public void customize(RestTemplate restTemplate) {
        // プロキシ設定
        final HttpHost proxy = new HttpHost(
                "my.proxy.host",          // プロキシサーバのホスト
                "my.proxy.port");         // プロキシサーバのポート
 
        // HTTPクライアント設定
        final HttpClient client = HttpClientBuilder.create()
                .setProxy(proxy)
                .build();
 
        // HTTPクライアントファクトリを定義
        final ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(client);
        restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(requestFactory));
    }
}
```
`MyRestTemplateCustomizer.java`では、`RestTemplateCustomizer`をインターフェースとして`MyRestTemplateCustomizer`を実装しています。
具体的には、`customize`をオーバーライドし、プロキシ情報を追加しています。

また、このとき新たに`client5`と`core5`を依存関係に追加します。
今回のアプリではMavenを用いているため、`pom.xml`ファイルに以下を追加しました。
```xml:pom.xml
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents.core5</groupId>
    <artifactId>httpcore5</artifactId>
</dependency>
```
次に、カスタムした`restTemplate`を以下のようにBean定義し、repository層でカスタム後の`restTemplate`を使えるようにしました。
```java:RestTemplateConfiguration.java
package com.example.backend.config;
 
 
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
 
@Configuration(proxyBeanMethods = false)
public class RestTemplateConfiguration {
 
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder, MyRestTemplateCustomizer customizer) {
        return builder.customizers(customizer).build();
    }
    @Bean
    public MyRestTemplateCustomizer myRestTemplateCustomizer() {
        return new MyRestTemplateCustomizer();
    }
}
```
以上でプロキシ設定が追加され、上記のエラーを解消することができました。

:::note warn 
**client5とcore5のバージョン不一致**
client5とcore5のバージョンが一致していないと以下のエラーが発生しました。わたしはバージョンを指定しないことで解決したのですが、依存関係でバージョンを指定しないのは本来はあまりよろしくないらしいです。。
```shell:エラー文
java.lang.NoSuchMethodError: 'org.apache.hc.core5.http.ClassicHttpResponse org.apache.hc.client5.http.classic.HttpClient.executeOpen(org.apache.hc.core5.http.HttpHost, org.apache.hc.core5.http.ClassicHttpRequest, org.apache.hc.core5.http.protocol.HttpContext)'
    at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:99) ~[spring-web-6.1.13.jar:6.1.13]
    at org.springframework.http.client.AbstractStreamingClientHttpRequest.executeInternal(AbstractStreamingClientHttpRequest.java:70) ~[spring-web-6.1.13.jar:6.1.13]
    at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:66) ~[spring-web-6.1.13.jar:6.1.13]
    at org.springframework.http.client.BufferingClientHttpRequestWrapper.executeInternal(BufferingClientHttpRequestWrapper.java:77) ~[spring-web-6.1.13.jar:6.1.13]
    ...
```
:::
## 問題➁ SSL認証の設定
プロキシ設定が完了後、今度は下記のような異なるエラーが発生しました。
```shell:エラー文
エラー: リソースへのアクセスに失敗しました。 I/O error on GET request for "https://webservice.recruit.co.jp/hotpepper/gourmet/v1/": (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://webservice.recruit.co.jp/hotpepper/gourmet/v1/": (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at org.springframework.web.client.RestTemplate.createResourceAccessException(RestTemplate.java:915)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:895)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:790)
    at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:431)
    ...
Caused by: javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:130)
    ...
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:388)
    ...
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at java.base/sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:148)
    ...
```
この原因を調べてみるとSSL認証がされていないことでエラーが発生していました。
どうやらホットペッパーAPIを使用するにはSSL証明書をインポートする必要があるらしいです。
そこで、以下の手順で証明書をインポートしました。
1. chromeで https://webservice.recruit.co.jp/hotpepper/gourmet/v1/ を開く。
1. URLの横のボタン→この接続は保護されています→証明書は有効です→詳細→エクスポートでcrtファイルをダウンロード。 <br> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3933656/6891d6a7-40dc-1d26-d506-3e5ca0b95994.png" alt="画像説明" width="400" height="300" />
1. 以下のコマンドを打ってファイルをインポートを行う。また、`your-alias-name`は任意の名前、`path-to-cacerts`は`${JAVA}/lib/security/cacerts`、`path-to-your-certificate`はcrtファイルの保存場所。
```shell:
keytool -import -alias <your-alias-name> -keystore <path-to-cacerts> -file <path-to-your-certificate>
```
　※ 保存したSSL証明書を削除したいときは`-delete`オプションを使用します。

また、本来行うのはよくないですが、下記のコードでSSL認証の無効化ができます。
```java:MyRestTemplateCustomizer.java
package com.example.backend.config;
 
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.apache.hc.core5.ssl.TrustStrategy;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
 
import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
 
 
public class MyRestTemplateCustomizer implements RestTemplateCustomizer {
 
    @Override
    public void customize(RestTemplate restTemplate) {
        // SSLを無効化するためのTrustStrategyを定義
        TrustStrategy trustStrategy = (chain, authType) -> true;
        try {
 
            // SSLの設定
            SSLContext sslContext = SSLContextBuilder.create()
                    .loadTrustMaterial(trustStrategy)
                    .build();
 
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext);
 
            HttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
                    .setSSLSocketFactory(sslConnectionSocketFactory)
                    .build();
 
            // プロキシ設定
            final HttpHost proxy = new HttpHost(
                    "my.proxy.host",   // プロキシサーバのホスト
                    "my.proxy.port");         // プロキシサーバのポート
 
            // HTTPクライアント設定
            final HttpClient client = HttpClientBuilder.create()
                    .setProxy(proxy)
                    .setConnectionManager(connectionManager)
                    .build();
 
            // HTTPクライアントファクトリを定義
            final ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(client);
            restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(requestFactory));
 
        } catch (KeyManagementException e) {
            throw new RuntimeException(e);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (KeyStoreException e) {
            throw new RuntimeException(e);
        }
    }
}
```

# 補足：Springの基礎学習に用いた参考書
今回初めてSpringについて学び、アプリを作成していきましたが、Springの基礎知識習得には参考書を活用しました。
参考程度にどの本を利用したのかご紹介します。

**[スッキリわかるJava入門](https://www.amazon.co.jp/%E3%82%B9%E3%83%83%E3%82%AD%E3%83%AA%E3%82%8F%E3%81%8B%E3%82%8BJava%E5%85%A5%E9%96%80-%E7%AC%AC4%E7%89%88-%E3%82%B9%E3%83%83%E3%82%AD%E3%83%AA%E3%82%8F%E3%81%8B%E3%82%8B%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E4%B8%AD%E5%B1%B1-%E6%B8%85%E5%96%AC-ebook/dp/B0CMCVK2FJ?_encoding=UTF8&qid=&sr=&linkCode=ll1&tag=java_books_001-22&linkId=f5ca7b2991570b33defe8ea3faa098f7&language=ja_JP&ref_=as_li_ss_tl "スッキリわかるJava入門")**
<a href="https://www.amazon.co.jp/%E3%82%B9%E3%83%83%E3%82%AD%E3%83%AA%E3%82%8F%E3%81%8B%E3%82%8BJava%E5%85%A5%E9%96%80-%E7%AC%AC4%E7%89%88-%E3%82%B9%E3%83%83%E3%82%AD%E3%83%AA%E3%82%8F%E3%81%8B%E3%82%8B%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E4%B8%AD%E5%B1%B1-%E6%B8%85%E5%96%AC-ebook/dp/B0CMCVK2FJ?_encoding=UTF8&qid=&sr=&linkCode=ll1&tag=java_books_001-22&linkId=f5ca7b2991570b33defe8ea3faa098f7&language=ja_JP&ref_=as_li_ss_tl" target="_blank" rel="noopener"><img border="0" src="https://m.media-amazon.com/images/I/415hhkWAZOL._SL160_.jpg" ></a>
**Springを行う前にまずJavaもほぼ触ったことがない方やあまり自信がない方はここから！**
Javaの入門。
Javaの文法からオブジェクト指向の考えまで一冊でかかれており、話し口調なので説明がわかりやすいのが特徴です。

**[スッキリわかるJava入門 実践編](https://www.amazon.co.jp/%E3%82%B9%E3%83%83%E3%82%AD%E3%83%AA%E3%82%8F%E3%81%8B%E3%82%8BJava%E5%85%A5%E9%96%80-%E5%AE%9F%E8%B7%B5%E7%B7%A8-%E7%AC%AC4%E7%89%88-%E3%82%B9%E3%83%83%E3%82%AD%E3%83%AA%E3%82%8F%E3%81%8B%E3%82%8B%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E4%B8%AD%E5%B1%B1-ebook/dp/B0CTPWZ2V8?_encoding=UTF8&qid=1707654423&sr=8-1&linkCode=ll1&tag=java_books_001-22&linkId=741999f0c5319c2702641f8c66643972&language=ja_JP&ref_=as_li_ss_tl "スッキリわかるJava入門 実践編")**
<a href="https://www.amazon.co.jp/%E3%82%B9%E3%83%83%E3%82%AD%E3%83%AA%E3%82%8F%E3%81%8B%E3%82%8BJava%E5%85%A5%E9%96%80-%E5%AE%9F%E8%B7%B5%E7%B7%A8-%E7%AC%AC4%E7%89%88-%E3%82%B9%E3%83%83%E3%82%AD%E3%83%AA%E3%82%8F%E3%81%8B%E3%82%8B%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E4%B8%AD%E5%B1%B1-ebook/dp/B0CTPWZ2V8?_encoding=UTF8&qid=1707654423&sr=8-1&linkCode=ll1&tag=java_books_001-22&linkId=741999f0c5319c2702641f8c66643972&language=ja_JP&ref_=as_li_ss_tl" target="_blank" rel="noopener"><img border="0" src="https://m.media-amazon.com/images/I/41sOF5feeGL._SL160_.jpg" ></a>
スッキリわかるJava入門の続編。
基礎をもとにした実践の方法が記載されており、個人的には入門をしっかり読んで実践編はざっとよむぐらいで大丈夫な印象を持ちました。

**[プロになるためのSpring入門](https://www.amazon.co.jp/dp/B0C9DL1K3V?tag=spring_boot_001-22&linkCode=ogi&th=1&psc=1 "プロになるためのSpring入門")**
<a href="https://www.amazon.co.jp/dp/B0C9DL1K3V?tag=spring_boot_001-22&linkCode=ogi&th=1&psc=1" target="_blank"><img border="0" src="https://m.media-amazon.com/images/I/511iEQyjNmL._SL160_.jpg" ></a>
**Javaはわかるという方はこの本を！**
Springを一から始めたいならこれ。
Springとはどういったものなのか、アノテーション、テストまで一冊の本で書かれており、**すべての章でハンズオンができる**ので実践的に学ぶことができます。