本節我們來學習JDBC API中比較重要的部分—Statement接口及它的子接口PreparedStatement和CallableStatement。Statement接口中定義了執行SQL語句的方法,這些方法不支援參數輸入,PreparedStatement接口中增加了設定SQL參數的方法,CallableStatement接口繼承自PreparedStatement,在此基礎上增加了調用存儲過程以及檢索存儲過程調用結果的方法。
1.java.sql.Statement接口
Statement是JDBC API操作資料庫的核心接口,具體的實作由JDBC驅動來完成。Statement對象的建立比較簡單,需要調用Connection對象的createStatement()方法,例如:
// 擷取Connection對象
Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
"sa", "");
Statement statement = connection.createStatement();
在應用程式中,每個Connection對象可以同時建立多個Statement對象,例如;
// 擷取Connection對象
Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
"sa", "");
Statement statement1 = connection.createStatement();
Statement statement2 = connection.createStatement();
此外,Connection接口中還提供了幾個重載的createStatement()方法,用于通過Statement對象指定ResultSet(結果集)的屬性,例如:
Connection conn = dataSource.getConnection(user,passwd);Statement stmt = conn.createStatement(
ResultSet.TYPE SCROLL INSENSITIVEResultSet.CONCUR UPDATABLE
ResultSet.HOLD CURSORS OVER COMMIT):
上面的代碼中,我們建立了一個Statement對象,通過參數指定該Statement對象建立的ResultSet對象是可滾動的,而且是可以修改的,當修改送出時ResultSet不會被關閉。關于ResultSet的更多細節會在後面的章節中介紹。
Statement的主要作用是與資料庫進行互動,該接口中定義了一些資料庫操作以及檢索SQL執行結果相關的方法,具體如下:
#批量執行 SQL
void addBatch(String sgl)
void clearBatch()
int[] executeBatch()
#執行未知 SQL語句
boolean execute(String sgl)
execute(String sql,int autoGeneratedKeys)boolean
boolean execute(String sgl,int門 columnIndexes)
boolean execute(String sql, String[] columnNames)#執行查詢語句
ResultSet executeQuery(String sql)#執行更新語句,包括UPDATE、DELETE、INSERT
intexecuteUpdate(String sql)
int executeUpdate(String sgl,int autoGeneratedKeys)int executeUpdate(String sgl,int[] columnIndexes)
int executeUpdate(String sgl,String[] columnIndexes)
int executeUpdate(String sgl,String[] columnNames)
#SOL 執行結果處理
long getLargeUpdateCount()
ResultSet getResultSet()
int getUpdateCount()
boolean getMoreResults()
boolean getMoreResults(int current)
ResultSet getGeneratedKeys()
#JDBC4.2新增,資料量大于 Integer.MAX VALUE 時使用
long[] executeLargeBatch()
long executeLargeUpdate(String sql
long executeLargeUpdate(String sql,int autoGeneratedKeys)
longexecuteLargeUpdate(Stringsq1,int[] columnIndexes)
long executeLargeUpdate(Stringsg1,String[] columnNames)
#取消 SOL 執行,需要資料庫和驅動支援
void cancel()
#關閉statement對象
void close()
void closeOnCompletion()
Statement接口中提供的與資料庫互動的方法比較多,具體調用哪個方法取決于SQL語句的類型。
如果使用Statement執行一條查詢語句,并傳回一個結果集(ResultSet對象),則可以調用executeQuery()方法。
如果SQL語句是一個傳回更新數量的DML語句,則需要調用executeUpdate()方法,該方法有幾個重載的方法,下詳細介紹。
int executeUpdate(String sql):執行一個UPDATE、INSERT或者DELETE語句,傳回更新數量。
int executeUpdate(String sql, int autoGeneratedKeys):執行一個UPDATE、INSERT或者DELETE語句。當SQL語句是INSERT語句時,autoGeneratedKeys參數用于指定自動生成的鍵是否能夠被檢索,取值為Statement.RETURN_GENERATED_KEYS或Statement.NO_GENERATED_KEYS。當參數值為Statement.RETURN_GENERATED_KEYS時,INSERT語句自動生成的鍵能夠被檢索。當我們向資料庫中插入一條記錄,希望擷取這條記錄的自增主鍵時,可以調用該方法,指定第二個參數值為Statement.RETURN_GENERATED_KEYS。
int executeUpdate(String sql, int[] columnIndexes):執行一個UPDATE、INSERT或者DELETE語句,通過columnIndexes參數告訴驅動程式哪些列中自動生成的鍵可以用于檢索。columnIndexes數組用于指定目标表中列的索引,這些列中自動生成的鍵必須能夠被檢索。如果SQL語句不是INSERT語句,columnIndexes參數将會被忽略。
int executeUpdate(String sql, String[]columnNames):這個方法的作用和executeUpdate(String sql, int[]columnIndexes)相同,不同的是columnNames參數是一個String數組,通過字段名的方式指定哪些字段中自動生成的鍵能夠被檢索。如果SQL語句不是INSERT語句,columnNames參數就會被忽略。
注意如果資料庫支援傳回的更新數量大于Integer.MAX_VALUE,則需要調用executeLargeUpdate()方法。
當我們在執行資料庫操作之前,若不确定SQL語句的類型,則可以調用excute()方法。該方法也有幾個重載的方法,分别說明如下。
boolean execute(String sql):執行一個SQL語句,通過傳回值判斷SQL類型,當傳回值為true時,說明SQL語句為SELECT語句,可以通過Statement接口中的getResultSet()方法擷取查詢結果集;否則為UPDATE、INSERT或者DELETE語句,可以通過Statement接口中的getUpdateCount()方法擷取影響的行數。
boolean execute(String sql, int autoGeneratedKeys):該方法通過autoGeneratedKeys參數(隻對INSERT語句有效)指定INSERT語句自動生成的鍵是否能夠被檢索。
boolean execute(String sql, String[]columnNames):columnNames參數是一個String數組,通過字段名的方式指定哪些字段中自動生成的鍵能夠被檢索。如果SQL語句不是INSERT語句,則columnNames參數會被忽略。
注意當資料庫支援傳回影響的行數大于Integer.MAX_VALUE時,需要使用getLargeUpdateCount()方法。
另外,execute()方法可能傳回多個結果。我們可以通過Statement對象的getMoreResults()方法擷取下一個結果,當getMoreResults()方法的傳回值為true時,說明下一個結果為ResultSet對象;當傳回值為false時,說明下一個結果為影響行數,或者沒有更多結果。
預設情況下,每次調用getMoreResults()方法都會關閉上一次調用getResultSet()方法傳回的ResultSet對象。但是,我們可以通過重載getMoreResults()方法的參數指定是否關閉ResultSet 對象。
Statement接口中定義了3個常量可以用作getMoreResults()的參數,具體如下。
CLOSE_CURRENT_RESULT:表明當傳回下一個ResultSet對象時,目前ResultSet對象應該關閉。
KEEP_CURRENT_RESULT:表明當傳回下一個ResultSet對象時,目前ResultSet對象不關閉。
CLOSE_ALL_RESULTS:表明當傳回下一個ResultSet對象時,目前所有未關閉的ResultSet對象都關閉。
如果目前結果是影響行數,而不是ResultSet對象,則getMoreResults()方法的參數将會被忽略。為了确定JDBC驅動是否支援通過getMoreResults()方法擷取下一個結果,我們可以調用DatabaseMetaData接口提供的supportsMultipleOpenResults()方法,DatabaseMetaData的相關細節将會在後面的章節中介紹。
除此之外,Statement接口中還提供了幾個方法,用于批量執行SQL語句,分别為:
void addBatch(String sql):把一條SQL語句添加到批量執行的SQL清單中。
void clearBatch():清空批量執行的SQL清單。
int[]executeBatch():批量地執行SQL清單中的語句。
Statement接口中除了提供操作資料庫相關的方法外,還提供了一系列屬性相關的方法,這些方法用于設定或擷取Statement相關的屬性,代碼如下:
#Statement 屬性相關
Connection getConnection()
int getFetchDirection()
int getFetchSize()
ResultSet getGeneratedKeys()
int getMaxFieldSize()
int getMaxRows()
boolean getMoreResults()
boolean getMoreResults(int current)
int getQueryTimeout()
int getResultSetConcurrency()
int getResultSetHoldability()
int getResultSetType()
boolean isClosed()
booleanisCloseOnCompletion()
boolean isPoolable()
void setCursorName(String name)
void setEscapeProcessing(boolean enable)
void setFetchDirection(int direction)
void setFetchSize(int rows)
void setLargeMaxRows(long max)
void setMaxFieldSize(int max)
void setMaxRows(int max)
void setPoolable(boolean poolable)
void setQueryTimeout(int seconds)
2.java.sql.PreparedStatement接口
PreparedStatement接口繼承自Statement接口,在Statement接口的基礎上增加了參數占位符功能。PreparedStatement接口中增加了一些方法,可以為占位符設定值。PreparedStatement的執行個體表示可以被預編譯的SQL語句,執行一次後,後續多次執行時效率會比較高。使用PreparedStatement執行個體執行SQL語句時,可以使用“?”作為參數占位符,然後使用PreparedStatement接口中提供的方法為占位符設定參數值。
PreparedStatement對象的建立比較簡單,與Statement類似,隻需要調用Connection對象的prepareStatement()方法。與建立Statement對象不同的是,prepareStatement()方法需要提供一個SQL語句作為參數,例如:
// 擷取Connection對象
Connection connection = dataSource.getConnection();
PreparedStatement stmt = connection.prepareStatement("insert into " +
"user(create_time, name, password, phone, nick_name) " +
"values(?,?,?,?,?);");
stmt.setString(1,"2010-10-24 10:20:30");
stmt.setString(2,"User1");
stmt.setString(3,"test");
stmt.setString(4,"18700001111");
stmt.setString(5,"User1");
前面的章節中有提到過,使用createStatement()方法建立Statement對象時,可以通過參數指定ResultSet的特性。與createStatement()方法類似,prepareStatement()也可以通過重載的方法指定ResultSet的特性.
PreparedStatement接口中定義了一系列的Setter方法,用于為SQL語句中的占位符指派,這些Setter方法名稱遵循set<Type>格式,其中Type為資料類型。例如,setString()方法用于為參數占位符設定一個字元串類型的值。這些Setter方法一般都有兩個參數,第一個參數為int類型,表示參數占位符的位置(從1開始);第二個參數為占位符指定的值。
需要注意的是,在使用PreparedStatement對象執行SQL語句之前必須為每個參數占位符設定對應的值,否則調用executeQuery()、executeUpdate()或execute()等方法時會抛出SQLException異常。
PreparedStatement對象設定的參數在執行後不能被重置,需要顯式地調用clearParameters()方法清除先前設定的值,再為參數重新設定值即可。
注意 在使用PreparedStatement對象執行SQL時,JDBC驅動通過setAsciiStream()、setBinaryStream()、setCharacterStream()、setNCharacterStream()或setUnicodeStream()等方法讀取參數占位符設定的值。這些參數值必須在下一次執行SQL時重置掉,否則将會抛出SQLException異常。
對于一個給定的Statement對象,在execute()、executeQuery()、executeUpdate()、executeBatch()或clearParameters()方法調用之前,如果占位符已經使用setXXX()方法設定值,應用程式不可以再次調用setXXX()方法修改已經設定的值。但是應用程式可以在execute()、executeQuery()、executeUpdate()、executeBatch()或clearParameters()方法調用後,再次調用setXXX()方法覆寫先前設定的值。不遵循這一限制可能會導緻不可預知的結果。
我們在使用setXXX()方法為參數占位符設定值時存在一個資料轉換過程。setXXX()方法的參數為Java資料類型,需要轉換為JDBC類型(java.sql.Types中定義的SQL類型),這一過程由JDBC驅動來完成。Java類型與JDBC類型之間的對應關系如表所示。
PreparedStatement接口中提供了一個setObject()方法,可以将Java類型轉換為JDBC類型。該方法可以接收三個參數,第一個參數為占位符位置,第二個參數為Java對象,第三個參數是要轉換成的JDBC類型。如果Java對象與JDBC類型不相容,就會抛出SQLException異常。
下面是使用setObject()方法将Java中的Integer類型轉換為JDBC中的SHORT類型的案例,具體代碼如下:
Integer value = new Integer(15);
ps.setObject(1, value, java.sql.Types.SHORT);
另外,setObject()方法可以隻接收兩個參數,不用指定JDBC類型。這種情況下,JDBC驅動會按照表2-2中的映射關系将Java類型隐式地轉換為對應的JDBC類型,例如:
Integer value= new Integer(15);// Integer 類型會轉換為java.sql.Types.INTEGER
ps.setObject(1, value);
PreparedStatement接口中提供了一個setNull()方法,可以将占位符參數設定為JDBC的NULL。該方法接收兩個參數,第一個參數為占位符的位置,第二個參數為JDBC類型。該方法的文法格式如下:
ps.setNul1(2, java.sql.Types.VARCHAR)
如果接收Java對象的setXXX()方法參數為null,則該參數的占位符被設定為JDBC的NULL。
JDBC API中提供了一個ParameterMetaData接口,用于描述PreparedStatement對象的參數資訊,包括參數個數、參數類型等。PreparedStatement接口中提供了一個getParameterMetaData()方法,用于擷取ParameterMetaData執行個體。下面是使用ParameterMetaData擷取參數資訊的案例,代碼如下:
public void testJdbc() {
try {
// 建立DataSource執行個體
DataSourceFactory dsf = new UnpooledDataSourceFactory();
Properties properties = new Properties();
InputStream configStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("database.properties");
properties.load(configStream);
dsf.setProperties(properties);
DataSource dataSource = dsf.getDataSource();
// 擷取Connection對象
Connection connection = dataSource.getConnection();
PreparedStatement stmt = connection.prepareStatement("insert into " +
"user(create_time, name, password, phone, nick_name) " +
"values(?,?,?,?,?);");
stmt.setString(1,"2010-10-24 10:20:30");
stmt.setString(2,"User1");
stmt.setString(3,"test");
stmt.setString(4,"18700001111");
stmt.setString(5,"User1");
ParameterMetaData pmd = stmt.getParameterMetaData();
for(int i = 1; i <= pmd.getParameterCount(); i++) {
String typeName = pmd.getParameterTypeName(i);
String className = pmd.getParameterClassName(i);
System.out.println("第" + i + "個參數," + "typeName:" + typeName + ", className:" + className);
}
stmt.execute();
// 關閉連接配接
IOUtils.closeQuietly(stmt);
IOUtils.closeQuietly(connection);
} catch (Exception e) {
e.printStackTrace();
}
}
3.java.sql.CallableStatement接口
CallableStatement接口繼承自PreparedStatement接口,在PreparedStatement的基礎上增加了調用存儲過程并檢索調用結果的功能。與Statement、PreparedStatement一樣,CallableStatement對象也是通過Connection對象建立的,我們隻需要調用Connection對象的prepareCall()方法即可,例如:
CallableStatement cstmt = conn.prepareCall("call validate(?,?)")
CallableStatement對象可以使用3種類型的參數:IN、OUT和INOUT。可以将參數指定為序數參數或命名參數,必須為IN或INOUT參數的每個參數占位符設定一個值,必須為OUT或INOUT參數中的每個參數占位符調用registerOutParameter()方法。存儲過程參數的數量、類型和屬性可以使用DatabaseMetaData接口提供的getProcedureColumns()方法擷取。需要注意的是,使用setXXX()方法為參數占位符設定值時,下标必須從1開始。語句中的字面量參數值不會增加參數占位符的序數值,例如:
CallableStatement cstmt = con.prepareCall("(CALL PROC(?"Literal Value",?)}");
cstmt.setString(1,"First");
cstmt.setString(2,"Third");
命名參數可以用來指定特定的參數,這在存儲過程有多個給定預設值的參數時特别有用,命名參數可以用于為那些沒有預設值的參數設定值,參數名稱可以通過DatabaseMetaData對象的getProcedureColumns()方法傳回的COLUMN_NAME字段擷取。例如,在下面的案例中,COMPLEX_PROC存儲過程可以接收10個參數,但是隻有第1個和第5個參數(PARAM_1和PARAM_5)需要設定值。
CallableStatement cstmt = con.prepareCall("{CALL COMPLEX PROC(?,?)}");
cstmt.setString("PARAM 1","Price");
cstmt.setFloat("PARAM 5",150.25);
CallableStatement接口中新增了一些額外的方法允許參數通過名稱注冊和檢索。
DatabaseMetaData接口中提供了supportsNamedParameters()方法,用于判斷JDBC驅動是否支援指定命名參數。
對于IN參數的設定,調用CallableStatement接口中提供的setXXX()方法即可;但是對于OUT和INOUT參數,在CallableStatement執行之前,必須為每個參數調用CallableStatement接口中提供的registerOutParameter()方法,例如:
CallableStatement cstmt = conn.prepareCall("(CALL GET NAME AND NUMBER(?, ?))");
cstmt.registerOutParameter(1,java.sql.Types.STRING);
cstmt.registerOutParameter(2,java.sql.Types.FLOAT);
cstmt.execute() ;
//取 OUT 參數值
String name = cstmt.getString(1);
float number = cstmt.getFloat(2);
與Statement、PreparedStatement類似,CallableStatement也是使用executeQuery()、executeUpdate()、execute()等方法執行存儲過程的調用,傳回結果可能是ResultSet對象或者影響的行數,存儲過程調用結果的處理與Statement對象執行SQL結果的處理過程類似,這裡就不重複介紹了。
4.擷取自增長的鍵值
目前大多數資料庫都支援自增長主鍵,當向表中插入資料時,資料庫引擎可以自動生成自增長主鍵。Statement接口中提供了getGeneratedKeys()方法,用于擷取資料庫自動生成的值,該方法傳回一個ResultSet對象,我們可以從ResultSet對象中擷取資料庫中所有自增長的鍵值。
Statement接口中的execute()、executeUpdate()和Connection接口的prepareStatement()方法都可以接收一個可選的參數,該參數用于指定由資料庫生成的值是否可以被檢索,例如:
public void testJdbc() {
try {
Class.forName("org.hsqldb.jdbcDriver");
// 擷取Connection對象
Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
"sa", "");
Statement stmt = conn.createStatement();
String sql = "insert into user(create_time, name, password, phone, nick_name) " +
"values('2010-10-24 10:20:30','User1','test','18700001111','User1');";
stmt.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
ResultSet genKeys = stmt.getGeneratedKeys();
if(genKeys.next()) {
System.out.println("自增長主鍵:" + genKeys.getInt(1));
}
IOUtils.closeQuietly(stmt);
IOUtils.closeQuietly(conn);
} catch (Exception e) {
e.printStackTrace();
}
}
另外,Statement接口中還提供了execute()、executeUpdate()重載方法,能夠通過下标或者字段名指定哪些字段中自動生成的值可以被檢索,例如:
public void testJdbc2() {
try {
Class.forName("org.hsqldb.jdbcDriver");
// 擷取Connection對象
Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
"sa", "");
Statement stmt = conn.createStatement();
// 指定主鍵
String[] columnNames = new String[]{"id"};
String sql = "insert into user(create_time, name, password, phone, nick_name) " +
"values('2010-10-24 10:20:30','User1','test','18700001111','User1');";
stmt.executeUpdate(sql);
sql = "insert into user(create_time, name, password, phone, nick_name) " +
"values('2010-10-24 10:20:30','User1','test','18700001111','User1');";
stmt.executeUpdate(sql, columnNames);
ResultSet genKeys = stmt.getGeneratedKeys();
if(genKeys.next()) {
System.out.println("自增長主鍵:" + genKeys.getInt(1));
}
IOUtils.closeQuietly(stmt);
IOUtils.closeQuietly(conn);
} catch (Exception e) {
e.printStackTrace();
}
}