ARTICLE DETAIL

资讯详情

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

JDBC核心原理与实战:从连接到连接池,高频报错排查指南

JDBC核心原理与实战:从连接到连接池,高频报错排查指南 做Java开发这么多年数据库连接这块绕来绕去终究绕不开JDBC。哪怕你现在用的都是MyBatis、Hibernate、Spring Data JPA底层走的还是JDBC这套规范。说白了JDBC就是Java程序和数据库之间的一座桥Sun公司定了一套接口规范各家数据库厂商MySQL、Oracle、达梦、Hive这些按照规范写自己的驱动实现。你写代码的时候面向接口编程驱动的事情交给厂商这就是JDBC最核心的设计思路。这篇文章从一个老开发的角度把JDBC概述这件事讲透。覆盖从Connection、Statement到ResultSet的核心API连接数据库的标准六步流程以及我在实际项目中踩过的高频坑——包括No suitable driver found、Hive2的could not open client transport、SQL injection violation这类报错的排查方法。适合刚学JDBC的菜鸟也适合用框架久了想回头补基础的中间层开发。1. JDBC到底是什么先把这个概念掰开揉碎1.1 JDBC的定位与核心价值JDBC全称Java Database Connectivity直译就是Java数据库连接。它本质上是Java标准库中定义的一组接口位于java.sql和javax.sql包里。这组接口做了什么它把连接数据库、发SQL、取结果这些动作全部抽象成标准API至于底层协议怎么走是TCP还是本地socket驱动内部怎么实现JDBC一概不管交给各个数据库厂商的JDBC Driver去做。打个比方JDBC就像USB接口标准。你买一个U盘不需要关心U盘内部是哪种闪存芯片只要接口是USB的插上就能用。换数据库也一样业务代码里你用的是Connection、PreparedStatement这些标准接口只要更换驱动jar包和连接URL代码基本不用改就能从MySQL切到Oracle或者从Oracle切到达梦。当然实际切换没这么轻松SQL方言、数据类型、驱动行为都有差异但JDBC至少从架构上把这种切换成本降到了最低这就是它最大的价值。JDBC解决了什么问题在JDBC诞生之前Java程序访问数据库需要依赖数据库厂商提供的私有API每个厂家的API都不一样代码和数据库强耦合。JDBC出现之后Sun制定了这套规范厂商负责实现开发者面对统一接口数据库访问才成了Java生态里一个标准化的基础能力。今天你见到的MyBatis、Hibernate、Spring Data JPA本质上都是在这套API之上的封装你把封装层扒开最终执行SQL的还是JDBC的Statement。1.2 JDBC的四个核心角色JDBC的接口体系里有几个核心角色理解它们是掌握JDBC的关键。第一个是Driver数据库驱动。它是一个接口具体实现放在各厂商的jar包里比如MySQL的com.mysql.cj.jdbc.DriverOracle的oracle.jdbc.driver.OracleDriver达梦的dm.jdbc.driver.DmDriver。驱动负责实现JDBC接口和数据库之间的真实通信协议也就是真正干活的底层模块。第二个是DriverManager驱动管理器。它是java.sql包里的一个类职责是管理已注册的驱动并根据连接URL选择合适的Driver来建立物理连接。Class.forName(com.mysql.cj.jdbc.Driver)这行代码干的事就是把驱动类加载进JVM触发其静态代码块让驱动把自己注册到DriverManager里。JDBC 4.0之后有了SPI机制驱动jar包里的META-INF/services/java.sql.Driver文件会自动被扫描其实可以不用显式Class.forName但很多人习惯写上也没问题。第三个是Connection数据库连接。它代表一次真实的数据库会话底层对应着一个物理网络连接。Connection负责创建Statement管理事务提交、回滚、设置隔离级别还能拿到数据库元数据。它是会话的入口也是所有数据库操作的载体。第四个是ResultSet结果集。它是SQL查询返回结果的映射内部维护一个游标机制类似于一个指向当前行的指针。很多新手把ResultSet理解成一张表其实不是它更像一个迭代器默认只会往下走next()方法把游标移动到下一行返回boolean表示是否还有数据。这四个角色互相配合就构成了JDBC最基础的工作模式DriverManager找Driver建ConnectionConnection造StatementStatement执行SQL返回ResultSet最后从ResultSet里取数据。整个流程记住这一条主线JDBC就不难了。2. JDBC连接数据库标准六步与URL写法2.1 标准六步从加载驱动到释放资源JDBC操作数据库的完整流程业内把它概括为六步我建议所有初学者都把这六步刻在脑子里第一步加载驱动。传统写法是Class.forName(com.mysql.cj.jdbc.Driver)作用是让驱动类被加载并注册到DriverManager。前面说过JDBC 4.0以后这一步可以省略但写上没坏处尤其在老版本数据库或特殊类加载环境下。第二步获取连接。通过DriverManager.getConnection(url, username, password)拿到Connection对象。这一步是最容易出问题的地方URL写错、网络不通、账号密码错误、数据库服务没启动全在这一步暴露。第三步创建Statement。通过connection.createStatement()或者connection.prepareStatement(sql)创建。这里我强烈建议永远用prepareStatement原因后面细说。第四步执行SQL。Statement有三个执行方法executeQuery()执行查询返回ResultSetexecuteUpdate()执行增删改返回影响行数execute()执行任意SQL返回boolean。选哪个方法取决于SQL类型选错了轻则功能不对重则报错。第五步处理结果。从ResultSet里按列名或列索引取值然后封装成对象或者直接输出。第六步释放资源。按ResultSet、Statement、Connection的顺序依次关闭先开后关。通常放在finally块里或者现在可以直接用try-with-resources语法Java 7之后推荐这个方式代码简洁还不会忘关。一个完整的示例是这样// JDBC六步完整示例Java 7 try-with-resources String url jdbc:mysql://192.168.1.100:3306/test_db?useSSLfalsecharacterEncodingUTF-8serverTimezoneAsia/Shanghai; String user root; String password 123456; String sql SELECT id, name, age FROM user WHERE age ?; try (Connection conn DriverManager.getConnection(url, user, password); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, 18); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { int id rs.getInt(id); String name rs.getString(name); int age rs.getInt(age); System.out.println(id - name - age); } } } catch (SQLException e) { e.printStackTrace(); }我遇到过不少同事前面五步都很溜就是不关资源。别小看这一步连接不释放连接池迟早耗尽生产环境你就等着半夜被电话叫醒吧。用了try-with-resources之后Connection、Statement、ResultSet都会自动关闭这个习惯越早养成越好。2.2 URL怎么写MySQL、Oracle、达梦、Hive对比连接URL是JDBC中使用频率最高、也最容易写错的一个配置项。URL的格式是各个驱动自己定义的但大体上都是jdbc:子协议:数据源。子协议通常就是数据库类型名称。MySQL的URL是jdbc:mysql://host:port/databaseName默认端口3306。老版本驱动类名是com.mysql.jdbc.DriverMySQL Connector/J 8.0之后改成了com.mysql.cj.jdbc.Driver包名多了cj很多人升级驱动后一直报错就是因为Class.forName里写的还是旧类名。Oracle的URL是jdbc:oracle:thin:host:port:serviceName比如jdbc:oracle:thin:192.168.1.100:1521:orcl。注意这里有个thin这是Oracle提供的纯Java驱动类型还有oci类型需要装Oracle客户端一般我们只用thin就够了。达梦数据库的URL是jdbc:dm://host:port默认端口5236。热词里那个no suitable driver found for jdbc:dm://192.168.102.161:30184:5236就是典型的达梦连接问题驱动没加载对或者jar包没引入一会儿在排查部分细说。Hive的JDBC URL比较特殊是jdbc:hive2://host:port/databaseName默认端口10000。它的驱动类是org.apache.hive.jdbc.HiveDriver注意这个驱动是HiveServer2的JDBC驱动不是老版的org.apache.hadoop.hive.jdbc.HiveDriver。Hive连接还要考虑是否启用Kerberos认证启用的话URL和配置都会复杂一些。把这些URL整理成一个表格方便对照数据库URL格式默认端口驱动类MySQLjdbc:mysql://host:port/db3306com.mysql.cj.jdbc.DriverOraclejdbc:oracle:thin:host:port:service1521oracle.jdbc.driver.OracleDriver达梦jdbc:dm://host:port5236dm.jdbc.driver.DmDriverHive2jdbc:hive2://host:port/db10000org.apache.hive.jdbc.HiveDriverPostgreSQLjdbc:postgresql://host:port/db5432org.postgresql.Driver这张表建议存下来记不住的时候翻一眼比临时去搜索引擎翻强多了。尤其是驱动类名每个数据库都不一样还经常因为版本变化改名这是踩坑重灾区。2.3 连接参数别乱加几个高频参数的真相URL后面可以带参数用?分隔多个参数用连接。这些参数直接影响连接行为但很多人是抄来的根本不知道自己在配什么。这里说几个最常见的。characterEncodingUTF-8指定字符集编码。如果不设置驱动的默认字符集可能跟数据库不一致导致中文乱码。我建议URL、数据库表结构、代码里全部统一用UTF-8少很多麻烦。useSSLfalseMySQL 8.0之后驱动默认把SSL开启如果你的MySQL服务端没配SSL证书连接时就会报错或者有一段很长的警告。开发环境直接useSSLfalse关掉生产环境按安全要求来。serverTimezoneAsia/Shanghai这个参数坑了不少人。MySQL 8.0的驱动要求设置时区否则报Server returns invalid timezone或者连接成功但时间对不上。注意Asia/Shanghai中间的斜杠在URL里要处理一下有些地方会写成serverTimezoneGMT%2B8代表东八区。connectTimeout和socketTimeout一个是建立连接的超时时间一个是读写操作超时。默认值可能是0也就是无限等待生产环境一定要设置不然数据库故障时应用会卡死一片。我之前处理过一次事故数据库主库挂了应用因为connectTimeout没设置所有线程都卡在建立连接上整个服务像死了一样最后加上了connectTimeout5000故障时快速失败应用就能及时切换到降级逻辑。allowMultiQueriestrue允许一条SQL里分号分隔多条语句。这个谨慎开启不是特殊情况别开它也是SQL注入风险放大器之一。3. 核心API深度拆解Statement家族与ResultSet3.1 PreparedStatement为什么它是默认选择Statement家族有三个成员Statement、PreparedStatement、CallableStatement。三者关系是继承关系PreparedStatement继承自StatementCallableStatement继承自PreparedStatement。Statement用于执行静态SQL直接把SQL字符串发给数据库。它的问题有两个一是每次执行都要让数据库编译SQL性能差二是SQL里拼了用户输入的话有注入风险。比如String sql SELECT * FROM user WHERE name input 用户输入一个1 OR 11SQL就变成了SELECT * FROM user WHERE name 1 OR 11全表数据就查出来了。这就是SQL注入。PreparedStatement解决这两个问题。它的SQL是预编译的用?占位符代替变量然后通过setString、setInt等方法给占位符赋值。预编译之后SQL的执行计划可以被数据库缓存复用性能好。更重要的是参数值通过set方法传递驱动会对参数做转义处理用户输入只被当作数据不会被拼进SQL结构里从根上切断注入。热词里那条jdbc链接mysql caused by: java.sql.sqlexception: sql injection violation就跟这个有关。有些数据库或中间件比如安全网关、Sharding JDBC某些配置会检测SQL中的注入特征一旦发现就把异常抛出来。后面排查部分细讲。CallableStatement用于调用存储过程支持注册输出参数比如registerOutParameter和getXXX获取返回值。存储过程用得少了但金融、银行类老系统还是很常见这玩意儿得会。3.2 ResultSet游标、类型映射与元数据ResultSet的核心是游标机制。刚拿到ResultSet时游标指向第一行之前的位置必须调用next()把游标移到第一行才能取值每次next()返回true说明当前行有数据。这是一个顺序向前的迭代过程默认的ResultSet类型是TYPE_FORWARD_ONLY只能往前走不能回头。取值方法按照列的类型来getString、getInt、getLong、getDate、getBigDecimal等等。参数可以是列的索引从1开始也可以是列名。用列名可读性好但性能略差一点点用索引性能好但可读性差。我的习惯是写通用工具类时用索引业务代码里用列名。还有两个getObject的重载一个按类型自动转换一个配合Class类型做强制转换。复杂场景下很有用。ResultSet的元数据是从ResultSetMetaData里拿的通过rs.getMetaData()得到。它可以获取列数、列名、列类型、列的精度等信息。很多通用查询工具和代码生成器就是靠这个东西实现的。你要写一个通用的结果集转JSON工具核心就是遍历ResultSetMetaData拿到每一列的名字和值。另外提醒一个点ResultSet默认一次只取一行数据到内存如果你的查询结果是上万行靠next()一行行读网络IO会很多性能堪忧。这时候可以用fetchSize来设置一次抓取的行数比如stmt.setFetchSize(1000)或者让驱动使用游标式的流式读取。MySQL的流式读取要先把fetchSize设置为Integer.MIN_VALUE细节比较多用的时候去查对应版本的驱动文档。3.3 事务边界与批量提交JDBC默认情况下每条SQL执行完就自动提交也就是autocommittrue。但在业务场景里一个操作往往涉及多条SQL要么全成功要么全失败这时候就要手动控制事务。标准做法是拿到Connection后先connection.setAutoCommit(false)然后执行多条SQL全部成功后connection.commit()任何一步出异常就connection.rollback()。注意事务的边界一定要清晰事务范围之外不要随意调用commit或rollback。批量操作是提升性能的一个重要手段。比如一次性插入一万条数据如果一条条执行executeUpdate每一轮都是完整的网络往返慢得离谱。用addBatch()和executeBatch()可以把多条SQL攒在一起发送给数据库一次批量执行性能提升可能是一个数量级。MySQL默认还要在URL上加上rewriteBatchedStatementstrue否则批处理只是客户端一个个发没有真正合并这个参数很容易被忽略。事务隔离级别也是连接层的重要配置。Connection支持setTransactionIsolation可选值有TRANSACTION_READ_UNCOMMITTED、TRANSACTION_READ_COMMITTED、TRANSACTION_REPEATABLE_READ、TRANSACTION_SERIALIZABLE。MySQL默认是可重复读Oracle默认是读已提交。在代码里显式设置隔离级别必须放在事务开启之前否则不生效。4. 高频报错排查一线排障实录4.1 No suitable driver found90%是这四种情况这个报错太经典了热词里就有一条no suitable driver found for jdbc:dm://192.168.102.161:30184:5236。完整报错一般是这样的java.sql.SQLException: No suitable driver found for jdbc:dm://xxx。意思是DriverManager里没有任何驱动能识别这个URL。我总结的常见原因有四种。第一种驱动jar包没引入。这是最基础的检查一下classpath里有没有对应的驱动jar。MySQL对应mysql-connector-java或mysql-connector-jOracle对应ojdbc8或ojdbc11达梦对应DmJdbcDriver18.jar。用Maven的话检查依赖坐标和版本别引了个空的或者被exclude掉的依赖。第二种驱动类没加载。JDBC 4.0之前必须显式Class.forName4.0之后虽然有SPI自动加载但如果你的类加载器环境特殊比如在应用服务器里或者自研的类加载隔离框架里SPI可能不生效这时候需要手动Class.forName(dm.jdbc.driver.DmDriver)显式加载。第三种URL前缀写错。每个驱动都有自己认的URL前缀驱动就是靠前缀判断这个连接该不该我管的。MySQL认jdbc:mysqlOracle认jdbc:oracle达梦认jdbc:dm。如果你把达梦的URL写成了jdbc:mysql://192.168.102.161:30184:5236那MySQL的驱动也认不了这个古怪的URL自然报No suitable driver found。第四种驱动的jar版本和数据库版本不匹配。这个比较隐蔽驱动识别URL成功了但握手协议对不上报的就不一定是这个错了但也可能因为初始化失败被当成找不到驱动。所以遇到这类报错除了看URL还要仔细看完整堆栈里有没有更底层的异常。排查这个报错我的思路是先确认URL前缀跟数据库类型匹配再确认jar包确实在classpath里再确认没有多个版本的驱动jar互相冲突最后实在不行加Class.forName硬加载试试。按这个顺序排查基本都能解决。4.2 Hive2连接失败could not open client transport热词里那条could not open client transport with jdbc uri: jdbc:hive2://127.0.0.1:10000是HiveServer2连接时的经典报错。出现这个问题的原因比较多我按经验排序一下。最常见的是HiveServer2服务根本没启动。hive --service metastore只是启了元数据服务JDBC连的是HiveServer2需要单独启动命令是hive --service hiveserver2或者在新版本里用hive --service hiveserver2 start。启动后默认监听10000端口可以先在服务器上执行lsof -i:10000或者netstat -tlnp | grep 10000确认端口有没有在监听。其次是版本或协议不匹配。Hive的JDBC驱动版本要和HiveServer2版本匹配老驱动连新服务经常出现握手失败。还有HiveServer2的传输模式默认binary模式但有些环境配置成了http模式这时候URL就要变成jdbc:hive2://host:10000/db;transportModehttp;httpPathcliservice否则连不上。还有一个很容易忽略的点认证。HiveServer2如果配置了LDAP或者Kerberos认证URL和连接参数都不一样。Kerberos模式下URL里要带principal比如jdbc:hive2://host:10000/;principalhive/hostREALM而且客户端环境必须配置好Kerberos的票据。有些同事在Kerberos环境里用非Kerberos的URL连报的就是could not open client transport。最后内存不足也会导致HiveServer2起不来。HiveServer2是常驻服务默认堆内存配置可能不够启动时看日志里有OutOfMemoryError的话去调整HIVE_SERVER2_HEAPSIZE或hiveserver2的JVM参数。这类问题日志永远是最好的老师先看服务端日志别只顾着在客户端反复试。4.3 SQL injection violation参数化未到位与安全网关热词里jdbc链接mysql caused by: java.sql.sqlexception: sql injection violation这个报错很多时候是在企业安全网关或者数据库防火墙环境下出现的。这类中间件会拦截SQL语句做特征检测如果SQL里有明显的注入特征比如多语句、恒真条件、注释符、UNION SELECT之类的直接拒绝执行抛SQLExceptionmessage里带sql injection violation字样。遇到这个报错先冷静分析自己SQL是不是真的有问题。常见情况是SQL里确实拼接了用户输入且拼进去的值碰巧触发了检测规则。比如用户搜索框里输入了1 OR 11这样的内容你如果用拼接的方式构造SQL网关就拦下了。解决办法也很简单把所有的SQL改成PreparedStatement参数化写法。参数化之后用户输入作为参数绑定传参不会出现在SQL文本里网关检测看到的是完整的预编译语句占位符自然不会误判。这一步是根治。如果项目里还在用字符串拼接SQL这种老写法趁着这个机会赶紧改掉不管有没有网关拦SQL注入漏洞本身就是在裸奔。但要注意即使用了PreparedStatement某些网关产品还是会检测整个SQL文本里的关键特征词比如一个字段值里本身包含or 11这种字符串经过PreparedStatement绑定后SQL文本里是?不会触发但如果你用的是存储过程存储过程内部的SQL文本有时会以文本形式出现在二进制协议里也可能会被某些严格模式的网关误伤。这种情况就要跟安全团队确认白名单或者检测规则的误报处理。另外补充一下有些情况是自己在代码里写了动态SQL拼接然后用PreparedStatement只处理了参数值但表名、列名、排序字段是拼进去的。这种标识符拼接风险也很高而且容易被网关检测。表名列名这种结构性的东西不能用占位符替代只能白名单校验或做映射不要直接拼用户输入。4.4 JMeter JDBC Request参数化压测脚本的常见问题JMeter里做数据库压测JDBC Request采样器是非常常用的。热词里就有jmeter jdbc request参数化这里说一下实操中容易踩的坑。JMeter使用JDBC采样器前要先配置JDBC Connection Configuration这是连接池配置元件。里面填Database URL、JDBC Driver class、Username、Password还有连接池参数。Variable Name for created pool这个字段很重要必须填一个变量名JDBC Request里要引用这个变量名否则它俩对不上请求就找不到连接池报错说没有可用的连接。参数化的方式是JDBC Request里的Parameter values和Parameter types。SQL语句里用?占位符Parameter values里填参数值可以用JMeter函数或变量比如${userId}Parameter types里填对应的JDBC类型比如VARCHAR、INTEGER。类型不填有时候也能跑但数据类型敏感的场景一定要填。比如日期类型的参数类型填不对数据库端可能报转换异常或者查出来的数据不对。另外一个高频问题是Result variable name。如果你在JDBC Request里设置了Result variable name查询结果会存成对象后续通过${varName_1}这种方式取第一行${varName_1_columnName}取第一行某个列的值很多人在这个命名规则上卡住。要注意这个变量是字符串形式的如果结果集太大还会撑爆内存压测时慎用尽量在SQL层面就做好聚合只查出需要的字段。还有一个实际经验压测过程中JDBC Connection Configuration里的Max Connections不要设太小不然压测线程一多连接池不够用大量请求排队压出来的性能数据全是假的。至少要大于等于并发线程数比如并发50线程Max Connections至少设50最好留点余量设到80。4.5 Flink JDBC连接器异常流批场景下的连接管理热词里还有一条flink的jdbc连接器异常这个属于大数据场景了。Flink的JDBC连接器在做维表关联、数据写入时非常常用但问题也不少。常见异常之一是连接被数据库主动断开。Flink任务长时间运行数据库侧的wait_timeout到了把空闲连接断了Flink连接器如果没做好自动重连任务就挂。解决思路是给JDBC连接设置合理的socketTimeout和连接保活策略或者用带连接池功能的实现。有些团队会在Flink的JDBC连接配置里启用HikariCP连接池连接校验和自动重连都交给连接池管理比裸连接稳得多。另一个是连接泄漏导致的连接耗尽。Flink提交数据时如果批量写入失败重试逻辑写得不好连接没有正确关闭长时间运行后连接池耗尽报错类似Too many connections或者连接超时。排查时看两个地方一是任务的并行度是不是太高导致同时打开的连接数超过数据库上限二是写入失败时有没有正确关闭statement和connection。并行度高的任务建议估算一下峰值连接数并行度乘以每个并行实例需要的连接数不要超过数据库max_connections的70%。跟Flink JDBC相关的一个经验是维表JOIN场景里JDBC连接器的缓存策略很重要。默认情况下每条记录都会查一次数据库流量一大数据库根本扛不住。建议设置缓存比如lookup.cache.max-rows和lookup.cache.ttl让热数据走本地缓存数据库只承受缓存未命中的查询。这个优化做完维表JOIN的性能通常能提升一个量级以上。5. 连接池与Sharding JDBC生产环境真正在用的东西5.1 DataSource与连接池为什么不能每次新建连接裸写JDBC每次getConnection都是建立一个新的物理连接这个过程要经过TCP握手、数据库认证、协议协商开销非常大。一台数据库能同时维持的连接数也有限高并发场景下根本扛不住。所以生产环境绝对不会直接用DriverManager而是用连接池。连接池的原理很简单预先创建一批连接放在池子里用的时候借出来用完还回去避免反复创建销毁。Java标准里把连接池抽象成了javax.sql.DataSource接口getConnection()从池子里拿连接。HikariCP、Druid、C3P0、DBCP都是DataSource的实现。HikariCP是目前Spring Boot默认的性能好号称快得可怕。Druid是阿里开源的功能全有监控、SQL防火墙、慢SQL日志国内用的多。选哪个看场景追求极致性能和标准场景用HikariCP要可视化监控和SQL审计用Druid。连接池的关键参数要先理解再配置maximumPoolSize最大连接数minimumIdle最小空闲连接数connectionTimeout获取连接的超时时间idleTimeout空闲连接存活时间maxLifetime连接最大存活时间。特别注意maxLifetime要小于数据库侧的wait_timeout否则连接被数据库先断了池里还留着死连接借出去就报错。这个对应关系很多人不知道等到线上莫名其妙报Communications link failure才反应过来。我在生产里遇到过一个问题连接池设置了最大20个连接但数据库的max_connections是150按理说够用可到了高峰所有请求都卡在获取连接上连接池被打满。查了半天发现是有一条慢SQL执行了10秒以上把连接全占住了后续请求全部排队。最后靠解决慢SQL和给连接池加监控才缓解。所以连接池参数不是越大越好关键还要看SQL执行效率和监控能力。5.2 Sharding JDBCJDBC之上的分库分表中间件Sharding JDBC现在叫ShardingSphere-JDBC是Apache ShardingSphere的子项目定位是轻量级的Java中间件以jar包形式直接嵌入应用在JDBC层做分库分表、读写分离、分布式事务。它的用法很巧妙应用代码里用的还是JDBC标准API但DataSource换成了ShardingSphere提供的实现。你在配置里声明分片规则比如订单表按照order_id取模分成4个库Sharding JDBC在内部把逻辑SQL改写成真实SQL路由到对应的物理库表上执行。对上层应用来说看到的还是那一张逻辑表。优点很明显不需要额外部署中间件服务没有额外的网络跳转性能损耗相对小。对应用侵入也小基本不用改业务代码。缺点是它跑在应用进程里分片规则变更需要应用发版而且和数据库的连接数是按分片数量乘以应用实例数算的分片多了连接数压力大。实操里用Sharding JDBC要注意几个点分片键必须写到SQL的WHERE条件里否则中间件无法路由只能全库扫描性能雪崩跨分片的聚合、排序、分页中间件会做归并但性能有限大页深分页是痛点分布式主键建议用中间件内置的雪花算法不要依赖数据库自增主键。这几条都是资深开发用血泪换来的经验踩上一次就记住了。它和JDBC的关系很多人刚接触容易懵。我的理解是Sharding JDBC什么都没发明它就是在JDBC规范里做了一层优雅的拦截和改写最终干活儿的还是底层各个数据库的JDBC驱动。所以把它放在JDBC概述这个主题下来讲非常合适。5.3 工具链DbVisualizer等客户端为什么也靠JDBC热词里有dbvisulizer 连接oracle jdbc和dbt这些词。这里再说一下数据库客户端工具。DbVisualizer是一个跨平台数据库管理工具支持Oracle、MySQL、PostgreSQL、SQL Server、达梦等几十种数据库。它连接数据库的方式就是通过JDBC驱动使用的时候需要在工具的驱动管理器里配置对应数据库的JDBC驱动jar和URL模板。所以你在DbVisualizer里连接Oracle本质是工具建立了一个JDBC连接跟代码里连数据库没有区别。这类工具的使用经验有几个一是驱动jar用官方最新版老版本连新版数据库经常出兼容问题二是URL模板要选对不同工具版本对Oracle的连接方式支持不一样Oracle要区分SID和服务名三是如果连的是内网数据库注意工具走的是JDBC直连不是SSH需要先确保网络可达。DbVisualizer里配JDBC驱动路径如果配错最简单的检验方式就是用工具的Driver Manager里的测试连接功能它会给出具体的报错比直接在数据库树上双击连接更直观。dbtData Build Tool是另一个话题了它是ELT工具核心在数据转换层。dbt本身用Python写的但底层连接数据仓库也是通过对应的Python数据库驱动跟JDBC不是一个生态不展开太多。提它只是为了说明整个数据工具链里驱动适配是绕不开的一环。不管你是Java代码、可视化客户端还是各种数据工具最终跟数据库对话的方式本质上都是一套类似的驱动机制。6. 最后分享几点个人体会写到这里JDBC的概述、实操和排查基本讲完了。最后说几点我在实际项目里的体会算是经验之谈。第一不要因为用了框架就放弃理解JDBC。我见过不少同事用MyBatis用了好几年遇到一个Error querying database. Cause: java.sql.SQLException: No suitable driver found就开始慌其实就是classpath里缺驱动jar。JDBC是Java访问数据库的根基把根基搞清楚用框架时遇到问题才有底气去排查。框架封装的越深底层报错被转译的越厉害不懂JDBC的人根本无从下手。第二连接和事务的管理一定要形成肌肉记忆。结合try-with-resources规范释放资源手动管理事务时把setAutoCommit(false)和commit/rollback成对写清楚。这些习惯平时看着不起眼关键时刻能救命。尤其是连接池环境里漏掉commit或者异常后没回滚后果是数据不一致和连接泄漏比单机环境严重得多。第三多看堆栈别只看报错第一行。JDBC的异常信息往往很长真正的根因经常藏在Caused by之后的嵌套异常里。比如No suitable driver found上面可能还有ClassNotFoundHive的could not open client transport下面可能还有认证失败的线索。沉下心来读完整堆栈比自己瞎猜快得多。排查问题的时候把完整堆栈贴到搜索引擎里也更容易匹配到有同样情况的人。JDBC这块内容看似基础实则是整个Java数据库生态的底座。这篇文章是我多年踩坑、填坑后的经验整理希望对正在学JDBC或者正被这些报错困扰的朋友有帮助。
返回列表