簡(jiǎn)介
剛開(kāi)始介紹了mysql基本語(yǔ)句,但是你會(huì)覺(jué)得好像不會(huì)知道怎么用,它的用途在什么地方,所以為了提高興趣今天我們來(lái)介紹一下JDBC,以后會(huì)和MySQL一起更新。
JDBC,到底jdbc是什么東西呢?
JDBC(Java Data Base Connectivity,java數(shù)據(jù)庫(kù)連接),是由一些接口和類構(gòu)成的API。是J2SE的一部分,由java.sql,javax.sql包組成。
概述
JDBC是JAVA與數(shù)據(jù)的連接。因?yàn)镺DBC是完全用C語(yǔ)言編寫(xiě)的,而JAVA中實(shí)現(xiàn)與C語(yǔ)言程序的通信是比較困難的,因此就產(chǎn)生了由JAVA語(yǔ)言編寫(xiě)的用于JAVA程序與數(shù)據(jù)庫(kù)連接的接口技術(shù)。JDBC與具體的某種數(shù)據(jù)庫(kù)連接,是通過(guò)由數(shù)據(jù)庫(kù)廠商提供的驅(qū)動(dòng)來(lái)作為中間橋梁實(shí)現(xiàn)的。在JDBC API類庫(kù)一般在java.sql包中,它包含了用于實(shí)現(xiàn)與數(shù)據(jù)庫(kù)連接的其它功能的類,包括與數(shù)據(jù)庫(kù)建立連接、傳送查詢和接受查詢結(jié)果。
建立數(shù)據(jù)庫(kù)
為了演示JDBC我們先來(lái)創(chuàng)建一個(gè)數(shù)據(jù)庫(kù),為下面的做鋪墊,之前裝的數(shù)據(jù)庫(kù)客戶端是英文版的,今天我們使用的是版本較低的中文yog客戶端,這樣看起來(lái)比較方便。

下載驅(qū)動(dòng)
既然java和數(shù)據(jù)庫(kù)建立連接的橋梁是驅(qū)動(dòng),那當(dāng)然首先我們得有驅(qū)動(dòng),我們今天說(shuō)的的mysql,所以先去mysql官網(wǎng)下載驅(qū)動(dòng)然后解壓,你不想去下載的話今天的文件可以關(guān)注公眾號(hào) 代碼黑洞 后臺(tái)獲取一下,里面這些都有了。官網(wǎng)下載地址:https://dev.mysql.com/downloads/connector/j/

下載好之后我們要去把這個(gè)jar包添加到我們創(chuàng)建的項(xiàng)目的Build Path中,如下圖。


