Azure FunctionsからMySQLに接続する～Java編～

# 対象者

- Azure FunctionsからAzure Database for MySQLに接続してみたい方
- Javaで開発したい方
- コマンドベースで操作したい方

# 概要

この記事では以下の内容について扱っています。  
基本的にコマンドベースで進めていきます。  

- Azure Database for MySQLフレキシブル サーバー のリソース作成
- Azure FunctionsからMySQLに接続
- 【おまけ】Azure FunctionsでAPIキーを使用する

## 前提

前回投稿した記事「[Azure FunctionsでAPIを作る～Java編～](https://qiita.com/fsdg-nagumo/items/5eec1e9b66d33e3f4687)」の続きとなります。  
Azure Functionsの開発環境構築、デプロイ方法については記事をご参照ください。

<h5>＜前回のあらすじ＞</h5>

1. Azure Functionsのローカル開発環境構築(Java)
2. 適当なダミーJSONデータを返すAPIを作成  
   → **【今回】このデータをMySQLから取得するように変更(ついでに登録・更新・削除も追加)**
3. Azure へのデプロイ

# 動作環境

作業時の環境は以下の通りです。

- OS
  - Windows 11 Pro (21H2)
  - Ubuntu 20.04 (WLS)  
- JDK
  - [OpenJDK 11](https://learn.microsoft.com/ja-jp/java/openjdk/download)
- Azure CLI 2.40.0

# データベースの準備

## Azure Database for MySQL フレキシブル サーバー の作成

:::note
Azure Database for MySQLには「[シングルサーバー](https://learn.microsoft.com/ja-jp/azure/mysql/single-server/single-server-overview)」と「[フレキシブルサーバー](https://learn.microsoft.com/ja-jp/azure/mysql/flexible-server/overview)」の2種類がありますが、シングルサーバーは提供終了予定のため、フレキシブルサーバーが推奨されているようです。
:::

MySQLサーバーの作成には [az mysql flexible-server create](https://learn.microsoft.com/ja-jp/cli/azure/mysql/flexible-server?view=azure-cli-latest#az-mysql-flexible-server-create) コマンドを使用します。

```bash:Bash
az mysql flexible-server create \
    --name <MySQLサーバー名> \
    --resource-group <リソースグループ名> \
    --location eastus \
    --admin-user <Adminユーザー名> \
    --admin-password <Adminパスワード> \
    --sku-name Standard_B1s \
    --public-access 0.0.0.0 \
    --version 8.0.21
```

<details>
<summary>PowerShell</summary>

```powershell:PowerShell
az mysql flexible-server create `
    --name <MySQLサーバー名> `
    --resource-group <リソースグループ名> `
    --location eastus `
    --admin-user <Adminユーザー名> `
    --admin-password <Adminパスワード> `
    --sku-name Standard_B1s `
    --public-access 0.0.0.0 `
    --version 8.0.21
```

</details>

### オプションの補足

上記オプション指定では、MySQLサーバーは[パブリックアクセス](https://learn.microsoft.com/ja-jp/azure/mysql/flexible-server/concepts-networking-public)モードで作成されます。  
パブリックといっても、ファイアウォールによって許可されたIPアドレスのみが接続可能です。  
ファイアウォールを設定しない場合、どのIPからの接続も受け付けません。  
`--public-access` を `0.0.0.0` に設定することで、**Azure内リソース**からMySQLサーバーにパブリックアクセス可能となるようファイアウォールが構成されます。

> 今回は触れませんが、[VNet統合](https://learn.microsoft.com/ja-jp/azure/app-service/overview-vnet-integration)という仕組みを使うと、Azure FunctionsとMySQL間でプライベート通信を行うことが可能になるようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884047/3e900267-bc00-b890-ed38-10f269f55c31.png)

## データベース作成

MySQLサーバーを作成すると、`flexibleserverdb` というデータベースが自動で作成されますが、新たに追加してみたいと思います。  

データベースの作成には [az mysql flexible-server db create](https://learn.microsoft.com/ja-jp/cli/azure/mysql/flexible-server/db?view=azure-cli-latest#az-mysql-flexible-server-db-create) コマンドを使用します。

```bash:Bash
az mysql flexible-server db create \
    --resource-group <リソースグループ名> \
    --server-name <MySQLサーバー名> \
    --charset utf8mb4 \
    --collation utf8mb4_bin \
    --database-name <データベース名> 
```

<details>
<summary>PowerShell</summary>

```powershell:PowerShell
az mysql flexible-server db create `
    --resource-group <リソースグループ名> `
    --server-name <MySQLサーバー名> `
    --charset utf8mb4 `
    --collation utf8mb4_bin `
    --database-name <データベース名> 
```

</details>

文字コード(charset)、照合順序(collation)等はお好みで。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884047/b7543704-1018-4e78-ba62-0992f4c56552.png)

## ファイアウォールルールの追加

次は作成したデータベースにテーブルを作成・・・と行きたいところなのですが、このままではクライアント端末からMySQLサーバーにアクセスすることができません。  
[上記](#オプションの補足)で書いたように、アクセスするにはファイアウォールで接続元のIP(グローバルIP)を許可する必要があります。

ファイアウォールルールの追加には [az mysql flexible-server firewall-rule create](https://learn.microsoft.com/ja-jp/cli/azure/mysql/flexible-server/firewall-rule?view=azure-cli-latest#az-mysql-flexible-server-firewall-rule-create) コマンドを使用します。

```bash:Bash
az mysql flexible-server firewall-rule create \
    --resource-group <リソースグループ名> \
    --name <MySQLサーバー名> \
    --rule-name <ファイアウォールルール名> \
    --start-ip-address <クライアントのIPアドレス> 
```

<details>
<summary>PowerShell</summary>

```powershell:PowerShell
az mysql flexible-server firewall-rule create `
    --resource-group <リソースグループ名> `
    --name <MySQLサーバー名> `
    --rule-name <ファイアウォールルール名> `
    --start-ip-address <許可するIPアドレス> 
```

</details>

### オプションの補足

`start-ip-address` と `end-ip-address` で範囲指定も可能です。  
`start-ip-address` のみ指定した場合は指定した、IPのみが対象となります。

自分のIPアドレスを確認するには、「IPアドレス　確認」とかでググってください。  
例えば[ここ](https://www.cman.jp/network/support/go_access.cgi)などで確認できます。  
(ここはコマンド使わんのかい！)  

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884047/c5b51d43-b96a-78ec-c03e-8013b7cd8d08.png)

## テーブルの作成

ファイアウォールルールの追加によりMySQLサーバーに接続できるようになっているはずなので、テーブルを作成します。

テーブル作成は [az mysql flexible-server execute](https://learn.microsoft.com/ja-jp/cli/azure/mysql/flexible-server?view=azure-cli-latest#az-mysql-flexible-server-execute) コマンドを使用して、クエリを実行します。

```bash:Bash
az mysql flexible-server execute \
    --name <MySQLサーバー名> \
    --admin-user <Adminユーザー名> \
    --admin-password <Adminパスワード> \
    --database-name <データベース名> \
    --querytext \
    "
        create table todo (
            id int(8) auto_increment,
            content varchar(60),
            done bit(1) not null default b'0',
            created_at datetime not null default current_timestamp,
            primary key (id)
        );
    "
```

<details>
<summary>PowerShell</summary>

```powershell:PowerShell
az mysql flexible-server execute `
    --name <MySQLサーバー名> `
    --admin-user <Adminユーザー名> `
    --admin-password <Adminパスワード> `
    --database-name <データベース名> `
    --querytext `
    "
        create table todo (
            id int(8) auto_increment,
            content varchar(60),
            done bit(1) not null default b'0',
            created_at datetime not null default current_timestamp,
            primary key (id)
        );
    "
```

</details>

# Azure Functions側の実装

細かい実装の話が続くので、面倒くさい方は[環境変数の設定](#環境変数の設定)までスキップしてください。  
ここで特筆したいのは**接続情報を環境変数から取ってくる**という部分です。

※ソースは抜粋しているので、全容は[Github](https://github.com/azure-learn-796/azure-functions/tree/azure-function-mysql)をご覧ください

## ORマッパーの導入

せっかくなのでORマッパーを導入したいと思います。  
今回は [MyBatis](https://mybatis.org/mybatis-3/ja/) を使用します。(SQLは自分で書きたいし、コードと分離したい派なので...)  
本筋から外れるので、MyBatisの説明は割愛します。  
興味のある方は調べてみてください。

<details><summary>どのORマッパーを選んだらよいか迷ったときは、こちらのスライドがとても参考になります。（クリックで展開）</summary>

<iframe src="//www.slideshare.net/slideshow/embed_code/key/85vvevesdrU6Q9" width="595" height="485" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> <div style="margin-bottom:5px"> <strong> <a href="//www.slideshare.net/masatoshitada7/java-or-jsug" title="Java ORマッパー選定のポイント #jsug" target="_blank">Java ORマッパー選定のポイント #jsug</a> </strong> from <strong><a href="//www.slideshare.net/masatoshitada7" target="_blank">Masatoshi Tada</a></strong> </div>

</details>

### 依存関係の追加

MyBatisとMySQLコネクタを使用するため、`pom.xml`に依存関係を追加します。

```xml:pom.xml (一部抜粋)
・・・
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
</dependency>
・・・
```

### 設定ファイルの追加

MyBatisの設定ファイルを追加します。  
DBの接続情報をソース管理されるソースに含めるのはセキュリティ上よろしくないので、変数化(`${xxx}`)しています。  
この変数には後述の[環境変数](#環境変数の設定)の値が入ります。  

```xml:mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <typeAliases>
    <package name="com.azure_functions.models"/>
  </typeAliases>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <!-- ↓ポイント↓ -->
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
        <!-- ↑ポイント↑ -->
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <package name="com.azure_functions.mappers"/>
  </mappers>
</configuration>
```

### SQL定義の追加

実際に発行されるSQL文を定義します。  

```xml:TodoItemMapper.xml (一部抜粋)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.azure_functions.mappers.TodoItemMapper">
  <!-- TODO一覧取得 -->
  <select id="selectTodo" resultType="TodoItem">
    select
        id
      , content
      , done
      , created_at
    from
      todo
  </select>
  ・・・
</mapper>
```

### SQLとJavaをつなぐインターフェースを追加

JavaからSQLを呼び出すためにインターフェースを追加します。  
メソッド名と[SQL定義](#sql定義の追加)の `id` が紐づきます。

```java:TodoItemMapper.java (一部抜粋)
/**
 * TodoItemのマッパー
 */
public interface TodoItemMapper {
  /**
   * TODO一覧を全件取得
   *
   * @return TODO一覧
   */
  List<TodoItem> selectTodo();
  ・・・
}
```

### DB接続周りのコードを追加

接続情報を環境変数から取得して、[設定ファイル](#設定ファイルの追加)で変数化していた部分にセットしています。

```java:SqlSessionManager.java
@RequiredArgsConstructor
public class SqlSessionManager {

  /** SQLセッションファクトリー */
  private static SqlSessionFactory sqlSessionFactory = sqlSessionFactory();

  /** 実行コンテキスト */
  private final ExecutionContext context;

  private static SqlSessionFactory sqlSessionFactory() {
    // ↓ポイント↓
    // DB接続情報を環境変数から取得
    Properties mybatisProps = new Properties();
    mybatisProps.put("url", System.getenv("MYSQL_URL"));
    mybatisProps.put("username", System.getenv("MYSQL_USER"));
    mybatisProps.put("password", System.getenv("MYSQL_PASSWORD"));
    // ↑ポイント↑

    try (Reader config = Resources.getResourceAsReader("mybatis-config.xml")) {
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(config, mybatisProps);
      return sqlSessionFactory;

    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }
  ・・・
}
```

### モデルの修正

作成したテーブルのカラムにあわせて、モデルを変更します。

```java:TodoItem.java
@Data
@Builder
@JsonDeserialize(builder = TodoItem.TodoItemBuilder.class)
public class TodoItem {

  // ↓追加↓
  /** ID */
  private int id;
  // ↑追加↑

  /** 内容 */
  private String content;

  /** 完了フラグ */
  private boolean done;

  /** 作成日時 */
  private LocalDateTime createdAt;

}
```

## エンドポイントの追加

[前回](https://qiita.com/fsdg-nagumo/items/5eec1e9b66d33e3f4687)はTODO一覧を取得するためのエンドポイントのみでしたが、登録・更新・削除用のエンドポイントを追加します。

### HTTPトリガーの追加

`@HttpTrigger` を付与したメソッドを作成します。  
また、前回対応しなかったAPIキーの使用についても対応しています。  
(`authLevel` を `AuthorizationLevel.FUNCTION` に変更するだけです！)

```java:Function.java (一部抜粋)
public class Function {

  /**
   * TODO一覧を取得
   *
   * @param request リクエスト
   * @param context 実行コンテキスト
   * @return レスポンス
   */
  @FunctionName("fetchTodoItems")
  public HttpResponseMessage fetchTodoList(
    @HttpTrigger(
      name = "req",
      methods = { HttpMethod.GET },
      // 承認レベルをFUNCTIONに変更（APIキーが必要となる）
      authLevel = AuthorizationLevel.FUNCTION,
      // エンドポイントを「/api/todos」に設定
      route = "todos"
    ) HttpRequestMessage<Optional<String>> request,
    final ExecutionContext context
  ) {
    ・・・
  }

  /**
   * TODOを登録
   *
   * @param request リクエスト
   * @param context 実行コンテキスト
   * @return レスポンス
   */
  @FunctionName("createTodoItem")
  public HttpResponseMessage createTodoItem(
    @HttpTrigger(
      name = "req",
      methods = { HttpMethod.POST },
      authLevel = AuthorizationLevel.FUNCTION,
      // エンドポイントを「/api/todos」に設定
      route = "todos"
    ) HttpRequestMessage<Optional<String>> request,
    final ExecutionContext context
  ) {
    ・・・
  }

  /**
   * TODOを更新
   *
   * @param request リクエスト
   * @param context 実行コンテキスト
   * @return レスポンス
   */
  @FunctionName("updateTodoItem")
  public HttpResponseMessage updateTodoItem(
    @HttpTrigger(
      name = "req",
      methods = { HttpMethod.PATCH },
      authLevel = AuthorizationLevel.FUNCTION,
      // エンドポイントを「/api/todos/<id>」に設定
      route = "todos/{id:int}"
    ) HttpRequestMessage<Optional<String>> request,
    final ExecutionContext context,
    // パスのidをバインド
    @BindingName("id") int id
  ) {
    ・・・
  }

  /**
   * TODOを削除
   *
   * @param request リクエスト
   * @param context 実行コンテキスト
   * @return レスポンス
   */
  @FunctionName("deleteTodoItem")
  public HttpResponseMessage deleteTodoItem(
    @HttpTrigger(
      name = "req",
      methods = { HttpMethod.DELETE },
      authLevel = AuthorizationLevel.FUNCTION,
      // エンドポイントを「/api/todos/<id>」に設定
      route = "todos/{id:int}"
    ) HttpRequestMessage<Optional<String>> request,
    final ExecutionContext context,
    // パスのidをバインド
    @BindingName("id") int id
  ) {
    ・・・
  }
  ・・・
}
```

#### HTTPトリガーの設定内容(サマリ)

|関数名|HTTPメソッド|エンドポイント|承認レベル|処理|
|--|--|--|--|--|
|fetchTodoItems|GET|/api/todos|FUNCTION|一覧取得|
|createTodoItem|POST|/api/todos|FUNCTION|登録|
|updateTodoItem|PATCH|/api/todos/{id}|FUNCTION|更新|
|deleteTodoItem|DELETE|/api/todos/{id}|FUNCTION|削除|

### リクエストボディ(JSON)をオブジェクトに変換するためのデシリアライズ処理を追加

前回はオブジェクト→JSON変換のためにシリアライズ処理を実装しましたが、今回はその逆で、JSON→オブジェクト変換のためのデシリアライズ処理を実装します。

```java:JsonUtil.java
/**
 * JSON関連のユーティリティ
 */
@RequiredArgsConstructor
public class JsonUtil {

  /** JSONシリアライズ用のマッパー */
  private static final ObjectMapper MAPPER = objectMapper();

  /** 実行コンテキスト */
  private final ExecutionContext context;

  private static ObjectMapper objectMapper() {
    ・・・
    // ↓追加↓
    // LocalDateTime用のデシリアライザを追加
    javaTimeModule.addDeserializer(
      LocalDateTime.class,
      new LocalDateTimeDeserializer(
        DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss")
      )
    );
    // ↑追加↑
    ・・・

    return mapper;
  }

  ・・・
  // ↓追加↓
  /**
   * JSONからオブジェクトに変換
   *
   * @param <T>  オブジェクトの型
   * @param json JSON文字列
   * @param type オブジェクトの型
   * @return オブジェクト
   */
  public <T> T deserialize(String json, Class<T> type) {
    try {
      return MAPPER.readValue(json, type);
    } catch (JsonProcessingException e) {
      context
        .getLogger()
        .severe(
          "JSON→オブジェクトの変換に失敗しました。エラーメッセージ：" +
          e.getMessage()
        );
      throw new IllegalArgumentException(e);
    }
  }
  // ↑追加↑
}
```

### データ操作ロジックの変更

前回ダミーデータを生成していた部分を、DBから取得するように変更します。  
また、今回追加した登録・更新・削除についてもロジックを追加します。

```java:TodoService.java (一部抜粋)
/**
 * TODOサービス
 */
@RequiredArgsConstructor
public class TodoService {

  /** 実行コンテキスト */
  private final ExecutionContext context;

  /** SQLセッション管理 */
  private final SqlSessionManager sqlSessionManager;

  /**
   * TODO一覧を取得
   *
   * @return TODO一覧
   */
  public List<TodoItem> fetchTodoItems() {
    context.getLogger().info("fetchTodoItemsが呼び出されました。");

    // ↓DBからデータ取得するよう変更↓
    // DBからTODO一覧取得
    List<TodoItem> todoItems = sqlSessionManager.transaction(
      sqlSession -> {
        TodoItemMapper todoItemMapper = sqlSession.getMapper(
          TodoItemMapper.class
        );
        return todoItemMapper.selectTodo();
      }
    );
    // ↑DBからデータ取得するよう変更↑

    return todoItems;
  }

  /**
   * TODOを登録
   * @param todo TODOインスタンス
   * @return 登録件数
   */
  public int insertTodo(TodoItem todo) {
    ・・・
  }

  /**
   * TODOを更新
   * @param todo TODOインスタンス
   * @return 更新件数
   */
  public int updateTodo(int id, TodoItem todo) {
    ・・・
  }

  /**
   * TODOを削除
   * @param id TODOのID
   * @return 削除件数
   */
  public int deleteTodo(int id) {
    ・・・
  }
}
```

## 環境変数の設定

ローカル実行とAzure上で環境変数の設定方法が異なります。

### ローカル実行時の環境変数

Mavenプロジェクト作成時に作成されている `local.settings.json` 内の `Values` に設定を追加します。  
こうすることで、実行時に設定が読み込まれ、環境変数にセットされます。

```json:local.settings.json
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "java",
    // ↓追加↓
    "MYSQL_URL": "jdbc:mysql://<MySQLサーバー名>.mysql.database.azure.com:3306/<データベース名>?useSSL=true",
    "MYSQL_USER": "<Adminユーザー>",
    "MYSQL_PASSWORD": "<Adminパスワード>"
    // ↑追加↑
  }
}
```

#### ◎個人的躓きポイント

`local.settings.json` は、**テスト時は読み込まれません**。  
そのため、テスト実行する場合は事前にターミナル等で環境変数を設定しておく必要があります。  
```bash
mvn clean package
```

でビルドした場合、デフォルトではテストが実行されてしまうので、スキップしたい場合は以下のようにオプションを指定する

```bash
mvn clean package -DskipTests=true
```

または、`pom.xml` に設定を追加してデフォルトの挙動を変更します。

```xml:pom.xml
<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">
    ・・・
    <properties>
        ・・・
        <!-- ↓追加↓ -->
        <skipTests>true</skipTests>
        <!-- ↑追加↑ -->
    </properties>
```

### Azure上にデプロイした関数アプリの環境変数(アプリケーション設定)

Azure上にデプロイした関数アプリの環境変数（アプリケーション設定）を追加するには  
[az functionapp config appsettings set](https://learn.microsoft.com/ja-jp/cli/azure/functionapp/config/appsettings?view=azure-cli-latest#az-functionapp-config-appsettings-set) コマンドを使用します。

```bash:Bash
az functionapp config appsettings set \
    --resource-group <リソースグループ> \
    --name <関数アプリ名> \
    --settings \
    MYSQL_URL=jdbc:mysql://<MySQLサーバー名>.mysql.database.azure.com:3306/<データベース名>?useSSL=true \
    MYSQL_USER=<Adminユーザー> \
    MYSQL_PASSWORD=<Adminパスワード>
```

<details>
<summary>PowerShell</summary>

```powershell:PowerShell
az functionapp config appsettings set `
    --resource-group <リソースグループ> `
    --name <関数アプリ名> `
    --settings `
    MYSQL_URL=jdbc:mysql://<MySQLサーバー名>.mysql.database.azure.com:3306/<データベース名>?useSSL=true `
    MYSQL_USER=<Adminユーザー> `
    MYSQL_PASSWORD=<Adminパスワード>
```

</details>

> ここではアプリケーション設定に設定値をべた書きしましたが、[Key Vault(Keyコンテナー)](https://learn.microsoft.com/ja-jp/azure/key-vault/general/overview) というサービスを使用することで、シークレットを一元管理して複数のアプリで共有するといったことも可能です。  
> ↓の記事が参考になりました。  
> [[Azure] Azure FunctionsからAzure Key Vaultの値を参照する - Qiita](https://qiita.com/iron-samurai/items/0ca65cf7262111bc62a0)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884047/375048b0-238a-d723-949e-ef87def58ed1.png)

## 最終的なフォルダ構成

```text
azure-functions
├── README.md
├── host.json
├── local.settings.json -------------------------------- 変更
├── pom.xml -------------------------------------------- 変更
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── azure_functions
    │   │           ├── Function.java ------------------ 変更
    │   │           ├── db
    │   │           │   └── SqlSessionManager.java ----- 追加
    │   │           ├── mappers
    │   │           │   └── TodoItemMapper.java -------- 追加
    │   │           ├── models
    │   │           │   └── TodoItem.java -------------- 変更
    │   │           ├── services
    │   │           │   └── TodoService.java ----------- 変更
    │   │           └── utils
    │   │               └── JsonUtil.java -------------- 変更
    │   └── resources
    │       ├── com
    │       │   └── azure_functions
    │       │       └── mappers
    │       │           └── TodoItemMapper.xml --------- 追加
    │       └── mybatis-config.xml --------------------- 追加
    └── test
```

## デプロイ

アプリをAzureにデプロイします。  
デプロイ手順は[前回](https://qiita.com/fsdg-nagumo/items/5eec1e9b66d33e3f4687#%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4)と同様なので割愛します。

【デプロイ後】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884047/a7e2da92-659c-7f7b-9a03-ef7b9cf36086.png)

# 動作確認

## APIキーの確認

今回は関数にAPIキーを設定しているため、パラメータとしてAPIキーを指定する必要があります。  
事前にコマンド叩いて確認します。

```bash:Bash
# 関数アプリのホストを取得
azFunctionHost=$(az functionapp show \
        --resource-group <リソースグループ名> \
        --name <関数アプリ名> \
        --query "defaultHostName" | sed 's/"//g') # 「"」を除去

# APIキーを取得
azFunctionKey=$(az functionapp keys list \
        --resource-group <リソースグループ名> \
        --name <関数アプリ名> \
        --query "functionKeys.default" | sed 's/"//g') # 「"」を除去
```

<details>
<summary>PowerShell</summary>

```powershell:PowerShell
# 関数アプリのホストを取得
$azFunctionHost = (az functionapp show `
        --resource-group <リソースグループ名> `
        --name <関数アプリ名> `
        --query "defaultHostName").Trim('"') # 「"」を除去

# APIキーを取得
$azFunctionKey = (az functionapp keys list `
        --resource-group <リソースグループ名> `
        --name <関数アプリ名> `
        --query "functionKeys.default").Trim('"') # 「"」を除去
```

</details>

> 上記コマンドの出力結果はJSONですが、`--query` オプションで絞り込み・ソート・整形ができます。  
> `--query` オプションには [JMESPath](https://jmespath.org/) というJSON向けのクエリを指定することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884047/48c8ec05-ff0c-b3ff-ea2b-53cb8c206b92.png)

## API呼び出し

`code` パラメータにAPIキーを指定します。

### 一覧

```bash
curl -i -X GET https://${azFunctionHost}/api/todos?code=${azFunctionKey}
```

### 登録

```bash:Bash
curl -i -X POST -d '{\"content\":\"Azureの資格取得\"}' https://${azFunctionHost}/api/todos?code=${azFunctionKey}
```

<details>
<summary>PowerShell</summary>

```powershell:PowerShell
# 日本語が文字化けするので一旦データをファイルに吐いて、ファイル指定にする
$tmpFile = New-TemporaryFile
Write-Output '{"content":"Azureの資格取得"}' | Set-Content $tmpFile
curl -i -X POST -d @$tmpFile https://${azFunctionHost}/api/todos?code=${azFunctionKey}
Remove-Item $tmpFile
```

</details>

### 更新

```bash:Bash
curl -i -X PATCH -d '{\"content\":\"Azureの資格取得(AZ-900)\", \"done\":true}' https://${azFunctionHost}/api/todos/1?code=${azFunctionKey}
```

<details>
<summary>PowerShell</summary>

```powershell:PowerShell
# 日本語が文字化けするので一旦データをファイルに吐いて、ファイル指定にする
$tmpFile = New-TemporaryFile
Write-Output '{"content":"Azureの資格取得(AZ-900)", "done":true}' | Set-Content $tmpFile
curl -i -X PATCH -d @$tmpFile https://${azFunctionHost}/api/todos/1?code=${azFunctionKey}
Remove-Item $tmpFile
```

</details>

### 削除

```bash
curl -i -X DELETE  https://${azFunctionHost}/api/todos/1?code=${azFunctionKey}
```

# まとめ

- MySQLサーバーにローカルから接続するときはファイアウォールでIPを許可する
- DBの接続情報は環境変数から取ってくる

これで簡単なバックエンドの処理ができたので、次はフロント部分からAPIを呼び出せるようにして、簡易的なSPAアプリ的な感じにしてみたいと思います。

**2022/10/24 続きの記事を投稿しました**

https://qiita.com/fsdg-nagumo/items/3eea64da5fdac0df7038

# 参考文献

- [Azure CLIリファレンス](https://learn.microsoft.com/ja-jp/cli/azure/reference-index?view=azure-cli-latest)
- [Java ORマッパー選定のポイント](https://www.slideshare.net/masatoshitada7/java-or-jsug)
- [[Azure] Azure FunctionsからAzure Key Vaultの値を参照する - Qiita](https://qiita.com/iron-samurai/items/0ca65cf7262111bc62a0)
- [JMESPath](https://jmespath.org/)