快速入门:将 Java 和 JDBC 与 Azure Database for MySQL 配合使用

适用于:Azure Database for MySQL - 单一服务器

重要

Azure Database for MySQL 单一服务器即将停用。 强烈建议升级到 Azure Database for MySQL 灵活服务器。 要详细了解如何迁移到 Azure Database for MySQL 灵活服务器,请参阅 Azure Database for MySQL 单一服务器发生了什么情况?

本文演示如何创建一个使用 Java 和 JDBCAzure Database for MySQL 中存储和检索信息的示例应用程序。

JDBC 是标准的 Java API,用于连接到传统的关系数据库。

在本文中,我们将讨论两种身份验证方法:Microsoft Entra 身份验证和 MySQL 身份验证。 “无密码”选项卡可显示 Microsoft Entra 身份验证,“密码”选项卡则显示 MySQL 身份验证。

Microsoft Entra 身份验证是一种使用 Microsoft Entra ID 中定义的标识连接到 Azure Database for MySQL 的机制。 使用 Microsoft Entra 身份验证,可以在一个中心位置管理数据库用户标识和其他 Azure 服务,从而简化权限管理。

MySQL 身份验证使用存储在 MySQL 中的帐户。 如果你选择使用密码作为帐户的凭据,这些凭据将存储在 user 表中。 由于这些密码存储在 MySQL 中,因此你需要自行管理密码的轮换。

先决条件

准备工作环境

首先设置一些环境变量。

export AZ_RESOURCE_GROUP=database-workshop
export AZ_DATABASE_SERVER_NAME=<YOUR_DATABASE_SERVER_NAME>
export AZ_DATABASE_NAME=demo
export AZ_LOCATION=<YOUR_AZURE_REGION>
export AZ_MYSQL_AD_NON_ADMIN_USERNAME=demo-non-admin
export AZ_LOCAL_IP_ADDRESS=<YOUR_LOCAL_IP_ADDRESS>
export CURRENT_USERNAME=$(az ad signed-in-user show --query userPrincipalName -o tsv)
export CURRENT_USER_OBJECTID=$(az ad signed-in-user show --query id -o tsv)

使用以下值替换占位符,在本文中将使用这些值:

  • <YOUR_DATABASE_SERVER_NAME>:MySQL 服务器的名称,该名称应在整个 Azure 中独一无二。
  • <YOUR_AZURE_REGION>:将使用的 Azure 区域。 建议配置一个离居住位置更近的区域。 可以通过输入 az account list-locations 查看可用区域的完整列表。
  • <YOUR_LOCAL_IP_ADDRESS>:你将在其中运行应用程序的本地计算机的 IP 地址。 一种查看 IP 地址的便捷方法是打开 whatismyip.akamai.com

接下来,请使用以下命令创建资源组:

az group create \
    --name $AZ_RESOURCE_GROUP \
    --location $AZ_LOCATION \
    --output tsv

创建 Azure Database for MySQL 实例

创建 MySQL 服务器并设置管理员用户

首先创建一个托管 MySQL 服务器。

注意

可以参阅快速入门:使用 Azure 门户创建 Azure Database for MySQL 服务器来详细了解如何创建 MySQL 服务器。

如果使用 Azure CLI,请运行以下命令以确保该用户拥有足够的权限:

az login --scope https://microsoftgraph.chinacloudapi.cn/.default

然后,运行以下命令来创建服务器:

az mysql server create \
    --resource-group $AZ_RESOURCE_GROUP \
    --name $AZ_DATABASE_SERVER_NAME \
    --location $AZ_LOCATION \
    --sku-name B_Gen5_1 \
    --storage-size 5120 \
    --output tsv

接下来,运行以下命令以设置 Microsoft Entra 管理员用户:

az mysql server ad-admin create \
    --resource-group $AZ_RESOURCE_GROUP \
    --server-name $AZ_DATABASE_SERVER_NAME \
    --display-name $CURRENT_USERNAME \
    --object-id $CURRENT_USER_OBJECTID

重要

