Cloud Functionsをjavaで書いてCloud SQL(MySQL)に接続する

# 概略
Cloud Functionsをjavaで書いて、Cloud SQLに接続するサンプルコードが見当たらなかったので備忘録が共有しようと思います。

※Cloud FunctionsとCloud SQLの設置手順は、Qiitaメンバーの解説記事がわかりやすいので割愛します。

## 割り当てられるメモリ の設定
256MiB以上をお勧めします。

最初、128MiBで動作確認したのですがメモリ不足でエラーになりました。

## ランタイム環境変数 の設定
以下の変数をセットします。

| 変数 | 値 |
|---|---|
| DB_NAME | MySqlでCREATEしたデータベース名 |
| DB_USER | データベースへの接続ユーザー |
| DB_PASS | データベースへの接続ユーザーのパスワード |
| CLOUD_SQL_CONNECTION_NAME | CloudSQLに構築したデータベースインスタンスのインスタンス接続名 |

※暗号化した値を設定する場合は実装側で複合処理を実装してください。
以降は平文設定している前提です。

## pom.xml
com.zaxxer(DBコネクション管理)、com.google.cloud.sql(CloudSQL接続用)、mysql(MySQL接続用)が最低限必要(だった)。

```xml:pom.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>

  <groupId>cloudfunctions</groupId>
  <artifactId>http-function</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.target>11</maven.compiler.target>
    <maven.compiler.source>11</maven.compiler.source>
  </properties>

  <dependencies>
    <dependency>
      <groupId>com.google.cloud.functions</groupId>
      <artifactId>functions-framework-api</artifactId>
      <version>1.0.1</version>
    </dependency>
     <dependency>
      <groupId>com.zaxxer</groupId>
      <artifactId>HikariCP</artifactId>
      <version>4.0.3</version>
    </dependency>
    <dependency>
      <groupId>com.google.cloud.sql</groupId>
      <artifactId>mysql-socket-factory</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.23</version>
    </dependency>
  </dependencies>

  <!-- Required for Java 11 functions in the inline editor -->
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <excludes>
            <exclude>.google/</exclude>
          </excludes>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
```

## Example.java
```java:Example.java

package com.example;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.BufferedWriter;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Example implements HttpFunction {
  private static final String CLOUD_SQL_CONNECTION_NAME = System.getenv("CLOUD_SQL_CONNECTION_NAME");
  private static final String DB_USER = System.getenv("DB_USER");
  private static final String DB_PASS = System.getenv("DB_PASS");
  private static final String DB_NAME = System.getenv("DB_NAME");
  private static DataSource pool;
  @Override
  public void service(HttpRequest request, HttpResponse response) throws Exception {

    if (pool == null) {
      HikariConfig config = new HikariConfig();
      config.setJdbcUrl(String.format("jdbc:mysql:///%s", DB_NAME));
      config.setUsername(DB_USER); // e.g. "root", "mysql"
      config.setPassword(DB_PASS); // e.g. "my-password"
      config.addDataSourceProperty("socketFactory", "com.google.cloud.sql.mysql.SocketFactory");
      config.addDataSourceProperty("cloudSqlInstance", CLOUD_SQL_CONNECTION_NAME);
      config.addDataSourceProperty("ipTypes", "PUBLIC,PRIVATE");
      config.setMaximumPoolSize(5);
      config.setMinimumIdle(5);
      config.setConnectionTimeout(10000); // 10 seconds
      config.setIdleTimeout(600000); // 10 minutes
      config.setMaxLifetime(1800000); // 30 minutes
      pool = new HikariDataSource(config);
    }

    BufferedWriter writer = response.getWriter();
    try (Connection conn = pool.getConnection()) {
      String stmt = "SELECT * FROM {DB名.テーブル名};";
      try (PreparedStatement secectTableStatement = conn.prepareStatement(stmt);) {
        try (ResultSet rs = secectTableStatement.executeQuery();) {
          while (rs.next()) {
            int id = rs.getInt("ID");
            String name = rs.getString("NAME");
            writer.write("id = " + id + ",Name = " + name);  //クライアントにreplyする値
          }
        }
      }
    } catch (Exception e) {
      writer.write("error cause by -> " + e.getClass().getName() + ": " + e.getMessage());
    }
  }
}
```

クライアント側との連携仕様に合わせてReply方法は設計してください。

以上。