ARTICLE DETAIL

资讯详情

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

JDBC连接数据库全攻略:配置、排错与连接池实战

JDBC连接数据库全攻略:配置、排错与连接池实战 周一早上刚到工位隔壁组的小王就抱着笔记本过来了“哥我连不上数据库了报错是CommunicationsException但昨天还好好的。”这种问题我一年少说碰十几次每次排查到最后绝大多数情况都回到同一个原点——用JDBC连接数据库时某些配置细节没做对。JDBC这个名字干了几年Java的人都不会陌生但说实话真正把它吃透的人不多。很多人天天用MyBatis、Spring Data JPA以为JDBC只是底层的一个名词其实不管是ORM框架还是连接池最终落到数据库的二进制协议上走的还是JDBC这套规范。搞懂JDBC连接数据库等于把Java访问数据库这条链路的地基打实了。这篇文章不写空理论就围绕“JDBC怎么连上数据库、连接参数怎么配、连不上怎么排查、连上了怎么安全地做增删改查”这几个核心问题把实操和坑一次说清楚。适合刚入门的新手也适合被连接问题折磨过、想系统补一遍的Java开发。1. 搞清楚JDBC到底解决了什么问题1.1 没有JDBC之前Java连数据库是什么样很多教材上来就讲JDBC API但很少讲它为什么存在。回到Java刚诞生的年代各家数据库厂商都有自己的通信协议如果你的业务系统要同时支持Oracle和MySQL就得分别调用两套完全不同的客户端库代码里全是厂商相关的东西。换数据库等于重写数据访问层这谁受得了。JDBCJava Database Connectivity做的事就是定了一套统一接口DriverManager负责管理驱动Connection表示一个连接Statement负责执行SQLResultSet封装查询结果。数据库厂商按照这套接口各自实现自己的驱动Java程序员只需要面向JDBC接口编程。打个比方JDBC就相当于电器上的国标插座不管你买的是哪家电器只要插头是国标插上就能用。数据库厂商的驱动就是那个把国标插头转换到各自内部电路的转接头。这套设计带来的直接好处有两个一是应用层的代码和数据库厂商解耦迁移数据库时只需要换驱动和调整部分SQL方言二是所有Java生态的工具从连接池到ORM框架都可以建立在同一套抽象之上。你去看Druid、HikariCP、MyBatis的源码底层处理的全是java.sql包下的这套接口这也是为什么我说不管用不用框架JDBC的核心机制都值得认真过一遍。1.2 一次JDBC连接的核心链路拆解一次完整的JDBC连接流程比很多人想象的要复杂。简单说分五步加载驱动、获取连接、创建Statement、执行SQL、释放资源。但每一步背后都有值得深挖的点。加载驱动这一步在老版本里需要显式执行Class.forName(com.mysql.cj.jdbc.Driver)目的是让驱动类被JVM加载并在静态代码块里把自己注册到DriverManager。JDBC 4.0之后只要驱动jar包在classpath里JVM会通过ServiceLoader机制自动发现驱动这行代码可以省略。但有些老项目里还保留着我建议别急着删原因后面在排查部分会细说。获取连接是重点。DriverManager.getConnection(url, username, password)做的事包括解析URL、匹配合适的驱动、建立TCP连接、完成握手认证、初始化会话参数。这个过程的耗时通常是几百毫秒到几秒不等取决于网络和数据库负载。正因如此生产环境绝对不能每执行一次SQL就新建连接必须有连接池兜底。创建Statement和执行SQL这步初学者最容易踩的坑就是直接拼字符串。SELECT * FROM user WHERE name name 这种写法一旦name里有单引号轻则SQL报错重则被注入。后面我会专门讲PreparedStatement的正确姿势。释放资源这步最容易被忽略。Java里Connection、Statement、ResultSet都持有底层资源不关闭会导致连接泄露。如果用了连接池连接不归还连接池的连接最终会被耗尽表现为“连接数用完、新请求全部超时”。这种问题隐蔽排查起来也费劲。2. 驱动选型和连接配置90%的连接问题都出在这里2.1 四种JDBC驱动类型以及你现在应该怎么选教科书上把JDBC驱动分成四类JDBC-ODBC桥接驱动、本地API驱动、网络协议驱动、纯Java驱动。前两类基本可以当历史故事听现在主流场景下用的几乎都是纯Java驱动也就是Type 4驱动它直接通过Socket和数据库的底层协议通信不依赖任何本地库。选驱动时最容易忽略的是版本匹配。拿MySQL来说mysql-connector-java从8.0.31之后版本号变成了com.mysql:mysql-connector-j包名从com.mysql.jdbc.Driver改成了com.mysql.cj.jdbc.Driver。如果你还在用5.x时代的URL模板连8.x驱动大概率会踩时区或SSL的坑。另外驱动版本和数据库版本不需要严格一一对应但建议驱动版本别低于数据库大版本因为旧驱动可能不认识新数据库的认证插件或新特性。这里给一个我常用的驱动选择参考表数据库驱动类典型Maven坐标MySQL 5.xcom.mysql.jdbc.Drivermysql:mysql-connector-java:5.1.49MySQL 8.xcom.mysql.cj.jdbc.Drivercom.mysql:mysql-connector-j:8.4.0PostgreSQLorg.postgresql.Driverorg.postgresql:postgresql:42.7.xOracleoracle.jdbc.OracleDrivercom.oracle.database.jdbc:ojdbc11SQL Servercom.microsoft.sqlserver.jdbc.SQLServerDrivercom.microsoft.sqlserver:mssql-jdbc:12.x有一个经验驱动jar包宁多勿缺但别在classpath里塞两个版本的同一个驱动JVM加载类时可能随机命中其中一个导致你改了配置却不生效这种问题最坑。2.2 URL里的每个参数到底是什么意思JDBC URL是连接配置的核心很多连接失败的根源是对URL理解不透。以MySQL 8.x为例标准格式是这样jdbc:mysql://host:port/database?参数名参数值参数名参数值逐个拆开看。jdbc:mysql:是协议前缀DriverManager靠这个前缀来定位具体驱动。如果拼错成jdbc:mysq系统会提示找不到合适的驱动。host和port是数据库服务器的网络地址和端口MySQL默认3306PostgreSQL默认5432Oracle默认1521。database是你要连接的库名不是数据库服务器的实例名。URL后面跟的参数才是真正的坑聚集地。useSSLfalse或者sslModeDISABLED不加的话8.x驱动默认开启SSL很多内网环境没配证书连接阶段会各种握手失败。serverTimezoneAsia/Shanghai不加的话驱动会用JVM默认时区去解释数据库返回的时间类型一旦两边时区不一致查出来的时间就会差8小时。allowPublicKeyRetrievaltrue这个参数和MySQL 8的caching_sha2_password认证插件有关如果服务器用的就是这个插件且连接走的是非SSL通道必须允许客户端获取公钥否则报错Public Key Retrieval is not allowed。PostgreSQL的URL格式稍有不同参数用?连接但常见参数比如currentSchema、stringtypeunspecified也有各自的用途。所以我的建议是不管用哪个数据库都去官方文档里把URL参数列表翻一遍花十分钟能省好几个小时排查问题的时间。2.3 连接参数里容易被忽略的“连接池级别”配置单条连接的URL参数解决的是“能不能连上”的问题但“连上之后稳不稳”还取决于一批连接池级别的配置。很多人只配了maximumPoolSize就以为完事了其实关键的还有三个。第一个是connectionTimeout也就是从连接池获取连接的等待时间默认30秒。我遇到过系统卡死的情况数据库没挂连接池的连接全被慢查询占住了新请求都在排队等连接表现为接口全部超时。如果connectionTimeout设成5秒系统会快速失败而不是无限等待。第二个是idleTimeout和maxLifetime。idleTimeout是空闲连接多久被回收maxLifetime是连接最多存活多久。数据库服务器一般有自己的wait_timeout默认8小时如果连接池里的连接超过这个时间但池子没做检测下次使用会拿到一个物理上已断开的连接执行第一条SQL时才报错。所以连接池的maxLifetime要小于数据库的wait_timeout。第三个是validationTimeout和连接测试机制。常用的方案是连接获取时执行一条SELECT 1确保物理连接是活的。HikariCP默认没有强制检测但你可以在JDBC URL里加connectionTestStatement或者在池配置里开isValid检测。生产环境建议开否则半夜数据库重启一次早上第一波流量可能全是连接错误。3. 手把手写一个标准的JDBC连接读取流程3.1 从加载驱动到拿到Connection完整步骤下面这段代码是标准的JDBC连接写法我特意保留了try-with-resources的写法因为这是Java 7引入后最不容易泄漏资源的姿势import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; public class JdbcConnectionDemo { public static void main(String[] args) { String url jdbc:mysql://127.0.0.1:3306/demo_db ?useSSLfalse serverTimezoneAsia/Shanghai allowPublicKeyRetrievaltrue characterEncodingutf8; Properties props new Properties(); props.setProperty(user, app_user); props.setProperty(password, your_password); // 连接超时设置单位是毫秒 props.setProperty(connectTimeout, 5000); props.setProperty(socketTimeout, 60000); // 驱动类直接加载不依赖DriverManager自动发现 try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(驱动类未找到请检查依赖, e); } try (Connection conn DriverManager.getConnection(url, props)) { System.out.println(连接成功数据库产品名 conn.getMetaData().getDatabaseProductName()); } catch (SQLException e) { System.err.println(连接失败错误码 e.getErrorCode()); e.printStackTrace(); } } }这里有几个细节值得说明。Class.forName在JDBC 4.0之后其实可以省略但显式保留的好处是如果驱动jar缺失会得到一个明确的ClassNotFoundException而不是一个含糊的“找不到合适的驱动”的SQLException。排查问题的时候错误信息越明确越好。connectTimeout和socketTimeout这两个参数我强烈建议一定要配。connectTimeout限制的是建连阶段的等待时间socketTimeout限制的是SQL执行过程中读响应的时间不配的话一旦网络抖动线程会一直卡在底层的Socket读操作上造成线程池被打满。3.2 Statement、PreparedStatement和ResultSet的正确用法拿到Connection之后就是执行SQL。教科书会告诉你Statement和PreparedStatement都能执行SQL但实际工作中PreparedStatement是唯一应该用的选项。区别在于PreparedStatement在数据库端会被预编译SQL结构固定参数用?占位符传递既避免了字符串拼接的注入风险又能在多次执行同一SQL时复用执行计划。ResultSet是一个游标式的结果集默认情况下只能向前遍历。用完之后一定要关闭否则占用的数据库游标资源不会释放。try-with-resources会把ResultSet、Statement一起关掉但注意关闭顺序先关ResultSet再关Statement最后关Connectiontry-with-resources正好按声明的逆序自动关闭。还有一个日常很容易犯的错拿到ResultSet之后在ResultSet还没有关闭时去执行新的SQL。某些驱动实现里同一个Connection上再次执行Statement会把上一个打开的ResultSet自动关掉导致你后面的数据没读完就报错。真要在一个连接上连续执行多条SQL最好先把上一批结果完整读进内存里的List再执行下一条。3.3 增删改查的标准写法与参数传递写一个完整的增删改查示例很多人觉得简单但细节都在边界情况里。下面这段代码展示了查询和更新两种典型操作import java.sql.*; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; public class CrudDemo { public record User(Long id, String name, Integer age, LocalDateTime createdAt) {} // 查询注意使用try-with-resources public ListUser queryUsers(int minAge) throws SQLException { String sql SELECT id, name, age, created_at FROM t_user WHERE age ? ORDER BY id DESC; ListUser users new ArrayList(); try (Connection conn getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, minAge); // 参数下标从1开始不是0 try (ResultSet rs ps.executeQuery()) { while (rs.next()) { // 游标逐行推进 User user new User( rs.getLong(id), rs.getString(name), rs.getInt(age), rs.getTimestamp(created_at).toLocalDateTime() ); users.add(user); } } } return users; } // 插入并且返回自增主键 public Long insertUser(String name, int age) throws SQLException { String sql INSERT INTO t_user(name, age, created_at) VALUES(?, ?, NOW()); try (Connection conn getConnection(); PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, name); ps.setInt(2, age); int affected ps.executeUpdate(); if (affected 0) { throw new SQLException(插入失败影响行数为0); } try (ResultSet keys ps.getGeneratedKeys()) { if (keys.next()) { return keys.getLong(1); } } throw new SQLException(未获取到自增主键); } } private Connection getConnection() throws SQLException { String url jdbc:mysql://127.0.0.1:3306/demo_db ?useSSLfalseserverTimezoneAsia/Shanghai; return DriverManager.getConnection(url, app_user, your_password); } }这段代码里有几个点需要特别强调。ps.setInt(1, minAge)的参数下标从1开始不是0这个错误频率出乎意料地高。ps.setTimestamp对应数据库的DATETIME/TIMESTAMP类型如果你传的是java.util.Date驱动也能处理但最好统一用java.sql.Timestamp或LocalDateTime配合JDBC 4.2的setObject。插入时传Statement.RETURN_GENERATED_KEYS是拿自增主键的标准姿势如果你不加这个参数直接执行insert之后再去查getGeneratedKeys()大概率返回空。还有一个细节executeUpdate的返回值是受影响的行数更新和删除操作都应该检查这个返回值如果为0说明你要操作的数据根本不存在或者已经被并发修改了这时候应该抛异常而不是静默通过。4. 连接失败、时区错乱、连接池耗尽高频故障排查实录4.1 从ClassNotFoundException到CommunicationsException连接数据库报错的信息五花八门但归根结底可以归成几类每一类的排查路径都不一样。如果报ClassNotFoundException: com.mysql.cj.jdbc.Driver说明驱动jar没在classpath里。用Maven的话检查依赖坐标对不对用传统方式部署的话检查lib目录下有没有jar。这里有个高级坑一个Tomcat里部署多个应用每个应用的lib是隔离的你把驱动放到了Tomcat的lib还是应用自己的lib类加载路径完全不同。放错位置会导致某些应用能连某些不能连。如果报No suitable driver found for jdbc:mysql://...说明DriverManager里没有匹配URL前缀的驱动。可能原因包括驱动jar没加载成功、URL协议头写错、用了DriverManager但驱动是通过ServiceLoader加载失败。排查时可以手动执行Class.forName如果这行能过大概率是URL前缀问题。如果报CommunicationsException: Communications link failure这是MySQL驱动对底层Socket异常的统一包装。它可能是网络不通、端口被封、防火墙拦截、MySQL服务挂了也可能是驱动和数据库版本不兼容。排查路径应该是先telnet 数据库IP 3306看看端口通不通再确认数据库服务进程是否存活最后看MySQL的错误日志。大部分时候这个问题跟应用代码没关系是环境问题。每次拿到异常别急着Google先把完整堆栈读一遍。SQLException的message通常很长里面会包含驱动版本、服务器版本、具体出错环节比如during handshake还是during query。根据出错环节能快速定位是建连阶段还是执行阶段的问题。4.2 时区、SSL和连接超时这三兄弟这三个问题几乎是MySQL 8连接初期最常见的三个坑。我列一张对照表方便你按症状排查报错或异常现象根因解决方案The server time zone value CST is unrecognized驱动无法识别数据库会话时区URL加serverTimezoneAsia/Shanghai查询出的时间比数据库实际时间少8小时或多8小时JVM时区和数据库时区不一致统一设置连接时区和JVM时区SSLConnectionException或SSL握手失败驱动默认启用SSL但服务端没配证书URL加useSSLfalse或sslModeDISABLEDPublic Key Retrieval is not allowedMySQL 8默认认证插件需要公钥URL加allowPublicKeyRetrievaltrueConnection is not available, request timed out连接池等待超时检查慢SQL、增大池上限、缩短connectionTimeout半夜过后第一次请求必报连接错误连接空闲超过数据库wait_timeout配置连接池maxLifetime和空闲检测时区这个问题我多解释一句。数据库存的TIMESTAMP是不带时区的MySQL驱动在读取时会把它解释成服务器时区下的时间然后转成Java的Timestamp对象。如果服务器时区是UTCJVM时区是Asia/Shanghai最终打印出来的时间就会差8小时。解决思路是让“数据库会话时区、驱动转换时区、JVM解析时区”三者保持一致最省事的做法是全部统一成Asia/Shanghai。SSL这块内网环境默认useSSLfalse是安全的因为内网流量一般不走公网。但如果你连的是云数据库公网地址建议反着来useSSLtrue并配置服务器证书防止账号密码和查询数据在链路上被截获。安全不是一刀切看场景。连接超时的排查则需要联系DBA一起看。数据库没挂但连接池耗尽的情况通常意味着有慢查询占着连接不释放或者有连接泄漏没归还。先看慢查询日志再查应用的连接池监控Druid和HikariCP都有现成的监控指标看看active连接数、等待线程数能很快定位是池子太小还是SQL太慢。4.3 一套屡试不爽的连接问题排查顺序排查JDBC连接问题我建议按这个顺序来避免像无头苍蝇一样乱试。第一步把完整异常信息和控制台输出的所有日志贴出来包括Caused by部分。很多时候真正的根因在Caused by里而不在最外面的message里。第二步确认驱动版本、数据库版本、JDK版本三者兼容去驱动官方文档看支持的矩阵。第三步用一个最简单的测试类只做DriverManager.getConnection排除业务代码干扰。如果这一步成功问题在业务层失败问题在环境或配置。第四步用数据库自己的客户端比如mysql命令行去连确认数据库本身服务正常。如果命令行能连而JDBC不能连重点检查驱动参数比如认证插件、SSL、公钥获取。第五步抓包看TCP层确认连接请求有没有到达数据库。这套流程跑下来90%的问题在第三步就能定位。剩下10%属于数据库配置层面的边边角角比如max_connections打满、skip-networking开启、账号host限制不匹配等这些需要结合数据库侧的状态变量看。5. 从单连接走向连接池生产环境的正确姿势5.1 为什么必须用连接池以及HikariCP参数怎么配有人说我用上面的代码每次执行SQL都新建一个Connection不行吗技术上能跑但生产环境不该这么干。建连的成本包含TCP三次握手、MySQL认证握手、初始化会话变量整套流程下来少说几十毫秒高并发下这个开销直接变成系统的瓶颈。更重要的是数据库对并发连接数是有限制的max_connections默认151单机应用每个请求新建连接压力一上来数据库的连接数瞬间被打满。连接池的核心思路是预先创建一批连接放在池子里应用需要时借出、用完归还所有权在池子手里。这样建连开销只发生一次后续都是复用。HikariCP是当前性能最好的连接池Spring Boot 2.x以后默认就是它。它的参数配置看着简单但有几个值需要认真调HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://127.0.0.1:3306/demo_db?useSSLfalseserverTimezoneAsia/Shanghai); config.setUsername(app_user); config.setPassword(your_password); config.setDriverClassName(com.mysql.cj.jdbc.Driver); config.setMaximumPoolSize(20); config.setMinimumIdle(5); config.setConnectionTimeout(5000); config.setIdleTimeout(600000); config.setMaxLifetime(1800000); config.setConnectionTestQuery(SELECT 1); HikariDataSource dataSource new HikariDataSource(config);maximumPoolSize的公式在HikariCP官方文档里有connections ((core_count * 2) effective_spindle_count)。但我觉得实践里更重要的口径是峰值并发请求数除以单连接能撑起的请求数再留20%冗余。设置过大的池子没用连接是数据库侧的稀有资源池子再大数据库连接数上限卡死等待时间反而更长。maxLifetime必须小于数据库的wait_timeout前面已经提过。connectionTestQuery的作用是借出连接时测试存活但HikariCP官方文档说如果你用了JDBC4的Connection.isValid()这个参数可以不配。MySQL的驱动原生支持isValid所以一般不用额外配test query。5.2 把连接池接进Spring Boot以及DataSource的正确打开方式Spring Boot里配置HikariCP非常顺滑因为框架已经内置了。你只需要在application.yml里写spring: datasource: url: jdbc:mysql://127.0.0.1:3306/demo_db?useSSLfalseserverTimezoneAsia/Shanghai username: app_user password: your_password driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 5000 idle-timeout: 600000 max-lifetime: 1800000但是这里有一个非常关键的坑很多人以为数据源配置好了Spring会自动帮我们把事务和连接管理全部搞定于是干脆不关心底层连接的事情。结果线上出现“获取连接超时”的时候根本不知道从哪里下手。我建议即使你用Spring Boot也要理解底层的执行链Controller调用ServiceService方法上的Transactional注解会让Spring在方法开始前从DataSource获取一个连接绑定到当前线程方法内所有DAO操作共用这个连接方法结束后归还。一旦方法内部有慢SQL这个连接被占住的时间就是整个事务的时长并发一高池子就容易被耗尽。还有一个容易被忽略的点Transactional的rollbackFor默认只对RuntimeException和Error生效。如果你在事务方法里抛了一个自定义的checked异常事务不会回滚数据会处于半提交状态。这个跟连接管理无关但却是事务使用中最常踩的坑一并提醒。6. 事务边界和批处理连接之外的进阶必修课6.1 在JDBC层面理解ACID的落点连接拿到手之后事务就是下一个必须掌握的概念。默认情况下Connection处于自动提交模式每执行一条SQL就提交一次。但业务往往要求多条SQL要么全部成功要么全部失败这时候就要关掉自动提交手动控制事务边界。标准写法是这样try (Connection conn getConnection()) { conn.setAutoCommit(false); // 关闭自动提交 try { try (PreparedStatement ps1 conn.prepareStatement( UPDATE t_account SET balance balance - ? WHERE id ?)) { ps1.setBigDecimal(1, new BigDecimal(100.00)); ps1.setLong(2, 1001L); ps1.executeUpdate(); } try (PreparedStatement ps2 conn.prepareStatement( UPDATE t_account SET balance balance ? WHERE id ?)) { ps2.setBigDecimal(1, new BigDecimal(100.00)); ps2.setLong(2, 1002L); ps2.executeUpdate(); } conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } }事务的隔离级别也是连接级别的重要属性。MySQL默认是REPEATABLE_READPostgreSQL默认是READ_COMMITTED。如果你在一个事务里对同一条数据先查询后更新又希望查询结果和更新后的结果一致要注意快照读和当前读的区别。练习阶段不用深究但在并发业务中SELECT ... FOR UPDATE和普通SELECT的行为差异会直接影响业务正确性这个可以之后单独写一篇深入展开。另外事务和连接池配合时要记住连接归还到池子之前事务状态必须被清理。HikariCP会在连接归还时自动rollback未提交的事务并恢复autoCommit默认值避免污染下一个使用者。但如果你用的是自己管理的连接务必在finally里处理否则一个连接残留的未提交事务会让下一个拿到该连接的操作看到脏数据或者干脆锁死相关行。6.2 批量插入为什么能快一个数量级批量处理是JDBC里见效最明显的优化手段之一。比如要往一个表里插入一万条数据一条一条executeUpdate每条都走一次网络往返耗时可能几十秒。用批量提交性能提升一个数量级不止。public void batchInsert(ListUser users) throws SQLException { String sql INSERT INTO t_user(name, age, created_at) VALUES(?, ?, NOW()); try (Connection conn getConnection()) { conn.setAutoCommit(false); try (PreparedStatement ps conn.prepareStatement(sql)) { for (User user : users) { ps.setString(1, user.name()); ps.setInt(2, user.age()); ps.addBatch(); // 加入批量队列 // 每500条执行一次避免batch太大占用过多内存 if (users.size() % 500 0) { ps.executeBatch(); conn.commit(); } } ps.executeBatch(); // 执行剩余批次 conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } } }批量提交能快是因为减少了客户端和数据库之间的网络交互次数同时数据库侧可以连续处理多条插入。但要注意rewriteBatchedStatementstrue这个MySQL驱动的URL参数没加它之前驱动会把批量SQL拆成一条一条发给服务器加了之后驱动会把多条INSERT重写成一条多值的INSERT性能进一步提升。这个参数值得在批量场景下专门加上。批量操作还有一个很隐蔽的坑executeBatch如果其中一条失败整个批次的状态会因为驱动不同而不同有的驱动会把批次全部回滚有的会保留成功的部分。建议设置rewriteBatchedStatementstrue之后在事务里执行批量并且对影响行数求和做校验不一致就报错。7. 我在实际项目中沉淀的几条连接管理经验最后分享几点真正经历过生产环境考验的经验这些细节不写进官方文档但往往能救命。第一连接字符串永远不要硬编码在代码里。就算现在是个人项目也应该走配置中心或环境变量。我见过同事把带密码的JDBC URL直接提交到Git仓库然后被扫描工具报警的事改密码、清历史折腾了一下午。第二数据库密码不要用明文存储在配置文件中。至少用Jasypt之类的组件做加密密钥放在部署环境变量里。云厂商的数据库服务一般都支持类似KMS的密钥管理能用就用成本很低。第三给连接池加上监控告警。HikariCP的指标可以暴露到Micrometer接入Prometheus之后对“活跃连接数”、“等待获取连接线程数”、“连接创建数”三个指标设置阈值告警。数据库连接问题很少直接导致宕机更多是默默拖慢应用等你发现的时候系统已经卡了半小时。第四在应用启动时做一次连接自检。比如Spring Boot的ApplicationRunner里从连接池拿一个连接执行SELECT 1成功才继续启动失败直接fail-fast。这样配置问题在发布阶段暴露而不是等服务上线后第一个请求进来才炸。第五定期阅读驱动升级日志。数据库驱动不是越新越好但也不能一个版本用到天荒地老。MySQL的驱动偶尔会调整默认行为比如某个小版本把useSSL默认值从false改成true或者把时区处理逻辑改了升级前多看一眼release notes性价比极高。JDBC连接数据库这件事写代码五分钟调参两小时排查一整天。但正因为这些坑都踩过一遍你才能对Java访问数据库这条链路有真正的掌控感。希望这篇内容能帮你少走几次弯路下次再遇到连接问题先稳住按步骤来问题会比你想象中更快现出原形。
返回列表