设置管理员时,将向具有完全管理员权限的 Azure Database for MySQL 服务器添加新用户。 对于每个 MySQL 服务器,只能创建一个 Microsoft Entra 管理员。 如果选择另一个用户,将覆盖针对该服务器配置的现有 Microsoft Entra 管理员。

此命令创建一个小型 MySQL 服务器,并将 Active Directory 管理员设置为已登录的用户。

为 MySQL 服务器配置防火墙规则

Azure Databases for MySQL 实例在默认情况下受保护。 这些实例具有一个不允许任何传入连接的防火墙。 为了能够使用数据库,需要添加一项防火墙规则,允许本地 IP 地址访问数据库服务器。

由于我们已在本文开头配置了本地 IP 地址,因此你可通过运行以下命令来打开服务器的防火墙:

az mysql server firewall-rule create \
    --resource-group $AZ_RESOURCE_GROUP \
    --name $AZ_DATABASE_SERVER_NAME-database-allow-local-ip \
    --server $AZ_DATABASE_SERVER_NAME \
    --start-ip-address $AZ_LOCAL_IP_ADDRESS \
    --end-ip-address $AZ_LOCAL_IP_ADDRESS \
    --output tsv

如果要在 Windows 计算机上从适用于 Linux 的 Windows 子系统 (WSL) 连接到 MySQL 服务器,需要将 WSL 主机 ID 添加到防火墙。

通过在 WSL 中运行以下命令获取主机的 IP 地址:

cat /etc/resolv.conf

复制 nameserver 一词后面的 IP 地址,然后使用以下命令为 WSL IP 地址设置环境变量:

AZ_WSL_IP_ADDRESS=<the-copied-IP-address>

然后使用以下命令向基于 WSL 的应用开放服务器的防火墙:

az mysql server firewall-rule create \
    --resource-group $AZ_RESOURCE_GROUP \
    --name $AZ_DATABASE_SERVER_NAME-database-allow-local-ip-wsl \
    --server $AZ_DATABASE_SERVER_NAME \
    --start-ip-address $AZ_WSL_IP_ADDRESS \
    --end-ip-address $AZ_WSL_IP_ADDRESS \
    --output tsv

配置 MySQL 数据库

之前创建的 MySQL 服务器为空。 使用以下命令创建新数据库。

az mysql db create \
    --resource-group $AZ_RESOURCE_GROUP \
    --name $AZ_DATABASE_NAME \
    --server-name $AZ_DATABASE_SERVER_NAME \
    --output tsv

创建 MySQL 非管理员用户并授予权限

接下来创建一个非管理员用户,并为其授予对数据库的所有权限。

注意

可以阅读在 Azure Database for MySQL 中创建用户来获取有关创建 MySQL 用户的更详细信息。

创建一个名为 create_ad_user.sql 的 SQL 脚本用于创建非管理员用户。 添加以下内容,在本地保存该脚本:

export AZ_MYSQL_AD_NON_ADMIN_USERID=$CURRENT_USER_OBJECTID

cat << EOF > create_ad_user.sql
SET aad_auth_validate_oids_in_tenant = OFF;

CREATE AADUSER '$AZ_MYSQL_AD_NON_ADMIN_USERNAME' IDENTIFIED BY '$AZ_MYSQL_AD_NON_ADMIN_USERID';

GRANT ALL PRIVILEGES ON $AZ_DATABASE_NAME.* TO '$AZ_MYSQL_AD_NON_ADMIN_USERNAME'@'%';

FLUSH privileges;

EOF

然后,使用以下命令运行该 SQL 脚本以创建 Microsoft Entra 非管理员用户:

mysql -h $AZ_DATABASE_SERVER_NAME.mysql.database.chinacloudapi.cn --user $CURRENT_USERNAME@$AZ_DATABASE_SERVER_NAME --enable-cleartext-plugin --password=$(az account get-access-token --resource-type oss-rdbms --output tsv --query accessToken) < create_ad_user.sql

现在使用以下命令删除临时 SQL 脚本文件:

rm create_ad_user.sql

新建一个 Java 项目

使用你偏好的 IDE 在 Java 8 或更高版本中创建新的 Java 项目。 在此项目的根目录中创建 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>

    <properties>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
        <dependency>
            <groupId>com.azure</groupId>
            <artifactId>azure-identity-extensions</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>
