将 Java 和 JDBC 与 Azure Database for MySQL 灵活服务器配合使用
本文演示如何创建示例应用程序,使其使用 Java 和 JDBC 在 Azure 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 中,因此你需要自行管理密码的轮换。
先决条件
- 具有活动订阅的 Azure 帐户。
如果没有 Azure 订阅,可在开始前创建一个 Azure 试用帐户。
- Azure CLI。
- 一个受支持的 Java 开发工具包,版本 8。
- Apache Maven 生成工具。
准备工作环境
首先,使用以下命令设置一些环境变量。
export AZ_RESOURCE_GROUP=database-workshop
export AZ_DATABASE_NAME=<YOUR_DATABASE_NAME>
export AZ_LOCATION=<YOUR_AZURE_REGION>
export AZ_MYSQL_AD_NON_ADMIN_USERNAME=demo-non-admin
export AZ_USER_IDENTITY_NAME=<YOUR_USER_ASSIGNED_MANAGED_IDENTITY_NAME>
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_NAME>
:Azure Database for MySQL 灵活服务器实例的名称,在 Azure 中应是唯一的。<YOUR_AZURE_REGION>
:将使用的 Azure 区域。 建议配置一个离居住位置更近的区域。 可以通过输入az account list-locations
查看可用区域的完整列表。<YOUR_USER_ASSIGNED_MANAGED_IDENTITY_NAME>
:用户分配的托管标识服务器的名称,该名称在整个 Azure 中应唯一。
接下来,创建一个资源组:
az group create \
--name $AZ_RESOURCE_GROUP \
--location $AZ_LOCATION \
--output tsv
创建 Azure Database for MySQL 实例
创建 Azure Database for MySQL 灵活服务器实例并设置管理员用户
首先需要创建托管的 Azure Database for MySQL 灵活服务器实例。
注意
可以在快速入门:使用 Azure 门户创建 Azure Database for MySQL 的实例中阅读有关创建 MySQL 服务器的更多详细信息。
如果使用 Azure CLI,请运行以下命令以确保该用户拥有足够的权限:
az login --scope https://microsoftgraph.chinacloudapi.cn/.default
运行以下命令以创建服务器:
az mysql flexible-server create \
--resource-group $AZ_RESOURCE_GROUP \
--name $AZ_DATABASE_NAME \
--location $AZ_LOCATION \
--yes \
--output tsv
运行以下命令以创建所需的用户分配的标识:
az identity create \
--resource-group $AZ_RESOURCE_GROUP \
--name $AZ_USER_IDENTITY_NAME
重要
创建用户分配的标识后,请让至少具有特权角色管理员角色的用户为此用户分配的托管标识授予以下权限:User.Read.All
、GroupMember.Read.All
和 Application.Read.ALL
。 也可为用户分配的托管标识授予目录读取者角色。 有关详细信息,请参阅 Azure Database for MySQL 灵活服务器的 Microsoft Entra 身份验证的权限部分。
运行以下命令,为 Azure Database for MySQL 灵活服务器分配标识以创建 Microsoft Entra 管理员:
az mysql flexible-server identity assign \
--resource-group $AZ_RESOURCE_GROUP \
--server-name $AZ_DATABASE_NAME \
--identity $AZ_USER_IDENTITY_NAME
运行以下命令以设置 Microsoft Entra 管理员用户:
az mysql flexible-server ad-admin create \
--resource-group $AZ_RESOURCE_GROUP \
--server-name $AZ_DATABASE_NAME \
--display-name $CURRENT_USERNAME \
--object-id $CURRENT_USER_OBJECTID \
--identity $AZ_USER_IDENTITY_NAME
重要
设置管理员时,将向具有完全管理员权限的 Azure Database for MySQL 灵活服务器实例添加新用户。 每个 Azure Database for MySQL 灵活服务器实例只能创建一个 Microsoft Entra 管理员,选择另一个实例将覆盖为服务器配置的现有 Microsoft Entra 管理员。
此命令创建一个小型 Azure Database for MySQL 灵活服务器实例,并将 Active Directory 管理员设置为已登录的用户。
创建的 Azure Database for MySQL 灵活服务器实例有一个名为 flexibleserverdb
的空数据库。
为 Azure Database for MySQL 灵活服务器实例配置防火墙规则
Azure Database for MySQL 灵活服务器实例在默认情况下受保护。 它们有不允许任何传入连接的防火墙。
如果你使用的是 Bash,则可以跳过此步骤,因为 flexible-server create
命令已检测到你的本地 IP 地址并在 MySQL 服务器上设置了该地址。
如果要在 Windows 计算机上从适用于 Linux 的 Windows 子系统 (WSL) 连接到 Azure Database for MySQL 灵活服务器实例,需要将 WSL 主机 ID 添加到防火墙。 通过在 WSL 中运行以下命令获取主机的 IP 地址:
sudo cat /etc/resolv.conf
复制 nameserver
一词后面的 IP 地址,然后使用以下命令为 WSL IP 地址设置环境变量:
AZ_WSL_IP_ADDRESS=<the-copied-IP-address>
然后使用以下命令向基于 WSL 的应用开放服务器的防火墙:
az mysql flexible-server firewall-rule create \
--resource-group $AZ_RESOURCE_GROUP \
--name $AZ_DATABASE_NAME \
--start-ip-address $AZ_WSL_IP_ADDRESS \
--end-ip-address $AZ_WSL_IP_ADDRESS \
--rule-name allowiprange \
--output tsv
配置 MySQL 数据库
使用以下命令创建名为 demo
的新数据库:
az mysql flexible-server db create \
--resource-group $AZ_RESOURCE_GROUP \
--database-name demo \
--server-name $AZ_DATABASE_NAME \
--output tsv
创建 MySQL 非管理员用户并授予权限
接下来创建一个非管理员用户,并为该用户授予对 demo
数据库的所有权限。
注意
可以阅读在 Azure Database for MySQL 中创建用户来获取有关创建 MySQL 用户的更详细信息。
创建一个名为 create_ad_user.sql 的 SQL 脚本用于创建非管理员用户。 添加以下内容,在本地保存该脚本:
export AZ_MYSQL_AD_NON_ADMIN_USERID=$(az ad signed-in-user show --query id --output tsv)
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 demo.* TO '$AZ_MYSQL_AD_NON_ADMIN_USERNAME'@'%';
FLUSH privileges;
EOF
然后,使用以下命令运行该 SQL 脚本以创建 Microsoft Entra 非管理员用户:
mysql -h $AZ_DATABASE_NAME.mysql.database.chinacloudapi.cn --user $CURRENT_USERNAME --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 项目,并在其根目录中添加 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_NAME}.mysql.database.chinacloudapi.cn:3306/demo?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}
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_NAME}.mysql.database.chinacloudapi.cn:3306/demo?sslMode=REQUIRED&serverTimezone=UTC&authenticationPlugins=com.azure.identity.extensions.jdbc.mysql.AzureMysqlAuthenticationPlugin
user=${AZ_MYSQL_AD_NON_ADMIN_USERNAME}
EOF
注意
配置属性 url
中追加了 ?serverTimezone=UTC
,以告知 JDBC 驱动程序在连接到数据库时使用 UTC 日期格式(或协调世界时)。 否则,Java 服务器将不使用与数据库相同的日期格式,这会导致错误。
创建 SQL 文件以生成数据库架构
你将使用 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);
编写应用程序代码
连接到数据库
接下来添加 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());
}
/*
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 文件连接到 Azure Database for 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