以下我是歸納的JDBC知識點圖:
圖上的知識點都可以在我其他的文章內找到相應內容。
JDBC常見面試題
JDBC運算元據庫的步驟 ?
JDBC運算元據庫的步驟 ?
- 註冊資料庫驅動。
- 建立資料庫連線。
- 建立一個Statement。
- 執行SQL語句。
- 處理結果集。
- 關閉資料庫連線
程式碼如下:
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
/*
* 載入驅動有兩種方式
*
* 1:會導致驅動會註冊兩次,過度依賴於mysql的api,脫離的mysql的開發包,程式則無法編譯
* 2:驅動只會載入一次,不需要依賴具體的驅動,靈活性高
*
* 我們一般都是使用第二種方式
* */
//1.
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//2.
Class.forName("com.mysql.jdbc.Driver");
//獲取與資料庫連線的物件-Connetcion
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/zhongfucheng", "root", "root");
//獲取執行sql語句的statement物件
statement = connection.createStatement();
//執行sql語句,拿到結果集
resultSet = statement.executeQuery("SELECT * FROM users");
//遍歷結果集,得到資料
while (resultSet.next()) {
System.out.println(resultSet.getString(1));
System.out.println(resultSet.getString(2));
}
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
/*
* 關閉資源,後呼叫的先關閉
*
* 關閉之前,要判斷物件是否存在
* */
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
複製程式碼
JDBC中的Statement 和PreparedStatement,CallableStatement的區別?
JDBC中的Statement 和PreparedStatement的區別?
區別:
- PreparedStatement是預編譯的SQL語句,效率高於Statement。
- PreparedStatement支援?操作符,相對於Statement更加靈活。
- PreparedStatement可以防止SQL隱碼攻擊,安全性高於Statement。
- CallableStatement適用於執行儲存過程。
JDBC中大資料量的分頁解決方法?
JDBC中大資料量的分頁解決方法?
最好的辦法是利用sql語句進行分頁,這樣每次查詢出的結果集中就只包含某頁的資料內容。
mysql語法:
SELECT *
FROM 表名
LIMIT [START], length;
複製程式碼
oracle語法:
SELECT *FROM (
SELECT 列名,列名,ROWNUM rn
FROM 表名
WHERE ROWNUM<=(currentPage*lineSize)) temp
WHERE temp.rn>(currentPage-1)*lineSize;
複製程式碼
說說資料庫連線池工作原理和實現方案?
說說資料庫連線池工作原理和實現方案?
工作原理:
- JAVA EE伺服器啟動時會建立一定數量的池連線,並一直維持不少於此數目的池連線。客戶端程式需要連線時,池驅動程式會返回一個未使用的池連線並將其表記為忙。如果當前沒有空閒連線,池驅動程式就新建一定數量的連線,新建連線的數量有配置引數決定。當使用的池連線呼叫完成後,池驅動程式將此連線表記為空閒,其他呼叫就可以使用這個連線。
實現方案:連線池使用集合來進行裝載,返回的Connection是原始Connection的代理,代理Connection的close方法,當呼叫close方法時,不是真正關連線,而是把它代理的Connection物件放回到連線池中,等待下一次重複利用。
具體程式碼:
@Override
public Connection getConnection() throws SQLException {
if (list.size() > 0) {
final Connection connection = list.removeFirst();
//看看池的大小
System.out.println(list.size());
//返回一個動態代理物件
return (Connection) Proxy.newProxyInstance(Demo1.class.getClassLoader(), connection.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果不是呼叫close方法,就按照正常的來呼叫
if (!method.getName().equals("close")) {
method.invoke(connection, args);
} else {
//進到這裡來,說明呼叫的是close方法
list.add(connection);
//再看看池的大小
System.out.println(list.size());
}
return null;
}
});
}
return null;
}
複製程式碼
Java中如何進行事務的處理?
Java中如何進行事務的處理?
- 事務是作為單個邏輯工作單元執行的一系列操作。
- 一個邏輯工作單元必須有四個屬性,稱為原子性、一致性、隔離性和永續性 (ACID) 屬性,只有這樣才能成為一個事務
Connection類中提供了4個事務處理方法:
- setAutoCommit(Boolean autoCommit):設定是否自動提交事務,預設為自動提交,即為true,通過設定false禁止自動提交事務;
- commit():提交事務;
- rollback():回滾事務.
- savepoint:儲存點
- 注意:savepoint不會結束當前事務,普通提交和回滾都會結束當前事務的
修改JDBC程式碼質量
下述程式是一段簡單的基於JDBC的資料庫訪問程式碼,實現了以下功能:從資料庫中查詢product表中的所有記錄,然後列印輸出到控制檯.該程式碼質量較低,如沒有正確處理異常,連線字串以”魔數”的形式直接存在於程式碼中等,請用你的思路重新編寫程式,完成相同的功能,提高程式碼質量.
原來的程式碼:
public void printProducts(){
Connection c = null;
Statements s = null;
ResultSet r = null;
try{
c=DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:sid","username","password");
s=c.createStatement();
r=s.executeQuery("select id, name, price from product");
System.out.println("Id\tName\tPrice");
while(r.next()){
int x = r.getInt("id");
String y = r.getString("name");
float z = r.getFloat("price");
System.out.println(x + "\t" + y + "\t" + z);
}
} catch(Exception e){
}
}
複製程式碼
修改後的程式碼:
class Constant{
public static final String URL="jdbc:oracle:thin:@127.0.0.1:1521:sid";
public static final String USERNAME="username";
public static final String PASSWORD="password";
}
class DAOException extends Exception{
public DAOException(){
super();
}
public DAOException(String msg){
super(msg);
}
}
public class Test{
public void printProducts() throws DAOException{
Connection c = null;
Statement s = null;
ResultSet r = null;
try{
c = DriverManager.getConnection(Constant.URL,Constant.USERNAME,Constant.PASSWORD);
s = c.createStatement();
r = s.executeQuery("select id,name,price from product");
System.out.println("Id\tName\tPrice");
while(r.next()){
int x = r.getInt("id");
String y = r.getString("name");
float z = r.getFloat("price");
System.out.println(x + "\t" + y + "\t" + z);
}
} catch (SQLException e){
throw new DAOException("資料庫異常");
} finally {
try{
r.close();
s.close();
c.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
複製程式碼
修改點:
- url、password等資訊不應該直接使用字串“寫死”,可以使用常量代替
- catch中應該回滾事務,丟擲RuntimeException也是回滾事務的一種方法
- 關閉資源
寫出一段JDBC連線本機MySQL資料庫的程式碼
寫出一段JDBC連線本機MySQL資料庫的程式碼
Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql://localhost/test";
Stirng user='root';
String password='root';
Connection conn = DriverManager.getConnection(url,user,password);
複製程式碼
JDBC是如何實現Java程式和JDBC驅動的鬆耦合的?
JDBC是如何實現Java程式和JDBC驅動的鬆耦合的?
通過制定介面,資料庫廠商來實現。我們只要通過介面呼叫即可。隨便看一個簡單的JDBC示例**,你會發現所有操作都是通過JDBC介面完成的,而驅動只有在通過Class.forName反射機制來載入的時候才會出現。**
execute,executeQuery,executeUpdate的區別是什麼?
execute,executeQuery,executeUpdate的區別是什麼?
- Statement的execute(String query)方法用來執行任意的SQL查詢,如果查詢的結果是一個ResultSet,這個方法就返回true。如果結果不是ResultSet,比如insert或者update查詢,它就會返回false。我們可以通過它的getResultSet方法來獲取ResultSet,或者通過getUpdateCount()方法來獲取更新的記錄條數。
- Statement的executeQuery(String query)介面用來執行select查詢,並且返回ResultSet。即使查詢不到記錄返回的ResultSet也不會為null。我們通常使用executeQuery來執行查詢語句,這樣的話如果傳進來的是insert或者update語句的話,它會丟擲錯誤資訊為 “executeQuery method can not be used for update”的java.util.SQLException。
- Statement的executeUpdate(String query)方法用來執行insert或者update/delete(DML)語句,或者 什麼也不返回DDL語句。返回值是int型別,如果是DML語句的話,它就是更新的條數,如果是DDL的話,就返回0。
- 只有當你不確定是什麼語句的時候才應該使用execute()方法,否則應該使用executeQuery或者executeUpdate方法。
PreparedStatement的缺點是什麼,怎麼解決這個問題?
PreparedStatement的缺點是什麼,怎麼解決這個問題?
PreparedStatement的一個缺點是,我們不能直接用它來執行in條件語句;需要執行IN條件語句的話,下面有一些解決方案:
- 分別進行單條查詢——這樣做效能很差,不推薦。
- 使用儲存過程——這取決於資料庫的實現,不是所有資料庫都支援。
- 動態生成PreparedStatement——這是個好辦法,但是不能享受PreparedStatement的快取帶來的好處了。
- 在PreparedStatement查詢中使用NULL值——如果你知道輸入變數的最大個數的話,這是個不錯的辦法,擴充套件一下還可以支援無限引數。
JDBC的髒讀是什麼?哪種資料庫隔離級別能防止髒讀?
JDBC的髒讀是什麼?哪種資料庫隔離級別能防止髒讀?
髒讀:一個事務讀取到另外一個事務未提交的資料
例子:A向B轉賬,A執行了轉賬語句,但A還沒有提交事務,B讀取資料,發現自己賬戶錢變多了!B跟A說,我已經收到錢了。A回滾事務【rollback】,等B再檢視賬戶的錢時,發現錢並沒有多。
下面的三種個隔離級別都可以防止:
- Serializable【TRANSACTION_SERIALIZABLE】
- Repeatable read【TRANSACTION_REPEATABLE_READ】
- Read committed【TRANSACTION_READ_COMMITTED】
什麼是幻讀,哪種隔離級別可以防止幻讀?
什麼是幻讀,哪種隔離級別可以防止幻讀?
是指在一個事務內讀取到了別的事務插入的資料,導致前後讀取不一致。
只有TRANSACTION_SERIALIZABLE隔離級別才能防止產生幻讀。
JDBC的DriverManager是用來做什麼的?
JDBC的DriverManager是用來做什麼的?
- JDBC的DriverManager是一個工廠類,我們通過它來建立資料庫連線。
- 當JDBC的Driver類被載入進來時,它會自己註冊到DriverManager類裡面
- 然後我們會把資料庫配置資訊傳成DriverManager.getConnection()方法**,DriverManager會使用註冊到它裡面的驅動來獲取資料庫連線,並返回給呼叫的程式**。
JDBC的ResultSet是什麼?
JDBC的ResultSet是什麼?
- **在查詢資料庫後會返回一個ResultSet,它就像是查詢結果集的一張資料表。 **
- ResultSet物件維護了一個遊標,指向當前的資料行。開始的時候這個遊標指向的是第一行。如果呼叫了ResultSet的next()方法遊標會下移一行,如果沒有更多的資料了,next()方法會返回false。可以在for迴圈中用它來遍歷資料集。
- 預設的ResultSet是不能更新的,遊標也只能往下移。也就是說你只能從第一行到最後一行遍歷一遍。不過也可以建立可以回滾或者可更新的ResultSet
- 當生成ResultSet的Statement物件要關閉或者重新執行或是獲取下一個ResultSet的時候,ResultSet物件也會自動關閉。
- 可以通過ResultSet的getter方法,傳入列名或者從1開始的序號來獲取列資料。
有哪些不同的ResultSet?
有哪些不同的ResultSet?
根據建立Statement時輸入引數的不同,會對應不同型別的ResultSet。如果你看下Connection的方法,你會發現createStatement和prepareStatement方法過載了,以支援不同的ResultSet和併發型別。
一共有三種ResultSet物件。
- ResultSet.TYPE_FORWARD_ONLY:這是預設的型別,它的遊標只能往下移。
- ResultSet.TYPE_SCROLL_INSENSITIVE:遊標可以上下移動,一旦它建立後,資料庫裡的資料再發生修改,對它來說是透明的。
- ResultSet.TYPE_SCROLL_SENSITIVE:遊標可以上下移動,如果生成後資料庫還發生了修改操作,它是能夠感知到的。
ResultSet有兩種併發型別。
- ResultSet.CONCUR_READ_ONLY:ResultSet是隻讀的,這是預設型別。
- ResultSet.CONCUR_UPDATABLE:我們可以使用ResultSet的更新方法來更新裡面的資料。
JDBC的DataSource是什麼,有什麼好處
JDBC的DataSource是什麼,有什麼好處
DataSource即資料來源,它是定義在javax.sql中的一個介面,跟DriverManager相比,它的功能要更強大**。我們可以用它來建立資料庫連線,當然驅動的實現類會實際去完成這個工作。除了能建立連線外,它還提供瞭如下的特性:**
- 快取PreparedStatement以便更快的執行
- 可以設定連線超時時間
- 提供日誌記錄的功能
- ResultSet大小的最大閾值設定
- 通過JNDI的支援,可以為servlet容器提供連線池的功能
如何通過JDBC的DataSource和Apache Tomcat的JNDI來建立連線池?
如何通過JDBC的DataSource和Apache Tomcat的JNDI來建立連線池?
Tomcat伺服器也給我們提供了連線池,內部其實就是DBCP
步驟:
- 在META-INF目錄下配置context.xml檔案【檔案內容可以在tomcat預設頁面的 JNDI Resources下Configure Tomcat's Resource Factory找到】
- 匯入Mysql或oracle開發包到tomcat的lib目錄下
- 初始化JNDI->獲取JNDI容器->檢索以XXX為名字在JNDI容器存放的連線池
context.xml檔案的配置:
<Context>
<Resource name="jdbc/EmployeeDB"
auth="Container"
type="javax.sql.DataSource"
username="root"
password="root"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/zhongfucheng"
maxActive="8"
maxIdle="4"/>
</Context>
複製程式碼
try {
//初始化JNDI容器
Context initCtx = new InitialContext();
//獲取到JNDI容器
Context envCtx = (Context) initCtx.lookup("java:comp/env");
//掃描以jdbc/EmployeeDB名字繫結在JNDI容器下的連線池
DataSource ds = (DataSource)
envCtx.lookup("jdbc/EmployeeDB");
Connection conn = ds.getConnection();
System.out.println(conn);
}
複製程式碼
Apache的DBCP是什麼?
Apache的DBCP是什麼
如果用DataSource來獲取連線的話,通常獲取連線的程式碼和驅動特定的DataSource是緊耦合的。另外,除了選擇DataSource的實現類,剩下的程式碼基本都是一樣的。
Apache的DBCP就是用來解決這些問題的,它提供的DataSource實現成為了應用程式和不同JDBC驅動間的一個抽象層。Apache的DBCP庫依賴commons-pool庫,所以要確保它們都在部署路徑下。
使用DBCP資料來源的步驟:
- 匯入兩個jar包【Commons-dbcp.jar和Commons-pool.jar】
- 讀取配置檔案
- 獲取BasicDataSourceFactory物件
- 建立DataSource物件
private static DataSource dataSource = null;
static {
try {
//讀取配置檔案
InputStream inputStream = Demo3.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
Properties properties = new Properties();
properties.load(inputStream);
//獲取工廠物件
BasicDataSourceFactory basicDataSourceFactory = new BasicDataSourceFactory();
dataSource = basicDataSourceFactory.createDataSource(properties);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
//這裡釋放資源不是把資料庫的物理連線釋放了,是把連線歸還給連線池【連線池的Connection內部自己做好了】
public static void release(Connection conn, Statement st, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if (st != null) {
try {
st.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製程式碼
常見的JDBC異常有哪些?
常見的JDBC異常有哪些?
有以下這些:
- java.sql.SQLException——這是JDBC異常的基類。
- java.sql.BatchUpdateException——當批處理操作執行失敗的時候可能會丟擲這個異常。這取決於具體的JDBC驅動的實現,它也可能直接丟擲基類異常java.sql.SQLException。
- java.sql.SQLWarning——SQL操作出現的警告資訊。
- java.sql.DataTruncation——欄位值由於某些非正常原因被截斷了(不是因為超過對應欄位型別的長度限制)。
JDBC中存在哪些不同型別的鎖?
JDBC中存在哪些不同型別的鎖?
從廣義上講,有兩種鎖機制來防止多個使用者同時操作引起的資料損壞。
- 樂觀鎖——只有當更新資料的時候才會鎖定記錄。
- 悲觀鎖——從查詢到更新和提交整個過程都會對資料記錄進行加鎖。
java.util.Date和java.sql.Date有什麼區別?
java.util.Date和java.sql.Date有什麼區別?
java.util.Date包含日期和時間,而java.sql.Date只包含日期資訊,而沒有具體的時間資訊。如果你想把時間資訊儲存在資料庫裡,可以考慮使用Timestamp或者DateTime欄位
SQLWarning是什麼,在程式中如何獲取SQLWarning?
SQLWarning是什麼,在程式中如何獲取SQLWarning?
SQLWarning是SQLException的子類,通過Connection, Statement, Result的getWarnings方法都可以獲取到它。 SQLWarning不會中斷查詢語句的執行,只是用來提示使用者存在相關的警告資訊。
如果java.sql.SQLException: No suitable driver found該怎麼辦?
如果java.sql.SQLException: No suitable driver found該怎麼辦?
如果你的SQL URL串格式不正確的話,就會丟擲這樣的異常。不管是使用DriverManager還是JNDI資料來源來建立連線都有可能丟擲這種異常。它的異常棧看起來會像下面這樣。
org.apache.tomcat.dbcp.dbcp.SQLNestedException: Cannot create JDBC driver of class 'com.mysql.jdbc.Driver' for connect URL ''jdbc:mysql://localhost:3306/UserDB'
at org.apache.tomcat.dbcp.dbcp.BasicDataSource.createConnectionFactory(BasicDataSource.java:1452)
at org.apache.tomcat.dbcp.dbcp.BasicDataSource.createDataSource(BasicDataSource.java:1371)
at org.apache.tomcat.dbcp.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044)
java.sql.SQLException: No suitable driver found for 'jdbc:mysql://localhost:3306/UserDB
at java.sql.DriverManager.getConnection(DriverManager.java:604)
at java.sql.DriverManager.getConnection(DriverManager.java:221)
at com.journaldev.jdbc.DBConnection.getConnection(DBConnection.java:24)
at com.journaldev.jdbc.DBConnectionTest.main(DBConnectionTest.java:15)
Exception in thread "main" java.lang.NullPointerException
at com.journaldev.jdbc.DBConnectionTest.main(DBConnectionTest.java:16)
複製程式碼
解決這類問題的方法就是,檢查下日誌檔案,像上面的這個日誌中,URL串是'jdbc:mysql://localhost:3306/UserDB,只要把它改成jdbc:mysql://localhost:3306/UserDB就好了。
JDBC的RowSet是什麼,有哪些不同的RowSet?
JDBC的RowSet是什麼,有哪些不同的RowSet?
RowSet用於儲存查詢的資料結果,和ResultSet相比,它更具靈活性。RowSet繼承自ResultSet,因此ResultSet能幹的,它們也能,而ResultSet做不到的,它們還是可以。RowSet介面定義在javax.sql包裡。
RowSet提供的額外的特性有:
- 提供了Java Bean的功能,可以通過settter和getter方法來設定和獲取屬性。RowSet使用了JavaBean的事件驅動模型,它可以給註冊的元件傳送事件通知,比如遊標的移動,行的增刪改,以及RowSet內容的修改等。
- RowSet物件預設是可滾動,可更新的,因此如果資料庫系統不支援ResultSet實現類似的功能,可以使用RowSet來實現。
RowSet分為兩大類:
- A. 連線型RowSet——這類物件與資料庫進行連線,和ResultSet很類似。JDBC介面只提供了一種連線型RowSet,javax.sql.rowset.JdbcRowSet,它的標準實現是com.sun.rowset.JdbcRowSetImpl。
- B. 離線型RowSet——這類物件不需要和資料庫進行連線,因此它們更輕量級,更容易序列化。它們適用於在網路間傳遞資料。
- 有四種不同的離線型RowSet的實現。
- CachedRowSet——可以通過他們獲取連線,執行查詢並讀取ResultSet的資料到RowSet裡。我們可以在離線時對資料進行維護和更新,然後重新連線到資料庫裡,並回寫改動的資料。
- WebRowSet繼承自CachedRowSet——他可以讀寫XML文件。
- JoinRowSet繼承自WebRowSet——它不用連線資料庫就可以執行SQL的join操作。
- FilteredRowSet繼承自WebRowSet——我們可以用它來設定過濾規則,這樣只有選中的資料才可見。
- 有四種不同的離線型RowSet的實現。
什麼是JDBC的最佳實踐?
什麼是JDBC的最佳實踐?
- 資料庫資源是非常昂貴的,用完了應該儘快關閉它。Connection, Statement, ResultSet等JDBC物件都有close方法,呼叫它就好了。
- 養成在程式碼中顯式關閉掉ResultSet,Statement,Connection的習慣,如果你用的是連線池的話,連線用完後會放回池裡,但是沒有關閉的ResultSet和Statement就會造成資源洩漏了。
- 在finally塊中關閉資源,保證即便出了異常也能正常關閉。
- 大量類似的查詢應當使用批處理完成。
- 儘量使用PreparedStatement而不是Statement,以避免SQL隱碼攻擊,同時還能通過預編譯和快取機制提升執行的效率。
- 如果你要將大量資料讀入到ResultSet中,應該合理的設定fetchSize以便提升效能。
- 你用的資料庫可能沒有支援所有的隔離級別,用之前先仔細確認下。
- 資料庫隔離級別越高效能越差,確保你的資料庫連線設定的隔離級別是最優的。
- 如果在WEB程式中建立資料庫連線,最好通過JNDI使用JDBC的資料來源,這樣可以對連線進行重用。
- 如果你需要長時間對ResultSet進行操作的話,儘量使用離線的RowSet。
如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章的同學,可以關注微信公眾號:Java3y