JDBC的全稱是Java DataBase Connection,也就是Java數據庫鏈接,咱們能夠用它來操做關係型數據庫。JDBC接口及相關類在java.sql包和javax.sql包裏。咱們能夠用它來鏈接數據庫,執行SQL查詢,存儲過程,並處理返回的結果。java
JDBC接口讓Java程序和JDBC驅動實現了鬆耦合,使得切換不一樣的數據庫變得更加簡單。mysql
有四類JDBC驅動。和數據庫進行交互的Java程序分紅兩個部分,一部分是JDBC的API,實際工做的驅動則是另外一部分。web
A JDBC-ODBC Bridge plus ODBC Driver(類型1):它使用ODBC驅動鏈接數據庫。須要安裝ODBC以便鏈接數據庫,正由於這樣,這種方式如今已經基本淘汰了。sql
B Native API partly Java technology-enabled driver(類型2):這種驅動把JDBC調用適配成數據庫的本地接口的調用。數據庫
C Pure Java Driver for Database Middleware(類型3):這個驅動把JDBC調用轉發給中間件服務器,由它去和不一樣的數據庫進行鏈接。用這種類型的驅動須要部署中間件服務器。這種方式增長了額外的網絡調用,致使性能變差,所以不多使用。apache
D Direct-to-Database Pure Java Driver(類型4):這個驅動把JDBC轉化成數據庫使用的網絡協議。這種方案最簡單,也適合經過網絡鏈接數據庫。不過使用這種方式的話,須要根據不一樣數據庫選用特定的驅動程序,好比OJDBC是Oracle開發的Oracle數據庫的驅動,而MySQL Connector/J是MySQL數據庫的驅動。數組
JDBC API使用Java的反射機制來實現Java程序和JDBC驅動的鬆耦合。隨便看一個簡單的JDBC示例,你會發現全部操做都是經過JDBC接口完成的,而驅動只有在經過Class.forName反射機制來加載的時候纔會出現。緩存
我以爲這是Java核心庫裏反射機制的最佳實踐之一,它使得應用程序和驅動程序之間進行了隔離,讓遷移數據庫的工做變得更簡單。在這裏能夠看到更多JDBC的使用示例。tomcat
JDBC鏈接是和數據庫服務器創建的一個會話。你能夠想像成是一個和數據庫的Socket鏈接。服務器
建立JDBC鏈接很簡單,只須要兩步:
A. 註冊並加載驅動:使用Class.forName(),驅動類就會註冊到DriverManager裏面並加載到內存裏。 B. 用DriverManager獲取鏈接對象:調用DriverManager.getConnnection()方法並傳入數據庫鏈接的URL,用戶名及密碼,就能獲取到鏈接對象。
1 Connection con = null; 2 try{ 3 // load the Driver Class 4 Class.forName("com.mysql.jdbc.Driver"); 5 // create the connection now 6 con = DriverManager.getConnection("jdbc:mysql://localhost:3306/DBName", 7 "username", 8 "password"); 9 }catch (SQLException e) { 10 System.out.println("Check database is UP and configs are correct"); 11 e.printStackTrace(); 12 }catch (ClassNotFoundException e) { 13 System.out.println("Please include JDBC MySQL jar in classpath"); 14 e.printStackTrace(); 15 } 16 }
JDBC的DriverManager是用來作什麼的?
JDBC的DriverManager是一個工廠類,咱們經過它來建立數據庫鏈接。當JDBC的Driver類被加載進來時,它會本身註冊到DriverManager類裏面,你能夠看下JDBC Driver類的源碼來了解一下。
而後咱們會把數據庫配置信息傳成DriverManager.getConnection()方法,DriverManager會使用註冊到它裏面的驅動來獲取數據庫鏈接,並返回給調用的程序。
使用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。
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方法。
PreparedStatement對象表明的是一個預編譯的SQL語句。用它提供的setter方法能夠傳入查詢的變量。
因爲PreparedStatement是預編譯的,經過它能夠將對應的SQL語句高效的執行屢次。因爲PreparedStatement自動對特殊字符轉義,避免了SQL注入攻擊,所以應當儘可能的使用它。
可使用它的setNull方法來把null值綁定到指定的變量上。setNull方法須要傳入參數的索引以及SQL字段的類型,像這樣:
ps.setNull(10, java.sql.Types.INTEGER);.
有的時候表會生成主鍵,這時候就能夠用Statement的getGeneratedKeys()方法來獲取這個自動生成的主鍵的值了。
它和Statement相比優勢在於:
PreparedStatement的一個缺點是,咱們不能直接用它來執行in條件語句;須要執行IN條件語句的話,下面有一些解決方案:
關於這個問題更詳細的分析能夠看下這篇文章。
在查詢數據庫後會返回一個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開始的序號來獲取列數據。
根據建立Statement時輸入參數的不一樣,會對應不一樣類型的ResultSet。若是你看下Connection的方法,你會發現createStatement和prepareStatement方法重載了,以支持不一樣的ResultSet和併發類型。
一共有三種ResultSet對象。
ResultSet有兩種併發類型。
setMaxRows能夠用來限制返回的數據集的行數。固然經過SQL語句也能夠實現這個功能。好比在MySQL中咱們能夠用LIMIT條件來設置返回結果的最大行數。
setFetchSize理解起來就有點費勁了,由於你得知道Statement和ResultSet是怎麼工做的。當數據庫在執行一條查詢語句時,查詢到的數據是在數據庫的緩存中維護的。ResultSet其實引用的是數據庫中緩存的結果。
假設咱們有一條查詢返回了100行數據,咱們把fetchSize設置成了10,那麼數據庫驅動每次只會取10條數據,也就是說得取10次。當每條數據須要處理的時間比較長的時候而且返回數據又很是多的時候,這個可選的參數就變得很是有用了。
咱們能夠經過Statement來設置fetchSize參數,不過它會被ResultSet對象設置進來的值所覆蓋掉。
存儲過程就是數據庫編譯好的一組SQL語句,能夠經過JDBC接口來進行調用。咱們能夠經過JDBC的CallableStatement接口來在數據庫中執行存儲過程。初始化CallableStatement的語法是這樣的:
1 CallableStatement stmt = con.prepareCall("{call insertEmployee(?,?,?,?,?,?)}"); 2 stmt.setInt(1, id); 3 stmt.setString(2, name); 4 stmt.setString(3, role); 5 stmt.setString(4, city); 6 stmt.setString(5, country); 7 //register the OUT parameter before calling the stored procedure 8 stmt.registerOutParameter(6, java.sql.Types.VARCHAR); 9 stmt.executeUpdate();
咱們得在執行CallableStatement以前註冊OUT參數。關於這個更詳細的資料能夠看這裏。
有時候相似的查詢咱們須要執行不少遍,好比從CSV文件中加載數據到關係型數據庫的表裏。咱們也知道,執行查詢能夠用Statement或者PreparedStatement。除此以外,JDBC還提供了批處理的特性,有了它,咱們能夠在一次數據庫調用中執行多條查詢語句。
JDBC經過Statement和PreparedStatement中的addBatch和executeBatch方法來支持批處理。
批處理比一條條語句執行的速度要快得多,由於它須要不多的數據庫調用,想進一步瞭解請點這裏。
默認狀況下,咱們建立的數據庫鏈接,是工做在自動提交的模式下的。這意味着只要咱們執行完一條查詢語句,就會自動進行提交。所以咱們的每條查詢,實際上都是一個事務,若是咱們執行的是DML或者DDL,每條語句完成的時候,數據庫就已經完成修改了。
有的時候咱們但願由一組SQL查詢組成一個事務,若是它們都執行OK咱們再進行提交,若是中途出現異常了,咱們能夠進行回滾。
JDBC接口提供了一個setAutoCommit(boolean flag)方法,咱們能夠用它來關閉鏈接自動提交的特性。咱們應該在須要手動提交時才關閉這個特性,否則的話事務不會自動提交,每次都得手動提交。數據庫經過表鎖來管理事務,這個操做很是消耗資源。所以咱們應當完成操做後儘快的提交事務。在這裏有更多關於事務的示例程序。
經過Connection對象的rollback方法能夠回滾事務。它會回滾此次事務中的全部修改操做,並釋放當前鏈接所持有的數據庫鎖。
JDBC的保存點(Savepoint)是什麼,如何使用?
有時候事務包含了一組語句,而咱們但願回滾到這個事務的某個特定的點。JDBC的保存點能夠用來生成事務的一個檢查點,使得事務能夠回滾到這個檢查點。
一旦事務提交或者回滾了,它生成的任何保存點都會自動釋放並失效。回滾事務到某個特定的保存點後,這個保存點後全部其它的保存點會自動釋放而且失效。能夠讀下這個瞭解更多關於JDBC Savepoint的信息。
DataSource即數據源,它是定義在javax.sql中的一個接口,跟DriverManager相比,它的功能要更強大。咱們能夠用它來建立數據庫鏈接,固然驅動的實現類會實際去完成這個工做。除了能建立鏈接外,它還提供了以下的特性:
關於JDBC數據源的示例請看下這裏。
對部署在servlet容器中的WEB程序而言,建立數據庫鏈接池很是簡單,僅須要如下幾步。
1 <Resource name="jdbc/MyDB" 2 global="jdbc/MyDB" 3 auth="Container" 4 type="javax.sql.DataSource" 5 driverClassName="com.mysql.jdbc.Driver" 6 url="jdbc:mysql://localhost:3306/UserDB" 7 username="pankaj" 8 password="pankaj123" 9 maxActive="100" 10 maxIdle="20" 11 minIdle="5" 12 maxWait="10000"/> 13 <ResourceLink name="jdbc/MyLocalDB" 14 global="jdbc/MyDB" 15 auth="Container" 16 type="javax.sql.DataSource" />
在WEB應用程序中,先用InitialContext來查找JNDI資源,而後獲取鏈接。
Context ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup("java:/comp/env/jdbc/MyLocalDB");
完整的示例請看這裏。
若是用DataSource來獲取鏈接的話,一般獲取鏈接的代碼和驅動特定的DataSource是緊耦合的。另外,除了選擇DataSource的實現類,剩下的代碼基本都是同樣的。
Apache的DBCP就是用來解決這些問題的,它提供的DataSource實現成爲了應用程序和不一樣JDBC驅動間的一個抽象層。Apache的DBCP庫依賴commons-pool庫,因此要確保它們都在部署路徑下。
完整的使用示例請看這裏。
當咱們爲了數據的一致性使用事務時,數據庫系統用鎖來防止別人訪問事務中用到的數據。數據庫經過鎖來防止髒讀,不可重複讀(Non-Repeatable Reads)及幻讀(Phantom-Read)的問題。
數據庫使用JDBC設置的隔離級別來決定它使用何種鎖機制,咱們能夠經過Connection的getTransactionIsolation和setTransactionIsolation方法來獲取和設置數據庫的隔離級別。
隔離級別 | 事務 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|---|
TRANSACTION_NONE | 不支持 | 不可用 | 不可用 | 不可用 |
TRANSACTION_READ_COMMITTED | 支持 | 阻止 | 容許 | 容許 |
TRANSACTION_READ_UNCOMMITTED | 支持 | 容許 | 容許 | 容許 |
TRANSACTION_REPEATABLE_READ | 支持 | 阻止 | 阻止 | 容許 |
TRANSACTION_SERIALIZABLE | 支持 | 阻止 | 阻止 | 阻止 |
RowSet用於存儲查詢的數據結果,和ResultSet相比,它更具靈活性。RowSet繼承自ResultSet,所以ResultSet能幹的,它們也能,而ResultSet作不到的,它們仍是能夠。RowSet接口定義在javax.sql包裏。
RowSet提供的額外的特性有:
RowSet分爲兩大類:
A. 鏈接型RowSet——這類對象與數據庫進行鏈接,和ResultSet很相似。JDBC接口只提供了一種鏈接型RowSet,javax.sql.rowset.JdbcRowSet,它的標準實現是com.sun.rowset.JdbcRowSetImpl。 B. 離線型RowSet——這類對象不須要和數據庫進行鏈接,所以它們更輕量級,更容易序列化。它們適用於在網絡間傳遞數據。有四種不一樣的離線型RowSet的實現。
RowSet繼承自ResultSet,所以它有ResultSet的所有功能,同時它本身添加了些額外的特性。RowSet一個最大的好處是它能夠是離線的,這樣使得它更輕量級,同時便於在網絡間進行傳輸。
具體使用哪一個取決於你的需求,不過若是你操做ResultSet對象的時間較長的話,最好選擇一個離線的RowSet,這樣能夠釋放數據庫鏈接。
有如下這些:
CLOB意思是Character Large OBjects,字符大對象,它是由單字節字符組成的字符串數據,有本身專門的代碼頁。這種數據類型適用於存儲超長的文本信息,那些可能會超出標準的VARCHAR數據類型長度限制(上限是32KB)的文本。
BLOB是Binary Larget OBject,它是二進制大對象,由二進制數據組成,沒有專門的代碼頁。它能用於存儲超過VARBINARY限制(32KB)的二進制數據。這種數據類型適合存儲圖片,聲音,圖形,或者其它業務程序特定的數據。
當咱們使用事務時,有可能會出現這樣的狀況,有一行數據剛更新,與此同時另外一個查詢讀到了這個剛更新的值。這樣就致使了髒讀,由於更新的數據尚未進行持久化,更新這行數據的業務可能會進行回滾,這樣這個數據就是無效的。
數據庫的TRANSACTIONREADCOMMITTED,TRANSACTIONREPEATABLEREAD,和TRANSACTION_SERIALIZABLE隔離級別能夠防止髒讀。
當咱們在分佈式系統上同時使用多個數據庫時,這時候咱們就須要用到兩階段提交協議。兩階段提交協議能保證是分佈式系統提交的原子性。在第一個階段,事務管理器發全部的事務參與者發送提交的請求。若是全部的參與者都返回OK,它會向參與者正式提交該事務。若是有任何一個參與方返回了停止消息,事務管理器會回滾全部的修改動做。
從廣義上講,有兩種鎖機制來防止多個用戶同時操做引發的數據損壞。
樂觀鎖——只有當更新數據的時候纔會鎖定記錄。 悲觀鎖——從查詢到更新和提交整個過程都會對數據記錄進行加鎖。
不只如此,一些數據庫系統還提供了行鎖,表鎖等鎖機制。
DDL(數據定義語言,Data Definition Language)語句用來定義數據庫模式。Create,Alter, Drop, Truncate, Rename都屬於DDL語句,通常來講,它們是不返回結果的。
DML(數據操做語言,Data Manipulation Language)語句用來操做數據庫中的數據。select, insert, update, delete, call等,都屬於DML語句。
java.util.Date包含日期和時間,而java.sql.Date只包含日期信息,而沒有具體的時間信息。若是你想把時間信息存儲在數據庫裏,能夠考慮使用Timestamp或者DateTime字段。
可使用BLOB類型將圖片或者原始的二進制數據存儲到數據庫裏。
幻讀是指一個事務屢次執行一條查詢返回的倒是不一樣的值。假設一個事務正根據某個條件進行數據查詢,而後另外一個事務插入了一行知足這個查詢條件的數據。以後這個事務再次執行了這條查詢,返回的結果集中會包含剛插入的那條新數據。這行新數據被稱爲幻行,而這種現象就叫作幻讀。
只有TRANSACTION_SERIALIZABLE隔離級別才能防止產生幻讀。
SQLWarning是SQLException的子類,經過Connection, Statement, Result的getWarnings方法均可以獲取到它。 SQLWarning不會中斷查詢語句的執行,只是用來提示用戶存在相關的警告信息。
若是Oracle的存儲過程的入參出參中包含數據庫對象,咱們須要在程序建立一個一樣大小的對象數組,而後用它來生成Oracle的STRUCT對象。而後能夠經過數據庫對象的setSTRUCT方法傳入這個struct對象,並對它進行使用。
若是你的SQL URL串格式不正確的話,就會拋出這樣的異常。無論是使用DriverManager仍是JNDI數據源來建立鏈接都有可能拋出這種異常。它的異常棧看起來會像下面這樣。
org.apache.tomcat.dbcp.dbcp.SQLNestedException: Cannot create JDBC driver of class 'com.mysql.jdbc.Driver' for connect URL ''jdbc:mysql://localhost:3306/UserDB' at org.apache.tomcat.dbcp.dbcp.BasicDataSource.createConnectionFactory(BasicDataSource.java:1452) at org.apache.tomcat.dbcp.dbcp.BasicDataSource.createDataSource(BasicDataSource.java:1371) at org.apache.tomcat.dbcp.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044) java.sql.SQLException: No suitable driver found for 'jdbc:mysql://localhost:3306/UserDB at java.sql.DriverManager.getConnection(DriverManager.java:604) at java.sql.DriverManager.getConnection(DriverManager.java:221) at com.journaldev.jdbc.DBConnection.getConnection(DBConnection.java:24) at com.journaldev.jdbc.DBConnectionTest.main(DBConnectionTest.java:15) Exception in thread "main" java.lang.NullPointerException at com.journaldev.jdbc.DBConnectionTest.main(DBConnectionTest.java:16)
解決這類問題的方法就是,檢查下日誌文件,像上面的這個日誌中,URL串是'jdbc:mysql://localhost:3306/UserDB,只要把它改爲jdbc:mysql://localhost:3306/UserDB就行了。
下面列舉了其中的一些: