一.JDBC原理概述
1,JDBC是一套協(xié)議,是JAVA開(kāi)發(fā)人員和數(shù)據(jù)庫(kù)廠商達(dá)成的協(xié)議,也就是由Sun定義一組接口,由數(shù)據(jù)庫(kù)廠商來(lái)實(shí)現(xiàn),并規(guī)定了JAVA開(kāi)發(fā)人員訪問(wèn)數(shù)據(jù)庫(kù)所使用的方法的調(diào)用規(guī)范。
2,JDBC的實(shí)現(xiàn)是由數(shù)據(jù)庫(kù)廠商提供,以驅(qū)動(dòng)程序形式提供。
3,JDBC在使用前要先加載驅(qū)動(dòng)。
JDBC對(duì)于使用者要有一致性,對(duì)不同的數(shù)據(jù)庫(kù)其使用方法都是相同的。
driver開(kāi)發(fā)必須要實(shí)現(xiàn)Driver接口。
四種數(shù)據(jù)庫(kù)驅(qū)動(dòng)的實(shí)現(xiàn)方式
JDBC-ODBC橋接式
JDBC網(wǎng)絡(luò)驅(qū)動(dòng),這種方式是通過(guò)中間服務(wù)器的協(xié)議轉(zhuǎn)換來(lái)實(shí)現(xiàn)的
JDBC+本地驅(qū)動(dòng),這種方式的安全性比較差
JDBC驅(qū)動(dòng),由數(shù)據(jù)庫(kù)廠商實(shí)現(xiàn)。
二.JDBC的API
java.sql包和javax.sql包
1.Driver接口 代表驅(qū)動(dòng)程序
2.DriverManager類(驅(qū)動(dòng)管理器),它可以創(chuàng)建連接,它本身就是一個(gè)創(chuàng)建Connection的工廠(Factory)。
3.Connection接口,代表數(shù)據(jù)庫(kù)連接,會(huì)根據(jù)不同的驅(qū)動(dòng)產(chǎn)生不同的連接
4.Statement接口,執(zhí)行sql語(yǔ)句
5. PreparedStatement接口執(zhí)行sql語(yǔ)句(預(yù)編譯)
6.CallableStatement接口 發(fā)送sql語(yǔ)句
7.ResultSet接口(結(jié)果集),是用來(lái)接收select語(yǔ)句返回的查尋結(jié)果的。其實(shí)質(zhì)類似
于集合。
8.DatabaseMetaData 數(shù)據(jù)庫(kù)元數(shù)據(jù)
9.ResultSetMetaData 結(jié)果集元數(shù)據(jù)
10.Types:特殊的類。只包含靜態(tài)的常量,代表JDBC類型。JDBC類型是標(biāo)準(zhǔn)SQL類型的子集
三、事務(wù)
1).原子性,一致性,隔離性,持久性
事務(wù)是一個(gè)數(shù)據(jù)操作單元,能夠保證在該單元內(nèi)執(zhí)行的多個(gè)SQL語(yǔ)句,或者一起成
功,或者一起失敗.
2).事務(wù)的并發(fā)控制:
(dirty)臟讀:一個(gè)事務(wù)能讀到另外一個(gè)事務(wù)沒(méi)有提交的數(shù)據(jù)
不可重復(fù)讀:在同一個(gè)事務(wù)的兩次查詢中,發(fā)現(xiàn)值不同,叫做不可重復(fù)讀.這是因?yàn)樵?/span>
兩次查詢之間,另外一個(gè)事務(wù)修改了數(shù)據(jù)并提交了.
(phantom)幻影讀:在同一個(gè)事務(wù)的兩次查詢中,發(fā)現(xiàn)記錄的數(shù)目不同,叫做幻影讀.
這是因?yàn)樵趦纱尾樵冎g,另外一個(gè)事務(wù)增加或者刪除了數(shù)據(jù)并提交了.
3).隔離級(jí)別
Connection 類中的五個(gè)靜態(tài)常量
A.不使用事務(wù) TRANSACTION_NONE
B.允許臟讀 TRANSACTION_READ_UNCOMMITTED
C.不允許臟讀 TRANSACTION_READ_COMMITTED
D.防止不可重復(fù)讀,臟讀 TRANSACTION_REPEATABLE_READ
F.事務(wù)串行化 TRANSACTION_SERIALIZABLE
四、JDBC編程步驟
1,注冊(cè)加載一個(gè)driver驅(qū)動(dòng)
2,創(chuàng)建數(shù)據(jù)庫(kù)連接(Connection)
3,創(chuàng)建一個(gè)Statement(發(fā)送sql)
4,執(zhí)行sql語(yǔ)句
5,處理sql結(jié)果ResultSet(select語(yǔ)句)
6,關(guān)閉sql結(jié)果ResultSet (如果有)
7,關(guān)閉Statement
8,關(guān)閉連接Connection
注意:6,7,8兩個(gè)步驟必須要做的,因?yàn)檫@些資源是不會(huì)自動(dòng)釋放的,必須要自己關(guān)閉
1,注冊(cè)加載驅(qū)動(dòng)driver,也就是強(qiáng)制類加載
一般來(lái)說(shuō)我們使用方法1
方法1) Class.forName(Driver包名.Driver類名)。
eg:加載oracle
Class.forName("oracle.jdbc.driver.OracleDriver");
方法2)Driver d=new oracle.jdbc.driver.OracleDriver();
DriverManager.registerDriver(d);
方法3)java -Djdbc.drivers=驅(qū)動(dòng)全名 類名
Oracle的Driver的全名:oracle.jdbc.driver.OracleDriver
mysql的Driver的全名:com.mysql.jdbc.Driver 或 org.gjt.mm.mysql.Driver
SQLServer的Driver的全名:com.microsoft.jdbc.sqlserver.SQLServerDriver
2,創(chuàng)建連接
DriverManager.getConnection(String url,String username,String
password);
Connection連接是通過(guò)DriverManager的靜態(tài)方法getConnection(.....)來(lái)得到
的,這個(gè)方法的實(shí)質(zhì)是把參數(shù)傳到實(shí)際的Driver中的connect()方法中來(lái)獲得數(shù)
據(jù)庫(kù)連接的。
Oracle的URL值是由連接數(shù)據(jù)庫(kù)的協(xié)議和數(shù)據(jù)庫(kù)的IP地址及端口號(hào)還有要連接的
庫(kù)名(DatebaseName)
Oracle URL的格式
jdbc:oracle:thin:@數(shù)據(jù)庫(kù)IP地址:端口號(hào):數(shù)據(jù)庫(kù)名(sid)
如:oracle所在服務(wù)器地址為192.168.0.254,而端口號(hào)為默認(rèn)的1521,數(shù)據(jù)
庫(kù)名為tarena,那么URL就應(yīng)寫成
jdbc:oracle:thin:@192.168.0.254:1521:tarena
MySql URL的格式
jdbc:mysql://數(shù)據(jù)庫(kù)IP地址:端口號(hào)/數(shù)據(jù)庫(kù)名
例: jdbc:mysql://localhost:3306/test
訪問(wèn)本機(jī)時(shí)IP地址用localhost和127.0.0.1都可以
SQLServer URL的寫法
例:jdbc:microsoft:sqlserver://localhost:1433
3、創(chuàng)建Statement
使用Connection對(duì)象獲得一個(gè)Statement
Statement中的方法:
1) executeQuery(String sql) 方法可以使用select語(yǔ)句查詢,并且返回一個(gè)結(jié)
果集ResultSet,通過(guò)遍歷這個(gè)ResultSet,可以獲得select語(yǔ)句的查尋結(jié)果。
ResultSet rs = stmt.executeQuery(“select * from person”);
2)executeUpdate(String sql) 方法用于執(zhí)行DDL和DML語(yǔ)句,可以update,
delete操作。
此方法返回一個(gè)int類型的值,表示此條sql語(yǔ)句影響的記錄條數(shù)
Int count = stmt.executeUpdate(“delete from person where pid=1”);
Int count = stmt.executeUpdate(“update person set name=’jack’,age=’20’
where pid=1”);
3)execute(String sql) 這個(gè)方法的返回值是boolean類型
如果返回true就表示sql是一個(gè)select語(yǔ)句,然后通過(guò)getResultSet()獲得結(jié)果
集
如果返回false ,sql就是DML語(yǔ)句或者是DDL語(yǔ)句,即沒(méi)有結(jié)果集,
然后通過(guò)getUpdateCount()方法 獲得更新的記錄條數(shù)。
在不能明確傳入sql是何種類型的操作時(shí)使用此方法。
if (stmt.execute(sql)) {
ResultSet rs = stmt.getResultSet();
} else {
int count = stmt.getUpdateCount();
}
4)getUpdateCount() 返回更新的記錄條數(shù)
4.處理結(jié)果集ResultSet(結(jié)果集里存儲(chǔ)的是二維的結(jié)構(gòu),相當(dāng)于一張表)
ResultSet rs = stmt.executeQuery(“select pid, name, age from person”);
Person person = null;
while(rs.next) {
person = new Person();
person.setPid(rs.getInt(1));//jdbc下標(biāo)是從1開(kāi)始
person.setName(rs.getString(“name”));//也可使用列名來(lái)得到數(shù)據(jù)
person.setAge(rs.getInt(3));
}
1) next()方法會(huì)操作一個(gè)游標(biāo)從第一條記錄的前邊開(kāi)始讀取,直到最后一條記錄。
由于結(jié)果集返回來(lái)之后游標(biāo)的位置在第一條記錄的前面,所以調(diào)用next()方
法不會(huì)丟失數(shù)據(jù)。
2) getXXX(int index)其中XXX代表數(shù)據(jù)庫(kù)中存儲(chǔ)的數(shù)據(jù)類型相對(duì)應(yīng)的java數(shù)據(jù)
類型,
此方法可以根據(jù)指定順序獲得字段值,注:順序是從1開(kāi)始的。
eg: int cid = rs.getInt(1);
String cname = rs.getString(2);
3) getXXX(String columnName):此方法可以根據(jù)指定字段的名字獲取字段的值
eg: int cid = rs.getInt("cid");
String cname = rs.getString("cname");
4)updateXXX() XXX代表的是相應(yīng)的類型,無(wú)返回值
5) isXXXX() XXXX代表的是游標(biāo)的位置First,Last。。。返回布爾值
5.資源的關(guān)閉順序:
要按先ResultSet結(jié)果集,然后Statement,最后Connection的順序關(guān)閉資源。
因?yàn)?/span>Statement和ResultSet是需要連接是才可以使用的,所以在使用結(jié)束之后
有可能起他的Statement還需要連接,
所以不能現(xiàn)關(guān)閉Connection。
//關(guān)閉結(jié)果集
if (rs != null) try {rs.close()} catch (SQLException e) { e.printStackTrace();}
//關(guān)閉Statement
if (stmt != null) try { stmt.close()} catch (SQLException e) {
e.printStackTrace();}
//關(guān)閉鏈接
if (conn != null) try { conn.close()} catch (SQLException e) {
e.printStackTrace();}
6.PreparedStatement(預(yù)編譯的Statement)
PreparedStatement 用來(lái)執(zhí)行同構(gòu)的SQL,它的創(chuàng)建方式:
PreparedStatement pstmt = con.prepareStatement("insert into clazz values(?,?)");
可以使用參數(shù)替代sql語(yǔ)句中的某些參數(shù)使用"?"代替,他先將帶參數(shù)的sql語(yǔ)句發(fā)送到數(shù)據(jù)庫(kù),
進(jìn)行編譯,然后PreparedStatement會(huì)將參數(shù)發(fā)送給數(shù)據(jù)庫(kù)。
在使用PreparedStatement時(shí),設(shè)置相應(yīng)參數(shù)要指明參數(shù)的位置和類型,以及給出參數(shù)值
根據(jù)不同的參數(shù)類型使用不同的setXXX(參數(shù)的位置,參數(shù)值)來(lái)設(shè)置參數(shù)
結(jié)構(gòu)跟此方法類似:setInt(int parameterIndex, int x)
pstmt.setInt(1,i);
pstmt.setString(2,"xxx"+i)
7.CallableStatement
CallableStatement是可以用非sql語(yǔ)句來(lái)訪問(wèn)數(shù)據(jù)庫(kù),他是通過(guò)調(diào)用存儲(chǔ)過(guò)程(PL/SQL)來(lái)訪問(wèn)數(shù)據(jù)
庫(kù)的。
可以直接使用連接來(lái)調(diào)用 prepareCall(...)方法,來(lái)執(zhí)行這個(gè)存儲(chǔ)過(guò)程,"..."是存儲(chǔ)過(guò)程的名字。
8.SQLException是檢查異常必須處理,要么throws ,要么try{}catch(){}
getErrorCode()可以獲得錯(cuò)誤碼,可以對(duì)錯(cuò)誤進(jìn)行查詢。
五、元數(shù)據(jù)
元數(shù)據(jù)就是描述數(shù)據(jù)庫(kù)或其組成部分的數(shù)據(jù)。(區(qū)別于存儲(chǔ)在數(shù)據(jù)庫(kù)中的實(shí)際數(shù)據(jù))
它用來(lái)輔助我們更好的處理數(shù)據(jù)庫(kù)的用戶數(shù)據(jù)[通俗的講就是指容器的結(jié)果的信息]
JDBC中有兩種元數(shù)據(jù),一種是數(shù)據(jù)庫(kù)元數(shù)據(jù)(DatabaseMetaData),另一種是結(jié)果集元數(shù)據(jù)(ResultSetMetaData)
1.數(shù)據(jù)庫(kù)元數(shù)據(jù)
如果要想了解數(shù)據(jù)庫(kù)的更多信息。可以從數(shù)據(jù)庫(kù)連接中獲取一個(gè)DatabaseMetaData
DatabaseMetaData dmd=conn.getMetaData();
然后可以調(diào)用如下的方法獲得數(shù)據(jù)庫(kù)相關(guān)的信息
getURL(),獲得連接數(shù)據(jù)庫(kù)的URL
getDatabaseProductName() 獲得數(shù)據(jù)庫(kù)產(chǎn)品的名稱
getDriverVersion() 獲得JDBC驅(qū)動(dòng)程序的String形式的版本號(hào)
getTables()獲得數(shù)據(jù)庫(kù)中該用戶的所有表
getUserName() 獲得數(shù)據(jù)庫(kù)用戶名。
2.結(jié)果集元數(shù)據(jù)
ResultSet rs=ps.executeQuery();
ResultSetMetaData m=rs.getMetaData();
getColumnCount(),獲得實(shí)際列數(shù)
getColumnName(int colnum),獲得指定列的列名
getColumnType(int colnum),獲得指定列的數(shù)據(jù)類型
getColumnTypeName(int colnum),獲得指定列的數(shù)據(jù)類型名
例如:ResultSetMetaData md = rs.getMetaData();
// 得到字段個(gè)數(shù)
int cols = md.getColumnCount();
// 根據(jù)字段個(gè)數(shù)遍歷和打印結(jié)果集
StringBuffer sb = new StringBuffer();
for (int i = 0; i < cols; i++) {
sb.append(md.getColumnName(i + 1) + " ");
}
動(dòng)態(tài)獲得表結(jié)構(gòu)
六、JDBC2.0新特性
默認(rèn)方式獲得的結(jié)果集都是1.0的結(jié)果集
能否使用JDBC2.0 ResultSet的新特性要看數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序是否支持。
1.可滾動(dòng)結(jié)果集(可雙向滾動(dòng)),(了解 能說(shuō)出結(jié)果集滾動(dòng)的方法即可)
這種結(jié)果集不但可以雙向滾動(dòng),相對(duì)定位,絕對(duì)定位,并且可以修改數(shù)據(jù)信息。
要使用可滾動(dòng)結(jié)果集時(shí),要在Statement創(chuàng)建時(shí)指定參數(shù),才可以使用
Statement st=null;(int,int)(可滾動(dòng)特性,可更新特性)
st=con.createStatement(ReusltSet.TYPE_SCROLL_INSENSITIVE,ResuleSet.CONCUR_UPDATABLE)
PreparedStatement
ps=con.createPrepareStatement(sql,ReusltSet.TYPE_SCROLL_INSENSITIVE,ResuleSet.CONCUR_
UPDATABLE);
ResultSet rs=ps.executeQuery(sql);
滾動(dòng)特性
next(),此方法是使游標(biāo)向下一條記錄移動(dòng)。
previous() ,此方法可以使游標(biāo)向上一條記錄移動(dòng),前提前面還有記錄。
absolute(int row),絕對(duì)定位函數(shù)可以使用此方法跳到指定的記錄位置。定位成功返回true,不成功返
回false,返回值為false,則游標(biāo)不會(huì)移動(dòng)。
afterLast() ,游標(biāo)跳到最后一條記錄 之后,(結(jié)果集一回來(lái)時(shí)就有的位置)。無(wú)返回值。
beforeFirst() ,游標(biāo)跳到第一條記錄 之前,(結(jié)果集一回來(lái)時(shí)就有的位置)。(跳到游標(biāo)初始位)無(wú)返回
值
first(),游標(biāo)指向第一條記錄。
last(),有彪指向最后一條記錄。
relative(int rows),相對(duì)定位方法,參數(shù)值可正可負(fù),參數(shù)為正,
游標(biāo)從當(dāng)前位置向下移動(dòng)指定值,參數(shù)為負(fù),游標(biāo)從當(dāng)前位置向上移動(dòng)指定值。
ResultSet可滾動(dòng)性的屬性值:
TYPE_FORWARD_ONLY ,單向,該常量指示指針只能向前移動(dòng)的 ResultSet 對(duì)象的類型。不可滾動(dòng)。
TYPE_SCROLL_INSENSITIVE ,雙向,對(duì)數(shù)據(jù)庫(kù)的變化不敏感
TYPE_SCROLL_SENSITIVE ,雙向,對(duì)數(shù)據(jù)庫(kù)的變化敏感
ResultSet 對(duì)象的類型。該特性某些數(shù)據(jù)庫(kù)不支持。
ResultSet可更新性的屬性值:
CUNCUR_READ_ONLY,不可更新的結(jié)果集
CUNCUR_UPDATEABLE,可更新的結(jié)果集
2.可更新的結(jié)果集:(不常用,也不推薦使用)(了解)
如果你想能夠編輯結(jié)果集中的數(shù)據(jù),并且將結(jié)果集上的數(shù)據(jù)自動(dòng)反應(yīng)到數(shù)據(jù)庫(kù)中,
那么就必須使用可更新的結(jié)果集,可更新結(jié)果集不一定是可滾動(dòng)的,獲得可更新結(jié)果集的方法:
Statement
stat=conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATE
ABLE);
這樣,調(diào)用executeQuery()方法返回的結(jié)果集就是可更新的結(jié)果集。
例如:
String query="SELECT * FROM books";
ResultSet rs=stat.executeQuery(query);
While(rs.next){
If(.......){
Double inscrease=.......;
Double price=rs.getDouble("Price");
rs.updateDouble("Price",price+inscrease);
rs.updateRow();
}
}
所有的對(duì)應(yīng)于SQL類型的數(shù)據(jù)類型都配有updateXXX方法。與getXXX方法類似。使用updateXXX方法是必須指定列的名稱或序列號(hào)。
然后,可以給 該字段設(shè)置新的值。
updateXXX方法改變的只是結(jié)果集中的行值,而非數(shù)據(jù)庫(kù)中的值。當(dāng)更新完行中的字段值后,必須調(diào)用
updateRow方法,這個(gè)方法將當(dāng)前的行中所有更新信息發(fā)送給數(shù)據(jù)庫(kù)。如果沒(méi)有調(diào)用updateRow方法就將
游標(biāo)移動(dòng)到其他行上。那么所以的更新信息就將被丟棄,而且永遠(yuǎn)不會(huì)被傳遞給數(shù)據(jù)庫(kù)。還可以調(diào)用cancelRowUpates方法來(lái)取消對(duì)當(dāng)前行的更新。
以上是更新數(shù)據(jù)庫(kù)中的一行記錄,如果想在數(shù)據(jù)庫(kù)中添加一條新的記錄,首先需要使用moveToInsertRow
()方法將游標(biāo)移動(dòng)到特定的位置。然后調(diào)用updateXXX()方法在插入行的位置上創(chuàng)建一個(gè)新的行。然后調(diào)用
insertRow()方法將新建的行發(fā)送給數(shù)據(jù)庫(kù)。完成插入操作后在調(diào)用moveToCurrentRow()方法講游標(biāo)移回到調(diào)用moveToInsertRow方法之前的位置:
例子:
rs.moveToInsertRow();
rs.updateString("Title",title);
rs.updateString("Price",price);
..........
rs.insertRow();
rs.moveToCurrentRow();
如果要?jiǎng)h除一行調(diào)用rs.deleteRow()即可刪除結(jié)果集和數(shù)據(jù)庫(kù)中的一行
能否使用可更新結(jié)果集,要看使用的數(shù)據(jù)庫(kù)驅(qū)動(dòng)是否支持,還有只能用于單表且表中有主鍵字段(可能會(huì)是
聯(lián)合主鍵),不能夠有表連接,會(huì)取所有非空字段且沒(méi)有默認(rèn)值。結(jié)果集用select * from t也不行,不能用
*,不能排序
3,批處理更新 (熟記于心)
Statement.
addBatch(String sql), 方法會(huì)在批處理緩存中加入一條sql語(yǔ)句
executeBatch() ,執(zhí)行批處理緩存中的所有sql語(yǔ)句。
PreparedStatement. 先準(zhǔn)備一組參數(shù)
addBatch() 將一組參數(shù)添加到此 PreparedStatement 對(duì)象的批處理命令中。
executeBatch() 將一批命令提交給數(shù)據(jù)庫(kù)來(lái)執(zhí)行,如果全部命令執(zhí)行成功,則返回更新計(jì)數(shù)組成的數(shù)組。
PreparedStatement中使用批量更新時(shí),要先設(shè)置好參數(shù)后使用addBatch()方法加入緩存。
注意:批量更新中用更新或插入語(yǔ)句
面向?qū)ο蟮臄?shù)據(jù)庫(kù)設(shè)計(jì)
Id通常是用來(lái)表示記錄的唯一性的,通常會(huì)使用業(yè)務(wù)無(wú)關(guān)的數(shù)字類型
Object id 對(duì)象的id,sequence只有Oracle才可用,對(duì)象id(OID)使用高低位算法先生成高位,在生成低位,通過(guò)運(yùn)算獲得對(duì)象id。
類應(yīng)當(dāng)對(duì)象到表,屬性對(duì)應(yīng)字段,對(duì)象對(duì)應(yīng)記錄。
類繼承關(guān)系對(duì)應(yīng)表,
1,每個(gè)類建一個(gè)表,為父子類每個(gè)類都對(duì)應(yīng)的創(chuàng)建表,這種方法類關(guān)系清晰,但是如果類比較多就不適合了
2,只有具體類才建表,也就是把父類中的屬性均勻分配到子類的表中,也就是父類不建表,這種表關(guān)系不能使用多態(tài)
3,所有類對(duì)應(yīng)一張表,這種方法是在表中加上一個(gè)字段來(lái)區(qū)分父子類,但是只能用于類屬性較少的情況下,而且數(shù)據(jù)會(huì)有冗余。
類關(guān)聯(lián)關(guān)系對(duì)應(yīng)表
1,一對(duì)一關(guān)聯(lián),類關(guān)系對(duì)應(yīng)成表時(shí)有兩種做法,一是引用主鍵,也就是一方引用另一方的主鍵既作為外鍵又作為自身的主鍵。
二是外鍵引用,一方引用另一方的主鍵作為自身的外鍵,并且自己擁有主鍵。
2,一對(duì)多關(guān)聯(lián),也就是多端引用一端的主鍵當(dāng)作外鍵,多端自身?yè)碛兄麈I。
3,多對(duì)多關(guān)系,多對(duì)多關(guān)系是通過(guò)中間表來(lái)實(shí)現(xiàn)的,中間表引用兩表的主鍵當(dāng)作聯(lián)合主鍵,就可以實(shí)現(xiàn)多對(duì)多關(guān)聯(lián)。