簡介
剛開始介紹了mysql基本語句普碎,但是你會覺得好像不會知道怎么用,它的用途在什么地方纸兔,所以為了提高興趣今天我們來介紹一下JDBC,以后會和MySQL一起更新梅桩。
JDBC,到底jdbc是什么東西呢拜隧?
JDBC(Java Data Base Connectivity,java數(shù)據(jù)庫連接)宿百,是由一些接口和類構(gòu)成的API。是J2SE的一部分洪添,由java.sql,javax.sql包組成垦页。
概述
JDBC是JAVA與數(shù)據(jù)的連接。因為ODBC是完全用C語言編寫的干奢,而JAVA中實現(xiàn)與C語言程序的通信是比較困難的痊焊,因此就產(chǎn)生了由JAVA語言編寫的用于JAVA程序與數(shù)據(jù)庫連接的接口技術(shù)。JDBC與具體的某種數(shù)據(jù)庫連接忿峻,是通過由數(shù)據(jù)庫廠商提供的驅(qū)動來作為中間橋梁實現(xiàn)的薄啥。在JDBC API類庫一般在java.sql包中,它包含了用于實現(xiàn)與數(shù)據(jù)庫連接的其它功能的類炭菌,包括與數(shù)據(jù)庫建立連接罪佳、傳送查詢和接受查詢結(jié)果。
建立數(shù)據(jù)庫
為了演示JDBC我們先來創(chuàng)建一個數(shù)據(jù)庫黑低,為下面的做鋪墊赘艳,之前裝的數(shù)據(jù)庫客戶端是英文版的,今天我們使用的是版本較低的中文yog客戶端克握,這樣看起來比較方便蕾管。
下載驅(qū)動
既然java和數(shù)據(jù)庫建立連接的橋梁是驅(qū)動,那當(dāng)然首先我們得有驅(qū)動菩暗,我們今天說的的mysql掰曾,所以先去mysql官網(wǎng)下載驅(qū)動然后解壓,你不想去下載的話今天的文件可以關(guān)注公眾號 代碼黑洞 后臺獲取一下停团,里面這些都有了旷坦。官網(wǎng)下載地址:https://dev.mysql.com/downloads/connector/j/
下載好之后我們要去把這個jar包添加到我們創(chuàng)建的項目的Build Path中,如下圖佑稠。
JDBC實例
實現(xiàn)jdbc有這么五個步驟:
1.注冊驅(qū)動
2.創(chuàng)建連接
3.創(chuàng)建語句
4.執(zhí)行語句
5.處理結(jié)果
6.關(guān)閉資源
我們一個一個來看秒梅。
1.注冊驅(qū)動
注冊驅(qū)動 有三種方式:
????方式一:
/*DriverManager中的registerDriver(new Drivers());參數(shù)傳的就是一個Drivers
?* 可以注冊很多驅(qū)動,比如SQL sever舌胶,MySQL捆蜀,Oracle等
?* 通過查看源碼我們可以只知道這個Drivers的存儲方式其實是一個vector
?* 在程序運(yùn)行時給定了url就會在這個vector里進(jìn)行比對看能不能建立連接
?* 如果可以就返回結(jié)果,如果把vector遍歷完了都不能建立連接那就會報錯
?*
?* 使用這種方式去注冊驅(qū)動會造成DriverManager中產(chǎn)生兩個一樣的
?* 驅(qū)動,并會對具體的驅(qū)動類產(chǎn)生依賴辆它。不利于移植誊薄,和擴(kuò)展。
?*/
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
方式一:
/*
?* 使用鍵值對的方式registerDriver里邊有一個loadInitialDrivers方法?* 會去找通過鍵值對注冊的驅(qū)動信息锰茉,但是分割方式是以:而不是=的方式
?* 所以用這個鍵值對的方式去注冊很多個驅(qū)動的時候中間使用:分割的
?* 如:System.setProperty("jdbc.drivers", "com.mysql.jdbc.Driver:com.oracle.jdbc.Driver");
?* 就注冊了mysql和oracle的兩個驅(qū)動
?* 雖然不會對具體的驅(qū)動類產(chǎn)生依賴呢蔫;但注冊不太方便,所以很少使用飒筑。?
*/
System.setProperty("jdbc.drivers", "com.mysql.jdbc.Driver");
方式三:
/*
?* 通過這種Class.forName的方式去找這個文件并裝載到虛擬機(jī)中來
?* 即Java反射機(jī)制咐刨,會根據(jù)這個名稱去找編譯好的.class文件
* com.mysql.jdbc這是包名,這個跟導(dǎo)不導(dǎo)包是沒有關(guān)系的
* 因為它只是一個字符串扬霜,這個根據(jù)這個classPath只是找到了包,
* 包這是個文件夾而并不會接著往包里邊去找所以要寫上包名
* 告訴虛擬機(jī)去這個路徑下找然后裝載到j(luò)vm虛擬機(jī)里來
* 所以異常就是ClassNotFoundException而涉,class文件沒有找到異常
* 推薦這種方式著瓶,不會對具體的驅(qū)動類產(chǎn)生依賴。
* 根據(jù)Java反射的特性就可以知道用反射是極大的提高了擴(kuò)展性的
*/
Class.forName("com.mysql.jdbc.Driver");
2.建立連接
/*
*? 2.建立連接
* Connection conn = DriverManager.getConnection(url, user, password);
* 參數(shù)是:url啼县,user材原,password
* 為什么是url呢因為一半請求的都是網(wǎng)絡(luò)主機(jī)
* 所以要指定網(wǎng)絡(luò)主機(jī)數(shù)據(jù)庫的url然后帶著用戶名和密碼去
* 建立連接,告訴服務(wù)器你要連接哪個用戶的數(shù)據(jù)庫
* 如果沒有密碼那就是password為空字符
*?
* url格式:(這不需要去記百度都能找到)
* JDBC:子協(xié)議:子名稱//主機(jī)名:端口/數(shù)據(jù)庫名季眷?屬性名=屬性值&…
* User,password可以用“屬性名=屬性值”方式告訴數(shù)據(jù)庫余蟹;
* 其他參數(shù)如:useUnicode=true&characterEncoding=GBK
* 這個其他參數(shù)就是指定解碼格式,用來解析返回來的數(shù)據(jù)
* 如果返回回來的是utf-8編碼方式的數(shù)據(jù)子刮,你用GBK的方式去
* 解析反饋回來的數(shù)據(jù)威酒,那就會出現(xiàn)亂碼的情況
* 所以如果節(jié)碼方式不指定,虛擬機(jī)程序時會給出警告
*/
String url = "jdbc:mysql://localhost:3306/jdbc";
String user = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, user, password);
3.創(chuàng)建語句
Statement 對象
一旦我們獲得了數(shù)據(jù)庫的連接挺峡,我們就可以和數(shù)據(jù)庫進(jìn)行交互葵孤。JDBC 的 ?Statement,CallableStatement 和 PreparedStatement 接口定義的方法和屬性橱赠,可以讓你發(fā)送 SQL 命令或 PL/SQL 命令到數(shù)據(jù)庫尤仍,并從你的數(shù)據(jù)庫接收數(shù)據(jù)。
在數(shù)據(jù)庫中狭姨,它們還定義了幫助 Java 和 SQL 數(shù)據(jù)類型之間轉(zhuǎn)換數(shù)據(jù)差異的方法宰啦。
下表提供了每個接口的用途概要,根據(jù)實際目的決定使用哪個接口饼拍。
接口推薦使用
Statement可以正常訪問數(shù)據(jù)庫赡模,適用于運(yùn)行靜態(tài) SQL 語句。 Statement 接口不接受參數(shù)惕耕。
PreparedStatement計劃多次使用 SQL 語句纺裁, PreparedStatement 接口運(yùn)行時接受輸入的參數(shù)。
CallableStatement適用于當(dāng)你要訪問數(shù)據(jù)庫存儲過程的時候, CallableStatement 接口運(yùn)行時也接受輸入的參數(shù)欺缘。
我們先演示的是Statement
Statement st = conn.createStatement();
4.執(zhí)行數(shù)據(jù)庫語句
當(dāng)你創(chuàng)建了一個 Statement 對象之后栋豫,你可以用它的三個執(zhí)行方法的任一方法來執(zhí)行 SQL 語句。
boolean execute(String SQL) :?如果 ResultSet 對象可以被檢索谚殊,則返回的布爾值為 true 丧鸯,否則返回 false 。當(dāng)你需要使用真正的動態(tài) SQL 時嫩絮,可以使用這個方法來執(zhí)行 SQL DDL 語句丛肢。
int executeUpdate(String SQL) :?返回執(zhí)行 SQL 語句影響的行的數(shù)目。使用該方法來執(zhí)行 SQL 語句剿干,是希望得到一些受影響的行的數(shù)目蜂怎,例如,INSERT置尔,UPDATE 或 DELETE 語句杠步。
ResultSet executeQuery(String SQL) :?返回一個 ?ResultSet 對象。當(dāng)你希望得到一個結(jié)果集時使用該方法榜轿,就像你使用一個 SELECT 語句幽歼。
我們就用ResultSet來執(zhí)行數(shù)據(jù)庫語句:
ResultSet rs = st.executeQuery("select * from user");
5.處理結(jié)果
/*
* 5.處理結(jié)果
* 學(xué)過集合框架都知道迭代器的使用,比如:iterator所以這個rs.next()
* 就是去查找下一行數(shù)據(jù)谬盐。按行遍歷的所以我們只需要按字段去拿
* While(rs.next()){
*? rs.getString(“col_name”);//col_name字段(列名)
*? rs.getInt(“col_name”);
*? ....
* }
*/
while (rs.next()) {
//這里時也是去拿字段只不過更加能體現(xiàn)面向?qū)ο蟮乃枷耄?/p>
//每一個字段都是一個對象
//和getString甸私,getInt等,是一樣的
System.out.println(rs.getObject(1) + "\t" + rs.getObject(2) + "\t"+ rs.getObject(3) + "\t" + rs.getObject(4));
}
6.釋放資源
/*
*? 6.釋放資源
*? 釋放ResultSet, Statement,Connection.
*? 數(shù)據(jù)庫連接(Connection)是非常稀有的資源飞傀,用完后必須馬上釋放
*? 如果Connection不能及時正確的關(guān)閉將導(dǎo)致系統(tǒng)宕機(jī)皇型。
*? Connection的使用原則是盡量晚的去建立連接,盡量早的釋放助析。
*? 也就是盡量減少占用數(shù)據(jù)庫的時間犀被,減輕數(shù)據(jù)庫的壓力
*/?
????rs.close();
????st.close();
????conn.close();
改進(jìn)
上述所示例子有很多問題需要處理,不夠嚴(yán)謹(jǐn)外冀。我們就需來對它一步一步的優(yōu)化寡键。
首先為了提高擴(kuò)展性,我們可以把建立連接時Connection參數(shù)定義為private static final
我們發(fā)現(xiàn)finally關(guān)閉資源的時候處理異常太麻煩雪隧,每次都要嵌套些這么多就很煩西轩,所以還是需要優(yōu)化。于是我們打算寫一個名叫JdbcUtils工具類來完成這件事脑沿,同樣的先把參數(shù)定義為全局常量
把構(gòu)造方法私有化藕畔,避免產(chǎn)生對象,我們寫這個工具類是直接使用的不需要創(chuàng)建對象
/*
*注冊驅(qū)動庄拇。將其聲明為static代碼塊即靜態(tài)代碼塊注服,是在類中獨立于類成員的static語句塊當(dāng)JVM加載類時就會執(zhí)行
*它不依賴類特定的實例韭邓,被類的所有實例共享。
*
* 在這里就非常有用了溶弟,使用jdbc第一個步驟就是要先注冊驅(qū)動
* 把注冊驅(qū)動寫成靜態(tài)代碼塊女淑,當(dāng)類加載時就會被執(zhí)行達(dá)到注冊的目的無需單獨寫成在方法再去調(diào)用,那樣麻煩9加鸭你!
*/
/*
*?建立連接,還是一樣擒权,把參數(shù)抽取出來袱巨,定義為全局變量,提高其擴(kuò)展性碳抄,比如當(dāng)你改了
* 數(shù)據(jù)庫密碼的時候愉老,就不用在去修改源代碼。
* 當(dāng)然還有更好的方法是用Properties集合把這些信息保存到配置文件中去剖效,大大提高了擴(kuò)展性
* 當(dāng)信息改動只需要更改配置文件俺夕,而不需要去改源代碼。這里就不再演示Properties贱鄙。
*/
/*
*將關(guān)閉資源單獨分裝到自己寫的工具類中,提高復(fù)用性姨谷,因為關(guān)閉資源需要處理的異常寫起來很麻煩
*而又是必須要處理逗宁,所以單獨封裝,可以節(jié)省時間和空間
*/
當(dāng)我們需要的去連接到數(shù)據(jù)庫的時候梦湘,就可以很方便的進(jìn)行瞎颗,如下所示:
測試改進(jìn)效果
這樣代碼就會簡潔很多。運(yùn)行結(jié)果如下捌议,這就查詢到了數(shù)據(jù)庫里的信息
繼續(xù)優(yōu)化
既然我們這種方式是需要私有化構(gòu)造函數(shù)的哼拔,那我們就想到了,能用單類來完成它
/*
?* 單類模式實現(xiàn)工具類 創(chuàng)建方法一 餓漢式(即時加載模式)
* 特點:
* 1瓣颅、單例類只能有一個實例倦逐。
* 2、單例類必須自己創(chuàng)建自己的唯一實例宫补。
* 3檬姥、單例類必須給所有其他對象提供這一實例。
* 單例模式保證了全局對象的唯一性
*/
/* 思路:
* 單類:通過將構(gòu)造方法限定為private避免了類在外部被實例化粉怕,
* 在同一個虛擬機(jī)范圍內(nèi)健民,JdbcUtilsSingle的唯一實例
* 只能通過getInstance()方法訪問。
*
* 第一步:創(chuàng)建自己的唯一實例
* 第二部:對外提供getInstance()方法獲取實例
*/
然后其他地方就和第一次改進(jìn)后一樣
注冊驅(qū)動
后面的一樣那就不在贅述贫贝。
測試單類工具類
懶漢式單類設(shè)計模式
單類實現(xiàn)二:有時候把對象構(gòu)造出來但是不一定會用的時候用這種延時加載(也叫惰性加載)的方式秉犹,我們什么時候需要再去實例化對象
創(chuàng)建getInstance()方法去讓其他類拿到這個對象
/*
* 但是對于以上的getInstance()方法來說蛉谜,還是有些問題到,如果此時有兩個線程崇堵,線程A執(zhí)行到1處型诚,讀取了instance為null,
* 然后cpu就被線程B搶去了筑辨,此時俺驶,線程A還沒有對instance進(jìn)行實例化。
* 因此棍辕,線程B讀取instance時仍然為null暮现,于是,它對instance進(jìn)行實例化了楚昭。
* 然后栖袋,cpu就被線程A搶去了。此時抚太,線程A由于已經(jīng)讀取了instance的值并且認(rèn)為它為null
* 所以塘幅,再次對instance進(jìn)行實例化。所以尿贫,線程A和線程B返回的不是同一個實例电媳。
* 這就出現(xiàn)了線程安全問題
*/
/*
* 那么線程安全問題怎么解決呢?
* 方法一:在方法前面加synchronized修飾庆亡。這樣肯定不會再有線程安全問題匾乓。?
* 但是,這種解決方式又谋,假如有100個線程同時執(zhí)行拼缝,那么,每次去
* 執(zhí)行g(shù)etInstance方法時都要先獲得鎖再去執(zhí)行方法體彰亥,如果沒有鎖咧七,
* 就要等待,耗時長任斋,效率就很低继阻。因此,還是需要改進(jìn)
*/
/* 解決方法改進(jìn):
* 加同步代碼塊废酷,減少鎖的顆粒大小穴翩,即能用局部鎖就不用函數(shù)鎖。我們發(fā)現(xiàn)锦积,只有第一次instance為null的
* 時候芒帕,才去創(chuàng)建實例,而判斷instance是否為null是讀的操作丰介,不可能存在線程安全
* 問題背蟆,所以鉴分,我們只需要對創(chuàng)建實例的代碼進(jìn)行同步代碼塊的處理,也就是所謂的對可
* 能出現(xiàn)線程安全的代碼進(jìn)行同步代碼塊的處理带膀。
*/
/*但是我們這樣處理就沒有問題了嗎志珍?
* 同理我們先來做個分析,假設(shè)有兩個線程垛叨,線程A和線程B伦糯,
* 首先線程A讀取instance值為null嗽元,然后cpu就被線程B搶了去獲得執(zhí)行權(quán)敛纲,
* 線程B再來判斷instance值為null,接著線程B依然持有執(zhí)行權(quán)往下執(zhí)行了同
* 步代碼塊中的代碼剂癌,對instance進(jìn)行實例化淤翔。
* 然后,線程A獲得cpu的執(zhí)行權(quán)佩谷,由于線程A之前已經(jīng)判斷instance值為null旁壮,
* 于是線程A直接就執(zhí)行了它后面的同步代碼塊代碼,對instance進(jìn)行實例化谐檀。
*這樣就實例化了兩個對象抡谐,實則只用的到一個對象,造成資源浪費桐猬。
*
*所以從上面的分析來看童叠,我們需要繼續(xù)改進(jìn)
*那就是加雙重if判斷
*/
/*但是。课幕。。哈哈盡管這樣還是不能保證代碼百分百一定沒有線程安全問題了
* 因為五垮,這里會涉及到一個指令重排序問題乍惊,雖然幾率很小,但是還是有存在的可能
* 所以再來一步把之前那句private static JdbcUtilsSingle1 instance = null;
* 加上volatile關(guān)鍵字放仗,因為volatile可以禁止指令重排序润绎。如下:
* private static volatile JdbcUtilsSingle1 instance = null;
*/
現(xiàn)在可以保證是完全沒有問題了,后面的結(jié)果處理诞挨,關(guān)閉資源和之前是一樣的那就不在贅述莉撇,如何使用,也是和單類設(shè)計模式一的測試一樣的也就不再說了惶傻。
那么通過上述示例棍郎,我們就把,jdbc银室,多線程中線程安全問題以及單類的兩種設(shè)計模式(懶漢式涂佃,餓漢式)融合貫通起來励翼,不禁讓我感嘆代碼之美。
如果有什么問題可以到公眾號咨詢辜荠,一般是機(jī)器人回復(fù)汽抚,但我看到后會和你一起討論。
如果這中有什么問題歡迎在評論區(qū)留言指正伯病,共同進(jìn)步造烁。
????????????????????????????????????????????????????????????????歡迎關(guān)注公眾號 代碼黑洞