Cloud SQL for PostgreSQL の使用

このページでは App Engine アプリケーションから Cloud SQL for PostgreSQL インスタンスに接続する方法を説明します。また、Cloud SQL に対して読み込みや書き込みを行う方法についても説明します。Cloud SQL は、Google のクラウドで稼働する SQL データベースです。

Cloud SQL の詳細については、Cloud SQL のドキュメントをご覧ください。Cloud SQL の料金と制限については、Cloud SQL の料金ページをご覧ください。App Engine アプリケーションには、App Engine の割り当ても適用されます。

始める前に

  1. GCP Console で GCP プロジェクトを作成または選択した後、必ずプロジェクトに App Engine アプリケーションを含め、課金を有効にしてください。
    App Engine に移動

    プロジェクトに App Engine アプリケーションがすでに存在し、課金が有効な場合、ダッシュボードが開きます。それ以外の場合には、プロンプトに従ってリージョンを選択し、課金を有効にします。

  2. Cloud SQL API を有効にします。

    APIを有効にする

  3. Java アプリを App Engine にデプロイするには、まず環境をセットアップする必要があります。詳細については、Apache Maven および App Engine プラグインを使用するをご覧ください。

Cloud SQL インスタンスの構成

Cloud SQL インスタンスを作成して構成するには、次の手順を行います。

  1. Cloud SQL for PostgreSQL のインスタンスを作成します
  2. Cloud SQL インスタンスのデフォルト ユーザーのパスワードを設定します(まだ行っていない場合)。
    gcloud sql users set-password postgres no-host --instance [INSTANCE_NAME] --password [PASSWORD]
    
  3. 接続にデフォルトのユーザーを使用しない場合は、ユーザーを作成します。
  4. インスタンスの接続名を記録します。
    gcloud sql instances describe [INSTANCE_NAME]
    

    次に例を示します。

    connectionName: project1:us-central1:instance1
    

    この値は Google Cloud Platform Console の [インスタンスの詳細] ページでも確認できます。

  5. この例では、sqldemo という名前の Cloud SQL インスタンスにデータベースを作成します。
    gcloud sql databases create sqldemo --instance=[INSTANCE_NAME]
    
    データベースの作成と管理の詳細については、Cloud SQL のドキュメントをご覧ください。

接続文字列の設定とライブラリの追加

ローカルテストとデプロイを行うために、Cloud SQL JDBC ソケット ファクトリのソケット ライブラリを使用して、Cloud SQL インスタンスに接続します。

  1. pom.xml の接続名、ユーザー名、およびパスワードを更新します。

      <properties>
    <!--
        INSTANCE_CONNECTION_NAME from Cloud Console > SQL > Instance Details > Properties
        or `gcloud sql instances describe <instance> | grep connectionName`
    -->
        <INSTANCE_CONNECTION_NAME>Project:Region:Instance</INSTANCE_CONNECTION_NAME>
        <user>root</user>
        <password>myPassword</password>
        <database>sqldemo</database>
        <sqlURL>jdbc:postgresql://google/${database}?useSSL=false&amp;socketFactoryArg=${INSTANCE_CONNECTION_NAME}&amp;socketFactory=com.google.cloud.sql.postgres.SocketFactory&amp;user=${user}&amp;password=${password}</sqlURL>
      </properties>

  2. 必要なリソース フィルタを pom.xml に追加します。

    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
      </resource>
    </resources>

  3. 次の行を含む config.properties というファイルを作成します。

    sqlUrl=${sqlURL}

    このファイルは、pom.xml で構成されたリソース フィルタに依存しています。

  4. 依存関係を pom.xml に追加して、JDBC ライブラリと Cloud SQL JDBC ソケット ファクトリをアプリケーションに追加します。

    <dependency>
      <groupId>org.postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <version>42.2.4</version>
    </dependency>
    
    <dependency>
      <groupId>com.google.cloud.sql</groupId>
      <artifactId>postgres-socket-factory</artifactId>
      <version>1.0.10</version>
    </dependency>

サンプルコードの実行

