JDBC常見面試題集錦(一)

deepinmind發表於2014-03-19

  什麼是JDBC,在什麼時候會用到它?

  JDBC的全稱是Java DataBase Connection,也就是Java資料庫連線,我們可以用它來操作關係型資料庫。JDBC介面及相關類在java.sql包和javax.sql包裡。我們可以用它來連線資料庫,執行SQL查詢,儲存過程,並處理返回的結果。

  JDBC介面讓Java程式和JDBC驅動實現了鬆耦合,使得切換不同的資料庫變得更加簡單。

  有哪些不同型別的JDBC驅動?

  有四類JDBC驅動。和資料庫進行互動的Java程式分成兩個部分,一部分是JDBC的API,實際工作的驅動則是另一部分。

  A JDBC-ODBC Bridge plus ODBC Driver(型別1):它使用ODBC驅動連線資料庫。需要安裝ODBC以便連線資料庫,正因為這樣,這種方式現在已經基本淘汰了。

  B Native API partly Java technology-enabled driver(型別2):這種驅動把JDBC呼叫適配成資料庫的本地介面的呼叫。

  C Pure Java Driver for Database Middleware(型別3):這個驅動把JDBC呼叫轉發給中介軟體伺服器,由它去和不同的資料庫進行連線。用這種型別的驅動需要部署中介軟體伺服器。這種方式增加了額外的網路呼叫,導致效能變差,因此很少使用。

  D Direct-to-Database Pure Java Driver(型別4):這個驅動把JDBC轉化成資料庫使用的網路協議。這種方案最簡單,也適合通過網路連線資料庫。不過使用這種方式的話,需要根據不同資料庫選用特定的驅動程式,比如OJDBC是Oracle開發的Oracle資料庫的驅動,而MySQL Connector/J是MySQL資料庫的驅動。

  JDBC是如何實現Java程式和JDBC驅動的鬆耦合的?

  JDBC API使用Java的反射機制來實現Java程式和JDBC驅動的鬆耦合。隨便看一個簡單的JDBC示例,你會發現所有操作都是通過JDBC介面完成的,而驅動只有在通過Class.forName反射機制來載入的時候才會出現。

  我覺得這是Java核心庫裡反射機制的最佳實踐之一,它使得應用程式和驅動程式之間進行了隔離,讓遷移資料庫的工作變得更簡單。在這裡可以看到更多JDBC的使用示例。

  什麼是JDBC連線,在Java中如何建立一個JDBC連線?

  JDBC連線是和資料庫伺服器建立的一個會話。你可以想像成是一個和資料庫的Socket連線。

  建立JDBC連線很簡單,只需要兩步:

  A. 註冊並載入驅動:使用Class.forName(),驅動類就會註冊到DriverManager裡面並載入到記憶體裡。 B. 用DriverManager獲取連線物件:呼叫DriverManager.getConnnection()方法並傳入資料庫連線的URL,使用者名稱及密碼,就能獲取到連線物件。

Connection con = null;
try{
    // load the Driver Class
    Class.forName("com.mysql.jdbc.Driver");
    // create the connection now
    con = DriverManager.getConnection("jdbc:mysql://localhost:3306/UserDB",
                    "pankaj",
                    "pankaj123");
    }catch (SQLException e) {
            System.out.println("Check database is UP and configs are correct");
            e.printStackTrace();
    }catch (ClassNotFoundException e) {
            System.out.println("Please include JDBC MySQL jar in classpath");
            e.printStackTrace();
    }

  JDBC的DriverManager是用來做什麼的?

  JDBC的DriverManager是一個工廠類,我們通過它來建立資料庫連線。當JDBC的Driver類被載入進來時,它會自己註冊到DriverManager類裡面,你可以看下JDBC Driver類的原始碼來了解一下。

  然後我們會把資料庫配置資訊傳成DriverManager.getConnection()方法,DriverManager會使用註冊到它裡面的驅動來獲取資料庫連線,並返回給呼叫的程式。

  在Java程式中,如何獲取資料庫伺服器的相關資訊?

  使用DatabaseMetaData可以獲取到伺服器的資訊。當和資料庫的連線成功建立了之後,可以通過呼叫getMetaData()方法來獲取資料庫的元資訊。DatabaseMetaData裡面有很多方法,通過它們可以獲取到資料庫的產品名稱,版本號,配置資訊等。

