JavaSE(四)數據庫

JDBC

  java數據庫連接,是java語言訪問數據庫的途徑,對於使用者JDBC是一組API,對於JDBC驅動的實現者,JDBC是一組SPI。

JDBC驅動

  sun公司提供了數據庫操作的統一接口(JDBC API),但不同的數據庫有自己的訪問協議,於是需要各個公司把針對自己數據庫的具體操作封裝成類,該類需要實現java.sql.Driver接口,這個類就是JDBC驅動,這就像linux系統調用有統一的API,根據不同的硬件,只要驅動實現相應的接口就可以對硬件進行操作。

JDBC訪問數據庫步驟

  1. 加載數據庫驅動:Class.forName(“驅動類全名如com.mysql.jdbc.Driver”);驅動類的靜態塊裏面會new一個驅動類實例並註冊進DriverManager,當然也可以直接註冊驅動DriverManager.register(new com.mysql.jdbc.Driver()),從JDBC4.0(jdk1.6)開始支持自動加載驅動,不需要這一步了,他會在DriverManager的靜態塊中讀取META-INF/services/java.sql.Driver文件中定義的驅動名,並通過反射創建一個驅動實例註冊到DriverManager,JDBC4.0要求提供的驅動包裏面包含文件META-INF/services/java.sql.Driver文件,文件裏有註冊的驅動類全名,一個驅動管理器裏可以註冊多個驅動。
  2. 獲取數據庫連接:Connection connection = DriverManager.getConnection(“url”, “user”, “password”);會遍歷所有註冊的驅動,並找到和url中子協議匹配的驅動,並用該驅動的connect方法創建一個連接返回。
  3. 數據庫操作:通過Connection創建SQL語句並執行。
  4. 關閉連接:connection.close()。

數據庫操作

  步驟:創建語句->執行語句
  創建並執行普通語句
    connection.createStatement(); // 創建普通語句Statement。
    statement.executeUpdate(“sql語句”); // 執行更新操作。
    statement.executeQuery(“sql語句”); // 執行查詢操作。
  創建並執行預備語句
    connection.preparedStatement(“sql預備語句”); // 創建預備語句preparedStatement,sql預備語句可以用?佔位,但這個佔位的?應該是某個類型的變量位置,PreparedStatement繼承自Statement。
    preparedStatement.setInt(1, 3); // 將第1個?用整形3代替,如果多次使用同一預備語句而沒調用set,那麼對應的參數(這裏是第1個)是沒有變的。
    preparedStatement.setString(2, “wanglang”); // 將第2個參數用字符串wanglang代替。
    preparedStatement.executeUpdate(); // 執行更新操作。
    preparedStatement.executeQuery(); // 執行查詢操作。  
  創建並執行存儲過程語句
    connection.prepareCall(“call 過程名(?,?)”); // 創建存儲過程語句callableStatement,可以沒有參數,CallableStatement繼承自PreparedStatement。
    callableStatement.setString(1, “wl”); // 設置每一個輸入參數的值,多個參數多次調用。
    callableStatement.registerOutParameter(2, java.sql.Types.INTEGER); // 註冊輸出參數,如第二個參數既是輸入又是輸出參數,須設置並註冊。
    callableStatement.execute(); // 當然如果該調用返回的只是一個結果集或更新計數也可以用executeQuery或executeUpdate。
    callableStatement.getXxx(2); // 返回註冊的指定參數,返回類型和註冊的數據庫類型對應,調用存儲過程可能爲了分析輸出參數或返回。
  凡是非查詢操作的sql語句都是通過executeUpdate方法執行,返回sql語句影響的行數,對於一些DDL和插入語句等不影響行數的操作返回0
  查詢操作(包括統計查詢等)的sql語句都是通過executeQuery方法執行,返回一個結果集ResultSet,結果集默認順序是不確定的,除非語句中用了order by子句指定了順序,要想得到查詢結果,需要遍歷結果集,而且結果集是不能隨機訪問的。
  遍歷結果集的方式 while (rs.next()) {rs.getInt(可以是列字段名或列別名字符串,也可以是查詢結果的第幾列索引); rs.getString(…); rs.getDouble(…)等},我們可以把一個具有n條記錄的結果集看成是一個n+2元素的列表,中間n個剛好存放數據,列表的遊標最開始指向第一個,調用next遊標往下移動一個,如果到了最後一個就返回false,否則返回true,而rs.getXxx返回的一定是遊標指向的當前記錄的某個字段,對於不可滾動的結果集,遊標是不能隨機手動移動的,只能通過next讓遊標指向下一個。findCloumn(“列或別名”)返回該列對應的列索引,通過列索引獲取結果集的值效率會高些。
  一個connection可以創建多個statement,一個statement可以進行多次查詢,但一個statement只能有一個打開的結果集,可以執行完一次查詢然後分析結果集,再執行下一次查詢然後分析結果集,同一個語句獲得新的結果集之前會關閉上一個結果集,如果需要同時分析多個查詢的結果集就需要創建多個statement了,使用DatabaseMetaData的getMaxStatements方法可以獲取JDBC驅動支持的同時活動的statement個數,比如SQL server只支持一個活動的statement,所以建議一個connect只創建一個statement。connection、statement和resultSet用完後都需要關閉,關閉connection會關閉它創建的所有statement,關閉statement會關閉其打開的結果集,可以用帶資源的try塊獲取連接。
  大對象Blob和Clob的操作,讀時用getBlob或getClob獲取大對象,然後通過大對象的getBinaryStream()或getCharacterStream()獲取字節流inputString或字符流reader,有了inputString或reader對象,一切就是流操作了;簡便的方法就是直接通過結果集的getBinaryStream()或getCharacterStream()方法獲取字節流inputString或字符流reader。寫大對象先要構造出大對象,connection.createBlob或connection.createClob返回Blob和Clob,然後通過blob.setBinaryStream(0)和clob.setCharacterStream(0)獲得一個OutputString或Writer,再往裏面添加數據,最後通過setBlob或setClob往預備語句裏面添加大對象;但簡單的方法就是setBlob或sheClob時調用直接放入輸入流的方法而無須構造大對象。
  自動生成鍵的獲取,在插入數據時使用statement/preperedstatement.executeUpdate(“插入語句”, Statement.RETURN_GENERATED_KEYS/Statement.NO_GENERATED_KEYS默認);然後通過statement/preperedstatement.getGeneratedKeys()獲得一個結果集,該結果集就有自動生成的鍵。
  statement.execute(“sql語句”);得到多個結果集和更新計數,如果該語句的第一個結果爲結果集就返回true,否則返回false。在使用時可以調用該語句,如果返回的是true,便通過statement.getResultSet獲得結果集,處理了再調用statement.getMoreResults();如果返回true則是結果集,同樣地處理,如果返回false還需要調用statement.getUpdateCount,如果返回-1那麼表 示下一個也不是更新計數,這時才表示把結果集和更新計數全部做完了。一般用在編譯時無法確定sql語句返回的是結果集或更新計數的情況下,或在執行存儲過程時使用它得到多個結果集或更新計數。對於可以用SQL語句解決的問題就不要用java代碼,這樣效率會高很多。
  無論是set設置預備語句參數還是從結果集中返回列的數據,都是從1開始不是像數組從0開始

可滾動的結果集和可更新的結果集

  可更新的結果集和可滾動的結果集並不是所有的驅動都支持,所以建議程序中不使用可滾動和可更新的結果集
  可滾動的結果集就是可以隨機指定遊標的位置,rs.first()遊標跑到第一個記錄、rs.last()、rs.beforeFirst()、rs.afterLast()、rs.isFirst()、rs.isLast()、rs.isBeforeFirst()、rs.isAfterLast()、re.previous()、rs.next()、rs.absolute(n)移動到指定的行、rs.relative(n)前進n行,負數就退回,除了beforeFirst和afterLast,其他函數一般都是先判斷定位的位置是否是有意義的行,是就返回true並跳過去,否則就返回false並不動。
  可更新的結果集就是在更新結果集的時候自動更新數據庫,rs.updateXxx(列號或列名, Xxx類型新值)更新結果集中當前行的值;rs.updateRow()將rs行的所有更改寫入數據庫,也可以通過cancelRowUpdates()來取消對當前行的修改。rs.deleteRow()同時從結果集中和數據庫中刪除當前行。rs.moveToInsertRow()建立一個空的插入行並將遊標移動過去,rs.updateXxx()對插入行添加數據,沒有添加數據的行默認爲null,rs.insertRow()將該行添加到數據庫,rs.moveToCurrentRow()跳回原來的行,插入的行位置是不確定的。
  要獲得可滾動和可更新的結果集,在創建語句時需要特殊參數,connection.createStatement(resultSetType, resultSetConcurrency)/connection.prepareCall(sql, resultSetType, resultSetConcurrency),resultSetType可以取ResultSet.TYPE_FORWARD_ONLY不可更新的結果集、ResultSet.TYPE_SCROLL_INSENSITIVE對數據庫變化不敏感的結果集、ResultSet.TYPE_SCROLL_SENSITIVE對數據庫變化敏感(如果數據庫變化了,新的結果集滾動時也會更新),resultSetConcurrency可以取ResultSet.CONCUR_READ_ONLY不可更新的結果集、ResultSet.CONCUR_UPDATABLE可更新的結果集,雖然參數指定更新結果集,但返回的結果集也不一定是可更新的,必須是有主鍵的單表或只通過主鍵連接的多個表聯合查詢纔會返回可更新的結果集,可以通過resultSet.getType和resultSet.getConcurrency查看其結果集的實際狀況是否可滾動和是否可更新。
  可滾動的結果集和可更新結果集整個過程中都保持着數據庫的連接,很耗資源

