简介:本资源是一套面向Java初学者与SpringBoot入门开发者的MySQL数据库集成实战方案,聚焦JDBC原生方式连接与操作MySQL的核心流程,覆盖环境搭建、驱动配置、连接池管理、CRUD实现及常见异常处理等关键环节。压缩包共136个文件,包含92个DLL动态库(含Oracle与MySQL相关本地依赖)、3个SQL建表与初始化脚本、3个Java核心业务类(如JdbcController、User)、6个XML配置文件(含Spring Boot配置与Maven依赖描述)、4个EXE安装工具及3个MSI数据库客户端安装包,整体达637.42MB,结构完整适配离线部署。已有601人学习下载,资源附带可直接运行的Spring Boot工程源码、预编译class文件、SSL证书(pem)及调试用二进制载荷(payload.bin),目录组织清晰,便于理解JDBC在Spring Boot中的底层集成逻辑与工程化落地细节。
1. SpringBoot + JDBC + MySQL 这套组合,不是“装上就能跑”,而是“配错一个参数就卡死在启动日志里”
你是不是也遇到过:新建一个 SpringBoot 项目,加了spring-boot-starter-jdbc和mysql-connector-java,application.yml 里填好 host、port、username、password,一启动——控制台疯狂刷Failed to obtain JDBC Connection,或者更玄学的Access denied for user ''@'localhost',连用户名都空了?这不是代码写错了,是环境链路断在了最底层:JDBC 驱动版本和 MySQL 服务端协议不兼容、时区没对齐、SSL 默认开启却没配证书、甚至 MySQL 用户权限根本没开远程访问……这套「Java SpringBoot 使用 JDBC 连接 MySQL」的整套方案,表面看只是三行配置,背后其实是 JDK 版本、MySQL Server 大版本、Connector/J 小版本、SpringBoot 自动装配机制、以及 Linux/macOS/Windows 文件权限与网络策略的四层咬合。它不是给初学者练手的玩具,而是某高校课程设计、某公司内部工具后台、某图像处理 Demo 的数据持久层真实基座——装不稳,后面所有业务逻辑都是空中楼阁。本文不讲 Spring Data JPA,不讲 MyBatis,就死磕最原始、最透明、最可控的 JDBC 路径,从 MySQL 安装包选型开始,到 SpringBoot 启动后jdbcTemplate.queryForObject("SELECT 1", Integer.class)成功返回1结束,全程可复现、可验证、可回滚。
2. 安装 MySQL 服务端:选对安装包比调参更重要
MySQL 官方提供多种分发形式:.msi(Windows 图形向导)、.dmg(macOS 拖拽安装)、.tar.gz(Linux 二进制免编译)、.deb/.rpm(Linux 包管理器安装)。但真正决定后续 JDBC 连接成败的,不是安装方式,而是MySQL Server 的主版本号和默认认证插件。MySQL 8.0.4+ 默认使用caching_sha2_password,而旧版 JDBC 驱动(如 5.1.x)根本不认识这个插件,直接报Unknown initial character set index或Public Key Retrieval is not allowed。所以第一步,必须锁定服务端版本与客户端驱动的兼容边界。
2.1 下载与安装:推荐 MySQL 8.0.33(LTS 稳定分支)
提示:不要用 MySQL 8.4+(2023 年底新发布),其默认启用了更强的密码策略和 TLS 1.3 强制要求,对本地开发极不友好;也不要倒退到 5.7 —— 它已停止维护,且不支持
json类型字段的原生 JDBC 映射。
- Windows 用户:下载
mysql-8.0.33-winx64.zip(非 MSI!ZIP 包无后台服务自动注册,避免权限冲突和端口占用残留) - macOS 用户:下载
mysql-8.0.33-macos12-x86_64.dmg(Apple Silicon M1/M2 请选aarch64版本,否则运行缓慢) - Linux 用户(Ubuntu/Debian):
wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.33-1ubuntu22.04-amd64.deb-bundle.tar tar -xf mysql-8.0.33-1ubuntu22.04-amd64.deb-bundle.tar sudo apt install ./mysql-community-server_8.0.33-1ubuntu22.04_amd64.deb注意:
.deb-bundle.tar是官方推荐安装包,它包含 server、client、common 等完整组件,比apt install mysql-server更可控,避免 Ubuntu 自带源混入 MariaDB。
2.2 初始化与首次配置:绕过 root 密码玄学
MySQL 8.0+ 不再生成随机 root 密码写入/var/log/mysqld.log,而是强制首次登录必须重置密码。但很多教程让你mysqld --initialize --console,结果报错Can't create test file—— 因为没指定--datadir或目录权限不对。正确做法是:
# 创建数据目录(Linux/macOS) sudo mkdir -p /usr/local/mysql/data sudo chown -R $USER:staff /usr/local/mysql # macOS # 或 sudo chown -R mysql:mysql /usr/local/mysql # Ubuntu # 初始化(关键:显式指定 datadir 和 basedir) mysqld --initialize-insecure --datadir=/usr/local/mysql/data --basedir=/usr/local/mysql
--initialize-insecure是关键:它创建空密码 root 用户,避免首次登录被ERROR 1045 (28000): Access denied卡死。生产环境才用--initialize,开发阶段先通再优。
2.3 启动服务并验证端口监听
# Linux/macOS 启动(不依赖 systemd,避免服务状态混乱) mysqld --defaults-file=/usr/local/mysql/my.cnf --user=$USER & # Windows 启动(cmd 中执行,不要用管理员权限) mysqld --defaults-file=C:\mysql\my.ini # 验证是否监听 3306 netstat -an | grep 3306 # Linux/macOS # 或 netstat -ano | findstr :3306 # Windows若看到LISTENING,说明服务已就绪。此时用命令行连接:
mysql -u root -p # 密码为空,直接回车成功进入mysql>提示符,即 MySQL 服务安装完成。
2.4 必改配置项:my.cnf 中这 4 行决定 JDBC 能不能握手成功
MySQL 默认配置对 JDBC 友好度极低。必须编辑my.cnf(Linux/macOS 在/etc/my.cnf或/usr/local/mysql/my.cnf;Windows 在C:\mysql\my.ini),在[mysqld]段落下添加:
[mysqld] # 1. 强制使用老式认证插件,兼容所有 JDBC 驱动 default_authentication_plugin = mysql_native_password # 2. 允许远程连接(开发机建议设为 0.0.0.0,生产务必限制 IP) bind-address = 0.0.0.0 # 3. 关闭 strict mode,避免 INSERT 时因字段默认值报错(JDBC 批量插入常见坑) sql_mode = ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION # → 改为(去掉 STRICT_TRANS_TABLES) sql_mode = ONLY_FULL_GROUP_BY,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION # 4. 设置时区为系统本地,避免 JDBC 查询时间字段时差 8 小时 default-time-zone = '+08:00'修改后重启 MySQL:
# Linux/macOS kill $(pgrep mysqld) mysqld --defaults-file=/etc/my.cnf --user=$USER & # Windows taskkill /F /IM mysqld.exe # 再双击启动脚本参数说明:
default_authentication_plugin是 JDBC 连接失败的头号元凶;bind-address决定 SpringBoot 应用能否从 localhost 外网(如 Docker 容器)连上;sql_mode中的STRICT_TRANS_TABLES会让 JDBCPreparedStatement.executeBatch()因某条记录字段超长而整个批次失败,而非跳过错误行;default-time-zone不配,LocalDateTime字段查出来永远比数据库少 8 小时。
3. 配置 SpringBoot 工程:Maven 依赖、YAML 配置与连接池选型
SpringBoot 的自动配置极大简化了 JDBC,但也隐藏了大量隐式行为。比如HikariCP连接池默认开启connection-test-query,但 MySQL 8.0+ 不认SELECT 1,必须改成SELECT 1;;又比如spring.datasource.url中的serverTimezone参数漏写,应用能启动但时间字段全乱。这一节,我们只做三件事:选对驱动版本、写死 URL 参数、禁用自动建表。
3.1 Maven 依赖:SpringBoot 2.7.x 对应的黄金组合
SpringBoot 版本与 JDBC 驱动存在强绑定。SpringBoot 2.7.x(当前主流稳定版)内嵌 Tomcat 9.0.x,要求 JDK 8+,其spring-boot-starter-jdbc默认拉取 HikariCP 4.0.x,而 HikariCP 4.0.x 要求 MySQL Connector/J ≥ 8.0.27。因此pom.xml必须显式声明驱动版本:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- 显式指定 MySQL 驱动,覆盖 SpringBoot 默认的 8.0.28 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> <scope>runtime</scope> </dependency> <!-- 可选:添加 Lombok 减少样板代码 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>注意:
<scope>runtime</scope>是必须的。因为 JDBC 驱动只在运行时加载,编译期不需要;若写成compile,可能触发 IDE 的类路径冲突警告。
3.2 application.yml:URL 参数一个都不能少
以下配置是经过 12 次翻车后提炼出的最小可用集,适用于localhost:3306开发环境:
spring: datasource: url: jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false&zeroDateTimeBehavior=CONVERT_TO_NULL username: root password: driver-class-name: com.mysql.cj.jdbc.Driver hikari: connection-timeout: 20000 maximum-pool-size: 10 minimum-idle: 2 idle-timeout: 300000 max-lifetime: 1800000 # 关键:MySQL 8.0+ 必须设置此测试 SQL,否则连接池初始化失败 connection-test-query: SELECT 1; # 禁用 JPA 自动建表(我们只用 JDBC,不走 ORM) jpa: hibernate: ddl-auto: none show-sql: false properties: hibernate: format_sql: false参数详解:
serverTimezone=Asia/Shanghai:必须显式指定,不能用GMT%2B8或UTC+8,MySQL 驱动只认 IANA 时区名;allowPublicKeyRetrieval=true:解决Public Key Retrieval is not allowed错误,MySQL 8.0+ 密码交换需公钥,开发环境可开;useSSL=false:本地开发关闭 SSL,避免证书配置;生产环境必须设为true并配trustCertificateKeyStoreUrl;zeroDateTimeBehavior=CONVERT_TO_NULL:将 MySQL 的0000-00-00日期转为null,避免 JDBC 解析异常;connection-test-query: SELECT 1;:注意末尾分号!HikariCP 4.0.x 要求 MySQL 语句以分号结尾,否则报Syntax error。
3.3 创建测试数据库与用户:别用 root 连生产,但开发可以
虽然配置里写了root,但最佳实践是建一个专用用户。登录 MySQL 后执行:
-- 创建数据库(UTF8MB4 支持 emoji) CREATE DATABASE IF NOT EXISTS testdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 创建用户(允许 localhost 和 127.0.0.1 两种连接方式) CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'AppPass123!'; CREATE USER 'appuser'@'127.0.0.1' IDENTIFIED BY 'AppPass123!'; -- 授权 GRANT ALL PRIVILEGES ON testdb.* TO 'appuser'@'localhost'; GRANT ALL PRIVILEGES ON testdb.* TO 'appuser'@'127.0.0.1'; FLUSH PRIVILEGES; -- 验证用户能否登录 mysql -u appuser -p -h 127.0.0.1 testdb然后把application.yml中的username/password改为:
username: appuser password: AppPass123!常见误区:很多人以为
localhost和127.0.0.1是等价的,但在 MySQL 权限系统中,它们是两个独立 host。SpringBoot 默认用127.0.0.1连接(因localhost触发 Unix socket,而 JDBC 驱动不走 socket),所以必须显式授权127.0.0.1。
4. 编写 JDBC 访问代码:从 DataSource 到 JdbcTemplate 的三层封装
SpringBoot 的JdbcTemplate是 JDBC 的最佳实践封装,但它不是黑匣子。理解DataSource→Connection→Statement的生命周期,才能在连接泄漏、事务不生效时快速定位。本节代码全部基于spring-boot-starter-jdbc,不引入任何 ORM。
4.1 手动注入 DataSource:验证连接池是否真建起来了
在@SpringBootApplication同包下新建Config.java:
@Configuration public class DatabaseConfig { @Bean @Primary public DataSource dataSource() { HikariDataSource ds = new HikariDataSource(); ds.setJdbcUrl("jdbc:mysql://localhost:3306/testdb?serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false"); ds.setUsername("appuser"); ds.setPassword("AppPass123!"); ds.setDriverClassName("com.mysql.cj.jdbc.Driver"); ds.setMaximumPoolSize(5); ds.setMinimumIdle(1); // 关键:设置连接池健康检查 ds.setConnectionTestQuery("SELECT 1;"); ds.setValidationTimeout(3000); return ds; } }为什么不用
@ConfigurationProperties("spring.datasource")?因为手动 newHikariDataSource能 100% 确认连接池初始化逻辑,避免 YAML 配置未生效的幻觉。当ApplicationRunner启动时打印ds.getHikariPoolMXBean().getActiveConnections()> 0,即证明连接池已活。
4.2 编写 DAO 层:用 JdbcTemplate 实现增删改查
新建UserDao.java:
@Repository public class UserDao { private final JdbcTemplate jdbcTemplate; public UserDao(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } // 创建表(仅用于演示,实际应由 DBA 统一管理) public void initTable() { String sql = """ CREATE TABLE IF NOT EXISTS users ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, email VARCHAR(100), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 """; jdbcTemplate.execute(sql); } // 插入单条 public long insert(String name, String email) { String sql = "INSERT INTO users(name, email) VALUES (?, ?)"; KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(connection -> { PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ps.setString(1, name); ps.setString(2, email); return ps; }, keyHolder); return Objects.requireNonNull(keyHolder.getKey()).longValue(); } // 查询单条 public User findById(long id) { String sql = "SELECT id, name, email, created_at FROM users WHERE id = ?"; return jdbcTemplate.queryForObject(sql, new Object[]{id}, (rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name"), rs.getString("email"), rs.getTimestamp("created_at").toLocalDateTime()) ); } // 批量插入(验证 connection pool 复用) public int[] batchInsert(List<User> users) { String sql = "INSERT INTO users(name, email) VALUES (?, ?)"; List<Object[]> batchArgs = users.stream() .map(u -> new Object[]{u.getName(), u.getEmail()}) .collect(Collectors.toList()); return jdbcTemplate.batchUpdate(sql, batchArgs); } }关键点:
batchUpdate内部会复用同一个Connection,若连接池配置错误(如max-lifetime过短),此处会抛SQLException: Connection is closed;queryForObject中rs.getTimestamp("created_at").toLocalDateTime()能正确转换,证明serverTimezone和useLegacyDatetimeCode=false(驱动默认)协同生效。
4.3 编写启动验证器:ApplicationRunner 确保启动即连通
@Component public class DbStartupRunner implements ApplicationRunner { private final UserDao userDao; public DbStartupRunner(UserDao userDao) { this.userDao = userDao; } @Override public void run(ApplicationArguments args) throws Exception { System.out.println("=== 正在初始化数据库 ==="); userDao.initTable(); System.out.println("=== 插入测试用户 ==="); long id = userDao.insert("张三", "zhangsan@example.com"); System.out.println("插入 ID: " + id); System.out.println("=== 查询刚插入的用户 ==="); User user = userDao.findById(id); System.out.println("查到用户: " + user); System.out.println("=== 批量插入 3 条 ==="); List<User> batch = Arrays.asList( new User(null, "李四", "lisi@example.com", null), new User(null, "王五", "wangwu@example.com", null), new User(null, "赵六", "zhaoliu@example.com", null) ); int[] results = userDao.batchInsert(batch); System.out.println("批量插入结果: " + Arrays.toString(results)); System.out.println("✅ JDBC 连接验证通过!"); } }运行效果:SpringBoot 启动后,控制台依次打印初始化、插入、查询、批量日志,最后出现 ✅。若卡在某一步,说明对应环节配置有误。
5. 常见问题排查:5 条血泪经验,每条都来自真实翻车现场
JDBC 连接问题有高度规律性。下面列出 5 个最高频、最隐蔽、最容易浪费半天的坑,按「现象 → 原因 → 解决」结构给出可立即执行的诊断步骤。
5.1 现象:java.sql.SQLException: Access denied for user ''@'localhost'
- 原因:
application.yml中spring.datasource.username为空,或 YAML 缩进错误导致该属性未被读取(如写成username: root但前面多了一个空格,SpringBoot 解析为 null)。 - 解决:
在ApplicationRunner中加一行调试:
若输出System.out.println("Username: [" + environment.getProperty("spring.datasource.username") + "]");[null]或[],检查 YAML 缩进;若输出[root]但仍有错,则确认 MySQL 用户是否存在''@'localhost'(空用户名),执行SELECT user,host FROM mysql.user;查看。
5.2 现象:java.sql.SQLException: Unknown system variable 'tx_isolation'
- 原因:MySQL 8.0.3 之后废弃
tx_isolation,改用transaction_isolation,但旧版 HikariCP 或 SpringBoot 2.4.x 以下仍尝试读取旧变量。 - 解决:升级 SpringBoot 至 2.7.18+,或在
my.cnf中添加兼容配置:[mysqld] transaction_isolation = READ-COMMITTED # 并创建别名(MySQL 8.0.23+ 支持) [mysqld] tx_isolation = READ-COMMITTED
5.3 现象:Caused by: com.mysql.cj.exceptions.CJException: Public Key Retrieval is not allowed
- 原因:MySQL 8.0+ 默认启用
caching_sha2_password认证,驱动需公钥解密密码,但allowPublicKeyRetrieval=false(默认值)禁止此行为。 - 解决:在 JDBC URL 中强制添加
allowPublicKeyRetrieval=true(开发环境安全),或重置用户认证插件:ALTER USER 'appuser'@'127.0.0.1' IDENTIFIED WITH mysql_native_password BY 'AppPass123!'; FLUSH PRIVILEGES;
5.4 现象:java.time.format.DateTimeParseException: Text '0000-00-00 00:00:00' could not be parsed
- 原因:MySQL 表中某 DATETIME 字段存了
0000-00-00,而 JDBC 驱动默认拒绝解析零日期。 - 解决:在 JDBC URL 中添加
zeroDateTimeBehavior=CONVERT_TO_NULL,或改为zeroDateTimeBehavior=ROUND(转为0001-01-01)。
5.5 现象:应用启动成功,但jdbcTemplate.queryForObject("SELECT 1", Integer.class)返回null
- 原因:
SELECT 1是标量查询,但queryForObject要求结果集必须有且仅有一行一列;若 MySQL 服务未响应,HikariCP 返回空连接,驱动抛SQLException被静默吞掉。 - 解决:
- 先用
mysql -u appuser -p -h 127.0.0.1 -e "SELECT 1;" testdb手动验证; - 在代码中捕获异常:
try { Integer result = jdbcTemplate.queryForObject("SELECT 1", Integer.class); System.out.println("JDBC 查询结果: " + result); // 应输出 1 } catch (Exception e) { System.err.println("JDBC 查询失败: " + e.getMessage()); e.printStackTrace(); } - 先用
注意:以上所有现象,90% 都能在
application.yml的url参数和my.cnf的default_authentication_plugin两处找到根源。我一般会先grep -r "authentication" /usr/local/mysql/确认 MySQL 实际生效的插件,再mvn clean compile && java -Ddebug -jar target/*.jar启动看 DEBUG 日志,比瞎猜快十倍。
6. 验证连接稳定性:用压力测试脚本跑通 1000 次连接复用
装完不是终点,验证连接池能否扛住并发才是关键。很多教程停在 “Hello World” 就结束,但真实场景中,连接泄漏、超时、事务未提交会导致HikariPool-1 - Connection is not available。这里提供一个轻量级压力验证脚本,不依赖 JMeter,纯 Java + JUnit,5 分钟跑完。
6.1 添加测试依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency>6.2 编写并发连接测试
@SpringBootTest class JdbcStressTest { @Autowired private JdbcTemplate jdbcTemplate; @Test void testConnectionReuseUnderLoad() throws InterruptedException { int threadCount = 10; int iterationsPerThread = 100; CountDownLatch latch = new CountDownLatch(threadCount); ExecutorService executor = Executors.newFixedThreadPool(threadCount); for (int i = 0; i < threadCount; i++) { executor.submit(() -> { try { for (int j = 0; j < iterationsPerThread; j++) { // 每次都走完整 JDBC 流程:获取连接 → 创建 Statement → 执行 → 关闭 Integer result = jdbcTemplate.queryForObject("SELECT 1", Integer.class); assertThat(result).isEqualTo(1); // 模拟业务处理延迟 Thread.sleep(10); } } catch (Exception e) { throw new RuntimeException("线程执行失败", e); } finally { latch.countDown(); } }); } latch.await(30, TimeUnit.SECONDS); executor.shutdown(); // 验证连接池状态(HikariCP 提供 MXBean) HikariPoolMXBean poolBean = getHikariPoolMXBean(); System.out.println("活跃连接数: " + poolBean.getActiveConnections()); System.out.println("空闲连接数: " + poolBean.getIdleConnections()); System.out.println("总连接数: " + poolBean.getTotalConnections()); // 断言:没有连接泄漏,活跃连接数 ≤ 最大池大小(10) assertThat(poolBean.getActiveConnections()).isLessThanOrEqualTo(10); assertThat(poolBean.getIdleConnections()).isGreaterThanOrEqualTo(0); } private HikariPoolMXBean getHikariPoolMXBean() { try { MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); ObjectName objectName = new ObjectName("com.zaxxer.hikari:type=Pool (HikariPool-1)"); return JMX.newMBeanProxy(mbs, objectName, HikariPoolMXBean.class); } catch (Exception e) { throw new RuntimeException(e); } } }运行命令:
mvn test -Dtest=JdbcStressTest#testConnectionReuseUnderLoad
预期输出:1000 次查询全部通过,ActiveConnections峰值稳定在 5~8,IdleConnections始终 > 0,证明连接复用正常,无泄漏。
6.3 生产部署前必做的 3 项检查清单
| 检查项 | 检查命令/操作 | 通过标准 |
|---|---|---|
| MySQL 服务监听状态 | ss -tuln | grep :3306(Linux)或lsof -i :3306(macOS) | 输出包含LISTEN且State为LISTEN |
| JDBC 驱动版本一致性 | mvn dependency:tree | grep mysql | 输出中mysql-connector-java版本与my.cnf中version()返回一致(登录 MySQL 执行SELECT VERSION();) |
| 连接池健康指标 | 启动应用后访问http://localhost:8080/actuator/metrics/hikaricp.connections.active(需加spring-boot-starter-actuator) | 返回 JSON 中value字段为数字,且随请求波动,非恒为 0 |
从那以后我每次部署新环境,都强制走一遍这个压力脚本 + 三项检查清单,哪怕只是本地 demo。因为 JDBC 连接问题从来不是“能不能连上”,而是“在高并发下会不会悄悄泄漏、超时、阻塞”。希望帮到你。
本文还有配套的精品资源,点击获取