次のサンプルは、訪問情報を Cloud SQL に書き込み、次に最新の 10 件の訪問情報を読み取って返します。
@SuppressWarnings("serial")
@WebServlet(name = "postgresql", value = "")
public class PostgresSqlServlet extends HttpServlet {
  Connection conn;

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,
      ServletException {

    final String createTableSql = "CREATE TABLE IF NOT EXISTS visits ( visit_id SERIAL NOT NULL, "
        + "user_ip VARCHAR(46) NOT NULL, ts timestamp NOT NULL, "
        + "PRIMARY KEY (visit_id) );";
    final String createVisitSql = "INSERT INTO visits (user_ip, ts) VALUES (?, ?);";
    final String selectSql = "SELECT user_ip, ts FROM visits ORDER BY ts DESC "
        + "LIMIT 10;";

    String path = req.getRequestURI();
    if (path.startsWith("/favicon.ico")) {
      return; // ignore the request for favicon.ico
    }

    PrintWriter out = resp.getWriter();
    resp.setContentType("text/plain");

    // store only the first two octets of a users ip address
    String userIp = req.getRemoteAddr();
    InetAddress address = InetAddress.getByName(userIp);
    if (address instanceof Inet6Address) {
      // nest indexOf calls to find the second occurrence of a character in a string
      // an alternative is to use Apache Commons Lang: StringUtils.ordinalIndexOf()
      userIp = userIp.substring(0, userIp.indexOf(":", userIp.indexOf(":") + 1)) + ":*:*:*:*:*:*";
    } else if (address instanceof Inet4Address) {
      userIp = userIp.substring(0, userIp.indexOf(".", userIp.indexOf(".") + 1)) + ".*.*";
    }

    Stopwatch stopwatch = Stopwatch.createStarted();
    try (PreparedStatement statementCreateVisit = conn.prepareStatement(createVisitSql)) {
      conn.createStatement().executeUpdate(createTableSql);
      statementCreateVisit.setString(1, userIp);
      statementCreateVisit.setTimestamp(2, new Timestamp(new Date().getTime()));
      statementCreateVisit.executeUpdate();

      try (ResultSet rs = conn.prepareStatement(selectSql).executeQuery()) {
        stopwatch.stop();
        out.print("Last 10 visits:\n");
        while (rs.next()) {
          String savedIp = rs.getString("user_ip");
          String timeStamp = rs.getString("ts");
          out.println("Time: " + timeStamp + " Addr: " + savedIp);
        }
        out.println("Elapsed: " + stopwatch.elapsed(TimeUnit.MILLISECONDS));
      }
    } catch (SQLException e) {
      throw new ServletException("SQL error", e);
    }
  }

  @Override
  public void init() throws ServletException {
    String url;

    Properties properties = new Properties();
    try {
      properties.load(
          getServletContext().getResourceAsStream("/WEB-INF/classes/config.properties"));
      url = properties.getProperty("sqlUrl");
    } catch (IOException e) {
      log("no property", e);  // Servlet Init should never fail.
      return;
    }

    log("connecting to: " + url);
    try {
      Class.forName("org.postgresql.Driver");
      conn = DriverManager.getConnection(url);
    } catch (ClassNotFoundException e) {
      throw new ServletException("Error loading JDBC Driver", e);
    } catch (SQLException e) {
      throw new ServletException("Unable to connect to PostGre", e);
    } finally {
      // Nothing really to do here.
    }
  }
}

テストとデプロイ

  1. アプリケーションをローカルでテストするには、以下のコマンドを実行します。

    mvn clean jetty:run
    

  2. ローカルテストの後、App Engine にアプリをデプロイします。

    mvn clean appengine:deploy
    

  3. ブラウザを起動して http://[YOUR_PROJECT_ID].appspot.com でアプリを表示するには、次のコマンドを実行します。

    gcloud app browse
    

Cloud SQL と App Engine を別々のプロジェクトで実行する

App Engine アプリケーションと Cloud SQL インスタンスが異なる Google Cloud Platform プロジェクトにある場合、サービス アカウントを使用して App Engine アプリケーションが Cloud SQL にアクセスできるようにする必要があります。

このサービス アカウントは App Engine アプリケーションを表し、Google Cloud Platform プロジェクトを作成するとデフォルトで作成されます。

  1. App Engine アプリケーションが Cloud SQL インスタンスと同じプロジェクトにある場合は、このセクションをスキップして、接続文字列の設定とライブラリの追加に進んでください。それ以外の場合は、次のステップに進みます。
  2. App Engine アプリケーションに関連付けられたサービス アカウントを特定します。デフォルトの App Engine サービス アカウント名は [PROJECT-ID]@appspot.gserviceaccount.com です。

    App Engine サービス アカウントは [IAM 権限] ページで確認できます。プロジェクトは Cloud SQL インスタンスではなく、必ず App Engine アプリケーションに対して選択してください。

    [IAM 権限] ページに移動

  3. Google Cloud Platform Console の [IAM と管理] ページに移動します。

    [IAM と管理] ページに移動

  4. Cloud SQL インスタンスを含むプロジェクトを選択します。
  5. サービス アカウント名を検索します。
  6. cloudsql.instances.connect 権限を含む役割を持つサービス アカウントがすでに存在する場合は、接続文字列の設定とライブラリの追加に進むことができます。

    Cloud SQL ClientCloud SQL EditorCloud SQL Admin の役割はすべて、従来の EditorOwner のプロジェクト役割と同様に、必要な権限を提供します。

  7. それ以外の場合、[追加] をクリックしてサービス アカウントを追加します。
  8. [メンバーの追加] ダイアログでサービス アカウントの名前を指定し、cloudsql.instances.connect 権限を含む役割を選択します(閲覧者以外の Cloud SQL 定義済み役割であればすべて機能します)。

    あるいは、[プロジェクト] > [編集者] の順に選択して、編集者の基本的な役割を使用できますが、編集者の役割には Google Cloud Platform 全体の権限が含まれます。

    これらの役割が表示されない場合、Google Cloud Platform ユーザーに resourcemanager.projects.setIamPolicy 権限がない可能性があります。権限を確認するには、Google Cloud Platform Console の [IAM] ページにアクセスし、自分のユーザー ID を検索します。

  9. [追加] をクリックします。

    これで、指定した役割を持つサービス アカウントがリストされます。

このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...

Java の App Engine フレキシブル環境に関するドキュメント