1. JDBC概述
JDBC全稱爲:JavaDataBase Connectivity(java數據庫連接)。SUN公司爲了簡化、統一對數據庫的操作,定義了一套Java操作數據庫的規範,稱之爲JDBC。
2. JDBC開發步驟
(1)註冊驅動
(2)獲得連接
(3)獲取執行SQL語句的對象
(4)執行SQL語句
(5)獲得結果集
(6)釋放資源
JDBC入門示例1:
@Test
public void test01() throws Exception{
//註冊驅動
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//獲取數據庫連接
Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
//創建執行SQL語句的對象
Statement st=conn.createStatement();
//執行SQL語句
ResultSet rs = st.executeQuery("select * from stu");
//遍歷結果集
while(rs.next()){
System.out.println(rs.getString("sid"));
System.out.println(rs.getString("sname"));
System.out.println(rs.getInt("age"));
System.out.println(rs.getString("gender"));
}
//釋放資源
rs.close();
st.close();
conn.close();
}
2.1 導入驅動jar包
創建lib目錄,用於存放當前項目需要的所有jar包
選擇jar包,右鍵執行buildpath / Add to Build Path
2.2 DriverManager類
static void registerDriver(Driver driver) 用於註冊JDBC驅動程序
注意:DriverManager中可以同時註冊多個JDBC驅動 例如:同時註冊 mysql、oralce、db2 驅動 ,通過對JDBC URL分析,決定採用哪個驅動
static Connection getConnection(String url, String user,String password) 根據jdbc url 和 用戶名、密碼獲得一個數據庫連接
實際開發中,不推薦使用DriverManager.registerDriver 會導致驅動註冊兩次、會使得程序依賴 具體數據庫API 推薦使用 :Class.forName("com.mysql.jdbc.Driver"); 加載Driver類時完成驅動註冊,使得程序不依賴MySQL的API
2.3 JDBC URL
jdbc:mysql://localhost:3306/test
jdbc: 是JDBC連接協議
mysql:// 是mysql數據庫連接協議,JDBC子協議
localhost:3306主機名和端口號
test數據庫名稱
MySQL 如果連接localhost:3306 可以省略
jdbc:mysql://localhost:3306/day13--------------- jdbc:mysql:///day11
JDBCURL 可以通過?和& 攜帶參數
常用屬性:useUnicode=true&characterEncoding=UTF-8 ----------- 解決操作數據庫亂碼問題
常用數據庫URL寫法:
MYSQLjdbc:mysql://localhost:3306/test
ORACLEjdbc:oracle:thin:@localhost:1521:sid
2.4 Connection接口
(1)獲得SQL的操作對象
Statement conn.createStatement() 該對象可以將SQL發送給數據庫進行執行
PreparedStatement conn.prepareStatement(sql) 對SQL語句進行預編譯,防止SQL注入
CallableStatement conn.prepareCall(sql); 該對象可以調用數據庫中存儲過程
(2)對數據庫事務進行管理
conn.setAutoCommit(boolean); 設置事務是否自動提交
conn.commit(); 提交數據庫事務
conn.rollback(); 回滾數據庫事務
2.5 Statement
用於將SQL發送給數據庫,獲得操作結果
發送單條SQL :
executeUpdate 用於向數據庫發送 insertupdate delete 語句,返回int 類型參數,代表影響記錄行數
executeQuery 用於向數據庫發送 select 語句,返回ResultSet 結果集對象
execute 用於數據庫發送任何SQL語句(包括 DDL DML DCL) 返回boolean ,SQL執行結果是ResultSet 返回true,否則 false
發送多條SQL:
addBatch(sql) 將SQL加入批處理隊列
executeBatch() 執行隊列中所有SQL語句 ,一次性向數據庫發送多條SQL
2.6 ResultSet遍歷結果集
while(rs.next()){
// 根據數據庫內部 列類型,選擇相應 getXXX方法
int ---- getInt
varchart ----getString
date ----- getDate
}
getXXX 有兩種寫法
第一種 getString(index) 結果集中列索引
第二種getString(列名)
boolean next():遊標下移。返回值是有無記錄
boolean previous():遊標上移。
boolean absolute(int count):定位到指定的行。第一行是1。
void beforeFirst():移動遊標到第一行的前面。
void afterLast():移動遊標到最後一行的後面。
2.7 滾動結果集
Connection 接口的 createStatement() 返回Statement對象,操作SQL後 產生ResultSet默認執行next 向前滾動,不支持在滾動中對數據進行修改 (只讀不執行滾動)
Connection 接口還提供createStatement(int resultSetType, int resultSetConcurrency) 在創建Statement對象 設置結果集類型,併發策略
結果集類型:
ResultSet.TYPE_FORWARD_ONLY 只能向前,只能調用next 不能向回滾動
ResultSet.TYPE_SCROLL_INSENSITIVE 支持結果集向回滾動,不能查看修改結果
ResultSet.TYPE_SCROLL_SENSITIVE 支持結果集向回滾動,查看修改結果
結果集併發策略:
ResultSet.CONCUR_READ_ONLY 只讀
ResultSet.CONCUR_UPDATABLE 支持修改
常見三種組合:
ResultSet.TYPE_FORWARD_ONLY 和 ResultSet.CONCUR_READ_ONLY (默認) 只讀不支持向回滾動
ResultSet.TYPE_SCROLL_INSENSITIVE 和ResultSet.CONCUR_READ_ONLY 只讀,支持向回滾動
ResultSet.TYPE_SCROLL_SENSITIVE 和 ResultSet.CONCUR_UPDATABLE 支持向回滾動,支持對數據修改
3. SQL注入
假設有登錄案例SQL語句如下: SELECT * FROM 用戶表 WHERE NAME = 用戶輸入的用戶名 AND PASSWORD = 用戶輸的密碼;
當用戶輸入正確的賬號與密碼後,查詢到了信息則讓用戶登錄。但是當用戶輸入的賬號爲XXX 密碼爲:XXX’ OR ‘1’=’1時,則真正執行的代碼變爲:SELECT* FROM 用戶表 WHERE NAME = ‘XXX’ AND PASSWORD =’ XXX’ OR ’1’=’1’;
此時,上述查詢語句時永遠可以查詢出結果的。那麼用戶就直接登錄成功了,這便是SQL注入問題。可以使用PreparedStatement來解決對應的問題。
4. 預處理對象
使用PreparedStatement預處理對象時,建議每條sql語句所有的實際參數,都使用逗號分隔。
String sql = "insert into sort(sid,sname)values(?,?)";;
PreparedStatement預處理對象代碼:
PreparedStatement psmt = conn.prepareStatement(sql)
常用方法:
(1)執行SQL語句:
int executeUpdate(); //執行insert updatedelete語句.
ResultSet executeQuery();//執行select語句.
boolean execute(); //執行select返回true 執行其他的語句返回false.
(2)設置實際參數
void setXxx(int index, Xxx xx) 將指定參數設置爲給定Java的xx
值。在將此值發送到數據庫時,驅動程序將它轉換成一個 SQL Xxx
類型
值。例如:setString(2, "薛之謙") 把SQL語句中第2個位置的佔位符?替換成實際參數 "演員"
@Test
public void run02() throws Exception{
//註冊驅動
Class.forName("com.mysql.jdbc.Driver");
//獲取連接
Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
//使用PreparedStatement預編譯SQL
PreparedStatement ps=conn.prepareStatement("insert into stu(sid,sname,age) values(?,?,?)");
ps.setString(1, "9527");
ps.setString(2, "kobe");
ps.setInt(3, 35);
//執行SQL語句
int i=ps.executeUpdate();
System.out.println(i);
//釋放資源
ps.close();
conn.close();
}
PreparedStatement 與Statement:
(1)語法不同:PreparedStatement可以使用預編譯的sql,而Statment只能使用靜態的sql
(2)效率不同:PreparedStatement可以使用sql緩存區,效率比Statment高
(3)安全性不同:PreparedStatement可以有效防止sql注入,而Statment不能防止sql注入。
5. properties配置文件
5.1 使用properties配置文件
開發中獲得連接的4個參數(驅動、URL、用戶名、密碼)通常都存在配置文件中,方便後期維護,程序如果需要更換數據庫,只需要修改配置文件即可。
通常情況下,我們習慣使用properties文件,此文件將做如下要求:
(1)文件位置:任意,建議src下
(2)文件名稱:任意,擴展名爲properties
(3)文件內容:一行一組數據,格式是“key=value”.
a) key命名自定義,如果是多個單詞,習慣使用點分隔。例如:jdbc.driver
b) value值不支持中文,如果需要使用非英文字符,將進行unicode轉換。
5.2 創建配置文件
在項目跟目錄下,創建文件,輸入“db.properties”文件名。
文件中的內容
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
user=root
password=root
5.3 加載配置文件
對應properties文件處理,開發中也使用Properties對象進行。將加載properties文件獲得流,然後使用Properties對象進行處理。
public void run03() throws Exception{
FileInputStream fis=new FileInputStream("db.properties");
Properties pps=new Properties();
pps.load(fis);
String driver=pps.getProperty("driver");
String url=pps.getProperty("url");
String username=pps.getProperty("username");
String password=pps.getProperty("password");
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
//加載驅動
Class.forName(driver);
//獲得連接
conn=DriverManager.getConnection(url, username, password);
//預編譯SQL
String sql="select * from stu";
ps=conn.prepareStatement(sql);
//執行SQL獲得結果集
rs=ps.executeQuery();
//遍歷結果集
while(rs.next()){
System.out.println(rs.getString("sid"));
System.out.println(rs.getString("sname"));
System.out.println(rs.getInt("age"));
System.out.println(rs.getString("gender"));
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
//釋放資源
if(rs!=null){
rs.close();
}
if(ps!=null){
ps.close();
}
if(conn!=null){
conn.close();
}
}
6. JDBC進行批處理
業務場景:當需要向數據庫發送一批SQL語句執行時,應避免向數據庫一條條的發送執行,而應採用JDBC的批處理機制,以提升執行效率。
實現批處理有兩種方式,第一種方式:
• Statement.addBatch(sql)
• 執行批處理SQL語句
• executeBatch()方法:執行批處理命令
• clearBatch()方法:清除批處理命令
採用Statement.addBatch(sql)方式實現批處理:
• 優點:可以向數據庫發送多條不同的SQL語句。
• 缺點:
(1)SQL語句沒有預編譯。
(2)當向數據庫發送多條語句相同,但僅參數不同的SQL語句時,需重複寫上很多條SQL語句。例如:
Insertinto user(name,password) values(‘aa’,’111’);
Insertinto user(name,password) values(‘bb’,’222’);
Insertinto user(name,password) values(‘cc’,’333’);
Insertinto user(name,password) values(‘dd’,’444’);
實現批處理的第二種方式:
• PreparedStatement.addBatch()
採用PreparedStatement.addBatch()實現批處理
• 優點:發送的是預編譯後的SQL語句,執行效率高。
• 缺點:只能應用在SQL語句相同,但參數不同的批處理中。因此此種形式的批處理經常用於在同一個表中批量插入數據,或批量更新表的數據.
注:
默認使用PreparedStatement是不能執行預編譯的,這需要在url中給出useServerPrepStmts=true參數(MySQL Server4.1之前的版本是不支持預編譯的,而Connector/J在5.0.5以後的版本,默認是沒有開啓預編譯功能的)。
例如:jdbc:mysql://localhost:3306/test?useServerPrepStmts=true
這樣才能保證mysql驅動會先把SQL語句發送給服務器進行預編譯,然後在執行executeQuery()時只是把參數發送給服務器。
當使用不同的PreparedStatement對象來執行相同的SQL語句時,還是會出現編譯兩次的現象,這是因爲驅動沒有緩存編譯後的函數key,導致二次編譯。如果希望緩存編譯後函數的key,那麼就要設置cachePrepStmts參數爲true。例如:
jdbc:mysql://localhost:3306/test?useServerPrepStmts=true&cachePrepStmts=true
MySQL的批處理也需要通過參數來打開:rewriteBatchedStatements=true
例如:jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
Mysql驅動要使用mysql-connector-java-5.1.13以上
7. 使用CablleStatement調用存儲過程
@Test
public void test2(){
Connection conn = null;
CallableStatement stmt = null;
ResultSet rs = null;
try {
//獲取連接
conn = JdbcUtil.getConnection();
//準備sql
String sql = "CALL pro_findById2(?,?)"; //第一個?是輸入參數,第二個?是輸出參數
//預編譯
stmt = conn.prepareCall(sql);
//設置輸入參數
stmt.setInt(1, 6);
//設置輸出參數(註冊輸出參數)
/**
* 參數一: 參數位置
* 參數二: 存儲過程中的輸出參數的jdbc類型 VARCHAR(20)
*/
stmt.registerOutParameter(2, java.sql.Types.VARCHAR);
//發送參數,執行
stmt.executeQuery(); //結果不是返回到結果集中,而是返回到輸出參數中
//得到輸出參數的值
/**
* 索引值: 預編譯sql中的輸出參數的位置
*/
String result = stmt.getString(2); //getXX方法專門用於獲取存儲過程中的輸出參數
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
JdbcUtil.close(conn, stmt ,rs);
}
}