淺析JDBC與數(shù)據(jù)庫如何進(jìn)行數(shù)據(jù)交互及類型轉(zhuǎn)換
JDBC 作為 Java 應(yīng)用程序和數(shù)據(jù)庫之間的橋梁,需要解決兩個核心問題:
- 網(wǎng)絡(luò)通信協(xié)議:如何在網(wǎng)絡(luò)中傳輸 SQL 命令和結(jié)果數(shù)據(jù)
- 數(shù)據(jù)類型映射:如何轉(zhuǎn)換 Java 類型和數(shù)據(jù)庫類型
一、數(shù)據(jù)交互流程
1. 整體交互架構(gòu)
Java 應(yīng)用程序
↓ [JDBC API 調(diào)用]
JDBC Driver 實(shí)現(xiàn)
↓ [協(xié)議轉(zhuǎn)換]
網(wǎng)絡(luò)協(xié)議包 (TCP/IP)
↓ [網(wǎng)絡(luò)傳輸]
數(shù)據(jù)庫服務(wù)器
↓ [協(xié)議解析]
數(shù)據(jù)庫引擎 (解析、優(yōu)化、執(zhí)行)
↓ [結(jié)果集序列化]
網(wǎng)絡(luò)協(xié)議包
↓ [網(wǎng)絡(luò)傳輸]
JDBC Driver (反序列化)
↓ [ResultSet 封裝]
Java 應(yīng)用程序
2. 底層通信協(xié)議示例
以 MySQL 為例,通信基于 MySQL 自有協(xié)議:
簡化的協(xié)議交互過程
1. 握手包 (服務(wù)端 → 客戶端):協(xié)議版本、線程ID、挑戰(zhàn)隨機(jī)數(shù)、服務(wù)器能力標(biāo)志
2. 認(rèn)證包 (客戶端 → 服務(wù)端):用戶名、密碼(加密后的挑戰(zhàn)響應(yīng))、數(shù)據(jù)庫名
3. 命令包 (客戶端 → 服務(wù)端):COM_QUERY (0x03) + "SELECT * FROM user"
4. 結(jié)果集包 (服務(wù)端 → 客戶端)
- 列數(shù)量包
- 列定義包 (每個列的名稱、類型)
- 行數(shù)據(jù)包 (每行數(shù)據(jù)的二進(jìn)制/文本格式)
- EOF 包 (結(jié)束標(biāo)志)
3. 請求-響應(yīng)序列
// 實(shí)際網(wǎng)絡(luò)抓包的數(shù)據(jù)示例 (簡化) // 客戶端發(fā)送: [0x03] [S E L E C T * F R O M u s e r] // 服務(wù)端響應(yīng): // 第一個包: 列數(shù) [0x1c 0x00 0x00 0x00] // 28字節(jié)長度的包 [0x03] // 結(jié)果集標(biāo)志 [0x02] // 2列 // 第二個包: 列定義 "id" [0x18 0x00 0x00 0x00] // 包長度 [0x04] // 列定義標(biāo)志 [0x03] [i d] // 列名 // 第三個包: 列定義 "name" [0x1a 0x00 0x00 0x00] [0x04] [0x05] [n a m e] // 第四包: 行數(shù)據(jù) [0x10 0x00 0x00 0x00] [0x00] // 行標(biāo)志 [0x01] [0x31] // id=1 [0x05] [T o m] // name='Tom' // 第五包: EOF [0x05 0x00 0x00 0x00] [0xfe] // EOF 標(biāo)志
二、數(shù)據(jù)類型轉(zhuǎn)換機(jī)制
1. 轉(zhuǎn)換核心:JDBC 類型系統(tǒng)
JDBC 定義了中間類型 java.sql.Types,作為 Java 和數(shù)據(jù)庫的橋梁:
Java 類型 ←→ JDBC Types ←→ 數(shù)據(jù)庫類型
2. 常見類型映射表
| JDBC Types | Java 類型 | MySQL | Oracle | PostgreSQL |
|---|---|---|---|---|
BIT | boolean, Boolean | BIT, BOOLEAN | - | BOOL |
TINYINT | byte, Byte | TINYINT | - | - |
SMALLINT | short, Short | SMALLINT | - | SMALLINT |
INTEGER | int, Integer | INT, INTEGER | NUMBER(10) | INTEGER |
BIGINT | long, Long | BIGINT | NUMBER(19) | BIGINT |
FLOAT | double, Double | FLOAT | FLOAT(16) | FLOAT8 |
DOUBLE | double, Double | DOUBLE | FLOAT(32) | FLOAT8 |
DECIMAL | java.math.BigDecimal | DECIMAL, NUMERIC | NUMBER | NUMERIC |
CHAR | String | CHAR | CHAR | CHAR |
VARCHAR | String | VARCHAR | VARCHAR2 | VARCHAR |
LONGVARCHAR | String | TEXT, MEDIUMTEXT | LONG | TEXT |
DATE | java.sql.Date | DATE | DATE | DATE |
TIME | java.sql.Time | TIME | - | TIME |
TIMESTAMP | java.sql.Timestamp | DATETIME, TIMESTAMP | TIMESTAMP | TIMESTAMP |
BINARY | byte[] | BINARY | RAW | BYTEA |
VARBINARY | byte[] | VARBINARY | RAW | BYTEA |
BLOB | java.sql.Blob | BLOB | BLOB | BYTEA |
CLOB | java.sql.Clob | TEXT, LONGTEXT | CLOB | TEXT |
ARRAY | java.sql.Array | - | VARRAY | ARRAY |
3. 轉(zhuǎn)換實(shí)現(xiàn)原理
寫入數(shù)據(jù)庫方向(Java → 數(shù)據(jù)庫)
// PreparedStatement 內(nèi)部轉(zhuǎn)換流程
pstmt.setInt(1, 123);
// 實(shí)際轉(zhuǎn)換步驟:
// 1. Java int → JDBC Types.INTEGER
// 2. 根據(jù)驅(qū)動實(shí)現(xiàn)轉(zhuǎn)換為數(shù)據(jù)庫特定格式
// 3. 按照協(xié)議編碼為網(wǎng)絡(luò)字節(jié)流
// MySQL 驅(qū)動內(nèi)部簡化實(shí)現(xiàn) (Connector/J)
public void setInt(int parameterIndex, int x) throws SQLException {
// 檢查參數(shù)位置有效性
checkBounds(parameterIndex);
// 存儲為參數(shù)對象,包含值和類型
Parameter p = getParameter(parameterIndex);
p.value = x;
p.type = Types.INTEGER;
p.isNull = false;
// 實(shí)際寫入時調(diào)用編碼方法
// byte[] encoded = encodeInteger(x, 4); // 4字節(jié)小端序
}從數(shù)據(jù)庫讀取方向(數(shù)據(jù)庫 → Java)
int id = rs.getInt("id");
// MySQL 驅(qū)動內(nèi)部簡化實(shí)現(xiàn)
public int getInt(String columnLabel) throws SQLException {
// 1. 找到列索引
int columnIndex = findColumn(columnLabel);
// 2. 獲取原始字節(jié)數(shù)據(jù)
byte[] rawData = rowData.getColumnData(columnIndex);
// 3. 根據(jù)協(xié)議格式解碼
// MySQL 整數(shù)格式: 小端序,固定長度
// 4. 類型轉(zhuǎn)換
if (rawData == null) return 0; // SQL NULL 返回 0
// 解碼為 int
int value = (rawData[0] & 0xff) |
((rawData[1] & 0xff) << 8) |
((rawData[2] & 0xff) << 16) |
((rawData[3] & 0xff) << 24);
return value;
}4. 類型轉(zhuǎn)換的靈活性
JDBC 允許一定的類型兼容轉(zhuǎn)換:
// 即使數(shù)據(jù)庫是 VARCHAR,也可以讀取為 int
ResultSet rs = stmt.executeQuery("SELECT '123' AS num");
int num = rs.getInt("num"); // 自動轉(zhuǎn)換 123
// 內(nèi)部實(shí)現(xiàn): String → Integer.parseInt()
// 支持的類型轉(zhuǎn)換矩陣 (部分)
// getInt(): VARCHAR, CHAR, DECIMAL, BIGINT, SMALLINT, TINYINT
// getString(): 幾乎所有類型(會調(diào)用 toString())
// getTimestamp(): DATE, TIME, VARCHAR(符合日期格式)三、高級數(shù)據(jù)交互機(jī)制
1. 預(yù)編譯與參數(shù)綁定
PreparedStatement pstmt = conn.prepareStatement(
"INSERT INTO user(id, name, salary) VALUES(?, ?, ?)"
);
pstmt.setInt(1, 100); // int → Types.INTEGER
pstmt.setString(2, "張三"); // String → Types.VARCHAR
pstmt.setBigDecimal(3, new BigDecimal("9999.99"));
pstmt.executeUpdate();
// MySQL 協(xié)議層面的參數(shù)替換:
// 服務(wù)端收到的是帶占位符的預(yù)編譯語句
// 客戶端發(fā)送 COM_STMT_PREPARE
// 服務(wù)端返回 statement_id
// 客戶端發(fā)送 COM_STMT_EXECUTE(statement_id, params...)2. 大對象處理(BLOB/CLOB)
// 流式傳輸,避免內(nèi)存溢出
try (PreparedStatement pstmt = conn.prepareStatement(
"INSERT INTO files(id, content) VALUES(?, ?)")) {
pstmt.setInt(1, 1);
// CLOB: 字符大對象
Reader reader = new FileReader("large.txt");
pstmt.setCharacterStream(2, reader);
// BLOB: 二進(jìn)制大對象
InputStream is = new FileInputStream("large.pdf");
pstmt.setBinaryStream(2, is);
pstmt.executeUpdate();
}
// 讀取時也使用流式
ResultSet rs = stmt.executeQuery("SELECT content FROM files WHERE id=1");
if (rs.next()) {
InputStream is = rs.getBinaryStream("content");
// 邊讀邊寫,不占用大量內(nèi)存
byte[] buffer = new byte[8192];
int len;
while ((len = is.read(buffer)) != -1) {
// 處理數(shù)據(jù)
}
}3. 批量操作優(yōu)化
// 減少網(wǎng)絡(luò)往返次數(shù)
PreparedStatement pstmt = conn.prepareStatement(
"INSERT INTO user(name) VALUES(?)"
);
conn.setAutoCommit(false);
for (int i = 0; i < 1000; i++) {
pstmt.setString(1, "user" + i);
pstmt.addBatch(); // 累積到批次中
if (i % 100 == 0) {
int[] results = pstmt.executeBatch(); // 一次發(fā)送100條
conn.commit();
}
}
// 協(xié)議層面: 批量發(fā)送多個命令包
// 服務(wù)端批量執(zhí)行,減少往返延遲四、性能優(yōu)化要點(diǎn)
1. 網(wǎng)絡(luò)交互優(yōu)化
// 1. 使用連接池減少連接建立開銷
// 2. 批量操作減少網(wǎng)絡(luò)往返
// 3. 只查詢需要的列
String sql = "SELECT id, name FROM user"; // 好
String sql = "SELECT * FROM user"; // 差,傳輸冗余數(shù)據(jù)
// 4. 使用 fetchSize 控制內(nèi)存
Statement stmt = conn.createStatement();
stmt.setFetchSize(100); // 每次從數(shù)據(jù)庫取100行
ResultSet rs = stmt.executeQuery("SELECT * FROM huge_table");2. 避免類型轉(zhuǎn)換開銷
// 直接使用匹配的類型
int id = rs.getInt("id"); // 高效:直接解碼
String idStr = rs.getString("id"); // 低效:int→String 轉(zhuǎn)換
// 使用具體類型而非 Object
Object obj = rs.getObject("amount"); // 需要類型判斷和轉(zhuǎn)換
BigDecimal amount = rs.getBigDecimal("amount"); // 直接獲取3. 預(yù)編譯的緩存利用
// 同一個 Connection 中的 PreparedStatement 可以被復(fù)用 // 某些驅(qū)動支持服務(wù)端預(yù)編譯緩存 // MySQL 需要配置: cachePrepStmts=true String url = "jdbc:mysql://localhost:3306/db?cachePrepStmts=true&useServerPrepStmts=true";
五、實(shí)際驅(qū)動示例分析
以 MySQL Connector/J 8.0 為例的關(guān)鍵實(shí)現(xiàn):
// 核心類層次
com.mysql.cj.jdbc.ConnectionImpl
└── com.mysql.cj.NativeSession // 管理網(wǎng)絡(luò)會話
└── com.mysql.cj.protocol.a.NativeProtocol // 協(xié)議處理
└── com.mysql.cj.protocol.a.SimplePacketReader // 讀取包
└── com.mysql.cj.protocol.a.TimeTrackingPacketWriter // 寫入包
// 發(fā)送 SQL 的關(guān)鍵方法
protected ResultSetInternalMethods executeInternal(
int maxRowsToRetrieve,
Buffer queryPacket,
boolean sendPacket,
int maxRows,
Catalog nor...
) throws SQLException {
// 1. 發(fā)送命令包
this.session.sendCommand(queryPacket, sendPacket);
// 2. 讀取響應(yīng)
this.protocol.readQueryResult(this.columnDefinitions, false, false,
this.session.getServerSession().getCapabilities());
// 3. 解析結(jié)果集并轉(zhuǎn)換為 Java 對象
return this.protocol.readAllResults(maxRowsToRetrieve,
true,
this.resultSetFactory,
this.columnDefinitions);
}JDBC 通過精心設(shè)計(jì)的協(xié)議和數(shù)據(jù)轉(zhuǎn)換機(jī)制,在保證類型安全的前提下,實(shí)現(xiàn)了 Java 與數(shù)據(jù)庫的高效通信,這套機(jī)制經(jīng)過多年演進(jìn)已經(jīng)非常成熟穩(wěn)定。
到此這篇關(guān)于淺析JDBC與數(shù)據(jù)庫如何進(jìn)行數(shù)據(jù)交互及類型轉(zhuǎn)換的文章就介紹到這了,更多相關(guān)JDBC與數(shù)據(jù)庫數(shù)據(jù)交互內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot各種格式轉(zhuǎn)pdf的實(shí)例代碼
這篇文章主要介紹了springboot各種格式轉(zhuǎn)pdf的實(shí)例代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
Java創(chuàng)建二叉搜索樹,實(shí)現(xiàn)搜索,插入,刪除的操作實(shí)例
下面小編就為大家分享一篇Java創(chuàng)建二叉搜索樹,實(shí)現(xiàn)搜索,插入,刪除的操作實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助2017-12-12
java并發(fā)編程專題(六)----淺析(JUC)Semaphore
這篇文章主要介紹了java JUC)Semaphore的相關(guān)資料,文中示例代碼非常詳細(xì),供大家參考和學(xué)習(xí),感興趣的朋友可以了解下2020-07-07
RabbitMQ消息隊(duì)列實(shí)現(xiàn)延遲任務(wù)示例
這篇文章主要為大家介紹了RabbitMQ消息隊(duì)列實(shí)現(xiàn)延遲任務(wù)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04
Spring Boot 中的任務(wù)執(zhí)行器基本概念及使用方法
務(wù)執(zhí)行器是 Spring Boot 中的一個非常實(shí)用的模塊,它可以簡化異步任務(wù)的開發(fā)和管理,在本文中,我們介紹了任務(wù)執(zhí)行器的基本概念和使用方法,以及一個完整的示例代碼,需要的朋友可以參考下2023-07-07
Java變量與常量一文全掌握(包含常量類、interface 與 final 的比較)
本文介紹Java變量與常量的基本概念,涵蓋變量分類(局部/實(shí)例/類)、聲明語法及使用場景,常量定義方式(final/字面量/枚舉)與命名規(guī)范,結(jié)合實(shí)例代碼給大家的非常詳細(xì),感興趣的朋友一起看看吧2025-08-08