DatabaseMetaData metaData = con.getMetaData();
String dbProduct = metaData.getDatabaseProductName();

  JDBC的Statement是什麼?

  Statement是JDBC中用來執行資料庫SQL查詢語句的介面。通過呼叫連線物件的getStatement()方法我們可以生成一個Statement物件。我們可以通過呼叫它的execute(),executeQuery(),executeUpdate()方法來執行靜態SQL查詢。

  由於SQL語句是程式中傳入的,如果沒有對使用者輸入進行校驗的話可能會引起SQL隱碼攻擊的問題,如果想了解更多關於SQL隱碼攻擊的,可以看下這裡

  預設情況下,一個Statement同時只能開啟一個ResultSet。如果想操作多個ResultSet物件的話,需要建立多個Statement。Statement介面的所有execute方法開始執行時都預設會關閉當前開啟的ResultSet。

  execute,executeQuery,executeUpdate的區別是什麼?

  Statement的execute(String query)方法用來執行任意的SQL查詢,如果查詢的結果是一個ResultSet,這個方法就返回true。如果結果不是ResultSet,比如insert或者update查詢,它就會返回false。我們可以通過它的getResultSet方法來獲取ResultSet,或者通過getUpdateCount()方法來獲取更新的記錄條數。

  Statement的executeQuery(String query)介面用來執行select查詢,並且返回ResultSet。即使查詢不到記錄返回的ResultSet也不會為null。我們通常使用executeQuery來執行查詢語句,這樣的話如果傳進來的是insert或者update語句的話,它會丟擲錯誤資訊為 “executeQuery method can not be used for update”的java.util.SQLException。

  Statement的executeUpdate(String query)方法用來執行insert或者update/delete(DML)語句,或者 什麼也不返回DDL語句。返回值是int型別,如果是DML語句的話,它就是更新的條數,如果是DDL的話,就返回0。

  只有當你不確定是什麼語句的時候才應該使用execute()方法,否則應該使用executeQuery或者executeUpdate方法。

  JDBC的PreparedStatement是什麼?

  PreparedStatement物件代表的是一個預編譯的SQL語句。用它提供的setter方法可以傳入查詢的變數。

  由於PreparedStatement是預編譯的,通過它可以將對應的SQL語句高效的執行多次。由於PreparedStatement自動對特殊字元轉義,避免了SQL隱碼攻擊,因此應當儘量的使用它。

  PreparedStatement中如何注入NULL值?

  可以使用它的setNull方法來把null值繫結到指定的變數上。setNull方法需要傳入引數的索引以及SQL欄位的型別,像這樣:

ps.setNull(10, java.sql.Types.INTEGER);.

  Statement中的getGeneratedKeys方法有什麼用?

  有的時候表會生成主鍵,這時候就可以用Statement的getGeneratedKeys()方法來獲取這個自動生成的主鍵的值了。

  相對於Statement,PreparedStatement的優點是什麼?

  它和Statement相比優點在於:

  • PreparedStatement有助於防止SQL隱碼攻擊,因為它會自動對特殊字元轉義。
  • PreparedStatement可以用來進行動態查詢。
  • PreparedStatement執行更快。尤其當你重用它或者使用它的拼量查詢介面執行多條語句時。
  • 使用PreparedStatement的setter方法更容易寫出物件導向的程式碼,而Statement的話,我們得拼接字串來生成查詢語句。如果引數太多了,字串拼接看起來會非常醜陋並且容易出錯。

  PreparedStatement的缺點是什麼,怎麼解決這個問題?

  PreparedStatement的一個缺點是,我們不能直接用它來執行in條件語句;需要執行IN條件語句的話,下面有一些解決方案:

  • 分別進行單條查詢——這樣做效能很差,不推薦。
  • 使用儲存過程——這取決於資料庫的實現,不是所有資料庫都支援。
  • 動態生成PreparedStatement——這是個好辦法,但是不能享受PreparedStatement的快取帶來的好處了。
  • 在PreparedStatement查詢中使用NULL值——如果你知道輸入變數的最大個數的話,這是個不錯的辦法,擴充套件一下還可以支援無限引數。

  關於這個問題更詳細的分析可以看下這篇文章

  JDBC的ResultSet是什麼?

  在查詢資料庫後會返回一個ResultSet,它就像是查詢結果集的一張資料表。

  ResultSet物件維護了一個遊標,指向當前的資料行。開始的時候這個遊標指向的是第一行。如果呼叫了ResultSet的next()方法遊標會下移一行,如果沒有更多的資料了,next()方法會返回false。可以在for迴圈中用它來遍歷資料集。

  預設的ResultSet是不能更新的,遊標也只能往下移。也就是說你只能從第一行到最後一行遍歷一遍。不過也可以建立可以回滾或者可更新的ResultSet,像下面這樣。

Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
                                   ResultSet.CONCUR_UPDATABLE);

  當生成ResultSet的Statement物件要關閉或者重新執行或是獲取下一個ResultSet的時候,ResultSet物件也會自動關閉。

  可以通過ResultSet的getter方法,傳入列名或者從1開始的序號來獲取列資料。

  有哪些不同的ResultSet?

  根據建立Statement時輸入引數的不同,會對應不同型別的ResultSet。如果你看下Connection的方法,你會發現createStatement和prepareStatement方法過載了,以支援不同的ResultSet和併發型別。

  一共有三種ResultSet物件。

  • ResultSet.TYPE_FORWARD_ONLY:這是預設的型別,它的遊標只能往下移。
  • ResultSet.TYPE_SCROLL_INSENSITIVE:遊標可以上下移動,一旦它建立後,資料庫裡的資料再發生修改,對它來說是透明的。
  • ResultSet.TYPE_SCROLL_SENSITIVE:遊標可以上下移動,如果生成後資料庫還發生了修改操作,它是能夠感知到的。

  ResultSet有兩種併發型別。

  • ResultSet.CONCUR_READ_ONLY:ResultSet是隻讀的,這是預設型別。
  • ResultSet.CONCUR_UPDATABLE:我們可以使用ResultSet的更新方法來更新裡面的資料。

  Statement中的setFetchSize和setMaxRows方法有什麼用處?

  setMaxRows可以用來限制返回的資料集的行數。當然通過SQL語句也可以實現這個功能。比如在MySQL中我們可以用LIMIT條件來設定返回結果的最大行數。

  setFetchSize理解起來就有點費勁了,因為你得知道Statement和ResultSet是怎麼工作的。當資料庫在執行一條查詢語句時,查詢到的資料是在資料庫的快取中維護的。ResultSet其實引用的是資料庫中快取的結果。

  假設我們有一條查詢返回了100行資料,我們把fetchSize設定成了10,那麼資料庫驅動每次只會取10條資料,也就是說得取10次。當每條資料需要處理的時間比較長的時候並且返回資料又非常多的時候,這個可選的引數就變得非常有用了。

  我們可以通過Statement來設定fetchSize引數,不過它會被ResultSet物件設定進來的值所覆蓋掉。

  如何使用JDBC介面來呼叫儲存過程?

  儲存過程就是資料庫編譯好的一組SQL語句,可以通過JDBC介面來進行呼叫。我們可以通過JDBC的CallableStatement介面來在資料庫中執行儲存過程。初始化CallableStatement的語法是這樣的:

CallableStatement stmt = con.prepareCall("{call insertEmployee(?,?,?,?,?,?)}");
stmt.setInt(1, id);
stmt.setString(2, name);
stmt.setString(3, role);
stmt.setString(4, city);
stmt.setString(5, country);
//register the OUT parameter before calling the stored procedure
stmt.registerOutParameter(6, java.sql.Types.VARCHAR);
stmt.executeUpdate();

  我們得在執行CallableStatement之前註冊OUT引數。關於這個更詳細的資料可以看這裡

  JDBC的批處理是什麼,有什麼好處?

  有時候類似的查詢我們需要執行很多遍,比如從CSV檔案中載入資料到關係型資料庫的表裡。我們也知道,執行查詢可以用Statement或者PreparedStatement。除此之外,JDBC還提供了批處理的特性,有了它,我們可以在一次資料庫呼叫中執行多條查詢語句。

  JDBC通過Statement和PreparedStatement中的addBatch和executeBatch方法來支援批處理。

  批處理比一條條語句執行的速度要快得多,因為它需要很少的資料庫呼叫,想進一步瞭解請點這裡

  JDBC的事務管理是什麼,為什麼需要它?

  預設情況下,我們建立的資料庫連線,是工作在自動提交的模式下的。這意味著只要我們執行完一條查詢語句,就會自動進行提交。因此我們的每條查詢,實際上都是一個事務,如果我們執行的是DML或者DDL,每條語句完成的時候,資料庫就已經完成修改了。

  有的時候我們希望由一組SQL查詢組成一個事務,如果它們都執行OK我們再進行提交,如果中途出現異常了,我們可以進行回滾。

  JDBC介面提供了一個setAutoCommit(boolean flag)方法,我們可以用它來關閉連線自動提交的特性。我們應該在需要手動提交時才關閉這個特性,不然的話事務不會自動提交,每次都得手動提交。資料庫通過表鎖來管理事務,這個操作非常消耗資源。因此我們應當完成操作後儘快的提交事務。在這裡有更多關於事務的示例程式。

  如何回滾事務?

  通過Connection物件的rollback方法可以回滾事務。它會回滾這次事務中的所有修改操作,並釋放當前連線所持有的資料庫鎖。

  譯註:先到這吧,後面還有20題,明天繼續。都比較基礎,不過有些同學可能長期使用ORM框架,就當複習一下了,萬一面試問到呢~

  相關文件:JDBC常見面試題集錦(二)

相關文章