</project>

此文件是一个 Apache Maven 文件,用于将项目配置为使用 Java 8 以及适用于 Java 的最新 MySQL 驱动程序。

准备用于连接到 Azure Database for MySQL 的配置文件

在项目根目录中运行以下脚本以创建 src/main/resources/database.properties 文件,并添加配置详细信息:

mkdir -p src/main/resources && touch src/main/resources/database.properties

cat << EOF > src/main/resources/database.properties
url=jdbc:mysql://${AZ_DATABASE_SERVER_NAME}.mysql.database.chinacloudapi.cn:3306/${AZ_DATABASE_NAME}?sslMode=REQUIRED&serverTimezone=UTC&defaultAuthenticationPlugin=com.azure.identity.extensions.jdbc.mysql.AzureMysqlAuthenticationPlugin&authenticationPlugins=com.azure.identity.extensions.jdbc.mysql.AzureMysqlAuthenticationPlugin
user=${AZ_MYSQL_AD_NON_ADMIN_USERNAME}@${AZ_DATABASE_SERVER_NAME}
EOF

注意

如果使用 MysqlConnectionPoolDataSource 类作为应用程序中的数据源,请移除 URL 中的“defaultAuthenticationPlugin=com.azure.identity.extensions.jdbc.mysql.AzureMysqlAuthenticationPlugin”。

mkdir -p src/main/resources && touch src/main/resources/database.properties

cat << EOF > src/main/resources/database.properties
url=jdbc:mysql://${AZ_DATABASE_SERVER_NAME}.mysql.database.chinacloudapi.cn:3306/${AZ_DATABASE_NAME}?sslMode=REQUIRED&serverTimezone=UTC&authenticationPlugins=com.azure.identity.extensions.jdbc.mysql.AzureMysqlAuthenticationPlugin
user=${AZ_MYSQL_AD_NON_ADMIN_USERNAME}@${AZ_DATABASE_SERVER_NAME}
EOF

注意

配置属性 url 中追加了 ?serverTimezone=UTC,以告知 JDBC 驱动程序在连接到数据库时使用 UTC 日期格式(或协调世界时)。 否则,Java 服务器将不使用与数据库相同的日期格式,这会导致错误。

创建 SQL 文件以生成数据库架构

接下来,使用 src/main/resources/schema.sql 文件创建数据库架构。 创建该文件,然后添加以下内容:

touch src/main/resources/schema.sql

cat << EOF > src/main/resources/schema.sql
DROP TABLE IF EXISTS todo;
CREATE TABLE todo (id SERIAL PRIMARY KEY, description VARCHAR(255), details VARCHAR(4096), done BOOLEAN);
EOF

编写应用程序代码

连接到数据库

接下来添加 Java 代码,以便使用 JDBC 在 MySQL 服务器中存储并检索数据。

创建 src/main/java/DemoApplication.java 文件并添加以下内容:

package com.example.demo;

import com.mysql.cj.jdbc.AbandonedConnectionCleanupThread;

import java.sql.*;
import java.util.*;
import java.util.logging.Logger;

public class DemoApplication {

    private static final Logger log;

    static {
        System.setProperty("java.util.logging.SimpleFormatter.format", "[%4$-7s] %5$s %n");
        log =Logger.getLogger(DemoApplication.class.getName());
    }

    public static void main(String[] args) throws Exception {
        log.info("Loading application properties");
        Properties properties = new Properties();
        properties.load(DemoApplication.class.getClassLoader().getResourceAsStream("database.properties"));

        log.info("Connecting to the database");
        Connection connection = DriverManager.getConnection(properties.getProperty("url"), properties);
        log.info("Database connection test: " + connection.getCatalog());

        log.info("Create database schema");
        Scanner scanner = new Scanner(DemoApplication.class.getClassLoader().getResourceAsStream("schema.sql"));
        Statement statement = connection.createStatement();
        while (scanner.hasNextLine()) {
            statement.execute(scanner.nextLine());
        }

        /* Prepare to store and retrieve data from the MySQL server.
        Todo todo = new Todo(1L, "configuration", "congratulations, you have set up JDBC correctly!", true);
        insertData(todo, connection);
        todo = readData(connection);
        todo.setDetails("congratulations, you have updated data!");
        updateData(todo, connection);
        deleteData(todo, connection);
        */

        log.info("Closing database connection");
        connection.close();
        AbandonedConnectionCleanupThread.uncheckedShutdown();
    }
}