JDBC實(shí)例
實(shí)現(xiàn)jdbc有這么五個(gè)步驟:
1.注冊(cè)驅(qū)動(dòng)
2.創(chuàng)建連接
3.創(chuàng)建語(yǔ)句
4.執(zhí)行語(yǔ)句
5.處理結(jié)果
6.關(guān)閉資源
我們一個(gè)一個(gè)來(lái)看。
1.注冊(cè)驅(qū)動(dòng)
注冊(cè)驅(qū)動(dòng) 有三種方式:
????方式一:
/*DriverManager中的registerDriver(new Drivers());參數(shù)傳的就是一個(gè)Drivers
?* 可以注冊(cè)很多驅(qū)動(dòng),比如SQL sever,MySQL,Oracle等
?* 通過(guò)查看源碼我們可以只知道這個(gè)Drivers的存儲(chǔ)方式其實(shí)是一個(gè)vector
?* 在程序運(yùn)行時(shí)給定了url就會(huì)在這個(gè)vector里進(jìn)行比對(duì)看能不能建立連接
?* 如果可以就返回結(jié)果,如果把vector遍歷完了都不能建立連接那就會(huì)報(bào)錯(cuò)
?*
?* 使用這種方式去注冊(cè)驅(qū)動(dòng)會(huì)造成DriverManager中產(chǎn)生兩個(gè)一樣的
?* 驅(qū)動(dòng),并會(huì)對(duì)具體的驅(qū)動(dòng)類產(chǎn)生依賴。不利于移植,和擴(kuò)展。
?*/
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
方式一:
/*
?* 使用鍵值對(duì)的方式registerDriver里邊有一個(gè)loadInitialDrivers方法?* 會(huì)去找通過(guò)鍵值對(duì)注冊(cè)的驅(qū)動(dòng)信息,但是分割方式是以:而不是=的方式
?* 所以用這個(gè)鍵值對(duì)的方式去注冊(cè)很多個(gè)驅(qū)動(dòng)的時(shí)候中間使用:分割的
?* 如:System.setProperty("jdbc.drivers", "com.mysql.jdbc.Driver:com.oracle.jdbc.Driver");
?* 就注冊(cè)了mysql和oracle的兩個(gè)驅(qū)動(dòng)
?* 雖然不會(huì)對(duì)具體的驅(qū)動(dòng)類產(chǎn)生依賴;但注冊(cè)不太方便,所以很少使用。?
*/
System.setProperty("jdbc.drivers", "com.mysql.jdbc.Driver");
方式三:
/*
?* 通過(guò)這種Class.forName的方式去找這個(gè)文件并裝載到虛擬機(jī)中來(lái)
?* 即Java反射機(jī)制,會(huì)根據(jù)這個(gè)名稱去找編譯好的.class文件
* com.mysql.jdbc這是包名,這個(gè)跟導(dǎo)不導(dǎo)包是沒(méi)有關(guān)系的
* 因?yàn)樗皇且粋€(gè)字符串,這個(gè)根據(jù)這個(gè)classPath只是找到了包,
* 包這是個(gè)文件夾而并不會(huì)接著往包里邊去找所以要寫(xiě)上包名
* 告訴虛擬機(jī)去這個(gè)路徑下找然后裝載到j(luò)vm虛擬機(jī)里來(lái)
* 所以異常就是ClassNotFoundException,class文件沒(méi)有找到異常
* 推薦這種方式,不會(huì)對(duì)具體的驅(qū)動(dòng)類產(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呢因?yàn)橐话胝?qǐng)求的都是網(wǎng)絡(luò)主機(jī)
* 所以要指定網(wǎng)絡(luò)主機(jī)數(shù)據(jù)庫(kù)的url然后帶著用戶名和密碼去
* 建立連接,告訴服務(wù)器你要連接哪個(gè)用戶的數(shù)據(jù)庫(kù)
* 如果沒(méi)有密碼那就是password為空字符
*?
* url格式:(這不需要去記百度都能找到)
* JDBC:子協(xié)議:子名稱//主機(jī)名:端口/數(shù)據(jù)庫(kù)名?屬性名=屬性值&…
* User,password可以用“屬性名=屬性值”方式告訴數(shù)據(jù)庫(kù);
* 其他參數(shù)如:useUnicode=true&characterEncoding=GBK
* 這個(gè)其他參數(shù)就是指定解碼格式,用來(lái)解析返回來(lái)的數(shù)據(jù)
* 如果返回回來(lái)的是utf-8編碼方式的數(shù)據(jù),你用GBK的方式去
* 解析反饋回來(lái)的數(shù)據(jù),那就會(huì)出現(xiàn)亂碼的情況
* 所以如果節(jié)碼方式不指定,虛擬機(jī)程序時(shí)會(huì)給出警告
*/
String url = "jdbc:mysql://localhost:3306/jdbc";
String user = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, user, password);
3.創(chuàng)建語(yǔ)句
Statement 對(duì)象
一旦我們獲得了數(shù)據(jù)庫(kù)的連接,我們就可以和數(shù)據(jù)庫(kù)進(jìn)行交互。JDBC 的 ?Statement,CallableStatement 和 PreparedStatement 接口定義的方法和屬性,可以讓你發(fā)送 SQL 命令或 PL/SQL 命令到數(shù)據(jù)庫(kù),并從你的數(shù)據(jù)庫(kù)接收數(shù)據(jù)。
在數(shù)據(jù)庫(kù)中,它們還定義了幫助 Java 和 SQL 數(shù)據(jù)類型之間轉(zhuǎn)換數(shù)據(jù)差異的方法。
下表提供了每個(gè)接口的用途概要,根據(jù)實(shí)際目的決定使用哪個(gè)接口。
接口推薦使用
Statement可以正常訪問(wèn)數(shù)據(jù)庫(kù),適用于運(yùn)行靜態(tài) SQL 語(yǔ)句。 Statement 接口不接受參數(shù)。
PreparedStatement計(jì)劃多次使用 SQL 語(yǔ)句, PreparedStatement 接口運(yùn)行時(shí)接受輸入的參數(shù)。
CallableStatement適用于當(dāng)你要訪問(wèn)數(shù)據(jù)庫(kù)存儲(chǔ)過(guò)程的時(shí)候, CallableStatement 接口運(yùn)行時(shí)也接受輸入的參數(shù)。
我們先演示的是Statement
Statement st = conn.createStatement();
4.執(zhí)行數(shù)據(jù)庫(kù)語(yǔ)句
當(dāng)你創(chuàng)建了一個(gè) Statement 對(duì)象之后,你可以用它的三個(gè)執(zhí)行方法的任一方法來(lái)執(zhí)行 SQL 語(yǔ)句。
boolean execute(String SQL) :?如果 ResultSet 對(duì)象可以被檢索,則返回的布爾值為 true ,否則返回 false 。當(dāng)你需要使用真正的動(dòng)態(tài) SQL 時(shí),可以使用這個(gè)方法來(lái)執(zhí)行 SQL DDL 語(yǔ)句。
int executeUpdate(String SQL) :?返回執(zhí)行 SQL 語(yǔ)句影響的行的數(shù)目。使用該方法來(lái)執(zhí)行 SQL 語(yǔ)句,是希望得到一些受影響的行的數(shù)目,例如,INSERT,UPDATE 或 DELETE 語(yǔ)句。
ResultSet executeQuery(String SQL) :?返回一個(gè) ?ResultSet 對(duì)象。當(dāng)你希望得到一個(gè)結(jié)果集時(shí)使用該方法,就像你使用一個(gè) SELECT 語(yǔ)句。
我們就用ResultSet來(lái)執(zhí)行數(shù)據(jù)庫(kù)語(yǔ)句:
ResultSet rs = st.executeQuery("select * from user");
5.處理結(jié)果
/*
* 5.處理結(jié)果
* 學(xué)過(guò)集合框架都知道迭代器的使用,比如:iterator所以這個(gè)rs.next()
* 就是去查找下一行數(shù)據(jù)。按行遍歷的所以我們只需要按字段去拿
* While(rs.next()){
*? rs.getString(“col_name”);//col_name字段(列名)
*? rs.getInt(“col_name”);
*? ....
* }
*/
while (rs.next()) {
//這里時(shí)也是去拿字段只不過(guò)更加能體現(xiàn)面向?qū)ο蟮乃枷耄?/p>
//每一個(gè)字段都是一個(gè)對(duì)象
//和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ù)庫(kù)連接(Connection)是非常稀有的資源,用完后必須馬上釋放
*? 如果Connection不能及時(shí)正確的關(guān)閉將導(dǎo)致系統(tǒng)宕機(jī)。
*? Connection的使用原則是盡量晚的去建立連接,盡量早的釋放。
*? 也就是盡量減少占用數(shù)據(jù)庫(kù)的時(shí)間,減輕數(shù)據(jù)庫(kù)的壓力
*/?
????rs.close();
????st.close();
????conn.close();
改進(jìn)
上述所示例子有很多問(wèn)題需要處理,不夠嚴(yán)謹(jǐn)。我們就需來(lái)對(duì)它一步一步的優(yōu)化。
首先為了提高擴(kuò)展性,我們可以把建立連接時(shí)Connection參數(shù)定義為private static final



我們發(fā)現(xiàn)finally關(guān)閉資源的時(shí)候處理異常太麻煩,每次都要嵌套些這么多就很煩,所以還是需要優(yōu)化。于是我們打算寫(xiě)一個(gè)名叫JdbcUtils工具類來(lái)完成這件事,同樣的先把參數(shù)定義為全局常量

把構(gòu)造方法私有化,避免產(chǎn)生對(duì)象,我們寫(xiě)這個(gè)工具類是直接使用的不需要?jiǎng)?chuàng)建對(duì)象
/*

*注冊(cè)驅(qū)動(dòng)。將其聲明為static代碼塊即靜態(tài)代碼塊,是在類中獨(dú)立于類成員的static語(yǔ)句塊當(dāng)JVM加載類時(shí)就會(huì)執(zhí)行
*它不依賴類特定的實(shí)例,被類的所有實(shí)例共享。
*
* 在這里就非常有用了,使用jdbc第一個(gè)步驟就是要先注冊(cè)驅(qū)動(dòng)
* 把注冊(cè)驅(qū)動(dòng)寫(xiě)成靜態(tài)代碼塊,當(dāng)類加載時(shí)就會(huì)被執(zhí)行達(dá)到注冊(cè)的目的無(wú)需單獨(dú)寫(xiě)成在方法再去調(diào)用,那樣麻煩!!
*/

/*
*?建立連接,還是一樣,把參數(shù)抽取出來(lái),定義為全局變量,提高其擴(kuò)展性,比如當(dāng)你改了
* 數(shù)據(jù)庫(kù)密碼的時(shí)候,就不用在去修改源代碼。
* 當(dāng)然還有更好的方法是用Properties集合把這些信息保存到配置文件中去,大大提高了擴(kuò)展性
* 當(dāng)信息改動(dòng)只需要更改配置文件,而不需要去改源代碼。這里就不再演示Properties。
*/

/*
*將關(guān)閉資源單獨(dú)分裝到自己寫(xiě)的工具類中,提高復(fù)用性,因?yàn)殛P(guān)閉資源需要處理的異常寫(xiě)起來(lái)很麻煩
*而又是必須要處理,所以單獨(dú)封裝,可以節(jié)省時(shí)間和空間
*/

當(dāng)我們需要的去連接到數(shù)據(jù)庫(kù)的時(shí)候,就可以很方便的進(jìn)行,如下所示:

測(cè)試改進(jìn)效果
這樣代碼就會(huì)簡(jiǎn)潔很多。運(yùn)行結(jié)果如下,這就查詢到了數(shù)據(jù)庫(kù)里的信息

繼續(xù)優(yōu)化
既然我們這種方式是需要私有化構(gòu)造函數(shù)的,那我們就想到了,能用單類來(lái)完成它

/*
?* 單類模式實(shí)現(xiàn)工具類 創(chuàng)建方法一 餓漢式(即時(shí)加載模式)
* 特點(diǎn):
* 1、單例類只能有一個(gè)實(shí)例。
* 2、單例類必須自己創(chuàng)建自己的唯一實(shí)例。
* 3、單例類必須給所有其他對(duì)象提供這一實(shí)例。
* 單例模式保證了全局對(duì)象的唯一性
*/
/* 思路:
* 單類:通過(guò)將構(gòu)造方法限定為private避免了類在外部被實(shí)例化,
* 在同一個(gè)虛擬機(jī)范圍內(nèi),JdbcUtilsSingle的唯一實(shí)例
* 只能通過(guò)getInstance()方法訪問(wèn)。
*
* 第一步:創(chuàng)建自己的唯一實(shí)例
* 第二部:對(duì)外提供getInstance()方法獲取實(shí)例
*/

然后其他地方就和第一次改進(jìn)后一樣
注冊(cè)驅(qū)動(dòng)

后面的一樣那就不在贅述。
測(cè)試單類工具類

懶漢式單類設(shè)計(jì)模式
單類實(shí)現(xiàn)二:有時(shí)候把對(duì)象構(gòu)造出來(lái)但是不一定會(huì)用的時(shí)候用這種延時(shí)加載(也叫惰性加載)的方式,我們什么時(shí)候需要再去實(shí)例化對(duì)象

創(chuàng)建getInstance()方法去讓其他類拿到這個(gè)對(duì)象

