持久化(persistence):對象在內存中建立後,不能永久存在。把對象永久的保存起來就是持久化的過程。而持久化的實現過程大多經過各類關係數據庫來完成。java
持久化的主要應用是將內存中的數據存儲在關係型數據庫中,固然也能夠存儲在磁盤文件、XML數據文件中。mysql
JDBC直接訪問數據庫。sql
第三方O/R工具,如Hibernate,mybatis。這些工具都是對JDBC的封裝。 數據庫
JDBC(Java Datebase Connectivity)是一個獨立於特定數據庫管理系統、通用的sql數據庫存取和操做的公共接口。它是JAVA語言訪問數據庫的一種標準。 緩存
Java.sql.Driver接口是全部JDBC驅動程序須要實現的接口。這個接口是提供給數據庫廠商使用的,不一樣數據庫廠商提供不用的實現。安全
在程序中不須要直接去訪問實現了Driver接口的類,而是由驅動程序管理器類(java.sql.DriverManager)去調用這些Driver實現。性能優化
DriverManager類,用來建立鏈接,它自己就是一個建立Connection的工廠,設計的時候使用的就是Factory模式,給各數據庫廠商提供接口,各數據庫廠商須要實現它;服務器
Connection接口,根據提供的不一樣驅動產生不一樣的鏈接;mybatis
Statement接口,用來發送SQL語句;併發
Resultset接口,用來接收查詢語句返回的查詢結果。
1.註冊加載一個驅動
2.建立數據庫鏈接(Connection)
3.建立statement,發送sql語句
4.執行sql語句
5.處理sql結果
6.關閉statement和connection
加載 JDBC 驅動需調用 Class 類的靜態方法 forName(),向其傳遞要加載的 JDBC 驅動的類名:
1
|
Class.forName(driver);
|
如:
註冊MYSQL數據庫驅動器
1
|
Class.forName(
"com.mysql.jdbc.Driver"
);
|
註冊ORACLE數據庫驅動器
1
|
Class.forName(
"oracle.jdbc.driver.OracleDriver"
);
|
能夠調用 DriverManager 類的 getConnection(…….) 方法創建到數據庫的鏈接:
1
|
Connection conn = DriverManager.getConnection(url,uid,pwd);
|
JDBC URL 用於標識一個被註冊的驅動程序,驅動程序管理器經過這個 URL 選擇正確的驅動程序,從而創建到數據庫的鏈接。
JDBC URL的標準由三部分組成,各部分間用冒號「:」分隔。
JDBC URL格式:
1
|
協議:<子協議>:<子名稱>
|
說明:
協議:JDBC URL中的協議老是jdbc
子協議:子協議用於標識一個數據庫驅動程序
子名稱:一種標識數據庫的方法。子名稱能夠依不一樣的子協議而變化,用子名稱的目的是爲了定位數據庫提供足夠的信息
jdbc:<子協議>:<子名稱>:是一個JNI方式的命名
注:JNI是Java Native Interface的縮寫。從Java 1.1開始,Java Native Interface (JNI)標準成爲java平臺的一部分,它容許Java代碼和其餘語言寫的代碼進行交互。
如:
mysql的JDBC URL: jdbc:mysql://localhost:3306/mydbname
oracle的JDBC URL: jdbc:oracle:thin: @localhost :1521:mydbname
數據庫鏈接被用於向數據庫服務器發送命令和 SQL 語句,在鏈接創建後,須要對數據庫進行訪問,執行 sql 語句。
在 java.sql 包中有 3 個接口分別定義了對數據庫的調用的不一樣方式:
Statement
PrepatedStatement
CallableStatement
Statement對象用於執行靜態的 SQL 語句,而且返回執行結果。
經過調用 Connection 對象的 createStatement 方法建立該對象:
1
|
Statement sm = conn.createStatement();
|
Statement 接口中定義了下列方法用於執行 SQL 語句:
sm.executeQuery(sql); // 執行數據查詢語句(select)
sm.executeUpdate(sql); // 執行數據更新語句(delete、update、insert、drop等)
PreparedStatement 接口是 Statement 的子接口,它表示一條預編譯過的 SQL 語句。
能夠經過調用 Connection 對象的 preparedStatement() 方法獲取 PreparedStatement 對象:
1
2
3
4
5
6
7
|
String sql =
"INSERT INTO user (id,name) VALUES (?,?)"
;
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(
1
,
1
);
ps.setString(
2
,
"admin"
);
ResultSet rs = ps.executeQuery();
// 查詢
int
c = ps.executeUpdate();
// 更新
|
PreparedStatement 對象所表明的 SQL 語句中的參數用問號(?)來表示,調用 PreparedStatement 對象的 setXXX() 方法來設置這些參數。 setXXX() 方法有兩個參數,第一個參數是要設置的 SQL 語句中的參數的索引(從 1 開始),第二個是設置的 SQL 語句中的參數的值。
(1)使用PreparedStatement,代碼的可讀性和可維護性比Statement高。
(2)PreparedStatement 能最大可能提升性能。
DBServer會對預編譯語句提供性能優化。由於預編譯語句有可能被重複調用,因此語句在被DBServer的編譯器編譯後的執行代碼被緩存下來,那麼下次調用時只要是相同的預編譯語句就不須要編譯,只要將參數直接傳入編譯過的語句執行代碼中就會獲得執行。
在statement語句中,即便是相同操做但由於數據內容不同,因此整個語句自己不能匹配,沒有緩存語句的意義。事實是沒有數據庫會對普通語句編譯後的執行代碼緩存。這樣每執行一次都要對傳入的語句編譯一次。
(3)PreparedStatement能保證安全性,但 Statement有sql注入等安全問題。
SQL 注入是利用某些系統沒有對用戶輸入的數據進行充分的檢查,而在用戶輸入數據中注入非法的 SQL 語句段或命令,從而利用系統的 SQL 引擎完成惡意行爲的作法。以下代碼:
1
2
3
|
String username=
"a' or 1=1 or 1='"
;
String psw=
"b"
;
String sql =
"select count(*) from t_user where username='"
+username+
"' and psw='"
+psw+
"'"
;
|
sql語句以下:
1
|
select
count
(*)
from
t_user
where
username=
'a'
or
1=1
or
1=
''
and
psw=
'b'
|
用 PreparedStatement 取代 Statement 就能夠解決。
當不直接使用SQL語句,而是調用數據庫中的存儲過程時,要用到Callable Statement。
CallabelStatement從PreparedStatement繼承。
例如:
1
2
3
4
5
6
7
|
String sql =
"{call insert_users(?,?)}"
;
// 調用存儲過程
CallableStatement st = conn.prepareCall(sql);
st.setInt(
1
,
1
);
st.setString(
2
,
"admin"
);
// 在此 CallableStatement對象中執行 SQL 語句,該語句能夠是任何種類的 SQL 語句。
st.execute();
|
查詢語句,返回記錄集ResultSet。
更新語句,返回數字,表示該更新影響的記錄數。
ResultSet:
ResultSet 對象以邏輯表格的形式封裝了執行數據庫操做的結果集,ResultSet 接口由數據庫廠商實現。
ResultSet 接口的經常使用方法:
next():將遊標日後移動一行,若是成功返回true;不然返回false。ResultSet 對象維護了一個指向當前數據行的遊標,初始的時候,遊標在第一行以前,能夠經過 ResultSet 對象的 next() 方法移動到下一行。
getXxx(String name):返回當前遊標下某個字段的值。如:getInt("id")或getSting("name")。
rs.close();
ps.close(); 或者 stat.close();
conn.close();
通常是在finally裏面進行釋放資源。
在數據庫中,所謂事務是指一組邏輯操做單元,使數據從一種狀態變換到另外一種狀態。
爲確保數據庫中數據的一致性,數據的操縱應當是離散的成組的邏輯單元:當它所有完成時,數據的一致性能夠保持,而當這個單元中的一部分操做失敗,整個事務應所有視爲錯誤,全部從起始點之後的操做應所有回退到開始狀態。
事務的操做:先定義開始一個事務,而後對數據做修改操做,這時若是提交(COMMIT),這些修改就永久地保存下來,若是回退(ROLLBACK),數據庫管理系統將放棄您所做的全部修改而回到開始事務時的狀態。
2.1 原子性(Atomicity)
原子性是指事務是一個不可分割的工做單位,事務中的操做要麼都發生,要麼都不發生。
2.2 一致性(Consistency)
事務必須使數據庫從一個一致性狀態變換到另一個一致性狀態。(數據不被破壞)
2.3 隔離性(Isolation)
事務的隔離性是指一個事務的執行不能被其餘事務干擾,即一個事務內部的操做及使用的數據對併發的其餘事務是隔離的,併發執行的各個事務之間不能互相干擾。
2.4 持久性(Durability)
持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來的其餘操做和數據庫故障不該該對其有任何影響。
在JDBC中,事務默認是自動提交的,每次執行一個 SQL 語句時,若是執行成功,就會向數據庫自動提交,而不能回滾。
爲了讓多個 SQL 語句做爲一個事務執行,需調用 Connection 對象的 setAutoCommit(false); 以取消自動提交事務:
1
|
conn.setAutoCommit(
false
);
|
在全部的 SQL 語句都成功執行後,調用 commit(); 方法提交事務
1
|
conn.commit();
|
在出現異常時,調用 rollback(); 方法回滾事務,通常再catch模塊中執行回滾操做。
1
|
conn.rollback();
|
能夠經過Connection的getAutoCommit()方法來得到當前事務的提交方式。
注意:在MySQL中的數據庫存儲引擎InnoDB支持事務,MyISAM不支持事務。
當須要批量插入或者更新記錄時。能夠採用Java的批量更新機制,這一機制容許多條語句一次性提交給數據庫批量處理。一般狀況下比單獨提交處理更有效率。
JDBC的批量處理語句包括下面兩個方法:
addBatch(String):添加須要批量處理的SQL語句或是參數;
executeBatch();執行批量處理語句;
一般咱們會遇到兩種批量執行SQL語句的狀況:
多條SQL語句的批量處理;
一個SQL語句的批量傳參;
1
2
3
4
5
6
7
8
|
Statement sm = conn.createStatement();
sm.addBatch(sql1);
sm.addBatch(sql2);
...
//批量處理
sm.executeBatch()
//清除sm中積攢的參數列表
sm.clearBatch();
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
PreparedStatement ps = conn.preparedStatement(sql);
for
(
int
i=
1
;i<
100000
;i++){
ps.setInt(
1
, i);
ps.setString(
2
,
"name"
+i);
ps.setString(
3
,
"email"
+i);
ps.addBatch();
if
((i+
1
)%
1000
==
0
){
//批量處理
ps.executeBatch();
//清空ps中積攢的sql
ps.clearBatch();
}
}
|
注意:MySQL不支持批量處理。
批量處理應該設置一個上限,當批量處理列表中的sql累積到必定數量後,就應該執行,並在執行完成後,清空批量列表。
通常在excel導入數據的時候會用到批處理。
Java 經過JDBC得到鏈接之後,獲得一個Connection 對象,能夠從這個對象得到有關數據庫管理系統的各類信息,包括數據庫中的各個表,表中的各個列,數據類型,觸發器,存儲過程等各方面的信息。根據這些信息,JDBC能夠訪問一個實現事先並不瞭解的數據庫。
獲取這些信息的方法都是在DatabaseMetaData類的對象上實現的,而DataBaseMetaData對象是在Connection對象上得到的。
DatabaseMetaData 類中提供了許多方法用於得到數據源的各類信息,經過這些方法能夠很是詳細的瞭解數據庫的信息:
getURL():返回一個String類對象,表明數據庫的URL。
getUserName():返回鏈接當前數據庫管理系統的用戶名。
isReadOnly():返回一個boolean值,指示數據庫是否只容許讀操做。
getDatabaseProductName():返回數據庫的產品名稱。
getDatabaseProductVersion():返回數據庫的版本號。
getDriverName():返回驅動驅動程序的名稱。
getDriverVersion():返回驅動程序的版本號。
可用於獲取關於 ResultSet 對象中列的類型和屬性信息的對象:
getColumnName(int column):獲取指定列的名稱
getColumnCount():返回當前 ResultSet 對象中的列數。
getColumnTypeName(int column):檢索指定列的數據庫特定的類型名稱。
getColumnDisplaySize(int column):指示指定列的最大標準寬度,以字符爲單位。
isNullable(int column):指示指定列中的值是否能夠爲 null。
isAutoIncrement(int column):指示是否自動爲指定列進行編號,這樣這些列仍然是隻讀的。
1
|
Statement stmt = conn.createStatement(type,concurrency);
|
1
|
PreparedStatement stmt = conn.prepareStatement(sql,type,concurrency);
|
type說明:
ResultSet的Type | 說明 |
TYPE_FORWARD_ONLY | 結果集不能滾動,只可向前滾動 |
TYPE_SCROLL_INSENSITIVE | 雙向滾動,但不及時更新,就是若是數據庫裏的數據修改過,並不在ResultSet中反應出來 |
TYPE_SCROLL_SENSITIVE | 雙向滾動,並及時跟蹤數據庫的更新,以便更改ResultSet中的數據 |
Concurrency(併發類型)說明:
ResultSet的Concurrency(併發類型) | 說明 |
CONCUR_READ_ONLY | 結果集不可用於更新數據庫 |
CONCUR_UPDATABLE | 結果集能夠用於更新數據庫 |
First:將指針移動到此 ResultSet 對象的第一行
Last:將指針移動到此 ResultSet 對象的最後一行
beforeFirst:將指針移動到此 ResultSet 對象的開頭,正好位於第一行以前
afterLast:將指針移動到此 ResultSet 對象的末尾,正好位於最後一行以後
isFirst:檢索指針是否位於此 ResultSet 對象的第一行
isLast:檢索指針是否位於此 ResultSet 對象的最後一行
isBeforeFirst:檢索指針是否位於此 ResultSet 對象的第一行以前
isAfterLast:檢索指針是否位於此 ResultSet 對象的最後一行以後
Relative:按相對行數(或正或負)移動指針
Next:將指針從當前位置下移一行
Previous:將指針移動到此 ResultSet 對象的上一行
Absolute:將指針移動到此 ResultSet 對象的給定行編號
如:
rs.absolute(80); //將指針移動到ResultSet 對象的第80行記錄。
注意:該特性對Oralce數據有效。可是在Mysql數據庫中無效,Mysql只支持TYPE_SCROLL_INSENSITIVE,CONCUR_READ_ONLY。
普通的JDBC數據庫鏈接使用 DriverManager 來獲取,每次向數據庫創建鏈接的時候都要將 Connection 加載到內存中,再驗證用戶名和密碼。須要數據庫鏈接的時候,就向數據庫要求一個,執行完成後再斷開鏈接。這樣的方式將會消耗大量的資源和時間。數據庫的鏈接資源並無獲得很好的重複利用.若同時有幾百人甚至幾千人在線,頻繁的進行數據庫鏈接操做將佔用不少的系統資源,嚴重的甚至會形成服務器的崩潰。
對於每一次數據庫鏈接,使用完後都得斷開。不然,若是程序出現異常而未能關閉,將會致使數據庫系統中的內存泄漏,最終將致使重啓數據庫。
這種開發不能控制被建立的鏈接對象數,系統資源會被毫無顧及的分配出去,如鏈接過多,也可能致使內存泄漏,服務器崩潰。
爲解決傳統開發中的數據庫鏈接問題,能夠採用數據庫鏈接池技術。
數據庫鏈接池的基本思想就是爲數據庫鏈接創建一個「緩衝池」。預先在緩衝池中放入必定數量的鏈接,當須要創建數據庫鏈接時,只需從「緩衝池」中取出一個,使用完畢以後再放回去。
數據庫鏈接池負責分配、管理和釋放數據庫鏈接,它容許應用程序重複使用一個現有的數據庫鏈接,而不是從新創建一個。
數據庫鏈接池在初始化時將建立必定數量的數據庫鏈接放到鏈接池中,這些數據庫鏈接的數量是由最小數據庫鏈接數來設定的。不管這些數據庫鏈接是否被使用,鏈接池都將一直保證至少擁有這麼多的鏈接數量。鏈接池的最大數據庫鏈接數量限定了這個鏈接池能佔有的最大鏈接數,當應用程序向鏈接池請求的鏈接數超過最大鏈接數量時,這些請求將被加入到等待隊列中。
(1)資源重用:
因爲數據庫鏈接得以重用,避免了頻繁建立,釋放鏈接引發的大量性能開銷。在減小系統消耗的基礎上,另外一方面也增長了系統運行環境的平穩性。
(2)更快的系統反應速度
數據庫鏈接池在初始化過程當中,每每已經建立了若干數據庫鏈接置於鏈接池中備用。此時鏈接的初始化工做均已完成。對於業務請求處理而言,直接利用現有可用鏈接,避免了數據庫鏈接初始化和釋放過程的時間開銷,從而減小了系統的響應時間。
(3)新的資源分配手段
對於多應用共享同一數據庫的系統而言,可在應用層經過數據庫鏈接池的配置,實現某一應用最大可用數據庫鏈接數的限制,避免某一應用獨佔全部的數據庫資源。
(4)統一的鏈接管理,避免數據庫鏈接泄露
在較爲完善的數據庫鏈接池實現中,可根據預先的佔用超時設定,強制回收被佔用鏈接,從而避免了常規數據庫鏈接操做中可能出現的資源泄露。
JDBC 的數據庫鏈接池使用 javax.sql.DataSource 來表示,DataSource 只是一個接口,該接口一般由服務器(Weblogic, WebSphere, Tomcat)提供實現,也有一些開源組織提供實現,如:
DBCP 數據庫鏈接池
C3P0 數據庫鏈接池
Proxpool 數據庫鏈接池
其中,DBCP和C3P0用得比較多。
Tomcat 在 7.0 之前的版本都是使用 commons-dbcp 作爲鏈接池的實現。
數據源和數據庫鏈接不一樣,數據源無需建立多個,它是產生數據庫鏈接的工廠,所以整個應用只須要一個數據源便可。
當數據庫訪問結束後,程序仍是像之前同樣關閉數據庫鏈接:conn.close(); 但它並無關閉數據庫的物理鏈接,它僅僅把數據庫鏈接釋放,歸還給了數據庫鏈接池。
----------------------------------我是代碼分割線---------------------------------------------
一個使用JDBC的小Demo
1 import java.sql.Connection; 2 import java.sql.DriverManager; 3 import java.sql.ResultSet; 4 import java.sql.SQLException; 5 import java.sql.Statement; 6 7 8 /** 9 * 使用JDBC鏈接數據庫 10 * @author GXF 11 * 12 */ 13 public class TestJdbc { 14 15 public static void main(String[] args) { 16 TestJdbc testJdbc = new TestJdbc(); 17 testJdbc.testJdbc(); 18 } 19 20 /** 21 * 鏈接MySQL數據庫 22 * test數據庫 23 * usertable表 24 */ 25 public void testJdbc(){ 26 //加載驅動->經過url獲得數據庫鏈接對象->獲取statement或者prestatment對象->執行SQL語句->處理結果集->關閉鏈接,釋放資源 27 try { 28 String userName = "root"; 29 String passwd = ""; 30 String dbName = "test"; 31 //加載驅動 32 Class.forName("com.mysql.jdbc.Driver"); 33 //獲取鏈接對象 34 String url = "jdbc:mysql://localhost:3306/" + dbName; 35 Connection con = DriverManager.getConnection(url, userName, passwd); 36 //建立statement 37 Statement st = con.createStatement(); 38 //執行SQL,查詢表中全部數據 39 String sql = "select * from usertable"; 40 ResultSet rs = st.executeQuery(sql); 41 42 //處理結果集 43 while(rs.next()){ 44 System.out.println(rs.getString("username") + " " + rs.getString("passwd")); 45 }//while 46 47 //關閉鏈接,釋放資源 48 rs.close(); 49 st.close(); 50 con.close(); 51 52 } catch (ClassNotFoundException e) { 53 54 e.printStackTrace(); 55 } catch (SQLException e) { 56 57 e.printStackTrace(); 58 } 59 } 60 61 }