一文快速回顧 Java 操作數(shù)據(jù)庫(kù)的方式-JDBC
前言
數(shù)據(jù)庫(kù)的重要性不言而喻,不管是什么系統(tǒng),什么應(yīng)用軟件,也不管它們是 Windows 上的應(yīng)用程序,還是 Web 應(yīng)用程序,存儲(chǔ)(持久化)和查詢(xún)(檢索)數(shù)據(jù)都是核心的功能。
大家學(xué)習(xí)數(shù)據(jù)庫(kù)時(shí),比如 MySQL 這個(gè)數(shù)據(jù)庫(kù)管理系統(tǒng),都是在 CLI(Command Line Interface)上操作數(shù)據(jù)庫(kù)的,現(xiàn)在,我們看看,在 Java Web 中,我們?nèi)绾问褂?Java 去操作數(shù)據(jù)庫(kù)。
JDBC
JDBC(Java Data Base Connectivity)是 Java 操作數(shù)據(jù)庫(kù)的一種規(guī)范,也是一種 API(與數(shù)據(jù)庫(kù)系統(tǒng)進(jìn)行通信的標(biāo)準(zhǔn)的 API),更是一門(mén)技術(shù)。
JDBC 是由一組用 Java 編寫(xiě)的類(lèi)和接口組成,對(duì)數(shù)據(jù)庫(kù)的操作提供了基本的方法。但是,對(duì)于數(shù)據(jù)庫(kù)細(xì)節(jié)的操作,那就是由數(shù)據(jù)庫(kù)的廠(chǎng)商實(shí)現(xiàn)的。使用 JDBC 操作數(shù)據(jù)庫(kù),需要數(shù)據(jù)庫(kù)廠(chǎng)商提供的數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序的支持。
那什么是數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序呢?這個(gè)驅(qū)動(dòng)(driver)可以理解成一種可以讓數(shù)據(jù)庫(kù)和 Java 彼此進(jìn)行互動(dòng)的程序。
簡(jiǎn)單來(lái)講,JDBC 提供了一種 API 的規(guī)范,告訴各大數(shù)據(jù)庫(kù)廠(chǎng)商按這種規(guī)范來(lái)實(shí)現(xiàn)這些 API 具體的實(shí)現(xiàn)代碼。可以從兩個(gè)角色的角度來(lái)說(shuō)這個(gè) JDBC。從咱們開(kāi)發(fā)人員的角度來(lái)說(shuō),JDBC 為我們開(kāi)發(fā)人員提供了統(tǒng)一的操作數(shù)據(jù)庫(kù)的 API,不用管這些 API 的具體實(shí)現(xiàn),專(zhuān)注于 API 的調(diào)用;從數(shù)據(jù)庫(kù)廠(chǎng)商的角度來(lái)說(shuō),JDBC 為他們提供了一套標(biāo)準(zhǔn)的模型接口,都按這個(gè)接口去做自己的實(shí)現(xiàn)。
如何使用 JDBC?
JDBC 的使用主要有如下幾個(gè)步驟:
注冊(cè)數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序(database driver program)到 JDBC 的驅(qū)動(dòng)管理器中。
在連接數(shù)據(jù)庫(kù)之前,需要將數(shù)據(jù)庫(kù)廠(chǎng)商提供的數(shù)據(jù)庫(kù)驅(qū)動(dòng)類(lèi)注冊(cè)到 JDBC 的驅(qū)動(dòng)管理器中,一般是把驅(qū)動(dòng)類(lèi)加載到 JVM 實(shí)現(xiàn)的。
Class.forName("com.mysql.jdbc.Driver");
構(gòu)建數(shù)據(jù)庫(kù)連接的 URL。
要與數(shù)據(jù)庫(kù)建立連接,那么就需要構(gòu)建數(shù)據(jù)庫(kù)連接的 URL,這個(gè) URL 由數(shù)據(jù)庫(kù)廠(chǎng)商指定,一般符合一種基本格式,即?JDBC協(xié)議+IP地址或域名+端口+數(shù)據(jù)庫(kù)名稱(chēng)
。MySQL 的 URL 是?jdbc:mysql://localhost:3306/dbname
獲取連接對(duì)象(Connection 對(duì)象)。
String url = "jdbc:mysql://localhost:3306/dbname";String username = "root";String password = "123456";// Connection 對(duì)象的獲取需要借助 DriverManager 對(duì)象Connection conn = DriverManager.getConnection(url, username, password);
進(jìn)行數(shù)據(jù)庫(kù)操作。
編寫(xiě) SQL,然后獲取 PreparedStatement 對(duì)象,對(duì) SQL 語(yǔ)句進(jìn)行執(zhí)行。SQL 語(yǔ)句的參數(shù)是可以使用占位符 “?” 代替,再通過(guò) PreparedStatement 對(duì)象對(duì) SQL 語(yǔ)句中的占位符進(jìn)行賦值。
Statment 這個(gè)單詞的意思在這里指的就是 SQL 語(yǔ)句。
// 編寫(xiě)SQLString sql = "INSERT INTO tb_game(name, price, platform) values(?, ?, ?)";// 獲取 PreparedStatement 對(duì)象PreparedStatement ps = conn.preparedStatement(sql);// 給占位符賦值ps.setString(1, "NBA2K");
ps.setDouble(2, 198.0);
ps.setString(3, "Windows");// 執(zhí)行 SQL,將這條數(shù)據(jù)寫(xiě)入數(shù)據(jù)庫(kù),返回影響的行數(shù)int row = ps.executeUpdate();
使用 PreparedStatement 對(duì)象對(duì) SQL 語(yǔ)句的占位符參數(shù)賦值,其參數(shù)的下標(biāo)是從 1 開(kāi)始的。
關(guān)閉連接
conn.close();
CRUD
新增操作
新增操作,就是上面的插入操作,請(qǐng)看上面。
查詢(xún)操作
ResultSet
使用 JDBC 查詢(xún)數(shù)據(jù),與插入數(shù)據(jù)的操作流程基本一樣,但是執(zhí)行查詢(xún)操作后需要通過(guò)一個(gè)對(duì)象來(lái)接收查詢(xún)的結(jié)果,這個(gè)對(duì)象就是 ResultSet (結(jié)果集)。
ResultSet 是 JDBC API 中封裝的對(duì)象,從數(shù)據(jù)表中查到的所有記錄都會(huì)放在這個(gè)集合中。ResultSet 中維護(hù)著一個(gè) cursor(游標(biāo))來(lái)指向當(dāng)前的數(shù)據(jù)行(數(shù)據(jù)記錄),初始化的時(shí)候,這個(gè)游標(biāo)指向第一行的前一行,可以通過(guò)?next()
?方法來(lái)移動(dòng)游標(biāo),讓游標(biāo)指向下一行。
調(diào)用這個(gè)?next()
?它返回的是一個(gè)布爾值,為 true 說(shuō)明 ResultSet 中還有下一行的數(shù)據(jù),為 false 說(shuō)明沒(méi)有,所以可以結(jié)合 while 循環(huán)使用這個(gè)方法來(lái)遍歷整個(gè) ResultSet。
// 由于一開(kāi)始的游標(biāo)在第一行的前一行,所以執(zhí)行 next() 后,游標(biāo)就指向第一行的數(shù)據(jù)了while (resultSet.next()) { ? ?// 處理結(jié)果集中每一行的數(shù)據(jù)}
獲取到 ResultSet 對(duì)象后,移動(dòng)了光標(biāo)指定了數(shù)據(jù)行,然后通過(guò) ResultSet 對(duì)象提供的一系列?getXxxx()
?方法來(lái)獲取當(dāng)前行的數(shù)據(jù),比如?resultSet.getInt("price")
?獲取當(dāng)前行中字段名為?price
?的數(shù)據(jù)。
默認(rèn)的 ResultSet 是不可更新的,同時(shí)它的游標(biāo)只能一步一步 next 下去,只能走一遍,不能回到上一行的。說(shuō)了默認(rèn),那說(shuō)明是可以設(shè)置的,通過(guò)如下代碼進(jìn)行設(shè)置:
Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);ResultSet rs = stmt.executeQuery("SELECT a, b FROM TABLE2");// rs 是可以滾動(dòng)的,也就是游標(biāo)走到最后又會(huì)回到開(kāi)頭繼續(xù)走,并且它的內(nèi)容是可以被改變的
查詢(xún)
找到價(jià)格大于50塊錢(qián)的所有游戲:
String sql = "SELECT id, name, price FROM tb_game WHERE price > ?";PreparedStatement ps = conn.preparedStatement(sql);
ps.setDouble(1, 50);// 執(zhí)行查詢(xún)ResultSet rs = ps.executeQuery(sql);
List<Game> gameList = new ArrayList<>();// 遍歷結(jié)果集while (rs.next()) { Game game = new Game(); ? ?// 獲取當(dāng)前行中字段名為 id 的數(shù)據(jù),并賦值到 game 對(duì)象中
? ?game.setId(rs.getInt("id"));
? ?game.setName(rs.getString("name"));
? ?game.setPrice(rs.getDouble("price"));
? ?gameList.add(game);
}
System.out.println(gameList);
修改(更新)操作
修改(更新)數(shù)據(jù)的操作,也是和插入數(shù)據(jù)的操作是類(lèi)似的。
更新 ID 為 3 的數(shù)據(jù)記錄,修改其價(jià)格為 298 塊錢(qián)。
String sql = "UPDATE tb_game SET price = ? WHERE id = ?";PreparedStatement ps = conn.preparedStatement(sql);
ps.setDouble(1, 298);
ps.setInt(2, 3);int row = ps.executeUpdate();
刪除操作
同理。
String sql = "DELETE FROM tb_game WHERE id = ?";PreparedStatement ps = conn.preparedStatement(sql);
ps.setInt(1, 1);int row = ps.executeUpdate();
分頁(yè)查詢(xún)
在 Java Web 中數(shù)據(jù)量非常大的情況下,是不利于將所有數(shù)據(jù)都展示到一個(gè)頁(yè)面中的,查看不方便,又占用系統(tǒng)資源。此時(shí)就需要對(duì)數(shù)據(jù)進(jìn)行分頁(yè)查詢(xún),同時(shí),以后的工作中,可以說(shuō)大部分的業(yè)務(wù)場(chǎng)景都會(huì)涉及到分頁(yè)查詢(xún)。
在 MySQL 中,分頁(yè)可以通過(guò)其自身的 LIMIT 關(guān)鍵字來(lái)實(shí)現(xiàn):
SELECT *FROM tb_gameWHERE price > 50ORDER BY price DESCLIMIT 0, 10; // 從表中下標(biāo)0開(kāi)始(第一行的下標(biāo)為0),限制返回10條記錄
目前分頁(yè)涉及到這樣的兩個(gè)參數(shù):當(dāng)前頁(yè)碼和頁(yè)面大小。
涉及的 SQL 語(yǔ)句:SELECT * FROM tb_game WHERE price > 50 ORDER BY price DESC LIMIT 當(dāng)前頁(yè)碼, 頁(yè)面大小
// 分頁(yè)參數(shù)int currentPage = 1, pageSize = 10;// 分頁(yè) SQLString sql = "SELECT * FROM tb_game WHERE price > 50 ORDER BY price DESC LIMIT ?, ?";PreparedStatement ps = conn.preparedStatement(sql);// 賦值ps.setInt(1, (currentPage - 1) * pageSize);
ps.setInt(2, pageSize);ResultSet rs = ps.executeQuery();
與此同時(shí),還需要計(jì)算獲取的數(shù)據(jù)的總記錄數(shù),用于計(jì)算分頁(yè)的總頁(yè)數(shù),便于前端傳遞是要哪一頁(yè)的數(shù)據(jù)給后端。
int count = 0;String sql = "SELECT COUNT(*) FROM tb_game WHERE price > 50";PreparedStatement ps = conn.preparedStatement(sql);ResultSet rs = ps.executeQuery();if (rs.next()) { ? ?// 獲取總記錄數(shù),getInt(1) 是獲取第一列的數(shù)據(jù)
? ?count = rs.getInt(1);
}
總結(jié)
目前在 Java 中通過(guò) JDBC 來(lái)操作數(shù)據(jù)庫(kù),就有幾個(gè)固定的步驟,先加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序,接著獲取數(shù)據(jù)庫(kù)的連接,有了這個(gè)連接后,才能進(jìn)行 CRUD 的操作,操作后也可以獲取操作的結(jié)果,最后關(guān)閉這些資源,比如數(shù)據(jù)庫(kù)連接。
不過(guò),在日常的開(kāi)發(fā)中,基本不會(huì)用到原生的 JDBC 來(lái)操作數(shù)據(jù)庫(kù),一般我們有多種選擇,可以使用 JdbcTemplate、Hibernate、MyBatis、JPA(Java Persistence API,Java 持久化 API)或者是其他任意的持久化框架。