/*
* 但是對(duì)于以上的getInstance()方法來(lái)說(shuō),還是有些問(wèn)題到,如果此時(shí)有兩個(gè)線程,線程A執(zhí)行到1處,讀取了instance為null,
* 然后cpu就被線程B搶去了,此時(shí),線程A還沒(méi)有對(duì)instance進(jìn)行實(shí)例化。
* 因此,線程B讀取instance時(shí)仍然為null,于是,它對(duì)instance進(jìn)行實(shí)例化了。
* 然后,cpu就被線程A搶去了。此時(shí),線程A由于已經(jīng)讀取了instance的值并且認(rèn)為它為null
* 所以,再次對(duì)instance進(jìn)行實(shí)例化。所以,線程A和線程B返回的不是同一個(gè)實(shí)例。
* 這就出現(xiàn)了線程安全問(wèn)題
*/
/*
* 那么線程安全問(wèn)題怎么解決呢?
* 方法一:在方法前面加synchronized修飾。這樣肯定不會(huì)再有線程安全問(wèn)題。?
* 但是,這種解決方式,假如有100個(gè)線程同時(shí)執(zhí)行,那么,每次去
* 執(zhí)行g(shù)etInstance方法時(shí)都要先獲得鎖再去執(zhí)行方法體,如果沒(méi)有鎖,
* 就要等待,耗時(shí)長(zhǎng),效率就很低。因此,還是需要改進(jìn)
*/

/* 解決方法改進(jìn):
* 加同步代碼塊,減少鎖的顆粒大小,即能用局部鎖就不用函數(shù)鎖。我們發(fā)現(xiàn),只有第一次instance為null的
* 時(shí)候,才去創(chuàng)建實(shí)例,而判斷instance是否為null是讀的操作,不可能存在線程安全
* 問(wèn)題,所以,我們只需要對(duì)創(chuàng)建實(shí)例的代碼進(jìn)行同步代碼塊的處理,也就是所謂的對(duì)可
* 能出現(xiàn)線程安全的代碼進(jìn)行同步代碼塊的處理。
*/

/*但是我們這樣處理就沒(méi)有問(wèn)題了嗎?
* 同理我們先來(lái)做個(gè)分析,假設(shè)有兩個(gè)線程,線程A和線程B,
* 首先線程A讀取instance值為null,然后cpu就被線程B搶了去獲得執(zhí)行權(quán),
* 線程B再來(lái)判斷instance值為null,接著線程B依然持有執(zhí)行權(quán)往下執(zhí)行了同
* 步代碼塊中的代碼,對(duì)instance進(jìn)行實(shí)例化。
* 然后,線程A獲得cpu的執(zhí)行權(quán),由于線程A之前已經(jīng)判斷instance值為null,
* 于是線程A直接就執(zhí)行了它后面的同步代碼塊代碼,對(duì)instance進(jìn)行實(shí)例化。
*這樣就實(shí)例化了兩個(gè)對(duì)象,實(shí)則只用的到一個(gè)對(duì)象,造成資源浪費(fèi)。
*
*所以從上面的分析來(lái)看,我們需要繼續(xù)改進(jìn)
*那就是加雙重if判斷
*/

/*但是。。。哈哈盡管這樣還是不能保證代碼百分百一定沒(méi)有線程安全問(wèn)題了
* 因?yàn)?,這里會(huì)涉及到一個(gè)指令重排序問(wèn)題,雖然幾率很小,但是還是有存在的可能
* 所以再來(lái)一步把之前那句private static JdbcUtilsSingle1 instance = null;
* 加上volatile關(guān)鍵字,因?yàn)関olatile可以禁止指令重排序。如下:
* private static volatile JdbcUtilsSingle1 instance = null;
*/

現(xiàn)在可以保證是完全沒(méi)有問(wèn)題了,后面的結(jié)果處理,關(guān)閉資源和之前是一樣的那就不在贅述,如何使用,也是和單類設(shè)計(jì)模式一的測(cè)試一樣的也就不再說(shuō)了。
那么通過(guò)上述示例,我們就把,jdbc,多線程中線程安全問(wèn)題以及單類的兩種設(shè)計(jì)模式(懶漢式,餓漢式)融合貫通起來(lái),不禁讓我感嘆代碼之美。
如果有什么問(wèn)題可以到公眾號(hào)咨詢,一般是機(jī)器人回復(fù),但我看到后會(huì)和你一起討論。
如果這中有什么問(wèn)題歡迎在評(píng)論區(qū)留言指正,共同進(jìn)步。
????????????????????????????????????????????????????????????????歡迎關(guān)注公眾號(hào) 代碼黑洞