此 Java 代码将使用前面创建的 database.properties 和 schema.sql 文件。 连接到 MySQL 服务器后,可以创建一个架构来存储数据。

在此文件中,可以看到我们注释了用于插入、读取、更新和删除数据的方法。 在本文的余下部分,你将实现这些方法,并可以逐个取消注释这些方法。

注意

数据库凭据存储在“database.properties”文件的“user”和“password”属性中。 执行 DriverManager.getConnection(properties.getProperty("url"), properties); 时使用这些凭据,因为属性文件是作为参数传递的。

注意

末尾的 AbandonedConnectionCleanupThread.uncheckedShutdown(); 行是 MySQL 驱动程序命令,用于在关闭应用程序时销毁内部线程。 可以放心忽略此行。

现在可以通过喜欢的工具执行此主类:

  • 使用你的 IDE,你应该能够右键单击 DemoApplication 类并执行它。
  • 在 Maven 中,可以使用以下命令运行应用程序:mvn exec:java -Dexec.mainClass="com.example.demo.DemoApplication"

应用程序应连接到 Azure Database for MySQL,创建数据库架构,然后关闭连接。 在控制台日志中应会看到类似于以下示例的输出:

[INFO   ] Loading application properties
[INFO   ] Connecting to the database
[INFO   ] Database connection test: demo
[INFO   ] Create database schema
[INFO   ] Closing database connection

创建域类

DemoApplication 类旁创建新的 Todo Java 类并添加以下代码:

package com.example.demo;

public class Todo {

    private Long id;
    private String description;
    private String details;
    private boolean done;

    public Todo() {
    }

