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` ( html
`id` int(11) NOT NULL AUTO_INCREMENT, java
`name` varchar(255) NOT NULL DEFAULT '默認姓名' COMMENT '姓名', sql
`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 app
IF EXISTS less
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的各種方法以及形式