農(nóng)行2面:JDBC 存在什么問題?MyBatis 是如何解決的?
JDBC(Java Database Connectivity)是 Java中用于連接和操作數(shù)據(jù)庫的標(biāo)準(zhǔn)API,它提供了一種通用的方式來訪問數(shù)據(jù)庫,但在實(shí)際應(yīng)用中,直接使用 JDBC會遇到很多的問題。這篇文章,我將詳細(xì)探討 JDBC存在的問題以及 MyBatis是如何解決這些問題的。
JDBC的核心組件
JDBC的核心組件包含以下 5個組件:
- DriverManager
- Connection
- Statement
- ResultSet
- SQLException
(1) DriverManager
DriverManager是JDBC API的核心類之一,用于管理JDBC驅(qū)動程序的集合,并為應(yīng)用程序提供數(shù)據(jù)庫連接。它通過加載適當(dāng)?shù)臄?shù)據(jù)庫驅(qū)動程序來建立與數(shù)據(jù)庫的連接。
(2) Connection
Connection接口表示與特定數(shù)據(jù)庫的連接。它提供了創(chuàng)建SQL語句、提交和回滾事務(wù)、關(guān)閉連接等方法。通過Connection,應(yīng)用程序可以與數(shù)據(jù)庫進(jìn)行交互。
(3) Statement
Statement接口用于執(zhí)行靜態(tài)SQL語句并返回其生成的結(jié)果。JDBC提供了三種類型的Statement:Statement、PreparedStatement和CallableStatement,分別用于執(zhí)行簡單的SQL語句、預(yù)編譯的SQL語句和存儲過程。
(4) ResultSet
ResultSet接口表示數(shù)據(jù)庫查詢的結(jié)果集。它提供了從結(jié)果集中檢索數(shù)據(jù)的方法,并支持迭代結(jié)果集中的行。
(5) SQLException
SQLException是 JDBC API中用于處理數(shù)據(jù)庫訪問錯誤的異常類。它提供了詳細(xì)的錯誤信息,包括錯誤代碼和SQL狀態(tài)。
JDBC存在的問題
(1) 繁瑣的代碼編寫
在JDBC中,開發(fā)者需要編寫大量的代碼來處理數(shù)據(jù)庫連接、SQL語句的創(chuàng)建和執(zhí)行、結(jié)果集的處理等。對于每一個數(shù)據(jù)庫操作,通常需要執(zhí)行一系列標(biāo)準(zhǔn)步驟,包括獲取連接、創(chuàng)建語句、執(zhí)行查詢、處理結(jié)果集和關(guān)閉連接。這種重復(fù)的代碼入侵性太強(qiáng),很容易導(dǎo)致代碼臃腫、不易維護(hù)。
(2) 手動管理資源
JDBC要求開發(fā)者手動管理數(shù)據(jù)庫連接、語句和結(jié)果集的關(guān)閉。這不僅容易導(dǎo)致資源泄露,而且增加了代碼的復(fù)雜性和出錯的可能性,特別是在異常處理部分,確保所有資源都被正確關(guān)閉是一項(xiàng)挑戰(zhàn)。
(3) SQL語句的硬編碼
在JDBC中,SQL語句通常是硬編碼在 Java代碼中的。這種做法使得 SQL和 Java代碼緊密耦合,增加了代碼的維護(hù)難度。如果數(shù)據(jù)庫表結(jié)構(gòu)發(fā)生變化,需要在代碼中手動更新SQL語句。
(4) 缺乏對象映射
JDBC直接處理結(jié)果集(ResultSet),開發(fā)者需要手動將結(jié)果集中的數(shù)據(jù)映射到 Java對象。這種手動映射不僅繁瑣,而且容易出錯,尤其是在處理復(fù)雜的對象關(guān)系時。
(5) 事務(wù)管理復(fù)雜
雖然JDBC支持事務(wù)管理,但開發(fā)者需要手動編寫代碼來處理事務(wù)的開始、提交和回滾。這種手動管理增加了代碼的復(fù)雜性,特別是在處理多個數(shù)據(jù)庫操作需要在一個事務(wù)中完成時。
(6) 缺乏緩存支持
JDBC本身不提供緩存機(jī)制,所有的查詢都直接從數(shù)據(jù)庫中讀取數(shù)據(jù)。這可能導(dǎo)致性能問題,特別是在頻繁訪問相同數(shù)據(jù)的情況下。
為了更好地展示JDBC存在的問題,我們用一個簡單的示例進(jìn)行說明:假設(shè)需要查詢一個用戶表,并將結(jié)果映射到 Java對象中,使用JDBC的代碼如下:
public User getUserById(int id) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
User user = null;
try {
// 1. 加載JDBC驅(qū)動程序
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 建立數(shù)據(jù)庫連接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password");
// 3. 創(chuàng)建SQL語句對象
String sql = "SELECT id, username, email FROM users WHERE id = ?";
stmt = connection.prepareStatement(sql);
stmt.setInt(1, id);
// 4. 執(zhí)行SQL語句
rs = stmt.executeQuery();
// 5. 處理結(jié)果集
if (rs.next()) {
user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setEmail(rs.getString("email"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
// 6. 關(guān)閉資源
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return user;
}
在上面這個例子中,管理數(shù)據(jù)庫連接、SQL語句的執(zhí)行和結(jié)果集的處理都需要我們手動來管理,看起來太臃腫,很容易出錯。估計(jì)大家看到這種代碼,肯定要吐槽一番。
既然 JDBC存在這么多問題,那么,MyBatis是如何解決這些問題的呢?
MyBatis如何解決這些問題?
MyBatis 是一個流行的持久層框架,在國內(nèi)占據(jù)了很大一部分市場,它支持定制化SQL、存儲過程以及高級映射等功能,大大簡化了JDBC的使用,并提供了一些額外的功能來解決上述問題。
MyBatis本質(zhì)上是一個對 JDBC的封裝,它利用JDBC來執(zhí)行底層的數(shù)據(jù)庫操作。所有 MyBatis的數(shù)據(jù)庫交互最終都是通過 JDBC來實(shí)現(xiàn)的。
(1) 簡化代碼編寫
MyBatis通過 XML文件或注解方式定義 SQL語句,將 SQL從 Java代碼中分離出來。開發(fā)者不再需要手動編寫獲取連接、創(chuàng)建語句和處理結(jié)果集的代碼,MyBatis會自動處理這些細(xì)節(jié)。
例如,MyBatis提供了自動映射機(jī)制,可以將查詢結(jié)果直接映射到Java對象上,減少了手動映射的代碼量。
(2) 自動資源管理
MyBatis框架自動管理數(shù)據(jù)庫連接的獲取和釋放,開發(fā)者不需要手動關(guān)閉連接、語句和結(jié)果集。這減少了資源泄露的風(fēng)險(xiǎn),并簡化了代碼。
(3) SQL與代碼分離
MyBatis允許將SQL語句放在XML配置文件中或者使用注解,這樣SQL和Java代碼就被分離開來。這種分離使得代碼變得更加清晰、易于維護(hù)。同時,SQL語句的修改不需要重新編譯Java代碼,只需要修改XML文件即可。
(4) 支持對象關(guān)系映射(ORM)
MyBatis提供了強(qiáng)大的結(jié)果映射功能,支持將數(shù)據(jù)庫中的結(jié)果集直接映射到復(fù)雜的Java對象結(jié)構(gòu)中。這種映射不僅支持簡單的屬性映射,還支持一對多、多對多等復(fù)雜關(guān)系的映射。
(5) 事務(wù)管理的簡化
MyBatis可以與Spring框架集成,從而利用Spring的聲明式事務(wù)管理功能。這樣,開發(fā)者可以通過簡單的注解來管理事務(wù),而不需要手動編寫事務(wù)管理代碼。
(6) 緩存支持
MyBatis內(nèi)置了一級緩存和二級緩存機(jī)制。一級緩存是SqlSession級別的緩存,默認(rèn)開啟,生命周期與SqlSession相同。二級緩存是Mapper級別的緩存,可以通過配置開啟。這些緩存機(jī)制可以顯著提高應(yīng)用的性能。
為了更好地理解 MyBatis是如何解決JDBC的問題,我們還是通過上面的示例來說明。
首先,我們需要定義一個 XML映射文件(UserMapper.xml):
<mapper namespace="com.example.UserMapper">
<select id="getUserById" parameterType="int" resultType="User">
SELECT id, username, email FROM users WHERE id = #{id}
</select>
</mapper>
然后,我們可以在 Java代碼中調(diào)用這個映射:
public interface UserMapper {
User getUserById(int id);
}
// 在服務(wù)層調(diào)用
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
在上面這個例子中,代碼職責(zé)很清晰,MyBatis自動處理了 SQL的執(zhí)行和結(jié)果集的映射,我們只需要定義 SQL語句和 Java接口即可。
總結(jié)
本文,我們分析了 JDBC的核心組件,使用存在的問題以及 Mybatis如何解決這些問題,對于一些出道比較早或者接觸過 JDBC老項(xiàng)目的Java程序員來說,對 JDBC的使用可能還有體感。而現(xiàn)在大部分項(xiàng)目,Hibernate和 Mybatis這些對象關(guān)系映射(ORM)框架對 JDBC做了很好的抽象和封裝,提供了更加面向?qū)ο蟮臄?shù)據(jù)庫操作方式。因此,開發(fā)者不需要直接處理 JDBC的API,而是直接面向 ORM。
但是,但是,但是,重要的事情說三遍:如果你想成為一個優(yōu)秀的工程師,理解底層原理是必修課,盡管ORM給我們的開發(fā)帶來了便捷,但是理解JDBC的原理,可以幫助我們更好地理解 Hibernate和 Mybatis這些優(yōu)秀的 ORM框架。