    public Todo(Long id, String description, String details, boolean done) {
        this.id = id;
        this.description = description;
        this.details = details;
        this.done = done;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getDetails() {
        return details;
    }

    public void setDetails(String details) {
        this.details = details;
    }

    public boolean isDone() {
        return done;
    }

    public void setDone(boolean done) {
        this.done = done;
    }

    @Override
    public String toString() {
        return "Todo{" +
                "id=" + id +
                ", description='" + description + '\'' +
                ", details='" + details + '\'' +
                ", done=" + done +
                '}';
    }
}

此类是在执行 schema .sql 脚本时创建的 todo 表上映射的域模型。

将数据插入到 Azure Database for MySQL

在 src/main/java/DemoApplication.java 文件中,在 main 方法之后添加以下方法,以将数据插入数据库:

private static void insertData(Todo todo, Connection connection) throws SQLException {
    log.info("Insert data");
    PreparedStatement insertStatement = connection
            .prepareStatement("INSERT INTO todo (id, description, details, done) VALUES (?, ?, ?, ?);");

    insertStatement.setLong(1, todo.getId());
    insertStatement.setString(2, todo.getDescription());
    insertStatement.setString(3, todo.getDetails());
    insertStatement.setBoolean(4, todo.isDone());
    insertStatement.executeUpdate();
}

你现在可以对 main 方法中的以下两行执行取消注释操作:

Todo todo = new Todo(1L, "configuration", "congratulations, you have set up JDBC correctly!", true);
insertData(todo, connection);

现在,执行主类应会生成以下输出:

[INFO   ] Loading application properties
[INFO   ] Connecting to the database
[INFO   ] Database connection test: demo
[INFO   ] Create database schema
[INFO   ] Insert data
[INFO   ] Closing database connection

从 Azure Database for MySQL 读取数据

接下来,读取先前插入的数据以验证代码是否正常工作。

在 src/main/java/DemoApplication.java 文件中,在 insertData 方法之后添加以下方法,以从数据库中读取数据:

private static Todo readData(Connection connection) throws SQLException {
    log.info("Read data");
    PreparedStatement readStatement = connection.prepareStatement("SELECT * FROM todo;");
    ResultSet resultSet = readStatement.executeQuery();
    if (!resultSet.next()) {
        log.info("There is no data in the database!");
        return null;
    }
    Todo todo = new Todo();
    todo.setId(resultSet.getLong("id"));
    todo.setDescription(resultSet.getString("description"));
    todo.setDetails(resultSet.getString("details"));
    todo.setDone(resultSet.getBoolean("done"));
    log.info("Data read from the database: " + todo.toString());
    return todo;
}

你现在可以对 main 方法中的以下行执行取消注释操作:

todo = readData(connection);

现在,执行主类应会生成以下输出:

[INFO   ] Loading application properties
[INFO   ] Connecting to the database
[INFO   ] Database connection test: demo
[INFO   ] Create database schema
[INFO   ] Insert data
[INFO   ] Read data
[INFO   ] Data read from the database: Todo{id=1, description='configuration', details='congratulations, you have set up JDBC correctly!', done=true}
[INFO   ] Closing database connection

更新 Azure Database for MySQL 中的数据

接下来,更新先前插入的数据。

在 src/main/java/DemoApplication.java 文件中,在 readData 方法之后添加以下方法,以更新数据库中的数据:

private static void updateData(Todo todo, Connection connection) throws SQLException {
    log.info("Update data");
    PreparedStatement updateStatement = connection
            .prepareStatement("UPDATE todo SET description = ?, details = ?, done = ? WHERE id = ?;");

    updateStatement.setString(1, todo.getDescription());
    updateStatement.setString(2, todo.getDetails());
    updateStatement.setBoolean(3, todo.isDone());
    updateStatement.setLong(4, todo.getId());
    updateStatement.executeUpdate();
    readData(connection);
}

你现在可以对 main 方法中的以下两行执行取消注释操作:

todo.setDetails("congratulations, you have updated data!");
updateData(todo, connection);

现在,执行主类应会生成以下输出:

[INFO   ] Loading application properties
[INFO   ] Connecting to the database
[INFO   ] Database connection test: demo
[INFO   ] Create database schema
[INFO   ] Insert data
[INFO   ] Read data
[INFO   ] Data read from the database: Todo{id=1, description='configuration', details='congratulations, you have set up JDBC correctly!', done=true}
[INFO   ] Update data
[INFO   ] Read data
[INFO   ] Data read from the database: Todo{id=1, description='configuration', details='congratulations, you have updated data!', done=true}
[INFO   ] Closing database connection

删除 Azure Database for MySQL 中的数据

最后,删除之前插入的数据。

在 src/main/java/DemoApplication.java 文件中,在 updateData 方法之后添加以下方法,以删除数据库中的数据:

private static void deleteData(Todo todo, Connection connection) throws SQLException {
    log.info("Delete data");
    PreparedStatement deleteStatement = connection.prepareStatement("DELETE FROM todo WHERE id = ?;");
    deleteStatement.setLong(1, todo.getId());
    deleteStatement.executeUpdate();
    readData(connection);
}

你现在可以对 main 方法中的以下行执行取消注释操作:

deleteData(todo, connection);

现在,执行主类应会生成以下输出:

[INFO   ] Loading application properties
[INFO   ] Connecting to the database
[INFO   ] Database connection test: demo
[INFO   ] Create database schema
[INFO   ] Insert data
[INFO   ] Read data
[INFO   ] Data read from the database: Todo{id=1, description='configuration', details='congratulations, you have set up JDBC correctly!', done=true}
[INFO   ] Update data
[INFO   ] Read data
[INFO   ] Data read from the database: Todo{id=1, description='configuration', details='congratulations, you have updated data!', done=true}
[INFO   ] Delete data
[INFO   ] Read data
[INFO   ] There is no data in the database!
[INFO   ] Closing database connection

清理资源

祝贺你! 你已创建了一个 Java 应用程序,该应用程序使用 JDBC 在 Azure Database for MySQL 中存储和检索数据。

若要清理本快速入门中使用的所有资源,请使用以下命令删除该资源组:

az group delete \
    --name $AZ_RESOURCE_GROUP \
    --yes

后续步骤