ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java连接MySQL 8.0实战指南:从JDBC原理到连接池调优

Java连接MySQL 8.0实战指南:从JDBC原理到连接池调优

1. 项目概述:从零构建Java与MySQL 8.0的桥梁

如果你刚开始接触Java后端开发,或者正被一个老项目升级到MySQL 8.0后出现的连接问题搞得焦头烂额,那么这篇内容就是为你准备的。我将带你完整地走一遍从安装MySQL 8.0开始,到用最基础的JDBC成功连接并操作数据库的全过程。这不仅仅是“安装-配置-写代码”的流水账,我会把每一步背后的“为什么”讲清楚,比如为什么MySQL 8.0的驱动包变了,为什么连接字符串里要加那些参数,以及那些让人头疼的时区和密码认证问题到底是怎么回事。无论你是想搭建一个全新的学习环境,还是为了解决生产环境中棘手的连接异常,这篇基于实战踩坑总结的指南,都能让你避开我当年走过的弯路,直抵核心。

2. 环境准备与核心组件解析

在动手敲代码之前,我们需要把“舞台”搭好。这个环境不仅仅是把软件装上,更要理解每个组件扮演的角色和它们之间的协作关系。很多人配置失败,根源就在于对这套体系的理解是割裂的。

2.1 组件选型与版本协同考量

首先明确我们的技术栈:Java作为应用程序语言,MySQL 8.0作为数据库服务器,JDBC作为两者之间的标准接口。这里的版本协同特别重要。

  • Java (JDK):建议选择JDK 8或JDK 11这两个长期支持(LTS)版本。它们拥有最广泛的生态兼容性。对于MySQL Connector/J(即JDBC驱动)8.0而言,它需要至少JDK 1.8。我推荐直接使用JDK 11,因为它能更好地支持后续可能用到的现代框架。安装后,务必确认环境变量JAVA_HOME已正确设置,并在命令行中能用java -versionjavac -version验证。
  • MySQL 8.0+:这是我们的核心数据存储。MySQL 8.0是一个里程碑式的大版本更新,带来了性能提升、更强的安全特性(如默认的身份认证插件从mysql_native_password改为caching_sha2_password),以及窗口函数、通用表表达式等新功能。正是这个认证插件的改变,成为了JDBC连接中最常见的“拦路虎”。我们稍后会详细处理。
  • JDBC驱动 (Connector/J):这是Java程序与MySQL对话的“翻译官”。对于MySQL 8.0,你必须使用 8.0 版本的驱动(如mysql-connector-java-8.0.xx.jar)。5.x 版本的驱动虽然可能能连上,但无法完全支持8.0的新特性,并且官方也不推荐,可能会遇到不可预知的问题。驱动本身只是一个JAR包,不需要安装,只需要在编译和运行时让Java程序能“找到”它即可。

注意:版本不匹配是环境搭建中最隐晦的坑。一个典型的错误是:服务器装了MySQL 8.0,但项目里引用的还是老版的mysql-connector-java-5.1.xx.jar,这会导致连接失败或认证错误。

2.2 MySQL 8.0 安装详解与关键配置

安装MySQL 8.0,我强烈建议从官网下载安装包,避免使用某些简化版或绿色版,以减少后期配置的麻烦。以Windows平台为例,选择MySQL Installer是最省心的方式。

  1. 运行安装程序:运行下载的mysql-installer-community-*.msi。在“Choosing a Setup Type”页面,对于开发和测试,选择“Developer Default”即可,它会包含MySQL Server、Workbench(图形化管理工具)等常用组件。
  2. 关键配置步骤
    • Authentication Method:这是重中之重。你会看到两个选项:“Use Strong Password Encryption for Authentication (RECOMMENDED)” 和 “Use Legacy Authentication Method (Retain MySQL 5.x Compatibility)”。
      • 如果选择推荐的强加密(对应caching_sha2_password),那么你的JDBC连接字符串里必须指明使用这个插件,或者使用8.0的新驱动(它默认支持)。这是未来趋势。
      • 如果为了兼容旧版程序或暂时减少麻烦,可以选择传统方式(对应mysql_native_password)。在本指南中,为了演示兼容性问题的解决,我们后续会涵盖这两种情况。我建议新手可以先选择传统方式,降低入门门槛。
    • 设置Root密码:设置一个强密码并牢记。同时,建议勾选“Add User”创建一个用于日常开发的专属账号(如dev_user),避免直接使用root账号进行操作,这是基本的安全规范。
    • Windows Service:默认会配置为Windows服务,开机自启。你可以修改服务名为MySQL80以便识别。
  3. 安装后验证:安装完成后,打开命令行,输入mysql -u root -p,输入你设置的root密码。如果能成功进入MySQL命令行提示符(mysql>),说明服务器安装成功。

