Java连接MySQL数据库:MySQL Connector/J详解 – wiki基地


Java 连接 MySQL 数据库:MySQL Connector/J 详解

在现代企业级应用中,Java 与关系型数据库的交互是核心功能之一。MySQL 作为世界上最流行的开源关系型数据库,与 Java 的结合尤为普遍。而要实现 Java 程序与 MySQL 数据库的通信,我们就需要用到 JDBC(Java Database Connectivity)API,以及针对 MySQL 的具体实现——MySQL Connector/J

本文将深入探讨如何使用 MySQL Connector/J 在 Java 应用程序中连接和操作 MySQL 数据库。

1. 什么是 JDBC 和 MySQL Connector/J?

1.1 JDBC (Java Database Connectivity)

JDBC 是 Java 语言中用于访问数据库的标准 API。它提供了一组接口和类,允许 Java 应用程序以统一的方式连接各种关系型数据库,执行 SQL 查询,并处理结果。JDBC 扮演了一个中间层的角色,使得开发者无需关心底层数据库的具体实现细节。

JDBC 驱动程序根据实现方式通常分为四种类型,MySQL Connector/J 属于 Type 4 驱动(纯 Java 驱动)。这意味着它完全由 Java 编写,并直接通过数据库特定的协议与 MySQL 服务器进行通信,不需要额外的本地库。

1.2 MySQL Connector/J

MySQL Connector/J 是 MySQL 官方提供的 JDBC 驱动程序。它是 Java 应用程序与 MySQL 数据库之间进行通信的桥梁。通过 Connector/J,Java 程序可以:
* 建立与 MySQL 数据库的连接。
* 发送 SQL 语句(包括查询、插入、更新、删除)。
* 处理从数据库返回的结果集。
* 管理事务等。

2. 前置条件

在开始之前,请确保您的开发环境中已安装以下软件:

  • Java Development Kit (JDK): 建议使用 JDK 8 或更高版本。
  • MySQL 服务器: 一个正在运行的 MySQL 数据库实例。
  • MySQL Connector/J JAR 文件: 这是您 Java 项目中需要包含的 JDBC 驱动。

3. 项目设置

要让您的 Java 应用程序能够使用 MySQL Connector/J,需要将其 JAR 文件添加到项目的类路径中。

3.1 下载 MySQL Connector/J

您可以从 MySQL 官方网站的下载页面获取最新版本的 MySQL Connector/J。通常,您会找到一个“Platform Independent”(平台无关)的选项,下载后会得到一个包含 mysql-connector-java-X.Y.Z.jar 文件的 ZIP 压缩包(其中 X.Y.Z 是版本号,例如 8.0.33)。

3.2 添加 Connector/J 到项目类路径

a. Maven 项目
如果您使用 Maven 进行项目管理,只需在 pom.xml 文件的 <dependencies> 部分添加以下依赖项。请将 8.0.33 替换为最新稳定版本。

xml
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version> <!-- 使用最新版本 -->
</dependency>

添加后,运行 mvn clean install 命令以下载依赖。

b. Gradle 项目
对于 Gradle 项目,在 build.gradle 文件的 dependencies 块中添加如下内容:

gradle
dependencies {
implementation 'mysql:mysql-connector-java:8.0.33' // 使用最新版本
}

然后运行 gradle buildgradle clean build

c. 手动设置 (不使用 Maven/Gradle)
1. 下载 mysql-connector-java-X.Y.Z.jar 文件(例如 mysql-connector-java-8.0.33.jar)。
2. 在您的项目根目录中创建一个 lib 文件夹。
3. 将下载的 JAR 文件复制到 lib 文件夹中。
4. 在您的集成开发环境(IDE,如 Eclipse 或 IntelliJ IDEA)中,将此 JAR 文件添加到项目的构建路径(Build Path)或作为库(Library)引入。

3.3 数据库准备

为了演示,我们先创建一个简单的数据库和 users 表。您可以登录 MySQL 客户端执行以下 SQL 命令:

“`sql
CREATE DATABASE jdbcdemo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE jdbcdemo;

CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL
);
“`

4. 核心 JDBC 概念

理解以下 JDBC 接口和类对于使用 MySQL Connector/J 至关重要:

  • java.sql.Driver: 所有 JDBC 驱动程序都必须实现的接口。它负责建立与特定数据库的连接。通常您不需要直接与 Driver 交互,而是通过 DriverManager
  • java.sql.DriverManager: 这是一个管理 JDBC 驱动程序的类。它是获取数据库连接的主要方式。
  • java.sql.Connection: 表示与数据库建立的活动连接。所有数据库通信都通过此对象进行。
  • java.sql.Statement: 用于执行不带参数的静态 SQL 语句。
  • java.sql.PreparedStatement: Statement 的子接口,用于执行预编译的带参数的 SQL 语句。它提供了更好的性能和防止 SQL 注入的安全性。
  • java.sql.ResultSet: 表示数据库查询(通常是 SELECT 语句)的结果集。它提供了遍历行和检索列值的方法。
  • java.sql.SQLException: JDBC 操作可能抛出的主要异常类,用于表示数据库访问错误。

5. 连接到 MySQL 数据库

要连接到数据库,您需要 JDBC URL、用户名和密码。MySQL 的 JDBC URL 格式通常为 jdbc:mysql://<host>:<port>/<database_name>[?properties]

下面是一个建立数据库连接的 Java 示例:

“`java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DatabaseConnection {

// JDBC URL,根据您的 MySQL 配置进行修改
private static final String JDBC_URL = "jdbc:mysql://localhost:3306/jdbcdemo?useSSL=false&serverTimezone=UTC";
private static final String USER = "root"; // 您的 MySQL 用户名
private static final String PASSWORD = "your_mysql_password"; // 您的 MySQL 密码

/**
 * 获取数据库连接。
 * @return 数据库连接对象
 * @throws SQLException 如果连接失败
 */
public static Connection getConnection() throws SQLException {
    // 对于现代 JDBC 驱动(Java 6+),通常不需要显式加载驱动。
    // DriverManager 会自动发现驱动。但如果遇到ClassNotFoundException,可以考虑取消注释此行。
    // try {
    //     Class.forName("com.mysql.cj.jdbc.Driver");
    // } catch (ClassNotFoundException e) {
    //     System.err.println("MySQL JDBC Driver not found.");
    //     throw new SQLException("MySQL JDBC Driver not found.", e);
    // }
    return DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
}

public static void main(String[] args) {
    try (Connection connection = getConnection()) {
        if (connection != null) {
            System.out.println("成功连接到数据库!");
        } else {
            System.out.println("未能连接到数据库!");
        }
    } catch (SQLException e) {
        System.err.println("SQL State: " + e.getSQLState());
        System.err.println("Error Code: " + e.getErrorCode());
        System.err.println("Message: " + e.getMessage());
        e.printStackTrace();
    }
}

}
``
**注意**: JDBC URL 中添加了
?useSSL=false&serverTimezone=UTC参数。useSSL=false用于禁用 SSL 连接(在本地开发环境中通常不需要),serverTimezone=UTC` 用于解决 MySQL 8.0+ 版本中可能出现的时区问题。在生产环境中,强烈建议配置 SSL 连接以确保数据安全。

6. CRUD 操作 (创建、读取、更新、删除)

我们将主要使用 PreparedStatement 进行 CRUD 操作,因为它是安全性和性能的最佳实践。

6.1 插入数据 (Create)

“`java
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class InsertData {

public static void insertUser(String username, String email) {
    String SQL_INSERT = "INSERT INTO users (username, email) VALUES (?, ?)";

    try (Connection connection = DatabaseConnection.getConnection();
         PreparedStatement preparedStatement = connection.prepareStatement(SQL_INSERT)) {

        preparedStatement.setString(1, username); // 设置第一个问号参数为用户名
        preparedStatement.setString(2, email);    // 设置第二个问号参数为邮箱

        int rowsAffected = preparedStatement.executeUpdate(); // 执行更新操作
        System.out.println(rowsAffected + " 行数据已插入。");
    } catch (SQLException e) {
        System.err.println("插入数据时出错: " + e.getMessage());
        e.printStackTrace();
    }
}

public static void main(String[] args) {
    insertUser("john_doe", "[email protected]");
    insertUser("jane_smith", "[email protected]");
    insertUser("peter_jones", "[email protected]");
}

}
“`

6.2 查询数据 (Read)

“`java
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class SelectData {

public static void selectAllUsers() {
    String SQL_SELECT = "SELECT id, username, email FROM users";

    try (Connection connection = DatabaseConnection.getConnection();
         PreparedStatement preparedStatement = connection.prepareStatement(SQL_SELECT);
         ResultSet resultSet = preparedStatement.executeQuery()) { // 执行查询操作

        System.out.println("--- 所有用户 ---");
        while (resultSet.next()) { // 遍历结果集中的每一行
            int id = resultSet.getInt("id");             // 根据列名获取 int 类型数据
            String username = resultSet.getString("username"); // 根据列名获取 String 类型数据
            String email = resultSet.getString("email");
            System.out.println("ID: " + id + ", 用户名: " + username + ", 邮箱: " + email);
        }
    } catch (SQLException e) {
        System.err.println("查询数据时出错: " + e.getMessage());
        e.printStackTrace();
    }
}

public static void selectUserById(int userId) {
    String SQL_SELECT_BY_ID = "SELECT id, username, email FROM users WHERE id = ?";

    try (Connection connection = DatabaseConnection.getConnection();
         PreparedStatement preparedStatement = connection.prepareStatement(SQL_SELECT_BY_ID)) {

        preparedStatement.setInt(1, userId); // 设置问号参数为用户ID

        try (ResultSet resultSet = preparedStatement.executeQuery()) {
            System.out.println("\n--- ID 为 " + userId + " 的用户 ---");
            if (resultSet.next()) { // 如果找到结果
                int id = resultSet.getInt("id");
                String username = resultSet.getString("username");
                String email = resultSet.getString("email");
                System.out.println("ID: " + id + ", 用户名: " + username + ", 邮箱: " + email);
            } else {
                System.out.println("未找到 ID 为 " + userId + " 的用户。");
            }
        }
    } catch (SQLException e) {
        System.err.println("查询数据时出错: " + e.getMessage());
        e.printStackTrace();
    }
}

public static void main(String[] args) {
    selectAllUsers();
    selectUserById(1);
    selectUserById(99); // 查询不存在的ID
}

}
“`

6.3 更新数据 (Update)

“`java
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class UpdateData {

public static void updateUserEmail(int userId, String newEmail) {
    String SQL_UPDATE = "UPDATE users SET email = ? WHERE id = ?";

    try (Connection connection = DatabaseConnection.getConnection();
         PreparedStatement preparedStatement = connection.prepareStatement(SQL_UPDATE)) {

        preparedStatement.setString(1, newEmail); // 设置新邮箱
        preparedStatement.setInt(2, userId);    // 设置用户ID

        int rowsAffected = preparedStatement.executeUpdate();
        System.out.println(rowsAffected + " 行数据已更新。");
    } catch (SQLException e) {
        System.err.println("更新数据时出错: " + e.getMessage());
        e.printStackTrace();
    }
}

public static void main(String[] args) {
    updateUserEmail(1, "[email protected]");
    SelectData.selectAllUsers(); // 验证更新
}

}
“`

6.4 删除数据 (Delete)

“`java
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class DeleteData {

public static void deleteUser(int userId) {
    String SQL_DELETE = "DELETE FROM users WHERE id = ?";

    try (Connection connection = DatabaseConnection.getConnection();
         PreparedStatement preparedStatement = connection.prepareStatement(SQL_DELETE)) {

        preparedStatement.setInt(1, userId); // 设置要删除的用户ID

        int rowsAffected = preparedStatement.executeUpdate();
        System.out.println(rowsAffected + " 行数据已删除。");
    } catch (SQLException e) {
        System.err.println("删除数据时出错: " + e.getMessage());
        e.printStackTrace();
    }
}

public static void main(String[] args) {
    deleteUser(3); // 删除 ID 为 3 的用户 (peter_jones)
    SelectData.selectAllUsers(); // 验证删除
}

}
“`

7. 事务管理

事务确保一系列 SQL 语句作为一个单一的原子操作执行。要么所有语句都成功并提交(commit),要么任何一个失败,所有更改都会回滚(rollback)。默认情况下,JDBC 连接处于自动提交模式,这意味着每条 SQL 语句都被视为一个独立的事务。要手动管理事务,您需要禁用自动提交。

“`java
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class TransactionManagement {

/**
 * 模拟转账操作,演示事务管理。
 * 假设存在一个 'accounts' 表,包含 'user_id' 和 'balance' 列。
 */
public static void transferFunds(int fromUserId, int toUserId, double amount) {
    String SQL_UPDATE_BALANCE = "UPDATE accounts SET balance = balance + ? WHERE user_id = ?";

    Connection connection = null;
    try {
        connection = DatabaseConnection.getConnection();
        connection.setAutoCommit(false); // 禁用自动提交,开始事务

        // 从发送方扣款
        try (PreparedStatement deductStmt = connection.prepareStatement(SQL_UPDATE_BALANCE)) {
            deductStmt.setDouble(1, -amount);
            deductStmt.setInt(2, fromUserId);
            int rowsAffected = deductStmt.executeUpdate();
            if (rowsAffected == 0) {
                throw new SQLException("发送方不存在或余额不足。");
            }
        }

        // // 可以通过取消注释以下行来模拟错误,以测试回滚
        // if (true) throw new SQLException("模拟转账过程中的错误。");

        // 向接收方加款
        try (PreparedStatement addStmt = connection.prepareStatement(SQL_UPDATE_BALANCE)) {
            addStmt.setDouble(1, amount);
            addStmt.setInt(2, toUserId);
            int rowsAffected = addStmt.executeUpdate();
            if (rowsAffected == 0) {
                throw new SQLException("接收方不存在。");
            }
        }

        connection.commit(); // 所有操作成功,提交事务
        System.out.println("资金转账成功!");

    } catch (SQLException e) {
        System.err.println("事务失败: " + e.getMessage());
        if (connection != null) {
            try {
                connection.rollback(); // 发生错误,回滚事务
                System.out.println("事务已回滚。");
            } catch (SQLException rollbackEx) {
                System.err.println("回滚时出错: " + rollbackEx.getMessage());
            }
        }
        e.printStackTrace();
    } finally {
        if (connection != null) {
            try {
                connection.setAutoCommit(true); // 重新启用自动提交
                connection.close(); // 关闭连接
            } catch (SQLException e) {
                System.err.println("关闭连接时出错: " + e.getMessage());
            }
        }
    }
}

public static void main(String[] args) {
    // 示例用法 (需要 'accounts' 表和一些初始数据)
    // transferFunds(1, 2, 100.00);
}

}
“`

8. 错误处理

JDBC 操作可能抛出 SQLException。正确地处理这些异常至关重要。SQLException 提供了获取错误详细信息的方法:

  • getMessage(): 错误的描述信息。
  • getSQLState(): SQLState 字符串,一个由 X/Open SQL 规范定义的五字符字母数字代码。
  • getErrorCode(): 厂商特定的错误代码。

总是先捕获更具体的异常,然后是更通用的异常。避免捕获泛型 Exception,除非绝对必要。

“`java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLSyntaxErrorException; // 更具体的异常

public class ExceptionHandlingExample {

// 故意使用一个不存在的数据库来演示连接错误
private static final String JDBC_URL = "jdbc:mysql://localhost:3306/non_existent_db?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASSWORD = "your_mysql_password";

public static void main(String[] args) {
    try (Connection connection = DriverManager.getConnection(JDBC_URL, USER, PASSWORD)) {
        System.out.println("成功连接 (此行在此示例中不会执行)。");
    } catch (SQLSyntaxErrorException e) { // 捕获具体的 SQL 语法错误
        System.err.println("SQL 语法错误: " + e.getMessage());
        System.err.println("SQL State: " + e.getSQLState());
        System.err.println("Error Code: " + e.getErrorCode());
    } catch (SQLException e) { // 捕获一般的 SQL 异常
        System.err.println("发生数据库访问错误!");
        System.err.println("Message: " + e.getMessage());
        System.err.println("SQL State: " + e.getSQLState());
        System.err.println("Error Code: " + e.getErrorCode());
        e.printStackTrace();
    } catch (Exception e) { // 捕获其他所有意外异常
        System.err.println("发生意外错误: " + e.getMessage());
        e.printStackTrace();
    }
}

}
“`

9. 关闭资源

为了防止资源泄露(特别是数据库连接和游标),关闭 JDBC 资源(ResultSetStatement/PreparedStatementConnection)至关重要。在现代 Java(Java 7+)中,推荐使用 try-with-resources 语句,它会自动关闭实现 AutoCloseable 接口的资源。

资源通常应该以创建的相反顺序关闭:先 ResultSet,然后 Statement/PreparedStatement,最后 Connection。本文中所有的示例都使用了 try-with-resources 来演示正确的资源管理。

“`java
// try-with-resources 示例 (已在之前的示例中广泛使用)
try (Connection connection = DatabaseConnection.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(SQL_QUERY);
ResultSet resultSet = preparedStatement.executeQuery()) {

// 在这里使用资源

} catch (SQLException e) {
// 处理异常
}
// 即使发生异常,资源也会在此处自动关闭。
“`

10. 完整示例

下面是一个整合了连接、CRUD 操作和资源管理的完整 Java 应用程序示例:

“`java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JdbcDemoApp {

private static final String JDBC_URL = "jdbc:mysql://localhost:3306/jdbcdemo?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASSWORD = "your_mysql_password";

public static Connection getConnection() throws SQLException {
    return DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
}

public static void createUser(String username, String email) {
    String SQL_INSERT = "INSERT INTO users (username, email) VALUES (?, ?)";
    try (Connection connection = getConnection();
         PreparedStatement preparedStatement = connection.prepareStatement(SQL_INSERT)) {
        preparedStatement.setString(1, username);
        preparedStatement.setString(2, email);
        int rowsAffected = preparedStatement.executeUpdate();
        System.out.println("创建用户: " + username + ". 影响行数: " + rowsAffected);
    } catch (SQLException e) {
        System.err.println("创建用户 " + username + " 时出错: " + e.getMessage());
        e.printStackTrace();
    }
}

public static void getAllUsers() {
    String SQL_SELECT = "SELECT id, username, email FROM users";
    System.out.println("\n--- 所有用户 ---");
    try (Connection connection = getConnection();
         PreparedStatement preparedStatement = connection.prepareStatement(SQL_SELECT);
         ResultSet resultSet = preparedStatement.executeQuery()) {
        while (resultSet.next()) {
            int id = resultSet.getInt("id");
            String username = resultSet.getString("username");
            String email = resultSet.getString("email");
            System.out.println("ID: " + id + ", 用户名: " + username + ", 邮箱: " + email);
        }
    } catch (SQLException e) {
        System.err.println("检索用户时出错: " + e.getMessage());
        e.printStackTrace();
    }
}

public static void updateUserName(int id, String newUsername) {
    String SQL_UPDATE = "UPDATE users SET username = ? WHERE id = ?";
    try (Connection connection = getConnection();
         PreparedStatement preparedStatement = connection.prepareStatement(SQL_UPDATE)) {
        preparedStatement.setString(1, newUsername);
        preparedStatement.setInt(2, id);
        int rowsAffected = preparedStatement.executeUpdate();
        System.out.println("更新用户 ID " + id + " 为 " + newUsername + ". 影响行数: " + rowsAffected);
    } catch (SQLException e) {
        System.err.println("更新用户 ID " + id + " 时出错: " + e.getMessage());
        e.printStackTrace();
    }
}

public static void deleteUser(int id) {
    String SQL_DELETE = "DELETE FROM users WHERE id = ?";
    try (Connection connection = getConnection();
         PreparedStatement preparedStatement = connection.prepareStatement(SQL_DELETE)) {
        preparedStatement.setInt(1, id);
        int rowsAffected = preparedStatement.executeUpdate();
        System.out.println("删除用户 ID " + id + ". 影响行数: " + rowsAffected);
    } catch (SQLException e) {
        System.err.println("删除用户 ID " + id + " 时出错: " + e.getMessage());
        e.printStackTrace();
    }
}

public static void main(String[] args) {
    // 1. 创建用户
    createUser("alice", "[email protected]");
    createUser("bob", "[email protected]");

    // 2. 获取所有用户
    getAllUsers();

    // 3. 更新用户
    updateUserName(1, "alice_smith");

    // 4. 再次获取所有用户以查看更新
    getAllUsers();

    // 5. 删除用户
    deleteUser(2);

    // 6. 最后一次获取所有用户
    getAllUsers();
}

}
“`

11. 总结

通过本文的详细介绍,您应该对 Java 如何使用 MySQL Connector/J 连接和操作 MySQL 数据库有了全面的理解。我们涵盖了从环境设置到核心 JDBC 概念、CRUD 操作、事务管理以及错误处理和资源关闭的最佳实践。

掌握这些知识是构建任何基于 Java 的数据库应用程序的基础。在实际开发中,您可能还会遇到连接池、ORM 框架(如 Hibernate、MyBatis)等高级概念,它们在 JDBC 的基础上提供了更高效、更抽象的数据库操作方式。但无论如何,理解底层的 JDBC 和 Connector/J 的工作原理,将帮助您更好地理解和调试复杂的数据库交互问题。


滚动至顶部