JDBC操作全攻略
JDBC簡(jiǎn)介
JDBC(Java Database Connectivity),也稱為Java數(shù)據(jù)庫(kù)連接林艘,是Java用于連接數(shù)據(jù)庫(kù)的接口規(guī)范竹祷,需要注意的是暴凑,JDBC并沒(méi)有提供的實(shí)現(xiàn),具體的實(shí)現(xiàn)是由數(shù)據(jù)庫(kù)提供商瓮孙,比如MySQL谜疤,Oracle等負(fù)責(zé)提供,JDBC提供了一整套完整的連接規(guī)范帽撑,包括了Driver
振劳,DriverManager
,Connection
油狂,Statement
,PreparedStatement
,ResultSet
寸癌,DatabaseMetaData
专筷,ResultSetMetaData
,ParameterMetaData
等等
連接數(shù)據(jù)庫(kù)
在Java程序中蒸苇,操作數(shù)據(jù)庫(kù)時(shí)磷蛹,通常需要幾個(gè)步驟
- 導(dǎo)入對(duì)應(yīng)的JDBC實(shí)現(xiàn):由于JDBC本身沒(méi)有提供對(duì)應(yīng)的實(shí)現(xiàn),也不太可能提供對(duì)應(yīng)的實(shí)現(xiàn)溪烤,所以需要導(dǎo)入對(duì)應(yīng)數(shù)據(jù)庫(kù)供應(yīng)商提供的實(shí)現(xiàn)味咳,比如MySQL的實(shí)現(xiàn)
- 在程序中手動(dòng)注冊(cè)對(duì)應(yīng)的數(shù)據(jù)庫(kù)驅(qū)動(dòng)
- 建立并且打開(kāi)對(duì)應(yīng)的連接
- 通過(guò)連接獲取一個(gè)Statement對(duì)象,用于發(fā)送SQL檬嘀,并且獲得對(duì)應(yīng)的結(jié)果
- 從Statement中獲得結(jié)果集對(duì)象
- 關(guān)閉對(duì)應(yīng)的結(jié)果集槽驶、Statement以及連接
接下來(lái)通過(guò)代碼來(lái)具體查看如何操作
首先是導(dǎo)入MySQL的實(shí)現(xiàn),對(duì)應(yīng)的Maven依賴為
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
然后建立測(cè)試類TestJDBC.java
注冊(cè)MySQL驅(qū)動(dòng)
// 注冊(cè)MySQL驅(qū)動(dòng)
Class.forName("com.mysql.jdbc.Driver");
建立連接
private static final String SQL_URL = "jdbc:mysql://localhost:3306/spring"; // 數(shù)據(jù)庫(kù)連接地址
private static final String SQL_USER_NAME = "root"; // 賬號(hào)
private static final String SQL_PASSWORD = "huanfeng"; // 密碼
// 建立連接
Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);
// 獲得Statment對(duì)象
Statement statement = connection.createStatement();
完整的過(guò)程如下所示
private static final String SQL_URL = "jdbc:mysql://localhost:3306/spring";
private static final String SQL_USER_NAME = "root";
private static final String SQL_PASSWORD = "huanfeng";
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 注冊(cè)MySQL驅(qū)動(dòng)
Class.forName("com.mysql.jdbc.Driver");
// 建立連接
connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);
String sql = "SELECT * FROM user";
// 獲得Statement
statement = connection.createStatement();
// 獲得ResultSet
resultSet = statement.executeQuery(sql);
// 操作ResultSet
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
// 注意關(guān)閉的順序鸳兽,應(yīng)該從ResultSet再到Statement再到Connection
// 關(guān)閉ResultSet
if (resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 關(guān)閉Statement
if (statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 關(guān)閉Connection
if (connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
這里一定要注意掂铐,資源用完之后一定要關(guān)閉,因?yàn)榻⒁粋€(gè)對(duì)應(yīng)的連接是非常消耗資源的揍异,而如果使用完之后不關(guān)閉資源全陨,會(huì)造成非常大的資源浪費(fèi),為了證實(shí)這一點(diǎn)衷掷,下面進(jìn)行一個(gè)簡(jiǎn)單的測(cè)試辱姨,測(cè)試建立一個(gè)Connection所消耗的時(shí)間
@Test
public void testConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
long start = System.currentTimeMillis();
Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);
long end = System.currentTimeMillis();
connection.close();
System.out.printf("cost time %d ms\n", (end - start));
}
對(duì)應(yīng)的輸出結(jié)果如下所示
cost time 360 ms
可以看到,建立一個(gè)Connection連接需要消耗非常多的時(shí)間戚嗅,原因在于雨涛,建立連接的時(shí)候枢舶,本質(zhì)上是通過(guò)驅(qū)動(dòng)程序與數(shù)據(jù)庫(kù)之間建立一個(gè)Socket連接,對(duì)于Socket有了解的朋友應(yīng)該知道镜悉,建立Socket連接是比較消耗時(shí)間的祟辟,而且維持一個(gè)Socket連接也是非常消耗資源的,所以當(dāng)資源使用完畢之后侣肄,應(yīng)該關(guān)閉對(duì)應(yīng)的資源旧困,當(dāng)然,更好的做法是使用數(shù)據(jù)庫(kù)連接池技術(shù)稼锅,將Connection對(duì)象緩存起來(lái)吼具,來(lái)避免頻繁創(chuàng)建Connection對(duì)象。
JDBC實(shí)戰(zhàn)
上面大概了解了JDBC的操作步驟以及基本的注意事項(xiàng)矩距,接下來(lái)通過(guò)一個(gè)例子來(lái)使用JDBC操作數(shù)據(jù)庫(kù)拗盒,加深對(duì)JDBC使用的了解
建立連接的過(guò)程同上,這里不重復(fù)敘述
插入數(shù)據(jù)
@Test
public void testInsert() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);
String sql = "Insert into user(id, name) value(4, 'Tom')";
Statement statement = connection.createStatement();
int result = statement.executeUpdate(sql);
System.out.println(result);
statement.close();
connection.close();
}
上面是最簡(jiǎn)單的插入操作锥债,不過(guò)一般來(lái)說(shuō)陡蝇,我們會(huì)插入?yún)?shù)而不是固定的數(shù)值,所以上面的內(nèi)容會(huì)變成
int id = 4;
String name = "Tom";
// 拼接SQL語(yǔ)句
String sql = "Insert into user(id, name) value("+ id +", '"+ name +"')";
通過(guò)拼接SQL語(yǔ)句哮肚,然后使用Statement來(lái)執(zhí)行語(yǔ)句是解決插入?yún)?shù)的一種方式登夫,但是,通過(guò)這種方式進(jìn)行數(shù)據(jù)庫(kù)操作會(huì)出現(xiàn)SQL注入的危險(xiǎn)允趟,比如說(shuō)恼策,當(dāng)有人把參數(shù)name的內(nèi)容填寫為 Tom'); DELETE FROM USER; --
,那么此時(shí)拼接后的SQL語(yǔ)句就會(huì)變成insert into user(id, name) values(4, 'tom'); delete from user; --
此時(shí)就非常危險(xiǎn)了潮剪,雖然這里是不會(huì)出現(xiàn)問(wèn)題的涣楷,因?yàn)閟tatement一次只能執(zhí)行一條語(yǔ)句,但是如果是查詢后面被加上這些內(nèi)容抗碰,那就非常危險(xiǎn)了狮斗。為了避免被注入的情況發(fā)生,JDBC提供了另外一種方式弧蝇,采用預(yù)編譯處理SQL情龄,然后使用PreparedStatement設(shè)置參數(shù)以及執(zhí)行對(duì)應(yīng)的SQL語(yǔ)句,如下捍壤。
// 采用占位符來(lái)處理
String sql = "Insert into user(id, name) value(?, ?)";
PreparedStatement statement = connection.prepareStatement(sql);
// 設(shè)置對(duì)應(yīng)的參數(shù)骤视,注意這里是從1開(kāi)始,不是從0開(kāi)始
statement.setInt(1, id);
statement.setString(2, name);
// 這里也可以直接使用
// statement.setObject(1, id);
// statement.setObject(2, name);
// 不需要指定對(duì)應(yīng)的參數(shù)類型鹃觉,將其交給JDBC進(jìn)行判斷即可
JDBC是推薦使用PreparedStatement來(lái)進(jìn)行SQL處理专酗,而不是Statement來(lái)處理,原因在于PreparedStatement提供了預(yù)處理機(jī)制盗扇,可以預(yù)防SQL注入的發(fā)送祷肯,而且由于是采用預(yù)處理機(jī)制沉填,所以PreparedStatement的執(zhí)行效率要高于Statement
刪除數(shù)據(jù)
@Test
public void testDelete() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);
int id = 3;
String sql = "DELETE FROM USER where id = ?";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setObject(1, id);
int result = statement.executeUpdate();
statement.close();
connection.close();
}
修改數(shù)據(jù)
@Test
public void testUpdate() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);
int id = 2;
String name = "Jack";
String sql = "UPDATE USER SET NAME = ? where id = ?";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setObject(1, name);
statement.setObject(2, id);
int result = statement.executeUpdate();
statement.close();
connection.close();
}
查詢數(shù)據(jù)
談到查詢數(shù)據(jù),這里就有一個(gè)新的對(duì)象需要介紹佑笋,ResultSet翼闹,在JDBC中,查詢的結(jié)果一般都是以行為單位的蒋纬,可以是單行也可以是多行猎荠,JDBC將每一行都封裝成一個(gè)ResultSet對(duì)象,對(duì)于ResultSet的操作蜀备,其實(shí)就是一個(gè)迭代的過(guò)程关摇,在獲取的時(shí)候,需要通過(guò) ResultSet.getXX(ID)
或者ResultSet.getXX(CLO_NAME)
來(lái)獲取對(duì)應(yīng)行的數(shù)據(jù)碾阁,然后通過(guò)ResultSet.next()
將指針移動(dòng)到下一行输虱。
@Test
public void testQuery() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);
int id = 1;
String sql = "SELECT id, name FROM USER where id = ?";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setObject(1, id);
// 查詢返回的ResultSet
ResultSet resultSet = statement.executeQuery();
// 遍歷ResultSet
while (resultSet.next()){
// 取出對(duì)應(yīng)的數(shù)據(jù)
int n_id = resultSet.getInt("id");
String n_name = resultSet.getString("name");
// 封裝成對(duì)應(yīng)的對(duì)象
User user = new User(n_id, n_name);
System.out.println("user: " + user);
}
// 關(guān)閉對(duì)應(yīng)的連接
resultSet.close();
statement.close();
connection.close();
}
大對(duì)象處理
所謂的大對(duì)象,指的是數(shù)據(jù)庫(kù)中定義的CLOB(Character Large Object )脂凶、BLOB(Binary Large Object)宪睹,這兩個(gè)數(shù)據(jù)類型是用于把包含信息量比較大的數(shù)據(jù)存儲(chǔ)在列中,當(dāng)做一個(gè)屬性蚕钦,其中CLOB主要是用于存放字符類型大對(duì)象横堡,比如說(shuō)一本書(shū)的內(nèi)容,BLOB主要用于存放二進(jìn)制類型的大對(duì)象冠桃,比如說(shuō)一部小電影。JDBC同樣為操作這兩種數(shù)據(jù)類型提供支持道宅,不過(guò)食听,操作起來(lái)跟普通的數(shù)據(jù)類型有所不同,具體如下所示
@Test
public void testBLOB() throws ClassNotFoundException, SQLException, IOException, InterruptedException {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);
// 存入Blob類型的數(shù)據(jù)
File file = new File("d:/img.jpg");
int id = 121;
String name = "jack";
String sql = "insert into user(id, name, b_data) values(?, ?, ?)";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setObject(1, id);
statement.setObject(2, name);
// 這里需要注意污茵,類型應(yīng)該為Blob樱报,直接傳一個(gè)輸入流即可
statement.setBlob(3, new FileInputStream(file));
int result = statement.executeUpdate();
System.out.println(result);
// 讀取Blob類型的數(shù)據(jù)
sql = "SELECT b_data FROM user WHERE id = ?";
statement = connection.prepareStatement(sql);
statement.setObject(1, id);
ResultSet resultSet = statement.executeQuery();
if (resultSet.next()){
// 注意這里,返回的類型是BLob
Blob b_data = resultSet.getBlob("b_data");
InputStream inputStream = b_data.getBinaryStream();
byte[] buf = new byte[inputStream.available()];
FileOutputStream fileOutputStream = new FileOutputStream(new File("d:/new_img.jpg"));
inputStream.read(buf);
fileOutputStream.write(buf);
fileOutputStream.flush();
System.out.println("finish");
}
resultSet.close();
statement.close();
connection.close();
}
CLOB的操作基本同BLOB泞当,只是對(duì)應(yīng)的類型設(shè)置為CLOB即可迹蛤,需要注意的是,在MySQL中襟士,沒(méi)有數(shù)據(jù)類型為CLOB的數(shù)據(jù)類型盗飒,其對(duì)應(yīng)的是text,這里在創(chuàng)建數(shù)據(jù)表的時(shí)候需要注意一下陋桂。
批量處理
所謂的批處理逆趣,其實(shí)就是同時(shí)指定多個(gè)SQL語(yǔ)句,通常由兩種做法嗜历,一種是循環(huán)執(zhí)行多個(gè)SQL語(yǔ)句宣渗,另外一種則是采用JDBC提供的Batch功能抖所,具體如下所示
@Test
public void testBatch() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);
String sql = "insert into user(id, name) values(?, 'Tom')";
PreparedStatement statement = connection.prepareStatement(sql);
long start = System.currentTimeMillis();
// 普通循環(huán)執(zhí)行
for (int i = 0; i < 1000; i++){
statement.setObject(1, i);
statement.executeUpdate();
}
long end = System.currentTimeMillis();
System.out.printf("cost time %d\n", end - start);
statement.close();
statement = connection.prepareStatement(sql);
start = System.currentTimeMillis();
// 使用Batch執(zhí)行
for (int i = 1001 ; i < 2000; i++){
statement.setObject(1, i);
statement.addBatch();
}
statement.executeBatch();
end = System.currentTimeMillis();
System.out.printf("cost time %d\n", end - start);
statement.close();
connection.close();
}
有點(diǎn)可惜的是,經(jīng)過(guò)測(cè)試痕囱,這兩種方式的性能很接近田轧,不知道是我操作的問(wèn)題還是事實(shí)確實(shí)是如此,還望有懂這個(gè)的朋友指點(diǎn)指點(diǎn)鞍恢,
元數(shù)據(jù)
所謂的元數(shù)據(jù)傻粘,也就是是用于定義其他數(shù)據(jù)的數(shù)據(jù),在SQL中有序,元數(shù)據(jù)可以理解為就是數(shù)據(jù)庫(kù)抹腿、表的定義數(shù)據(jù),在JDBC中旭寿,有三種類型的元數(shù)據(jù)警绩,分別是DatabaseMetaData
,ResultSetMetaData
盅称,ParameterMetaData
肩祥,分別是數(shù)據(jù)庫(kù)相關(guān)信息,結(jié)果集相關(guān)信息缩膝,以及PreparedStatement語(yǔ)句中的相關(guān)參數(shù)信息混狠,具體操作如下所示
@Test
public void testMetaData() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);
// DatabaseMetaData 相關(guān)的信息,比如數(shù)據(jù)庫(kù)供應(yīng)商名稱疾层,版本等
DatabaseMetaData databaseMetaData = connection.getMetaData();
System.out.println("version : " + databaseMetaData.getDatabaseMajorVersion());
System.out.println("name : " + databaseMetaData.getDatabaseProductName());
int id = 4;
String sql = "select id, name from user where id < ? ";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setObject(1, id);
// ParameterMetaData 相關(guān)信息将饺,比如參數(shù)個(gè)數(shù)
ParameterMetaData parameterMetaData = statement.getParameterMetaData();
int paramCount = parameterMetaData.getParameterCount();
System.out.println("parameter count : " + paramCount);
ResultSet resultSet = statement.executeQuery();
// ResultSetMetaData 相關(guān)信息,比如取出來(lái)的數(shù)據(jù)的列名
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
int resultCount = resultSetMetaData.getColumnCount();
System.out.println("resultCount : " + resultCount);
for (int i = 0; i < resultCount; i++) {
System.out.print(resultSetMetaData.getColumnName(i + 1) + " ");
}
System.out.println();
}
事務(wù)管理
所謂的事務(wù)痛黎,是指做一件事情予弧,這件事情有多個(gè)步驟,這多個(gè)步驟這件湖饱,要么都完成掖蛤,要么都不完成,事務(wù)具有四個(gè)特性ACID井厌,分別是原子性蚓庭,一致性,獨(dú)立性仅仆,持久性
在JDBC中器赞,可以通過(guò)connection.setAutoCommit(true/false);
開(kāi)控制事務(wù),默認(rèn)為true墓拜,也就是默認(rèn)自動(dòng)提交事務(wù)拳魁。通過(guò)connection.commit()
手動(dòng)提交事務(wù),connection.setSavepoint();
設(shè)置保存點(diǎn)撮弧,connection.rollback();
回滾到保存到潘懊。