目标听众

  • 想要从 Azure Functions 连接到 Azure Database for MySQL 的用户
  • 想用Java开发的人
  • 那些想要在命令的基础上进行操作的人

概述

本文涵盖:
基本上,我们将在命令的基础上进行。

  • 为 Azure Database for MySQL 灵活服务器创建资源
  • 从 Azure Functions 连接到 MySQL
  • [Bonus] 在 Azure Functions 中使用 API 密钥

前提

这是上一篇文章“使用 Azure Functions 创建 API - Java 版”的续篇。
请参阅文章了解如何构建和部署 Azure Functions 的开发环境。

<上一集>
  1. 为 Azure Functions (Java) 构建本地开发环境
  2. 创建一个返回随机虚拟 JSON 数据的 API
    [这次] 改为从 MySQL 获取此数据(添加注册/更新/删除)
  3. 部署到 Azure

    操作环境

    工作环境如下。

    • 操作系统
      • Windows 11 专业版 (21H2)
      • Ubuntu 20.04 (WLS)
    • JDK
    • Azure CLI 2.40.0

    准备数据库

    创建 Azure Database for MySQL 灵活服务器

    Azure Database for MySQL 具有单服务器“什么时候”灵活的服务器”,但由于单台服务器计划停产,所以似乎更推荐弹性服务器。

    创建 MySQL 服务器az mysql 灵活服务器创建使用命令

    重击
    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
    
    电源外壳
    电源外壳
    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
    

    可选补充

    指定上述选项后,MySQL 服务器为公共访问在模式中创建。
    即使它是公共的,也只有防火墙允许的 IP 地址才能连接。
    如果您不配置防火墙,它将不接受来自任何 IP 的连接。
    通过将--public-access 设置为0.0.0.0Azure 中的资源防火墙配置为允许从公共访问 MySQL 服务器

    这次我就不多说了,不过VNet 集成通过使用该机制,似乎可以在 Azure Functions 和 MySQL 之间进行私有通信。

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

    数据库创建

    创建 MySQL 服务器时,会自动创建一个名为 flexibleserverdb 的数据库,但我想添加一个新的。

    创建数据库az mysql 灵活服务器数据库创建使用命令

    重击
    az mysql flexible-server db create 
        --resource-group <リソースグループ名> 
        --server-name <MySQLサーバー名> 
        --charset utf8mb4 
        --collation utf8mb4_bin 
        --database-name <データベース名> 
    
    电源外壳
    电源外壳
    az mysql flexible-server db create `
        --resource-group <リソースグループ名> `
        --server-name <MySQLサーバー名> `
        --charset utf8mb4 `
        --collation utf8mb4_bin `
        --database-name <データベース名> 
    

    字符代码(字符集)、排序规则等是您的偏好。

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

    添加防火墙规则

    接下来,我想在我创建的数据库中创建一个表,但是我无法从客户端访问 MySQL 服务器。
    以上正如我在 中写的,您需要允许防火墙中的连接源的IP(全局IP)访问。

    添加防火墙规则az mysql 灵活服务器防火墙规则创建使用命令

    重击
    az mysql flexible-server firewall-rule create 
        --resource-group <リソースグループ名> 
        --name <MySQLサーバー名> 
        --rule-name <ファイアウォールルール名> 
        --start-ip-address <クライアントのIPアドレス> 
    
    电源外壳
    电源外壳
    az mysql flexible-server firewall-rule create `
        --resource-group <リソースグループ名> `
        --name <MySQLサーバー名> `
        --rule-name <ファイアウォールルール名> `
        --start-ip-address <許可するIPアドレス> 
    

    可选补充

    您还可以使用start-ip-addressend-ip-address 指定范围。
    如果只指定start-ip-address,则只针对指定的IP。

    要检查您自己的IP地址,请谷歌“IP地址确认”。
    例如这里你可以检查它
    (不要在这里使用命令!)

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

    创建表

    您应该能够通过添加防火墙规则连接到 MySQL 服务器,因此创建一个表。

    表创建az mysql 灵活服务器执行使用命令运行查询。

    重击
    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)
            );
        "
    
    电源外壳
    电源外壳
    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)
            );
        "
    

    Azure Functions 端的实现

    既然详细执行的故事还在继续,麻烦的人设置环境变量跳到
    我想在这里指出的是从环境变量中获取连接信息这就是部分。

    * 由于来源是摘录的,所以整个故事是Github请看

    OR映射器介绍

    我想介绍 OR 映射器,因为它很重要。
    这次MyBatis使用。 (因为想自己写SQL,和代码分开……)
    我将省略对 MyBatis 的解释,因为它不在主题范围内。
    如果您有兴趣,请查看。

    如果您想知道选择哪个 OR 映射器,这张幻灯片将非常有帮助。 (点击展开)
    Java OR 映射器选择点#jsug仅正志

    添加依赖项

    添加对 pom.xml 的依赖以使用 MyBatis 和 MySQL 连接器。

    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 配置文件。
    由于在源代码控制的源中包含数据库连接信息对安全性不利,因此它是可变的 (${xxx})。
    这个变量包含环境变量输入值。

    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 语句。

    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 定义id 的关联。

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

    围绕数据库连接添加代码

    从环境变量中获取连接信息,设置文件它设置在 中可变的部分。

    Sql Touching Onma Throw r。爪哇
    @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);
        }
      }
      ・・・
    }
    

    模型的修改

    根据创建的表的列更改模型。

    达到米。爪哇
    @Data
    @Builder
    @JsonDeserialize(builder = TodoItem.TodoItemBuilder.class)
    public class TodoItem {
    
      // ↓追加↓
      /** ID */
      private int id;
      // ↑追加↑
    
      /** 内容 */
      private String content;
    
      /** 完了フラグ */
      private boolean done;
    
      /** 作成日時 */
      private LocalDateTime createdAt;
    
    }
    

    添加端点

    上次只有一个端点用于获取 TODO 列表,但现在我们将添加端点用于注册、更新和删除。

    添加 HTTP 触发器

    使用@HttpTrigger 创建一个方法。
    它还支持使用 API 密钥,上次不支持。
    (只需将authLevel 更改为AuthorizationLevel.FUNCTION!)

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

    HTTP 触发器设置(摘要)

    函数名 HTTP方法 终点 审批级别 过程
    fetchTodoItems 得到 /api/todo/list 功能 获取列表
    创建待办事项 邮政 /api/todo/create 功能 登记
    更新待办事项 /api/todo/{id}/update 功能 更新
    删除待办事项 删除 /api/todo/{id}/delete 功能 删除

    添加了将请求正文 (JSON) 转换为对象的反序列化过程

    上次我们实现了对象→JSON转换的序列化处理,但这次我们将实现JSON→对象转换的反序列化处理。

    J孙宇智l.爪哇
    /**
     * 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);
        }
      }
      // ↑追加↑
    }
    

    更改数据操作逻辑

    更改上次生成虚拟数据的部分以从数据库中获取它。
    此外,添加这次添加的注册、更新和删除逻辑。

    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
    这将在运行时读取配置并设置环境变量。

    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测试时未加载.
    因此,在执行测试时需要提前在终端等中设置环境变量。

    mvn clean package
    

    如果用 构建,默认会执行测试,所以如果你想跳过它,指定选项如下

    mvn clean package -DskipTests=true
    

    或者将设置添加到pom.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 设置使用命令

    重击
    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パスワード>
    
    电源外壳
    电源外壳
    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パスワード>
    

    这里,我在应用设置中记下了设置值,密钥库通过使用此服务,还可以集中管理秘密并与多个应用程序共享。
    下面的文章很有帮助。
    [Azure] 参考 Azure Functions 中的 Azure Key Vault 值 - Qiita

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

    最终文件夹结构

    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。
    部署过程和上次一样,我就省略了。

    [部署后]
    Azure FunctionsからMySQLに接続する~Java編~

    操作检查

    检查 API 密钥

    由于这次我们在函数中设置了 API 密钥,所以我们需要将 API 密钥指定为参数。
    提前输入命令进行确认。

    重击
    # 関数アプリのホストを取得
    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') # 「"」を除去
    
    电源外壳
    电源外壳
    # 関数アプリのホストを取得
    $azFunctionHost = (az functionapp show `
            --resource-group <リソースグループ名> `
            --name <関数アプリ名> `
            --query "defaultHostName").Trim('"') # 「"」を除去
    
    # APIキーを取得
    $azFunctionKey = (az functionapp keys list `
            --resource-group <リソースグループ名> `
            --name <関数アプリ名> `
            --query "functionKeys.default").Trim('"') # 「"」を除去
    

    上述命令的输出结果为 JSON,但--query 选项可用于缩小、排序和格式化。
    --query 选项包括JMES 路径您可以为调用的 JSON 指定查询。

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

    API 调用

    code 参数中指定您的 API 密钥。

    列表

    curl -i -X GET https://${azFunctionHost}/api/todo/list?code=${azFunctionKey}
    

    登记

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

    更新

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

    删除

    curl -i -X DELETE  https://${azFunctionHost}/api/todo/1/delete?code=${azFunctionKey}
    

    概括

    • 在本地连接到 MySQL 服务器时允许防火墙中的 IP
    • 数据库连接信息取自环境变量

    现在我们已经完成了简单的后端处理,接下来我们将可以从前端调用 API 并使其感觉像一个简单的无服务器应用程序。

    参考


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308629958.html

相关文章: