MySQL Connector/J 完全教程

Connector/J 是 MySQL 官方提供的 Java JDBC 驱动程序,它允许 Java 应用程序通过标准的 JDBC API 与 MySQL 数据库进行交互。

connector j教程
(图片来源网络,侵删)

目录

  1. 环境准备
    • 安装 Java Development Kit (JDK)
    • 安装 MySQL 服务器
    • 获取 Connector/J 驱动
  2. 第一个连接程序
    • 添加依赖
    • 编写 Java 代码
    • 运行与排错
  3. 核心操作:增删改查 (CRUD)
    • 查询数据
    • 插入数据
    • 更新数据
    • 删除数据
  4. 使用 PreparedStatement 防止 SQL 注入
    • 什么是 SQL 注入
    • PreparedStatement 的优势
    • 代码示例
  5. 处理结果集 (ResultSet)
    • 遍历结果
    • 获取不同类型的数据
  6. 事务管理
    • 什么是事务
    • ACID 特性
    • 代码示例
  7. 连接池 (Connection Pooling)
    • 为什么需要连接池
    • 主流连接池库
    • 使用 HikariCP 示例
  8. 高级配置与最佳实践
    • 配置 URL 参数
    • 资源管理 (关闭 Connection, Statement, ResultSet)
    • 使用 try-with-resources (推荐)
    • 错误处理

环境准备

在开始之前,请确保你的系统上已安装以下软件:

a. 安装 Java Development Kit (JDK)

Connector/J 是一个 Java 库,所以你需要 JDK,推荐使用 JDK 8 或更高版本。

验证安装: 打开终端或命令提示符,输入:

java -version
javac -version

如果显示版本号,则表示安装成功。

connector j教程
(图片来源网络,侵删)

b. 安装 MySQL 服务器

你需要一个 MySQL 数据库来连接,你可以从 MySQL 官网 下载并安装。

验证安装并创建用户/数据库:

  1. 登录到 MySQL:
    mysql -u root -p
  2. 创建一个用于测试的数据库:
    CREATE DATABASE mytestdb;
  3. 创建一个专门的用户并授予权限(这是最佳实践):
    CREATE USER 'javauser'@'localhost' IDENTIFIED BY 'your_strong_password';
    GRANT ALL PRIVILEGES ON mytestdb.* TO 'javauser'@'localhost';
    FLUSH PRIVILEGES;

c. 获取 Connector/J 驱动

你可以通过以下两种方式获取驱动:

手动下载 (适合传统项目) 访问 MySQL Connector/J 下载页面,下载最新的平台独立的 ZIP 压缩包(Platform Independent),解压后,你会得到一个 .jar 文件(mysql-connector-j-8.0.xx.jar),在你的 Java 项目中,需要将此 JAR 文件添加到类路径中。

connector j教程
(图片来源网络,侵删)

使用构建工具 (强烈推荐) 在现代 Java 项目中,我们通常使用 Maven 或 Gradle 来管理依赖。

Maven (pom.xml):pom.xml 文件中添加以下依赖:

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.0.33</version> <!-- 建议使用最新稳定版 -->
</dependency>

Gradle (build.gradle):

implementation 'com.mysql:mysql-connector-j:8.0.33' // 建议使用最新稳定版

构建工具会自动帮你下载并管理依赖。


第一个连接程序

让我们编写一个简单的 Java 程序来连接到我们刚刚创建的 mytestdb 数据库。

a. 添加依赖

如果你使用 Maven 或 Gradle,请确保已添加上述依赖,如果你是手动管理,请将下载的 JAR 文件添加到你的 IDE 项目库中。

b. 编写 Java 代码

创建一个 JdbcExample.java 文件。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class JdbcExample {
    // 数据库连接信息
    private static final String DB_URL = "jdbc:mysql://localhost:3306/mytestdb";
    private static final String USER = "javauser";
    private static final String PASS = "your_strong_password";
    public static void main(String[] args) {
        // try-with-resources 语句,确保连接在完成后自动关闭
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS)) {
            if (conn != null) {
                System.out.println("成功连接到数据库!");
                // 可以在这里执行其他数据库操作
            }
        } catch (SQLException e) {
            System.err.println("连接数据库失败!");
            e.printStackTrace();
        }
    }
}

代码解释:

  1. DB_URL: 连接字符串。
    • jdbc:mysql://: 协议和子协议,表示使用 MySQL 的 JDBC 驱动。
    • localhost:3306: 数据库服务器的地址和端口。
    • /mytestdb: 要连接的数据库名称。
  2. USERPASS: 你之前创建的数据库用户名和密码。
  3. DriverManager.getConnection(): 这是获取数据库连接的核心方法,它会尝试加载所有可用的 JDBC 驱动,并使用提供的 URL、用户名和密码建立连接。
  4. try-with-resources: 这是一个 Java 7 引入的语法糖,任何实现了 AutoCloseable 接口的资源(如 Connection)都可以放在 try 后面的括号中,当 try 块执行完毕(无论是否发生异常),这个资源都会被自动关闭,有效防止资源泄漏。

c. 运行与排错

  • 成功: 如果看到 "成功连接到数据库!",说明一切正常。
  • 失败: 如果出现 SQLException,请检查:
    • MySQL 服务器是否正在运行?
    • 数据库名称 (mytestdb)、用户名 (javauser)、密码是否正确?
    • 防火墙是否阻止了连接?
    • Connector/J 的 JAR 文件是否在类路径中?

核心操作:增删改查 (CRUD)

成功连接后,我们就可以执行 SQL 语句了,执行 SQL 的核心接口是 StatementPreparedStatement

a. 查询数据

使用 Statement.executeQuery() 来执行 SELECT 语句,它会返回一个 ResultSet 对象。

import java.sql.*;
public class SelectExample {
    private static final String DB_URL = "jdbc:mysql://localhost:3306/mytestdb";
    private static final String USER = "javauser";
    private static final String PASS = "your_strong_password";
    public static void main(String[] args) {
        String sql = "SELECT id, name, email FROM users";
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(sql)) {
            System.out.println("用户列表:");
            System.out.println("----------------------------");
            // 遍历结果集
            while (rs.next()) {
                // 通过列名获取数据,更具可读性且不易出错
                int id = rs.getInt("id");
                String name = rs.getString("name");
                String email = rs.getString("email");
                System.out.println("ID: " + id + ", 姓名: " + name + ", 邮箱: " + email);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

ResultSet 方法:

  • rs.next(): 将光标移动到下一行,如果存在下一行,则返回 true,否则返回 false,这是遍历结果集的标准方式。
  • rs.getXxx("column_name"): 根据列名获取指定类型的数据。Xxx 可以是 String, Int, Double, Date 等。
  • rs.getXxx(int columnIndex): 根据列的索引(从 1 开始)获取数据,不推荐使用,因为当 SQL 顺序改变时,代码容易出错。

b. 插入数据

使用 Statement.executeUpdate() 来执行 INSERT, UPDATE, DELETE 语句,它返回一个整数,表示受影响的行数。

import java.sql.*;
public class InsertExample {
    // ... DB_URL, USER, PASS 同上 ...
    public static void main(String[] args) {
        String sql = "INSERT INTO users (name, email) VALUES ('张三', 'zhangsan@example.com')";
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
             Statement stmt = conn.createStatement()) {
            int affectedRows = stmt.executeUpdate(sql);
            if (affectedRows > 0) {
                System.out.println("成功插入 " + affectedRows + " 行数据。");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

c. 更新数据

// ... DB_URL, USER, PASS 同上 ...
String sql = "UPDATE users SET email = 'new.email@example.com' WHERE name = '张三'";
// ... 其余代码与 InsertExample 类似 ...

d. 删除数据

// ... DB_URL, USER, PASS 同上 ...
String sql = "DELETE FROM users WHERE name = '张三'";
// ... 其余代码与 InsertExample 类似 ...

使用 PreparedStatement 防止 SQL 注入

直接拼接 SQL 字符串是非常危险的,会导致 SQL 注入 攻击。

a. 什么是 SQL 注入

假设你的登录逻辑是这样写的:

// 危险的代码!
String username = request.getParameter("username");
String password = request.getParameter("password");
String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";

攻击者可以在用户名输入框中输入:' OR '1'='1,最终执行的 SQL 会变成: SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '...' 这个 WHERE 条件永远为真,攻击者就能轻易登录。

b. PreparedStatement 的优势

PreparedStatement 是预编译的 SQL 语句,它将 SQL 语句和数据分开处理,从而从根本上杜绝了 SQL 注入的风险。

  1. 安全性高: 数据被作为参数传递,数据库驱动会对其进行转义,不会被视为 SQL 代码的一部分。
  2. 性能高: 如果需要多次执行同一条 SQL(只是参数不同),数据库可以重用预编译的计划,提高效率。
  3. 代码更清晰: 使用 作为占位符,代码更整洁。

c. 代码示例

import java.sql.*;
public class PreparedStatementExample {
    // ... DB_URL, USER, PASS 同上 ...
    public static void main(String[] args) {
        // 使用 ? 作为占位符
        String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
             // 创建 PreparedStatement 对象
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            // 设置参数 (index 从 1 开始)
            pstmt.setString(1, "李四");
            pstmt.setString(2, "lisi@example.com");
            // 执行更新
            int affectedRows = pstmt.executeUpdate();
            System.out.println("成功插入 " + affectedRows + " 行数据。");
            // 可以重用同一个 pstmt 来插入另一条数据
            pstmt.setString(1, "王五");
            pstmt.setString(2, "wangwu@example.com");
            affectedRows = pstmt.executeUpdate();
            System.out.println("成功插入 " + affectedRows + " 行数据。");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

对于查询,PreparedStatement 的用法类似:

String sql = "SELECT * FROM users WHERE name = ?";
try (Connection conn = ...;
     PreparedStatement pstmt = conn.prepareStatement(sql)) {
    pstmt.setString(1, "张三");
    try (ResultSet rs = pstmt.executeQuery()) {
        // 处理结果集
    }
}

处理结果集 (ResultSet)

ResultSet 是一个指向 SQL 查询结果集数据的游标,默认情况下,这个游标位于第一行之前。

  • rs.next(): 移动到下一行,这是最常用的方法。
  • rs.previous(): 移动到上一行。
  • rs.first() / rs.last(): 移动到第一行/最后一行。
  • rs.beforeFirst() / rs.afterLast(): 移动到第一行之前/最后一行之后。
  • rs.isFirst() / rs.isLast(): 判断是否在第一行/最后一行。
  • rs.getMetaData(): 获取结果集的元数据(如列名、列类型等),可用于动态处理结果。

事务管理

事务是一组原子性的 SQL 操作,要么全部成功,要么全部失败。

a. 什么是事务

ACID 特性:

  • 原子性: 事务是一个不可分割的工作单位。
  • 一致性: 事务必须使数据库从一个一致性状态变换到另一个一致性状态。
  • 隔离性: 一个事务的执行不能被其他事务干扰。
  • 持久性: 一个事务一旦提交,它对数据库中数据的改变就是永久性的。

b. 代码示例

默认情况下,JDBC 的每个 SQL 语句都在一个单独的事务中自动提交,要管理事务,需要手动关闭自动提交模式。

import java.sql.*;
public class TransactionExample {
    // ... DB_URL, USER, PASS 同上 ...
    public static void main(String[] args) {
        Connection conn = null;
        try {
            conn = DriverManager.getConnection(DB_URL, USER, PASS);
            // 关闭自动提交,开启事务
            conn.setAutoCommit(false);
            Statement stmt = conn.createStatement();
            // 操作1:从账户A转出100元
            stmt.executeUpdate("UPDATE accounts SET balance = balance - 100 WHERE name = 'Alice'");
            // 模拟一个错误
            // if (true) throw new RuntimeException("模拟出错!");
            // 操作2:向账户B转入100元
            stmt.executeUpdate("UPDATE accounts SET balance = balance + 100 WHERE name = 'Bob'");
            // 如果所有操作都成功,则提交事务
            conn.commit();
            System.out.println("事务提交成功!");
        } catch (SQLException e) {
            // 如果发生任何异常,则回滚事务
            try {
                if (conn != null) {
                    conn.rollback();
                    System.out.println("事务已回滚!");
                }
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            // 恢复自动提交模式(可选,但推荐)
            if (conn != null) {
                try {
                    conn.setAutoCommit(true);
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

关键点:

  1. conn.setAutoCommit(false);: 关闭自动提交。
  2. conn.commit();: 提交事务,使更改永久生效。
  3. conn.rollback();: 回滚事务,撤销所有未提交的更改。
  4. 通常在 catch 块中调用 rollback()

连接池 (Connection Pooling)

创建和销毁数据库连接是一个非常耗费资源的操作,连接池通过预先创建一组连接并缓存起来,应用程序需要时从中获取,用完后归还给池,而不是销毁,从而极大地提高了性能。

a. 为什么需要连接池

  • 性能提升: 避免了频繁创建和销毁连接的开销。
  • 资源控制: 限制了数据库连接的最大数量,防止因连接过多而压垮数据库。
  • 稳定性: 提供了更可靠的连接管理。

b. 主流连接池库

  • HikariCP: 目前性能最好的连接池,是 Spring Boot 2.x 的默认选择。
  • Apache DBCP: 老牌连接池,功能稳定。
  • C3P0: 另一个老牌连接池。

c. 使用 HikariCP 示例

添加 HikariCP 依赖 (Maven):

<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>5.0.1</version> <!-- 使用最新版本 -->
</dependency>

编写代码

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public class HikariCPExample {
    public static void main(String[] args) {
        // 1. 配置 HikariCP
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/mytestdb");
        config.setUsername("javauser");
        config.setPassword("your_strong_password");
        // 连接池配置 (可选)
        config.addDataSourceProperty("cachePrepStmts", "true");
        config.addDataSourceProperty("prepStmtCacheSize", "250");
        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
        config.setMaximumPoolSize(10); // 最大连接数
        config.setMinimumIdle(5);     // 最小空闲连接数
        config.setConnectionTimeout(30000); // 连接超时时间 (毫秒)
        // 2. 创建数据源
        HikariDataSource dataSource = new HikariDataSource(config);
        // 3. 从连接池获取连接
        try (Connection conn = dataSource.getConnection();
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT 'Hello from HikariCP!' as message")) {
            if (rs.next()) {
                System.out.println(rs.getString("message"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 4. 应用关闭时,关闭数据源
            if (dataSource != null && !dataSource.isClosed()) {
                dataSource.close();
            }
        }
    }
}

关键点:

  1. HikariDataSource 是连接池的管理者,它应该在整个应用生命周期内只创建一次(通常作为单例)。
  2. 通过 dataSource.getConnection() 获取连接,这个方法非常快,因为连接是预先创建好的。
  3. 重要: 调用 conn.close() 不是真的关闭连接,而是将连接归还给连接池,以便下次使用。
  4. 在应用关闭时,调用 dataSource.close() 来关闭整个连接池,释放所有资源。

高级配置与最佳实践

a. 配置 URL 参数

你可以在 JDBC URL 后面添加参数来配置连接行为。 jdbc:mysql://host:port/database?param1=value1&param2=value2

常用参数:

  • useSSL=false: 在开发环境中可以禁用 SSL(生产环境应启用)。
  • serverTimezone=UTC: 设置服务器时区,防止时区警告。
  • allowPublicKeyRetrieval=true: 在某些环境下(如使用 AWS RDS),需要此参数来获取公钥。
  • useUnicode=true&characterEncoding=UTF-8: 设置字符集,防止乱码。

示例:

String DB_URL = "jdbc:mysql://localhost:3306/mytestdb?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true";

b. 资源管理

必须记住: 使用完 Connection, Statement, ResultSet 后,必须调用 close() 方法将它们关闭,以释放数据库资源,忘记关闭会导致连接泄漏,最终耗尽数据库连接。

c. 使用 try-with-resources (强烈推荐)

这是 Java 7+ 推荐的最佳实践,可以自动关闭实现了 AutoCloseable 接口的资源,从根本上忘记关闭的问题。

// 推荐
try (Connection conn = dataSource.getConnection();
     PreparedStatement pstmt = conn.prepareStatement("SELECT ...");
     ResultSet rs = pstmt.executeQuery()) {
    // 处理逻辑
} // conn, pstmt, rs 在这里会自动关闭
// 不推荐 (容易出错)
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
    conn = dataSource.getConnection();
    pstmt = conn.prepareStatement(...);
    rs = pstmt.executeQuery();
    // ...
} finally {
    if (rs != null) try { rs.close(); } catch (SQLException e) { /* ignored */ }
    if (pstmt != null) try { pstmt.close(); } catch (SQLException e) { /* ignored */ }
    if (conn != null) try { conn.close(); } catch (SQLException e) { /* ignored */ }
}

d. 错误处理

  • SQLException: 这是所有 JDBC 操作都可能抛出的异常,它包含了一些有用的信息,如 SQL 状态码 (getSQLState()) 和错误代码 (getErrorCode()),可以帮助你定位问题。
  • 不要忽略异常: 至少要打印堆栈跟踪 e.printStackTrace() 或使用日志框架(如 SLF4J + Logback)记录错误。

本教程涵盖了使用 MySQL Connector/J 进行数据库编程的方方面面,从建立第一个连接,到执行 CRUD 操作,再到使用 PreparedStatement 保证安全,以及管理事务和使用连接池提升性能,这些都是构建健壮、高效 Java 应用的基石。

核心要点回顾:

  1. 依赖管理: 优先使用 Maven 或 Gradle。
  2. 连接: 使用 DriverManager 或连接池(推荐 HikariCP)获取 Connection
  3. 执行操作:
    • 查询: Statement.executeQuery() -> ResultSet
    • 增/删/改: Statement.executeUpdate() -> 受影响的行数。
    • 优先使用 PreparedStatement,以防止 SQL 注入并提高性能。
  4. 资源管理: 始终关闭 Connection, Statement, ResultSet强烈推荐使用 try-with-resources
  5. 事务: 通过 setAutoCommit(false), commit(), rollback() 来管理。
  6. 性能: 在生产环境中,必须使用连接池

掌握了这些知识,你就可以自信地在 Java 项目中与 MySQL 数据库进行高效、安全的交互了。