行集

  行集RowSet是ResultSet的子接口。行集有很多實現類,主要介紹CachedRowSet。
  結果集有一個弊端就是要始終保持連接不關閉,因爲斷開連接就會銷燬語句和結果集,這是很耗資源的,而行集無須始終保持數據庫連接。
  得到一個行集的步驟爲先通過RowSetProvider.newFactory()獲得行集工廠RowSetFactory,再通過工廠創建行集rowSetFactory.createCachedRowSet(),再往行集裏面添加內容rowSetFactory.populate(resultSet);這樣得到一個行集,相當於把resultSet克隆下來了,然後關閉連接把原來的語句和結果集一起關掉了,但我們任然可以分析查詢結果。在普通的結果集依次分析結果集的情況倒沒什麼,分析完就可以關閉連接,不怎麼消耗資源,但如果是可滾動的結果集,由於需要長期滾動查詢,所以結果集不能關,所以語句和連接也不能關,浪費資源,這時候就可以用行集。
  另一種方式就是不用結果集來克隆行集,而是通過行集執行語句用返回結果填充行集,步驟先獲得行集對象同上,然後設置數據庫參數cachedRowSet.setUrl(“url”);cachedRowSet.setUserName(“userName”);cachedRowSet.setPassword(“password”);再設置預備語句cachedRowSet.setCommand(“sql預備語句”);cachedRowSet.setXxx(第n個?, value);最後執行語句並用結果填充行集cachedRowSet.execute();在執行語句之前可以cachedRowSet.setPageSize(n)來設置一次查詢的行數; cachedRowSet.nextPage()加載下一頁; cachedRowSet.previousPage()加載上一頁。
  將行集的改變更新到數據,cachedRowSet.acceptChanges(); cachedRowSet.acceptChanges(connection)如果行集沒有設置數據庫參數;同結果集一樣,也並不是所有的行集都是可更新的。

元數據

  描述數據庫、表本身而非業務的數據,主要有關於數據庫、結果集和預備語句參數的元數據。
  connection.getMetaData()獲取數據庫相關的元數據對象DatabaseMetaData,然後通過數據庫元數據對象獲取需要的數據庫信息,如獲取所有的表databaseMetaData.getTables(catalog, schemaPattern, tableNamePattern, types表類型可以是表、視圖、系統表等null爲所有)返回一個結果集,每一個結果集代表一張表,第3列就是表的名稱,具體每一列什麼意思可以查文檔,databaseMetaData.getMaxConnections()獲取數據庫的最大並行連接數,databaseMetaData.supportsBatchUpdates()是否支持批量更新。
  resultSet.getMetaData()獲取結果集相關的元數據對象ResultSetMetaData,然後通過結果集元數據對象獲取結果集相關的結果集信息,如resultSetMetaData.getColumnCount()獲取結果集列數,resultSetMetaData.getColumnName(column)獲取某一列的名稱等。

事務

  事務具有ACID四大特性,即原子性(Atomicity)、一致性(Correspondence)、隔離性(Isolation)、持久性(Durability)。一個事務裏面的多個語句要麼都執行,要麼都不執行,具有原子性。
  默認情況下獲得的連接在執行語句時都是自動提交的,如果要使用事務,需要讓連接禁止自動提交connection.setAutoCommit(false);然後創建並執行一系列語句,最後需要提交connection.commit();當然中途如果發生意外或自己想回到上次提交之前,可以回滾事務connection.rollback()。
  如果不想完全回滾到事務的開頭,而是回滾到某一個位置,可以在事務的一系列語句中某處添加保存點connection.setSavePoint()返回一個savePoint對象,在需要回滾到savePoint的時候調用connection.rollback(savePoint),在不使用保存點的時候一定要釋放保存點connection.releaseSavePoint(savePoint)。
  將一些列的更新操作作爲批量操作可以提高效率,批量操作不能添加查詢語句,statement.addBatch(“sql更新語句1”);statement.addBatch(“sql更新語句2”); statement.executeBatch();返回一個int[]對應每一個操作的返回值,也可以用預處理語句,preparedStatement.set(1,3); preparedStatement.addBatch(); preparedStatement.set(1,5); preparedStatement.addBatch(); preparedStatement.executeBatch; 如果批處理語句過多,需要分批操作,比如100條分爲一批進行操作,批量操作並不是原子性的,所以我們可以把批量操作用事務處理,在開始關閉自動提交,再是批量操作,然後手動提交,如果出錯回滾事務。

數據源

  數據源都須要實現javax.sql.DataSource接口,其最重要的接口函數就是getConnection(),通常具有連接池的功能,一個數據源可以有多個連接池。
  自己寫連接池要注意,連接池中空閒的連接可能由於某些原因已經斷開,所以可以在每次從連接池中獲取連接時先檢查連接是否可用connection.isValid(超時時間單位秒),或者用新的線程不斷檢測連接池中連接的可用狀態(這種方式交互性好,因爲檢查連接可用否是需要時間的),另外將用過的連接放回連接池時應該釋放連接的statement資源,可以在每次創建statement的時候,用個鏈表把同一個Connection裏面所有的statement連起來,這樣在釋放一個連接(實際上並未真正地關閉它)的時候就知道該釋放哪些statement了,否則同一個連接的statement會越來越多。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章