Java操作數(shù)據(jù)庫(kù)--JDBC

簡(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) 代碼黑洞

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 34,673評(píng)論 18 399
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 4,011評(píng)論 0 11
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,551評(píng)論 19 139
  • 前些天,Jenny發(fā)了一條朋友圈,附上兩張照片,第一張是一個(gè)男生的背影,他牽著她的手;第二張,是兩張紅本本。配文是...
    TF_Java閱讀 205評(píng)論 0 1
  • 很多人早上起來(lái)只用清水洗臉,晚上睡覺(jué)面部是會(huì)排毒的,會(huì)分泌出來(lái)很多油脂和死皮垃圾,此時(shí)如果你沒(méi)有清潔到位的話,時(shí)間...
    唯有妳懂我閱讀 243評(píng)論 0 0

友情鏈接更多精彩內(nèi)容