JDBC API 允許用戶訪問任何形式的表格數(shù)據(jù),尤其是存儲在關系數(shù)據(jù)庫中的數(shù)據(jù)。
執(zhí)行流程如下:
- 連接數(shù)據(jù)源,如:數(shù)據(jù)庫。
- 為數(shù)據(jù)庫傳遞查詢和更新指令。
- 處理數(shù)據(jù)庫響應并返回的結果。
1.JDBC操作數(shù)據(jù)庫的步驟
加載驅動。
Class.forName("oracle.jdbc.driver.OracleDriver");
創(chuàng)建連接。
Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "scott", "tiger");
創(chuàng)建語句。
PreparedStatement ps = con.prepareStatement("select * from emp where sal between ? and ?");
ps.setInt(1, 1000);
ps.setInt(2, 3000);
執(zhí)行語句。
ResultSet rs = ps.executeQuery();
處理結果。
while(rs.next()) {
System.out.println(rs.getInt("empno") + " - " + rs.getString("ename"));
}
關閉資源。
finally {
if(con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
提示:關閉外部資源的順序應該和打開的順序相反,也就是說先關閉ResultSet、再關閉Statement、在關閉Connection。上面的代碼只關閉了Connection(連接),雖然通常情況下在關閉連接時,連接上創(chuàng)建的語句和打開的游標也會關閉,但不能保證總是如此,因此應該按照剛才說的順序分別關閉。此外,第一步加載驅動在JDBC 4.0中是可以省略的(自動從類路徑中加載驅動),但是我們建議保留
一次完整增刪改查的java代碼案例:
public class DbUtil {
public static final String URL = "jdbc:mysql://localhost:3306/imooc";
public static final String USER = "liulx";
public static final String PASSWORD = "123456";
private static Connection conn = null;
static{
try {
//1.加載驅動程序
Class.forName("com.mysql.jdbc.Driver");
//2. 獲得數(shù)據(jù)庫連接
conn = DriverManager.getConnection(URL, USER, PASSWORD);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static Connection getConnection(){
return conn;
}
}
//模型
package liulx.model;
import java.util.Date;
public class Goddess {
private Integer id;
private String user_name;
private Integer sex;
private Integer age;
private Date birthday; //注意用的是java.util.Date
private String email;
private String mobile;
private String create_user;
private String update_user;
private Date create_date;
private Date update_date;
private Integer isDel;
//getter setter方法。。。
}
//---------dao層--------------
package liulx.dao;
import liulx.db.DbUtil;
import liulx.model.Goddess;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public class GoddessDao {
//增加
public void addGoddess(Goddess g) throws SQLException {
//獲取連接
Connection conn = DbUtil.getConnection();
//sql
String sql = "INSERT INTO imooc_goddess(user_name, sex, age, birthday, email, mobile,"+
"create_user, create_date, update_user, update_date, isdel)"
+"values("+"?,?,?,?,?,?,?,CURRENT_DATE(),?,CURRENT_DATE(),?)";
//預編譯
PreparedStatement ptmt = conn.prepareStatement(sql); //預編譯SQL,減少sql執(zhí)行
//傳參
ptmt.setString(1, g.getUser_name());
ptmt.setInt(2, g.getSex());
ptmt.setInt(3, g.getAge());
ptmt.setDate(4, new Date(g.getBirthday().getTime()));
ptmt.setString(5, g.getEmail());
ptmt.setString(6, g.getMobile());
ptmt.setString(7, g.getCreate_user());
ptmt.setString(8, g.getUpdate_user());
ptmt.setInt(9, g.getIsDel());
//執(zhí)行
ptmt.execute();
}
public void updateGoddess(){
//獲取連接
Connection conn = DbUtil.getConnection();
//sql, 每行加空格
String sql = "UPDATE imooc_goddess" +
" set user_name=?, sex=?, age=?, birthday=?, email=?, mobile=?,"+
" update_user=?, update_date=CURRENT_DATE(), isdel=? "+
" where id=?";
//預編譯
PreparedStatement ptmt = conn.prepareStatement(sql); //預編譯SQL,減少sql執(zhí)行
//傳參
ptmt.setString(1, g.getUser_name());
ptmt.setInt(2, g.getSex());
ptmt.setInt(3, g.getAge());
ptmt.setDate(4, new Date(g.getBirthday().getTime()));
ptmt.setString(5, g.getEmail());
ptmt.setString(6, g.getMobile());
ptmt.setString(7, g.getUpdate_user());
ptmt.setInt(8, g.getIsDel());
ptmt.setInt(9, g.getId());
//執(zhí)行
ptmt.execute();
}
public void delGoddess(){
//獲取連接
Connection conn = DbUtil.getConnection();
//sql, 每行加空格
String sql = "delete from imooc_goddess where id=?";
//預編譯SQL,減少sql執(zhí)行
PreparedStatement ptmt = conn.prepareStatement(sql);
//傳參
ptmt.setInt(1, id);
//執(zhí)行
ptmt.execute();
}
public List<Goddess> query() throws SQLException {
Connection conn = DbUtil.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT user_name, age FROM imooc_goddess");
List<Goddess> gs = new ArrayList<Goddess>();
Goddess g = null;
while(rs.next()){
g = new Goddess();
g.setUser_name(rs.getString("user_name"));
g.setAge(rs.getInt("age"));
gs.add(g);
}
return gs;
}
public Goddess get(){
Goddess g = null;
//獲取連接
Connection conn = DbUtil.getConnection();
//sql, 每行加空格
String sql = "select * from imooc_goddess where id=?";
//預編譯SQL,減少sql執(zhí)行
PreparedStatement ptmt = conn.prepareStatement(sql);
//傳參
ptmt.setInt(1, id);
//執(zhí)行
ResultSet rs = ptmt.executeQuery();
while(rs.next()){
g = new Goddess();
g.setId(rs.getInt("id"));
g.setUser_name(rs.getString("user_name"));
g.setAge(rs.getInt("age"));
g.setSex(rs.getInt("sex"));
g.setBirthday(rs.getDate("birthday"));
g.setEmail(rs.getString("email"));
g.setMobile(rs.getString("mobile"));
g.setCreate_date(rs.getDate("create_date"));
g.setCreate_user(rs.getString("create_user"));
g.setUpdate_date(rs.getDate("update_date"));
g.setUpdate_user(rs.getString("update_user"));
g.setIsDel(rs.getInt("isdel"));
}
return g;
}
}
2.Statement和PreparedStatement有什么區(qū)別?哪個性能更好?
與Statement相比,
-
PreparedStatement接口代表預編譯的語句,它主要的優(yōu)勢在于可以減少SQL的編譯錯誤并增加SQL的安全性(減少SQL注射攻擊的可能性); -
PreparedStatement中的SQL語句是可以帶參數(shù)的,避免了用字符串連接拼接SQL語句的麻煩和不安全; - 當批量處理SQL或頻繁執(zhí)行相同的查詢時,
PreparedStatement有明顯的性能上的優(yōu)勢,由于數(shù)據(jù)庫可以將編譯優(yōu)化后的SQL語句緩存起來,下次執(zhí)行相同結構的語句時就會很快(不用再次編譯和生成執(zhí)行計劃)。
補充:為了提供對存儲過程的調(diào)用,JDBC API中還提供了CallableStatement接口。存儲過程(Stored Procedure)是數(shù)據(jù)庫中一組為了完成特定功能的SQL語句的集合,經(jīng)編譯后存儲在數(shù)據(jù)庫中,用戶通過指定存儲過程的名字并給出參數(shù)(如果該存儲過程帶有參數(shù))來執(zhí)行它。雖然調(diào)用存儲過程會在網(wǎng)絡開銷、安全性、性能上獲得很多好處,但是存在如果底層數(shù)據(jù)庫發(fā)生遷移時就會有很多麻煩,因為每種數(shù)據(jù)庫的存儲過程在書寫上存在不少的差別。
3.使用JDBC操作數(shù)據(jù)庫時,如何提升讀取數(shù)據(jù)的性能?如何提升更新數(shù)據(jù)的性能?
要提升讀取數(shù)據(jù)的性能,可以指定通過結果集(ResultSet)對象的setFetchSize()方法指定每次抓取的記錄數(shù)(典型的空間換時間策略);
要提升更新數(shù)據(jù)的性能可以使用PreparedStatement語句構建批處理,將若干SQL語句置于一個批處理中執(zhí)行。
4.在進行數(shù)據(jù)庫編程時,連接池有什么作用?
由于創(chuàng)建連接和釋放連接都有很大的開銷(尤其是數(shù)據(jù)庫服務器不在本地時,每次建立連接都需要進行TCP的三次握手,釋放連接需要進行TCP四次握手,造成的開銷是不可忽視的),為了提升系統(tǒng)訪問數(shù)據(jù)庫的性能,可以事先創(chuàng)建若干連接置于連接池中,需要時直接從連接池獲取,使用結束時歸還連接池而不必關閉連接,從而避免頻繁創(chuàng)建和釋放連接所造成的開銷,這是典型的用空間換取時間的策略(浪費了空間存儲連接,但節(jié)省了創(chuàng)建和釋放連接的時間)。池化技術在Java開發(fā)中是很常見的,在使用線程時創(chuàng)建線程池的道理與此相同?;贘ava的開源數(shù)據(jù)庫連接池主要有:C3P0、Proxool、DBCP、BoneCP、Druid等。
補充:在計算機系統(tǒng)中時間和空間是不可調(diào)和的矛盾,理解這一點對設計滿足性能要求的算法是至關重要的。大型網(wǎng)站性能優(yōu)化的一個關鍵就是使用緩存,而緩存跟上面講的連接池道理非常類似,也是使用空間換時間的策略。可以將熱點數(shù)據(jù)置于緩存中,當用戶查詢這些數(shù)據(jù)時可以直接從緩存中得到,這無論如何也快過去數(shù)據(jù)庫中查詢。當然,緩存的置換策略等也會對系統(tǒng)性能產(chǎn)生重要影響,對于這個問題的討論已經(jīng)超出了這里要闡述的范圍。
5.什么是DAO模式?
DAO(Data Access Object)顧名思義是一個為數(shù)據(jù)庫或其他持久化機制提供了抽象接口的對象,在不暴露底層持久化方案實現(xiàn)細節(jié)的前提下提供了各種數(shù)據(jù)訪問操作。在實際的開發(fā)中,應該將所有對數(shù)據(jù)源的訪問操作進行抽象化后封裝在一個公共API中。用程序設計語言來說,就是建立一個接口,接口中定義了此應用程序中將會用到的所有事務方法。在這個應用程序中,當需要和數(shù)據(jù)源進行交互的時候則使用這個接口,并且編寫一個單獨的類來實現(xiàn)這個接口,在邏輯上該類對應一個特定的數(shù)據(jù)存儲。DAO模式實際上包含了兩個模式,一是Data Accessor(數(shù)據(jù)訪問器),二是Data Object(數(shù)據(jù)對象),前者要解決如何訪問數(shù)據(jù)的問題,而后者要解決的是如何用對象封裝數(shù)據(jù)。
6.事務的ACID是指什么?
- 原子性(Atomic):事務中各項操作,要么全做要么全不做,任何一項操作的失敗都會導致整個事務的失??;
- 一致性(Consistent):事務結束后系統(tǒng)狀態(tài)是一致的;
- 隔離性(Isolated):并發(fā)執(zhí)行的事務彼此無法看到對方的中間狀態(tài);
- 持久性(Durable):事務完成后所做的改動都會被持久化,即使發(fā)生災難性的失敗。通過日志和同步備份可以在故障發(fā)生后重建數(shù)據(jù)。
補充:關于事務,在面試中被問到的概率是很高的,可以問的問題也是很多的。首先需要知道的是,只有存在并發(fā)數(shù)據(jù)訪問時才需要事務。當多個事務訪問同一數(shù)據(jù)時,可能會存在5類問題,包括3類數(shù)據(jù)讀取問題(臟讀、不可重復讀和幻讀)和2類數(shù)據(jù)更新問題(第1類丟失更新和第2類丟失更新)。
數(shù)據(jù)并發(fā)訪問所產(chǎn)生的問題,在有些場景下可能是允許的,但是有些場景下可能就是致命的,數(shù)據(jù)庫通常會通過鎖機制來解決數(shù)據(jù)并發(fā)訪問問題,按鎖定對象不同可以分為表級鎖和行級鎖;按并發(fā)事務鎖定關系可以分為共享鎖和獨占鎖,具體的內(nèi)容大家可以自行查閱資料進行了解。
直接使用鎖是非常麻煩的,為此數(shù)據(jù)庫為用戶提供了自動鎖機制,只要用戶指定會話的事務隔離級別,數(shù)據(jù)庫就會通過分析SQL語句然后為事務訪問的資源加上合適的鎖,此外,數(shù)據(jù)庫還會維護這些鎖通過各種手段提高系統(tǒng)的性能,這些對用戶來說都是透明的(就是說你不用理解,事實上我確實也不知道)。ANSI/ISO SQL 92標準定義了4個等級的事務隔離級別,如下表所示:

需要說明的是,事務隔離級別和數(shù)據(jù)訪問的并發(fā)性是對立的,事務隔離級別越高并發(fā)性就越差。所以要根據(jù)具體的應用來確定合適的事務隔離級別,這個地方?jīng)]有萬能的原則。
7.JDBC中如何進行事務處理?
答:Connection提供了事務處理的方法,通過調(diào)用setAutoCommit(false)可以設置手動提交事務;當事務完成后用commit()顯式提交事務;如果在事務處理過程中發(fā)生異常則通過rollback()進行事務回滾。除此之外,從JDBC 3.0中還引入了Savepoint(保存點)的概念,允許通過代碼設置保存點并讓事務回滾到指定的保存點。