对于macOS,推荐使用Homebrew安装:brew install mysql,安装后使用brew services start mysql启动服务。Linux(如Ubuntu)则可以使用APT仓库安装。

2.3 集成开发环境(IDE)与项目管理

你需要一个IDE来编写和运行Java代码。IntelliJ IDEA Community Edition(免费)或 Eclipse 都是绝佳选择。这里以IDEA为例,说明如何管理JDBC驱动。

  1. 创建项目:新建一个Java项目,选择正确的JDK版本(如11)。
  2. 导入JDBC驱动JAR包
    • 从MySQL官网下载mysql-connector-java-8.0.xx.jar文件。
    • 在IDEA中,右键点击你的项目模块 -> “Open Module Settings” -> “Libraries” -> 点击“+” -> “Java” -> 选择你下载的JAR包。这样就将驱动库添加到了项目的类路径(Classpath)中。
    • 更现代的方式(推荐):如果你使用Maven或Gradle构建项目,只需在pom.xmlbuild.gradle中添加对应的依赖即可,依赖管理工具会自动下载和管理JAR包。这是生产项目的标准做法。
      • Maven依赖
        <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> <!-- 使用最新稳定版本 --> </dependency>

3. JDBC核心原理与连接字符串剖析

在开始写连接代码之前,我们必须先理解JDBC是如何工作的,以及连接字符串里每个参数的意义。知其然知其所以然,才能在出问题时快速定位。

3.1 JDBC架构与驱动加载机制

JDBC(Java Database Connectivity)是Java定义的一套操作数据库的标准API。它的核心思想是“面向接口编程”。java.sql包下的Connection,Statement,ResultSet等都是接口。而MySQL提供的mysql-connector-java-8.0.xx.jar就是这些接口的具体实现,我们称之为驱动。

连接数据库的第一步是加载驱动类。在旧版(如JDBC 3.0及以前)代码中,你会看到Class.forName("com.mysql.jdbc.Driver");这行代码。它的作用是通过反射机制,将驱动类注册到DriverManager中。但在JDBC 4.0(随JDK 1.6引入)之后,这个过程变成了自动的。驱动包在META-INF/services目录下有一个配置文件,Java服务加载机制会自动发现并注册驱动。所以,在现代代码中,这行代码通常可以省略。但为了代码的清晰和兼容性,有时显式写出也无妨。

3.2 连接字符串(URL)参数全解

连接字符串是告诉JDBC驱动“如何找到并登录数据库”的关键信息。它的基本格式是:jdbc:mysql://[host]:[port]/[database]?[key1]=[value1]&[key2]=[value2]...

让我们拆解每一个部分,特别是MySQL 8.0中至关重要的几个参数:

  • 协议与地址jdbc:mysql://是固定前缀。localhost127.0.0.1代表本机。3306是MySQL默认端口。
  • 数据库名/your_database_name指定要连接的具体数据库。如果省略,则连接到MySQL实例,但后续需要执行USE database_name命令。
  • 关键查询参数
    • useSSL=false:在本地开发环境(非生产),为了简单起见,通常禁用SSL加密连接。生产环境必须启用SSL(useSSL=true)并配置证书。
    • serverTimezone这是MySQL 8.0连接中最容易忽略且必踩的坑!MySQL 8.0默认对时区要求更严格。如果Java应用服务器的时区(如UTC)与MySQL服务器的时区(如SYSTEM)不一致,在存储和读取时间类型(TIMESTAMP,DATETIME)时会出现几小时的偏差。必须明确指定。对于中国区,通常设置为Asia/ShanghaiGMT%2B8+号需要编码为%2B)。我强烈建议始终在连接字符串中设置此参数
      // 示例 String url = "jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8";
    • characterEncoding:指定客户端与服务器通信的字符编码,通常设为utf8utf8mb4(支持更全的字符,如表情符号),避免中文乱码。
    • allowPublicKeyRetrieval=true:这是一个与caching_sha2_password认证插件相关的参数。在某些网络配置或驱动版本下,可能需要将此参数设为true来允许客户端从服务器获取公钥,用于密码加密。注意:此参数存在一定的安全风险,仅在信任的网络环境中(如本地开发)使用,生产环境应寻求更安全的替代方案。
    • useUnicode=true:通常与characterEncoding配合使用,指示使用Unicode字符集。

3.3 处理MySQL 8.0身份认证插件问题

这是连接MySQL 8.0时特有的挑战。如前所述,MySQL 8.0默认使用caching_sha2_password插件,而旧版驱动或某些客户端可能不支持。

解决方案有两种:

  1. 升级驱动并适配新插件(推荐):确保使用mysql-connector-java-8.0+.jar。在连接字符串中,可以显式指定使用这个插件(虽然8.0驱动默认已处理):jdbc:mysql://localhost:3306/db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8如果还出现Public Key Retrieval is not allowed错误,可能需要加上allowPublicKeyRetrieval=true(理解其安全风险)。

  2. 修改MySQL用户认证方式(兼容旧版):如果因为某些原因必须使用旧版驱动,可以修改MySQL中相应用户的认证插件。

    -- 登录MySQL后执行 ALTER USER 'your_username'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password'; FLUSH PRIVILEGES;

    执行后,该用户将回退到旧的认证方式,旧版驱动即可连接。这只是一个临时的兼容方案,从安全和技术演进角度看,应优先考虑方案一。

4. 完整开发流程与代码实现

现在,我们将理论付诸实践,编写一个完整的、健壮的JDBC连接示例。这个示例将包含资源管理、异常处理等最佳实践。

4.1 基础连接与资源释放模板

以下是一个标准的JDBC操作模板,它清晰地展示了“获取连接 -> 执行操作 -> 释放资源”的流程。资源释放(关闭Connection, Statement, ResultSet)至关重要,否则会导致数据库连接泄漏,最终拖垮数据库。

