SQL的執行需要編譯和解析
Statement每次的執行都需要編譯SQL
PreparedStatement會預編譯,會被緩沖,在緩存區中可以發現預編譯的命令,雖然會被再次解析,但不會被再次編譯,能夠有效提高系統性能
使用PreparedStatement能夠預防SQL注入攻擊
假如登錄SQL為select * from user where name='姓名' and password='密碼' ,如果在登錄框密碼處輸入 “密碼 or 1=1”,那么SQL就成為了
select * from user where name='姓名' and password='密碼' or 1=1 ,這就是SQL注入
所謂SQL注入就是將SQL語句片段插入到被執行的語句中,把SQL命令插入到Web表單提交或者輸入域名或者頁面請求的查詢字符串,最終達到欺騙服務器,達到執行惡意SQL命令的目的。
PreparedStatement通過預編譯,原有的SQL語句中的參數轉換為占位符? 的形式,相當於變成了填空題,不管你輸入的內容是什么,都是作為參數,而不可能作為SQL的一部分
(要注意 #與$的區別)
你把密碼輸入為‘密碼 or 1=1’然后提交,他會轉換為 and password='密碼' or 1=1' 輸入內容都轉換為純粹參數
小結:
靜態SQL可以用Statement和PreparedStatement,帶參數的用PreparedStatement,存儲過程用CallableStatement
但是基本上沒有道理非要使用Statement,而且很少情況不需要參數,所以能使用PreparedStatement的情況下就不要使用Statement了
Statement、PreparedStatement和CallableStatement三種執行對象,為執行SQL而生,所以他們的重中之重全都是執行SQL
Statement詳解
Statement有四種形式的執行
- executeQuery
- executeUpdate
- execute
- Batch
executeQuery
用於產生單個結果集的語句,用於執行 SELECT 語句(SELECT無疑是是使用最多的 SQL 語句) ,返回值為ResultSet
executeUpdate
用於執行 INSERT、UPDATE 或 DELETE 語句以及 SQL DDL(數據定義語言)語句,例如 CREATE TABLE 和 DROP TABLE。
executeUpdate 的返回值是一個整數,指示受影響的行數(即更新計數)。對於 CREATE TABLE 或 DROP TABLE 等不操作行的語句,executeUpdate 的返回值總為零。
execute
用於執行返回多個結果集、多個更新計數或二者組合的語句。execute對與結果的處理比較麻煩
execute方法應該僅在語句能返回多個ResultSet對象、多個更新計數或ResultSet對象與更新計數的組合時使用。
返回值指示類型情況:如果下一個結果為 ResultSet 對象,則返回 true;如果其為更新計數或者不存在更多結果,則返回 false
小結:
executeQuery 執行SELECT,返回結果集
executeUpdate 執行INSERT UPDATE DELETE 以及SQL DDL(數據定義語言)語句,返回受影響的行
execute可以執行所有SQL,所以他可能返回結果集,也可能返回受影響的行
所以execute的返回值用於區分是返回的結果集還是受影響的行,換句話說,true表示SELECT false表示INSERT UPDATE DELETE
如果是返回結果集,必須使用方法 getResultSet 或 getUpdateCount 來獲取結果,使用 getMoreResults 來移動后續結果。
=================================================================================
executeQuery
ResultSet executeQuery(String sql)
執行給定的 SQL 語句,該語句返回單個 ResultSet 對象
executeQuery只能夠用來查詢,比如試圖進行插入操作,將會拋出一樣
內部有校驗方法
=================================================================================
executeUpdate
int executeUpdate(String sql)
執行給定 SQL 語句,該語句可能為 INSERT、UPDATE 或 DELETE 語句,或者不返回任何內容的 SQL 語句(如 SQL DDL 語句)
int executeUpdate(String sql, int autoGeneratedKeys)
執行給定的 SQL 語句,並用給定標志通知驅動程序由此 Statement 生成的自動生成鍵是否可用於獲取
int executeUpdate(String sql, int[] columnIndexes)
執行給定的 SQL 語句,並通知驅動程序在給定數組中指示的自動生成的鍵應該可用於獲取
int executeUpdate(String sql, String[] columnNames)
執行給定的 SQL 語句,並通知驅動程序在給定數組中指示的自動生成的鍵應該可用於獲取
executeUpdate能夠執行的SQL類型比較多,可以執行INSERT、UPDATE 或 DELETE 語句,或者不返回任何內容的 SQL 語句(如 SQL DDL 語句)。
對於 SQL 數據操作語言 (DML) 語句,返回行計數, 對於那些什么都不返回的 SQL 語句,返回 0
對於尋常的應用程序執行SQL來說就是返回受影響的行
在Connection的prepareStatement方法中,有提到過自動創建鍵值的返回
對於PrepareStatement在構造執行對象PrepareStatement時進行設置,而對於Statement的executeUpdate方法,則是在執行executeUpdate方法時進行設置
參數的語意是相同的
=================================================================================
execute
boolean execute(String sql)
執行給定的 SQL 語句,該語句可能返回多個結果
boolean execute(String sql, int autoGeneratedKeys)
執行給定的 SQL 語句(該語句可能返回多個結果),並通知驅動程序所有自動生成的鍵都應該可用於獲取
boolean execute(String sql, int[] columnIndexes)
執行給定的 SQL 語句(該語句可能返回多個結果),並通知驅動程序在給定數組中指示的自動生成的鍵應該可用於獲取
boolean execute(String sql, String[] columnNames)
執行給定的 SQL 語句(該語句可能返回多個結果),並通知驅動程序在給定數組中指示的自動生成的鍵應該可用於獲取
execute可以執行所有形式的語句,既然也可以執行INSERT,自然也有返回鍵值的需求,所以類似executeUpdate,也提供了相關的支持用於返回鍵值
對於execute一定要注意返回值:如果第一個結果為 ResultSet 對象,則返回 true;如果其為更新計數或者不存在任何結果,則返回 false
通過返回值指示第一個結果的形式。然后,必須使用方法 getResultSet 或 getUpdateCount 來獲取結果,使用 getMoreResults 來移動后續結果。
看得出來execute對於結果的處理是比較麻煩的
你要分情況判斷,然后才能獲取解析結果
=================================================================================
Batch
void addBatch(String sql)
將給定的 SQL 命令添加到此 Statement 對象的當前命令列表中
void clearBatch()
清空此 Statement 對象的當前 SQL 命令列表
int[] executeBatch()
將一批命令提交給數據庫來執行,如果全部命令執行成功,則返回更新計數組成的數組
對於batch操作,簡單說就是有一個列表,保存了執行命令。
add是添加方法,clear就是清空方法,execute就是執行列表內命令。
如下面示例,將李麗麗1 ~ 李麗麗100 分10次批量插入到數據庫中
如果不分批次,只需要addBatch和executeBatch即可。
=================================================================================
JDK8 新增了對於大數據的處理,對於行數超過Integer.MAX_VALUE(2的31次方減一)時才應該被使用。
一般情況下用不到。
default long[] executeLargeBatch()
default long executeLargeUpdate(String sql)
default long executeLargeUpdate(String sql, int autoGeneratedKeys)
default long executeLargeUpdate(String sql, int[] columnIndexes)
default long executeLargeUpdate(String sql, String[] columnNames)
=================================================================================
以上就是Statement提供的SQL執行相關的方法
execute結果處理
因為execute可以CRUD,所以可能是ResultSet也可能是UpdateCount,根據返回值進行判斷
如果true 可以使用getResultSet進行獲取結果,並且借助於getMoreResults獲取接下來的結果
如果是false可以通過getUpdateCount獲取受影響的行。
ResultSet getResultSet()
以 ResultSet 對象的形式獲取當前結果
int getUpdateCount()
以更新計數的形式獲取當前結果;如果結果為 ResultSet 對象或沒有更多結果,則返回 -1
boolean getMoreResults()
移動到此 Statement 對象的下一個結果,如果其為 ResultSet 對象,則返回 true,並隱式關閉利用方法 getResultSet 獲取的所有當前 ResultSet 對象
boolean getMoreResults(int current)
將此 Statement 對象移動到下一個結果,根據給定標志指定的指令處理所有當前 ResultSet 對象;如果下一個結果為 ResultSet 對象,則返回 true
還有新增的default long getLargeUpdateCount()
連接信息與對象關閉
Statement由Connection創建,所以自然知道創建他的Connection信息,所以有獲取方法
執行對象Statement如同連接Connection,使用后需要關閉,所以也提供了關閉方法
既然可以關閉,那么有是否關閉狀態一說,所以也提供了狀態檢驗方法
另外還可以終止執行SQL(如果支持的話)
相關方法如下:
Connection getConnection()
獲取生成此 Statement 對象的 Connection 對象
void close()
立即釋放此 Statement 對象的數據庫和 JDBC 資源,而不是等待該對象自動關閉時發生此操作
boolean isClosed()
獲取是否已關閉了此 Statement 對象
void cancel()
如果 DBMS 和驅動程序都支持中止 SQL 語句,則取消此 Statement 對象
鍵值返回
數據庫可以自動生成鍵,對於這個鍵值,提供了相關的獲取方法getGeneratedKeys
ResultSet getGeneratedKeys()
獲取由於執行此 Statement 對象而創建的所有自動生成的鍵
在創建PrepareStatement以及executeUpdate方法以及execute方法中,都可以對鍵值返回進行設置
如果此 Statement 對象沒有生成任何鍵,則返回空的 ResultSet 對象。
結果集類型、並發性、可保存性
Connection中的createStatement方法,創建Statement對象時,有關於結果集類型、並發性、可保存性的設置
可以在Statement中進行獲取
int getResultSetType()
獲取此 Statement 對象生成的 ResultSet 對象的結果集合類型
int getResultSetConcurrency()
獲取此 Statement 對象生成的 ResultSet 對象的結果集合並發性
int getResultSetHoldability()
獲取此 Statement 對象生成的 ResultSet 對象的結果集合可保存性
超時設置
語句執行需要時間,一個執行對象的執行,不可能是無限時的,那么驅動程序到底要等待多久呢?
這個時長,是可以設置和獲取的
void setQueryTimeout(int seconds)
將驅動程序等待 Statement 對象執行的秒數設置為給定秒數
int getQueryTimeout()
獲取驅動程序等待 Statement 對象執行的秒數
長度限制
執行對象執行SQL,不可避免的需要返回結果,這也是我們需要的
但是一個字段長度最長是多少? 最多可以返回多少行的數據呢?這些都是可以設置的
void setMaxFieldSize(int max)
設置此 Statement 對象生成的 ResultSet 對象中字符和二進制列值可以返回的最大字節數限制
int getMaxFieldSize()
獲取可以為此 Statement 對象所生成 ResultSet 對象中的字符和二進制列值返回的最大字節數
void setMaxRows(int max)
將此 Statement 對象生成的所有 ResultSet 對象可以包含的最大行數限制設置為給定數
int getMaxRows()
獲取由此 Statement 對象生成的 ResultSet 對象可以包含的最大行數
還有新增的
default void setLargeMaxRows(long max)
default long getLargeMaxRows()
default方法,你懂得,對於這種新增加的方法,無權要求別人一定立即實現,所以到底有沒有實現,你還需要查看數據庫驅動的版本情況。
默認是不可用的,比如下面這個,如果你沒實現,是不能用的,直接拋出異常
告警信息
SQLWarning getWarnings()
獲取此 Statement 對象上的調用報告的第一個警告
void clearWarnings()
清除在此 Statement 對象上報告的所有警告
池化(連接池)
語句的可池化的值對驅動程序實現的內部語句緩存以及應用程序服務器和其他應用程序實現的外部語句緩存都適用。
默認情況下,Statement 在創建時不是可池化的,而 PreparedStatement 和 CallableStatement 在創建時是可池化的。
void setPoolable(boolean poolable)
請求將 Statement 池化或非池化
boolean isPoolable()
返回指示 Statement 是否是可池化的值
數據返回檢索
默認情況下,數據庫會將查詢結果一次性返回給應用程序,這些數據會保存在內存中。
平常情況下不會有什么問題,但是,如果一旦返回結果巨大,很可能造成內存不足,發生OOM
為此,設置了這么一個類似MYSQL 分頁LIMIT的東西,LIMIT分頁從數據庫檢索數據,而FetchSize 控制的是從數據庫向應用程序客戶端發送數據的頁面大小
不再是一口氣發送了,通過setFetchSize設置,getFetchSize獲取,這個方法跟具體的驅動程序以及結果集類型都有關系,使用時要留心注意
void setFetchSize(int rows)
為 JDBC 驅動程序提供一個提示,它提示此 Statement 生成的 ResultSet 對象需要更多行時應該從數據庫獲取的行數
int getFetchSize()
獲取結果集合的行數,該數是根據此 Statement 對象生成的 ResultSet 對象的默認獲取大小
void setFetchDirection(int direction)
向驅動程序提供關於方向的提示,在使用此 Statement 對象創建的 ResultSet 對象中將按該方向處理行,默認值是 ResultSet.FETCH_FORWARD
int getFetchDirection()
獲取從數據庫表獲取行的方向,該方向是根據此 Statement 對象生成的結果集合的默認值
其他
void setCursorName(String name)
將 SQL 光標名稱設置為給定的 String,后續 Statement 對象的 execute 方法將使用此字符串
void setEscapeProcessing(boolean enable)
將轉義處理設置為開或關
如果轉義掃描為開啟(默認值),則驅動程序在將 SQL 語句發送到數據庫之前執行轉義替換。
因為預編譯語句通常在進行此調用之前解析,所以對 PreparedStatements 對象禁用轉義處理無效。
自動關閉
可以指定語句所有依賴的結果集都被關閉時,關閉這個Statement,1.7新增
如果語句的執行不產生任何結果集,則此方法無效。
void closeOnCompletion()
throws SQLException
還有檢測方法
boolean isCloseOnCompletion()
throws SQLException
PreparedStatement詳解
PreparedStatement表示預編譯的 SQL 語句的對象
SQL 語句被預編譯並存儲在 PreparedStatement 對象中。然后可以使用此對象多次高效地執行該語句。
如前面所述,PreparedStatement繼承了Statement,PreparedStatement接口添加了處理輸入參數的方法;
PreparedStatement定義了execute、executeQuery、addBatch
既然添加了處理輸入參數的方法,所以也附帶給了一個清除參數的方法
還有兩個元數據相關的方法
boolean execute()
在此 PreparedStatement 對象中執行 SQL 語句,該語句可以是任何種類的 SQL 語句
ResultSet executeQuery()
在此 PreparedStatement 對象中執行 SQL 查詢,並返回該查詢生成的 ResultSet 對象
int executeUpdate()
在此 PreparedStatement 對象中執行 SQL 語句,該語句必須是一個 SQL 數據操作語言(Data Manipulation Language,DML)語句,比如 INSERT、UPDATE 或 DELETE 語句;或者是無返回內容的 SQL 語句,比如 DDL 語句
void addBatch()
將一組參數添加到此 PreparedStatement 對象的批處理命令中
void clearParameters()
立即清除當前參數值
ResultSetMetaData getMetaData()
獲取包含有關 ResultSet 對象列信息的 ResultSetMetaData 對象,ResultSet 對象將在執行此 PreparedStatement 對象時返回
ParameterMetaData getParameterMetaData()
獲取此 PreparedStatement 對象的參數的編號、類型和屬性
=================================================================================
其余所有的方法,全部都是“處理輸入參數”相關的
setXXX方法,第一個參數是int parameterIndex
表示的是參數的索引位置,第一個為1,第二個為2
setXXX,XXX大多數都是類型,但是也有一些特殊的,可以設置流
比如:
setAsciiStream(int parameterIndex, InputStream x)
將指定參數設置為給定輸入流。
setBinaryStream(int parameterIndex, InputStream x)
將指定參數設置為給定輸入流。
流是來做什么的呢?
這是因為個別時候,可能字段值很大,當你需要將一個很大的 ASCII 值輸入到 LONGVARCHAR 參數時或者二進制值輸入到 LONGVARBINARY 參數時,使用InputStream發送可能更好。
CallableStatement詳解
CallableStatement繼承自prepareStatement,實現了存儲過程函數調用的方法以及對於輸出的處理。
以一個簡單的示例簡單了解一下存儲過程的調用,以及存儲過程中輸入輸出參數的處理。
有這么一個表
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL DEFAULT '默認姓名' COMMENT '姓名',
`age` int(11) DEFAULT '1',
`sex` varchar(255) DEFAULT NULL,
`random` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
一個最簡單的存儲過程,輸入年齡,性別,統計個數
DROP PROCEDURE
IF EXISTS
select_student;
CREATE PROCEDURE select_student(
IN age_param INT,
IN sex_param VARCHAR(1),
OUT result INT
)
BEGIN
SELECT COUNT(*) FROM student WHERE age=age_param and sex=sex_param INTO result;
END;
現在我的數據的查詢結果是這樣子的:
一個簡單的函數
DROP FUNCTION IF EXISTS select_student_function;
CREATE FUNCTION select_student_function(age_param INT,sex_param VARCHAR(1))
RETURNS INT
BEGIN
DECLARE count INT;
SELECT COUNT(*) FROM student WHERE age=age_param and sex=sex_param INTO count;
return count;
END;
Navicat測試
通過調用執行,可以看到,與數據庫直接查詢結果一致
上面給出了在MYSQL中,對於存儲過程和函數的調用
再回過頭來看CallableStatement的API解釋就很容易理解了
CallableStatement是用於執行 SQL 存儲過程的接口
JDBC API 提供了一個存儲過程 SQL 轉義語法,該語法允許對所有 RDBMS 使用標准方式調用存儲過程
此轉義語法有一個包含結果參數的形式和一個不包含結果參數的形式
如果使用結果參數,則必須將其注冊為 OUT 參數。其他參數可用於輸入、輸出或同時用於二者。
參數是根據編號按順序引用的,第一個參數的編號是 1。
{?= call <procedure-name>[(<arg1>,<arg2>, ...)]}
{call <procedure-name>[(<arg1>,<arg2>, ...)]}
IN 參數值是使用繼承自 PreparedStatement 的 set 方法設置的。
在執行存儲過程之前,必須注冊所有 OUT 參數的類型;它們的值是在執行后通過此類提供的 get 方法獲取的。
CallableStatement 可以返回一個 ResultSet 對象或多個 ResultSet 對象。多個 ResultSet 對象是使用繼承自 Statement 的操作處理的。
兩種形式
{?= call <procedure-name>[(<arg1>,<arg2>, ...)]} 返回結果
{call <procedure-name>[(<arg1>,<arg2>, ...)]} 不返回結果
他們的使用是一致的,比如setXXX設置輸入參數或者registerOutParameter 注冊OUT參數,然后使用getXXX讀取輸出參數
對於有返回結果的形式(上面第一種),那么必然第一個? 占位符是輸出,所以必然有registerOutParameter
但是其他的arg1,arg2.....可能是輸出,也可能是輸入,比如我們上面存儲過程的例子,前兩個參數是輸入,第三個參數是輸出
對於不返回結果的形式(第二種),arg1,arg2.....的含義也是如此,可能是輸入,也可能是輸出。
簡言之,兩種形式的arg1,arg2.....可能是輸入也可能是輸出,如果是輸出那么需要使用registerOutParameter注冊
但是有返回結果的形式,第一個占位符? 必然是輸出,必須要使用registerOutParameter注冊
CallableStatement繼承自prepareStatement,實現了對輸入和輸出的支持,在prepareStatement大量setXXX方法基礎上擴展了getXXX
所以API中絕大多數是setXXX和getXXX
在PrepareStatement中,setXXX中第一個參數為parameterIndex,表示參數索引順序
在CallableStatement中,不僅僅支持參數索引順序,還有一些是支持參數名稱的,比如
getDouble(String parameterName)
setString(String parameterName, String x)
CallableStatement調用存儲過程和函數,一個很重要的部分就是輸出的處理
在JDBC中需要使用registerOutParameter將參數注冊為輸出
registerOutParameter的責任就是申明XXX參數是一個輸出
對於這個參數可以使用int parameterIndex 下標索引(1開始)也可以使用String parameterName來指明
對於參數對應的類型也需要指明
java.sql.Types ,這個類定義了用於標識一般 SQL 類型(稱為 JDBC 類型)的常量的類。比如static int VARCHAR
所有常量均為static int
對於類型的描述使用java.sql.Types類中定義的常量相對於枚舉使用起來自然是沒有那么順手,枚舉可讀性更好,健壯性更強
所以還有類型的枚舉版本JDBCType,定義用於標識通用SQL類型(稱為JDBC類型)的常量。始於1.8
public enum JDBCType implements SQLType
以下截取部分對比(左Types 右JDBCType),可以看得出來,邏輯含義如出一轍。
既然是數據類型,那么某些數據類型就會涉及到精度的問題,就如同Java里面的double,小數部分終歸要有一個精度約束
所以有一個參數int scale用於定義小數點右邊所需的位數。該參數必須大於等於 0。
JDBC 類型 NUMERIC 或 DECIMAL 時,應該使用帶scale參數的方法
另外還有用戶命名的輸出參數或 REF(引用)輸出參數,用戶命名類型的示例有:STRUCT、DISTINCT、JAVA_OBJECT 和指定的數組類型。
對於用戶命名的參數,還應該提供參數的完全限定 SQL 類型名稱,而 REF 參數則要求提供所引用類型的完全限定類型名稱。
不需要類型代碼和類型名稱信息的 JDBC 驅動程序可以忽略它。
為了便於移植,應用程序應該為用戶命名的參數和 REF 參數提供這些值。盡管此方法是供用戶命名的參數和 REF 參數使用的,但也可以將其用於注冊任何 JDBC 類型的參數。
如果參數沒有用戶命名的類型或 REF 類型,則忽略 typeName 參數。
對於這種情況,還提供了參數 String typeName 用於描述,表示SQL 結構類型的完全限定名稱。
概括的說,registerOutParameter的主要參數為:
- 用於指明列的int parameterIndex或者String parameterName
- 用於指明類型的int sqlType或者SQLType sqlType(應該使用新的枚舉方式)
- 用於指明精度的int sqlType(部分類型的字段才需要)
- 用於指明SQL 結構類型的完全限定名稱的String typeName
所有的方法都是這幾種信息的組合
default為1.8新增
----------------------------------------------------------------------------------------------------
void registerOutParameter(int parameterIndex, int sqlType)
void registerOutParameter(int parameterIndex, int sqlType, int scale)
void registerOutParameter(int parameterIndex, int sqlType, String typeName)
default void registerOutParameter(int parameterIndex, SQLType sqlType)
default void registerOutParameter(int parameterIndex, SQLType sqlType, int scale)
default void registerOutParameter(int parameterIndex, SQLType sqlType, String typeName)
void registerOutParameter(String parameterName, int sqlType)
void registerOutParameter(String parameterName, int sqlType, int scale)
void registerOutParameter(String parameterName, int sqlType, String typeName)
default void registerOutParameter(String parameterName, SQLType sqlType)
default void registerOutParameter(String parameterName, SQLType sqlType, int scale)
default void registerOutParameter(String parameterName, SQLType sqlType, String typeName)
----------------------------------------------------------------------------------------------------
總結
以上為三種執行對象的API了解部分,盡管方法繁多,但是核心根本卻並不復雜
CallableStatement 擴展自PrepareStatement,PrepareStatement又擴展自Statement
Statement定義了基本的SQL的執行,PrepareStatement擴展了對於參數的處理部分,也就是擁有了IN的能力,並且提供了一系列的setXXX
CallableStatement在PrepareStatement的基礎上擴展了OUT的能力,並且提供了存儲過程以及函數的執行處理。
這就是三大執行對象內在血統的聯系。
Statement是始祖,所有的方法邏輯根本來自於他,所以要理解記憶Statement的各類方法以及形式