Java 核心技術 卷II (4章)
---數據庫編程
1、jdbc設計
1)Jdbc是java能夠通過SQL訪問數據庫的一套javaAPI;
2)Jdbc是一套能與多種關係數據庫提供統一訪問Java接口方法;
2、核心API方法介紹
創建執行對象:
Stattement stat = conn.createStatement();
stat.executeUpdate():返回受SQL命令影響的行數,或者對不返回行數的語句(DDL語句)返回0
ResultSet rs = stat.executeQuery(String SQL)
結果集使用:
while(rs.next()){ . . . . . .}
如何取值?不同的參數類型有不同的訪問器,可以通過下標和類名獲取
rs.getDouble (1);
rs.getDouble(“price”)
說明:
1)數據庫索引與數組的索引不同,數據庫的列序號從1開始計算的。
2)使用get方法的類型與列的數據類型不一致,get方法會嘗試合理的類型轉換,如rs.getString(“price”),會將price列的浮點值轉換爲字符串。
3)做通用話數據封裝時,可以使用getObject進行通用取。
如何管理連接??
使用完ResultSet、Statement或Connection對象,應立即使用close方法關閉,調用順序從裏到外(ResultSet、Statement、Connection)
如何解析SQL異常
SQLWarning w = stat.getWarning();
while(w != null){
do something with w
w = w.nextWarning();
}
說明:還有其他的很多方法,見書!!
相關其他方法:
Connection :
Statement createStatement()
voidcolse()
Statement
ResultSetexecuteQuery(String sql)
intexecuteUpdate(String sql)
booleanexecute(String sql)
ResultSetgetResultSet()
intgetUpdateCount()
voidclose()
booleanisColsed()
void coseOncompleion()
ResultSet
booleannext()
XxxgetXxx(int columnNumber)
XxxgetXxx(String columnName)
<T> TgetObject(int columnNumber,Class<T> type)
<T> T getObject(intcolumnName,Class<T> type)
int findColumn(String columnName)
void close()
boolean isClosed();
3、不同的執行對象
3.1預備語句(預編譯語句)
如何進行參數化查詢??
PreparedStatement stat = conn.prepareStatement(“select *from user where id = ?”);
stat.serInt(1,12);
ResultSet rs = stat.executeQuery();
許多數據庫通常都會有自動緩存預備語句,如果相同的查詢被預備兩次,數據庫通常會直接重用查詢策略。
不使用Statement:
1)使用Statement,若是進行參數化查詢,每次查詢都會建立新的查詢語句,消耗查詢性能;
2)使用Statement,如果進行參數化查詢,需要拼接SQL,容易造成SQL注入。
使用statement:
如果進行的查詢是不帶參數的,使用Statement比PrepredStatement更高校,
相關其他方法:
Connection
PreparedStatementprepareStatement(String sql)
PreparedStatement
voidsetXxx(int n, Xxx x);
voidclearParameters();
ResultSetexecuteQuery()
intexecuteUpdate() 【如果執行的是數據定義語句(DDL),如CREATE TABLE,則返回0】
3.2 CallableStatement執行存儲過程
n CALL pro_findById2(5,@NAME);
(1)存儲過程
mysql> DELIMITER $
mysql> CREATE PROCEDURE pro_findById2(IN eidINT,OUT vname VARCHAR(20))
-> BEGIN
-> SELECT empname INTO vname FROM employeeWHERE id = eid;
-> END $;
(2)測試
String sql = "CALL pro_findById2(?,?)"; //第一個?是輸入參數,第二個?是輸出參數
cstmt =conn.prepareCall(sql);
//設置輸入參數
cstmt.setInt(1, 6);
//設置輸出參數(註冊輸出參數)
cstmt.registerOutParameter(2,java.sql.Types.VARCHAR);
//返回結果到輸出參數中
cstmt.executeQuery();
//從輸出參數的索引中,獲取結果
String result = cstmt.getString(2);
4、其他
4.1讀寫Lob
Lob分兩種:一種字符型大對象Clob,二進制大對象Blob
Blob:
存入數據庫:
String sql = " insert into test(img)values(?)";
// 連接
con = JdbcUtil.getConnection();
// pstmt 對象
pstmt = con.prepareStatement(sql);
// 獲取圖片流
InputStream in = App_text.class.getResourceAsStream("7.jpg");
pstmt.setBinaryStream(1, in);
// 執行保存圖片
pstmt.execute();
從數據庫讀取:
String sql = "select img from test where id=2;";
// 連接
con = JdbcUtil.getConnection();
pstmt = con.prepareStatement(sql);
rs = pstmt.executeQuery();
if (rs.next()) {
// 獲取圖片流
InputStream in =rs.getBinaryStream("img");
// 圖片輸出流
FileOutputStream out = new FileOutputStream(new File("c://1.jpg"));
int len = -1;
byte b[] = new byte[1024];
while ((len = in.read(b)) != -1) {
out.write(b, 0,len);
}
}
Clob:
存入數據庫:
String sql = "insert into test(content)values(?)";
con = JdbcUtil.getConnection();
pstmt = con.prepareStatement(sql);
// 設置參數
// 先獲取文件路徑
String path = App_text.class.getResource("tips.txt").getPath();
FileReader reader = new FileReader(new File(path));
pstmt.setCharacterStream(1,reader);
// 執行sql
pstmt.executeUpdate();
從數據庫讀取:
String sql = "select * from test;";
con = JdbcUtil.getConnection();
pstmt = con.prepareStatement(sql);
rs = pstmt.executeQuery();
if (rs.next()) {
// 獲取長文本數據,方式1:
//Reader r = rs.getCharacterStream("content");
// 獲取長文本數據,方式2:
System.out.print(rs.getString("content"));
}
4.2獲取主鍵
String sql = "insert into articlevalues(null,?,now(),?)";
//執行語句的同時返回一個主鍵集合
PreparedStatement pst =DB.prepareStmt(conn, sql,Statement.RETURN_GENERATED_KEYS);
pst.setInt(1,0);
pst.setInt(2, 0);
pst.executeUpdate();
//得到主鍵集合
ResultSet rsKey =pst.getGeneratedKeys();
rsKey.next();
rootId = rsKey.getInt(1);
4.3多結果集
若是執行存儲過程或者在單個查詢中提交了多個select語句,那麼一個數據庫能返回多個結果集。
關鍵點:重複調用getMoreResult方法移動到下一項結果集。
CREATE PROCEDURE proc_test()
BEGIN
select * from person;
select * from person;
END;
Connection conn = getConn();
String sql = "{call proc_test()}";
CallableStatement ctmt = conn.prepareCall(sql);
boolean hadResults = ctmt.execute();
int i=0;
ResultSet rs = null;
while (hadResults) {
System.out.println("result No:----"+(++i));
rs = ctmt.getResultSet();
while (rs !=null && rs.next()){
int id1 = rs.getInt(1);
String name1 = rs.getString(2);
System.out.println(id1 +":" + name1);
}
hadResults = ctmt.getMoreResults(); //檢查是否存在更多結果集
}
4.4可滾動&可更新結果集
可滾動結果集:
Connection conn = getConn();
/*
*conn.createStatement(type,concurrency)
*/
// TYPE_SCROLL_SENSITIVE:結果集能滾動,對數據庫變化敏感
// CONCUR_UPDATABLE:結果集可以用於更新數據庫
Statement stat = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stat.executeQuery("select * from user");
// 移動遊標方法
rs.next();
rs.relative(3);
rs.previous();
rs.relative(-1);
rs.absolute(5);
// 特殊位置
rs.first();
rs.last();
rs.beforeFirst();
rs.afterLast();
ps:因爲在操作過程中,一直連接着數據庫,可滾動結果集,消耗數據庫資源巨大。
可更新結果集:
Connection conn = getConn();
// TYPE_SCROLL_SENSITIVE:結果集能滾動,對數據庫變化敏感
// CONCUR_UPDATABLE:結果集可以用於更新數據庫
Statement stat = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stat.executeQuery("select * from user");
rs.next();
rs.updateString("name","u-2");
rs.updateInt("age", 22);
rs.updateRow();
rs.insertRow();
rs.deleteRow();
ps:
①更新數據庫行,必須要updateRow(),這樣可以將變化同步到數據庫;插入數據庫一行,使用updateXxx,之後要使用insertRow(),rs.moveToCurrentRow();deleteRow()直接將當前行刪除。
②如果查詢語句涉及到多個表的連接操作,那麼它所產生的結果集將是不可更新的;若是利用主鍵連接表的,那麼它還是可更新結果集。
4.5行集
RowSet接口擴展自ResultSet接口,卻無需始終保持與數據庫連接,可以完美解決可滾動結果集的,數據庫資源消耗問題。
行集分幾種:
CachedRowSet:緩存的行集。
WebRowSet:緩存的行集,可保存爲XML文件,該文件可移動到Web應用。
FilteredRowSet和JoinRowSet:支持對行集的輕量級操作。
JdbcRowSet:是ResultSet接口的瘦包裝器。
行集中的數據來源??
使用JDBC驅動程序從數據庫檢索的數據
從其他數據源獲得的數據,如文件數據
行集(Row Set)的優點??
1)可以斷開數據庫連接操作數據;
2)可以在分佈式系統中的不同組件之間傳遞;
3)默認可更新,可滾動,可序列化,可以方便的在網絡間傳輸;
4)可以方便的使數據在行集與JavaBean對象之間進行轉換。行集中的一行數據可以封裝爲一個JavaBean對象。
Connection conn = getConn();
PreparedStatement pst=conn.prepareStatement("select * from user");
//必須設置非自動提交
conn.setAutoCommit(false);
ResultSet rs=pst.executeQuery();
//創建行集實例
CachedRowSetImpl rowset=newCachedRowSetImpl();
//填充
rowset.populate(rs);
//這裏已經關閉結果集了!!!
rs.close();
pst.close();
// 在關閉連接之前進行更新操作
rowset.absolute(5);
rowset.updateInt("English", 55);
rowset.updateRow(); //更新
rowset.acceptChanges(conn); //提交
//輸出結果集之前,關閉連接
conn.close();
//輸出行集數據
while(rowset.next()){
System.out.print(rowset.getInt("id")+"\t");
System.out.print(rowset.getInt("Chinese")+"\t");
System.out.print(rowset.getInt("English")+"\t");
System.out.println(rowset.getInt("history"));
}
5、元數據
元數據:描述數據庫或者其組成部分的數據。
元數據分爲:
關於數據庫的元數據(DatabaseMetaData)
關於結果集的元數據(ResultSetMetaData)
關於預備語句參數的元數據(ParameterMetaData)
數據庫元數據
// 獲取連接
Connection conn = JdbcUtil.getConnection();
// 獲取數據庫元數據
DatabaseMetaData metaData = conn.getMetaData();//alt + shift + L 快速獲取方法返回值
System.out.println(metaData.getUserName());
System.out.println(metaData.getURL());
System.out.println(metaData.getDatabaseProductName());
參數元數據
// 獲取連接
Connection conn = JdbcUtil.getConnection();
// SQL
String sql = "select * from dept wheredeptid=? and deptName=?";
// Object[] values = {"tom","888"};
PreparedStatement pstmt = conn.prepareStatement(sql);
// 參數元數據
ParameterMetaData p_metaDate = pstmt.getParameterMetaData();
// 獲取參數的個數
int count = p_metaDate.getParameterCount();
// 測試
System.out.println(count);
結果集元數據
Connection conn = JdbcUtil.getConnection();
PreparedStatement pstmt = conn.prepareStatement("select * from dept ");
ResultSet rs = pstmt.executeQuery();
// 得到結果集元數據(目標:通過結果集元數據,得到列的名稱)
ResultSetMetaData rs_metaData = rs.getMetaData();
// 迭代每一行結果
while (rs.next()) {
// 1. 獲取列的個數
int count = rs_metaData.getColumnCount();
// 2. 遍歷,獲取每一列的列的名稱
for (int i=0; i<count; i++) {
// 得到列的名稱
String columnName= rs_metaData.getColumnName(i + 1);
// 獲取每一行的每一列的值
ObjectcolumnValue = rs.getObject(columnName);
// 測試
System.out.print(columnName +"=" + columnValue + ",");
}
System.out.println();
}
說明:getObject可以拿到任意類型行的值
6、事務
事務:將一組語句構建成一個事務,當所有語句都順利執行,事務可以被提交。否則,如果其中某個語句遇到錯誤,那麼事務將被回滾。
事務:確保數據庫萬整性。
默認情況下,數據庫連接處於自動提交模式。
Connection con;
PreparedStatement pstmt;
String sql_zs = "UPDATE account SETmoney=money-1000 WHERE accountName='張三';";
String sql_ls = "UPDATE1 account SETmoney=money+1000 WHERE accountName='李四';";
try {
con = JdbcUtil.getConnection();// 默認開啓的隱士事務
// 一、設置事務爲手動提交
con.setAutoCommit(false);
/*** 第一次執行SQL ***/
pstmt = con.prepareStatement(sql_zs);
pstmt.executeUpdate();
/*** 第二次執行SQL ***/
pstmt = con.prepareStatement(sql_ls);
pstmt.executeUpdate();
} catch(Exception e) {
try {
// 二、出現異常,需要回滾事務
con.rollback();
} catch (SQLException e1) {
e.printStackTrace();
}
} finally {
try {
// 三、所有的操作執行成功,提交事務
con.commit();
JdbcUtil.closeAll(con,pstmt, null);
} catch (SQLException e) {
}
}
保存點:使用保存點可以更細粒度的控制回滾操作
. . .
try {
con = JdbcUtil.getConnection();// 默認開啓的隱士事務
// 一、設置事務爲手動提交
con.setAutoCommit(false);
/*** 第一次執行SQL ***/
pstmt = con.prepareStatement(sql_zs);
pstmt.executeUpdate();
Savepoint svpt = pstmt.setSavepoint();
/*** 第二次執行SQL ***/
pstmt = con.prepareStatement(sql_ls);
pstmt.executeUpdate();
} catch(Exception e) {
// 二、出現異常,需要回滾到回滾點
con.rollback(svpt);
. . .
} finally {
. . .
}
Ps:當不需要回滾點時,可以釋放回滾點:使用conn.releaseSavepoint(svpt)。
7、批量操作
當程序需要執行多條語句時,可以使用批量操作,如執行上萬條insert語句,可以使用批量更新來提高程序性能。
// SQL
String sql = "INSERT INTOadmin(userName,pwd) values(?,?)";
try {
// 獲取連接
con = JdbcUtil.getConnection();
// 創建stmt
pstmt = con.prepareStatement(sql); // 【預編譯SQL語句】
for (int i=0; i<list.size(); i++) {
Admin admin =list.get(i);
// 設置參數
pstmt.setString(1,admin.getUserName());
pstmt.setString(2, admin.getPwd());
// 添加批處理
pstmt.addBatch(); // 【不需要傳入SQL】
// 測試:每5條執行一次批處理
if (i % 5 == 0) {
// 批量執行
pstmt.executeBatch();
// 清空批處理
pstmt.clearBatch();
}
}
// 批量執行
pstmt.executeBatch();
// 清空批處理
pstmt.clearBatch();
} catch(Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.closeAll(con, pstmt, rs);
}