在 Java 應用中透過 JDBC 呼叫 SQL Server 預存程序,核心類別為 CallableStatement。本文對比 Oracle 差異,提供查詢型、異動型 (INSERT/UPDATE/DELETE) 完整範例,涵蓋驅動載入、連線建立、參數綁定、結果集處理、資源釋放等完整流程。
mssql-jdbc-x.x.x.jre8.jar (Microsoft 官方,支援 JDBC 4.2+)<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>12.4.2.jre11</version>
</dependency>
com.microsoft.sqlserver.jdbc.SQLServerDriverjdbc:sqlserver://host:1433;databaseName=DB;encrypt=true;trustServerCertificate=trueClass.forName(driverClass)DriverManager.getConnection(url, user, pwd)conn.prepareCall("{call ProcName(?, ?)}")cs.setInt(1, id); cs.setString(2, name); cs.registerOutParameter(3, Types.INTEGER)cs.execute() 或 cs.executeQuery() / cs.executeUpdate()ResultSet rs = cs.getResultSet(); int out = cs.getInt(3);cs.close(); conn.close();-- SQL Server
CREATE PROCEDURE dbo.SelBook
AS
SELECT BookID, BookName, Author FROM dbo.Books;
// Java
public void queryBooks() {
String url = "jdbc:sqlserver://localhost:1433;databaseName=Library;encrypt=true;trustServerCertificate=true";
String user = "app_user", pwd = "P@ssw0rd";
try (Connection conn = DriverManager.getConnection(url, user, pwd);
CallableStatement cs = conn.prepareCall("{call dbo.SelBook}"); // 無參數
ResultSet rs = cs.executeQuery()) {
while (rs.next()) {
int id = rs.getInt("BookID");
String name = rs.getString("BookName");
String author = rs.getString("Author");
System.out.printf("%d\t%s\t%s%n", id, name, author);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
-- SQL Server
CREATE PROCEDURE dbo.AddBook
@BookID INT,
@BookName VARCHAR(100),
@Author VARCHAR(100),
@Result INT OUTPUT -- 0:成功, -1:重複
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (SELECT 1 FROM Books WHERE BookName = @BookName)
BEGIN
SET @Result = -1; RETURN;
END
INSERT INTO Books (BookID, BookName, Author) VALUES (@BookID, @BookName, @Author);
SET @Result = 0;
END;
// Java
public int addBook(int id, String name, String author) {
String sql = "{call dbo.AddBook(?, ?, ?, ?)}";
try (Connection conn = DriverManager.getConnection(url, user, pwd);
CallableStatement cs = conn.prepareCall(sql)) {
cs.setInt(1, id);
cs.setString(2, name);
cs.setString(3, author);
cs.registerOutParameter(4, Types.INTEGER); // 註冊輸出參數
cs.execute(); // 異動型用 execute()
return cs.getInt(4); // 取得輸出參數值
} catch (SQLException e) {
e.printStackTrace();
return -99;
}
}
若預存程序包含多個 SELECT,需依序呼叫 getMoreResults() 切換:
boolean hasResults = cs.execute();
int resultSetIndex = 0;
while (hasResults || cs.getUpdateCount() != -1) {
if (hasResults) {
ResultSet rs = cs.getResultSet();
System.out.println("ResultSet #" + (++resultSetIndex));
while (rs.next()) { /* 處理 */ }
rs.close();
}
hasResults = cs.getMoreResults();
}
conn.setAutoCommit(false);
try {
// 多個 CallableStatement 操作
conn.commit();
} catch (SQLException e) {
conn.rollback();
throw e;
} finally {
conn.setAutoCommit(true);
}
maximumPoolSize、minimumIdle、connectionTimeout、idleTimeout、maxLifetime。validationQuery="SELECT 1" 並設定 testOnBorrow=true。| 錯誤 | 原因 | 解決 |
|---|---|---|
| ClassNotFoundException | 驅動 JAR 不在 Classpath | Maven 依賴正確、打包含驅動、或放入容器共用 lib |
| No suitable driver | URL 格式錯誤 | 確認 jdbc:sqlserver:// 前綴、主機:埠、databaseName 拼寫 |
| Parameter index out of range | 參數索引不匹配 | 檢查 ? 數量與 setX(index, val) 索引 (從 1 開始) |
| Output parameter not registered | 未呼叫 registerOutParameter | 輸出參數務必先註冊型別 |
| 項目 | SQL Server | Oracle |
|---|---|---|
| 驅動類別 | com.microsoft.sqlserver.jdbc.SQLServerDriver | oracle.jdbc.OracleDriver |
| URL 格式 | jdbc:sqlserver://host:port;databaseName=DB | jdbc:oracle:thin:@host:port:SID |
| 呼叫語法 | {call Proc(?, ?)} | {call Proc(?, ?)} (相同) |
| 輸出參數註冊 | registerOutParameter(idx, Types.INTEGER) | 相同 |
| 游標回傳 | 直接 SELECT 回傳 ResultSet | 需 SYS_REFCURSOR 輸出參數 |
| 參數長度 | VARCHAR(100) 必須指定長度 | VARCHAR2(100) 亦需指定 |
掌握 CallableStatement 標準流程、參數綁定、結果集切換、交易控制與連線池整合,即能在 Java 應用中高效、安全地呼叫 SQL Server 預存程序。建議將資料存取層封裝為 DAO/Repository,統一異常處理、資源管理與日誌記錄,提升程式碼可維護性。