import java.sql.*; public class JdbcBasicExample { // 将配置信息定义为常量,便于管理和修改 private static final String URL = "jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8"; private static final String USER = "dev_user"; // 使用你创建的开发账号 private static final String PASSWORD = "YourPassword123"; public static void main(String[] args) { Connection conn = null; Statement stmt = null; ResultSet rs = null; try { // 1. 注册驱动 (JDBC 4.0+ 可省略,但写上更清晰) Class.forName("com.mysql.cj.jdbc.Driver"); // 2. 建立连接 conn = DriverManager.getConnection(URL, USER, PASSWORD); System.out.println("数据库连接成功!"); // 3. 创建Statement对象 stmt = conn.createStatement(); // 4. 执行查询 String sql = "SELECT id, name, email FROM users"; rs = stmt.executeQuery(sql); // 5. 处理结果集 while (rs.next()) { int id = rs.getInt("id"); String name = rs.getString("name"); String email = rs.getString("email"); System.out.printf("ID: %d, Name: %s, Email: %s%n", id, name, email); } } catch (ClassNotFoundException e) { System.err.println("找不到JDBC驱动类: " + e.getMessage()); e.printStackTrace(); } catch (SQLException e) { System.err.println("数据库操作错误: " + e.getMessage()); System.err.println("SQL状态: " + e.getSQLState()); System.err.println("错误码: " + e.getErrorCode()); e.printStackTrace(); } finally { // 6. 关闭资源,必须放在finally块中确保执行 // 关闭顺序:ResultSet -> Statement -> Connection (与创建顺序相反) try { if (rs != null) rs.close(); } catch (SQLException e) { e.printStackTrace(); // 记录日志,通常不向上抛出 } try { if (stmt != null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

4.2 使用PreparedStatement防止SQL注入与提升性能

直接使用Statement拼接SQL字符串是极其危险的,会引发SQL注入攻击。在任何情况下,只要SQL涉及用户输入,就必须使用PreparedStatement

PreparedStatementStatement的子接口,它使用占位符(?)预编译SQL语句,然后将参数安全地传递进去。数据库会对预编译的SQL模板进行缓存,多次执行同一模板(仅参数不同)时,性能也远高于每次都重新编译的Statement

// 插入数据的例子 String insertSql = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)"; try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) { // 使用try-with-resources自动关闭 pstmt.setString(1, "张三"); // 第一个问号对应索引1 pstmt.setString(2, "zhangsan@example.com"); pstmt.setInt(3, 25); int affectedRows = pstmt.executeUpdate(); // 执行更新,返回受影响的行数 System.out.println("插入了 " + affectedRows + " 行数据。"); // 如果需要获取自增主键ID if (affectedRows > 0) { try (ResultSet generatedKeys = pstmt.getGeneratedKeys()) { if (generatedKeys.next()) { long newId = generatedKeys.getLong(1); System.out.println("新生成的ID是: " + newId); } } } } catch (SQLException e) { e.printStackTrace(); }

注意代码中使用的try-with-resources语法(Java 7+),它可以自动关闭实现了AutoCloseable接口的资源(如Connection,Statement,ResultSet),代码更简洁,且能避免因异常导致的资源泄漏。这是现代Java JDBC编程的首选方式。

4.3 事务管理基础

默认情况下,JDBC连接处于“自动提交”模式,即每条SQL语句都被视为一个独立的事务并立即提交。对于需要保证原子性的一组操作(比如银行转账:扣款和加款必须同时成功或失败),必须手动管理事务。

// 一个简单的事务示例:转账 Connection conn = ... // 获取连接 try { conn.setAutoCommit(false); // 1. 关闭自动提交,开启事务 // 2. 执行一系列数据库操作 PreparedStatement debit = conn.prepareStatement("UPDATE accounts SET balance = balance - ? WHERE id = ?"); debit.setBigDecimal(1, transferAmount); debit.setInt(2, fromAccountId); debit.executeUpdate(); PreparedStatement credit = conn.prepareStatement("UPDATE accounts SET balance = balance + ? WHERE id = ?"); credit.setBigDecimal(1, transferAmount); credit.setInt(2, toAccountId); credit.executeUpdate(); // 3. 所有操作成功,提交事务 conn.commit(); System.out.println("转账成功!"); } catch (SQLException e) { // 4. 如果任何操作失败,回滚事务 try { if (conn != null) { conn.rollback(); System.out.println("操作失败,事务已回滚。"); } } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); } finally { // 5. 恢复自动提交模式,并关闭连接 try { if (conn != null) { conn.setAutoCommit(true); conn.close(); } } catch (SQLException e) { e.printStackTrace(); } }

5. 高级配置、性能调优与连接池

当你的应用从Demo走向实际开发,就需要考虑更高级的配置和性能问题。直接使用DriverManager.getConnection每次创建物理连接开销很大,在生产环境中必须使用连接池。

5.1 连接池的必要性与选型

数据库连接的建立和销毁是昂贵的操作。连接池(Connection Pool)在应用启动时就创建一定数量的连接放在“池”中,程序需要时从池中取用,用完后归还,而不是真正关闭。这极大地提升了性能。

常见的开源连接池有:

  • HikariCP:以高性能和轻量著称,Spring Boot 2.x 后的默认连接池。对于新项目,我无脑推荐HikariCP。
  • Apache DBCP2/Tomcat JDBC Pool:老牌且功能丰富的连接池。
  • C3P0:非常经典,但近年来性能和维护活跃度不如HikariCP。

5.2 使用HikariCP整合JDBC

以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.SQLException; public class HikariCPExample { private static HikariDataSource dataSource; static { // 配置连接池 HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=Asia/Shanghai"); config.setUsername("dev_user"); config.setPassword("YourPassword123"); config.setDriverClassName("com.mysql.cj.jdbc.Driver"); // 连接池调优参数(根据实际负载调整) config.setMaximumPoolSize(10); // 最大连接数 config.setMinimumIdle(5); // 最小空闲连接数 config.setIdleTimeout(600000); // 连接空闲超时时间(毫秒) config.setConnectionTimeout(30000); // 获取连接的超时时间 config.setMaxLifetime(1800000); // 连接的最大生命周期 // 可选:MySQL优化参数 config.addDataSourceProperty("cachePrepStmts", "true"); config.addDataSourceProperty("prepStmtCacheSize", "250"); config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); dataSource = new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); // 从池中获取连接 } public static void main(String[] args) { try (Connection conn = getConnection()) { // 使用try-with-resources,归还连接给池 System.out.println("从HikariCP连接池获取连接成功!"); // ... 执行你的数据库操作 } catch (SQLException e) { e.printStackTrace(); } // 应用关闭时,需要关闭数据源(通常在应用生命周期结束时) // dataSource.close(); } }

使用连接池后,你不再需要关心连接的创建和销毁,只需在需要时getConnection(),用完后close()(实际上是归还给池)。这大幅简化了代码并提升了性能。

5.3 JDBC驱动高级属性配置

除了连接字符串中的基本参数,MySQL Connector/J 驱动还提供了许多高级属性,可以通过Properties对象或在连接池配置中设置。

import java.util.Properties; Properties props = new Properties(); props.setProperty("user", USER); props.setProperty("password", PASSWORD); props.setProperty("useSSL", "false"); props.setProperty("serverTimezone", "Asia/Shanghai"); props.setProperty("characterEncoding", "utf8"); // 连接超时和socket超时 props.setProperty("connectTimeout", "5000"); // 连接建立超时(毫秒) props.setProperty("socketTimeout", "30000"); // 网络读写超时(毫秒) // 预编译语句缓存,提升性能 props.setProperty("useServerPrepStmts", "true"); // 启用服务端预编译 props.setProperty("cachePrepStmts", "true"); props.setProperty("prepStmtCacheSize", "250"); props.setProperty("prepStmtCacheSqlLimit", "2048"); Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test_db", props);

这些参数对于优化生产环境的连接稳定性和性能非常有帮助。例如,设置合理的超时可以防止网络波动导致线程长时间挂起。

6. 实战问题排查与调试技巧

即使按照指南操作,在实际编码中仍可能遇到各种问题。这里我总结了一些最常见的“坑”和排查方法。

6.1 常见连接异常与解决方案速查表

异常信息可能原因解决方案
java.sql.SQLException: The server time zone value '...' is unrecognized...未指定或指定了错误的服务器时区。在连接URL中添加&serverTimezone=Asia/Shanghai(或GMT%2B8)。
java.sql.SQLException: Unable to load authentication plugin 'caching_sha2_password'.使用旧版驱动(如5.x)连接MySQL 8.0。1.推荐:升级驱动至mysql-connector-java-8.0+.jar
2.临时:修改MySQL用户认证方式为mysql_native_password
Public Key Retrieval is not allowed使用caching_sha2_password插件时,驱动无法安全获取公钥。在信任的开发环境中,在连接URL中添加&allowPublicKeyRetrieval=true生产环境慎用。
java.sql.SQLException: Access denied for user '...'@'localhost' (using password: YES)用户名/密码错误;用户没有从指定主机访问的权限。1. 检查密码。
2. 在MySQL中检查用户权限:SELECT host, user FROM mysql.user;。可能需要创建'user'@'%''user'@'specific_ip'的用户。
java.net.ConnectException: Connection refusedMySQL服务未启动;端口被防火墙阻止;连接地址/端口错误。1. 检查MySQL服务状态 (sudo systemctl status mysql或 服务管理器)。
2. 检查防火墙规则。
3. 确认连接URL中的主机和端口。
Communications link failure连接超时或网络中断。1. 检查网络。
2. 增大connectTimeoutsocketTimeout参数值。
No suitable driver found for jdbc:mysql://...驱动JAR包未正确添加到类路径;连接URL格式错误。1. 确认驱动JAR在项目的构建路径中。
2. 检查URL拼写,特别是jdbc:mysql://前缀。
中文乱码数据库、表、连接字符集不统一。1. 确保数据库和表使用utf8mb4字符集。
2. 在连接URL中添加&characterEncoding=utf8&useUnicode=true

6.2 有效的调试与日志记录

当问题不那么明显时,启用JDBC和MySQL驱动的详细日志是终极武器。

  1. 启用MySQL驱动日志:Connector/J 使用SLF4J作为日志门面。你需要引入一个SLF4J的实现(如Logback, Log4j2)并在配置文件中设置日志级别。

    • Maven依赖示例 (Logback):
      <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.11</version> </dependency>
    • logback.xml配置文件中,将com.mysql.cj的日志级别设为DEBUGTRACE
      <configuration> <logger name="com.mysql.cj" level="DEBUG"/> <root level="INFO"> <appender-ref ref="CONSOLE" /> </root> </configuration>

    这样,所有网络数据包、协议交互、查询详情都会输出到控制台或日志文件,对排查连接、认证、查询问题有奇效。

  2. 在代码中打印关键信息:在获取连接和执行语句前后,打印连接URL(隐藏密码)、执行的SQL等。

    System.out.println("尝试连接: " + URL.replaceAll("password=[^&]*", "password=****")); // ... System.out.println("执行SQL: " + sql); // 对于PreparedStatement,可以打印带参数的SQL
  3. 使用数据库监控工具:MySQL Workbench或命令行工具SHOW PROCESSLIST;可以查看当前所有的数据库连接,确认你的应用是否成功建立了连接,以及连接在做什么。

6.3 连接池相关问题排查

使用连接池后,问题可能变得更隐蔽。

  • 连接泄漏:程序从池中获取连接后,没有调用close()方法归还。连接池会认为该连接一直被占用,最终导致池中所有连接耗尽,新的请求获取不到连接而超时。务必使用 try-with-resources 或确保在 finally 块中关闭连接。
  • 连接超时:如果数据库服务器或中间网络设备设置了连接空闲超时(如wait_timeout),而连接池中的连接长时间空闲,就可能被服务器断开。当应用再次从池中取出这个“僵尸连接”使用时,就会报错。HikariCP有connectionTestQuery(如SELECT 1)或connectionInitSql配置,可以在将连接交给应用前执行一个简单查询来验证连接有效性。也可以设置合理的maxLifetimeidleTimeout,让池主动淘汰旧连接。
  • 性能问题:连接池参数配置不当。maximumPoolSize不是越大越好,设置过大会增加数据库和应用的负载。通常建议在10-50之间,根据实际并发量和数据库性能调整。minimumIdle可以设置为比maximumPoolSize小,以减少空闲资源占用。

搭建Java JDBC连接MySQL 8.0+的开发环境,远不止是安装软件和写几行代码。它涉及版本兼容性、安全认证、字符编码、时区、资源管理、性能优化等一系列环环相扣的知识点。我最深的体会是,“魔鬼在细节里”。很多看似玄学的问题,比如时间不对、偶尔连不上、运行一段时间后崩溃,根源往往就在一两个配置参数上。养成好习惯:使用连接池、总是用PreparedStatement、明确设置时区和字符集、在开发阶段打开详细日志。把这些基础打牢,后续无论使用MyBatis还是JPA等ORM框架,你都能更从容地应对底层问题,因为它们最终都是通过JDBC与数据库通信的。

返回列表