1. 簡介
JDBC(Java Data Base Connectivity) 是 Java 訪問數(shù)據(jù)庫的標(biāo)準(zhǔn)規(guī)范.,為 多種關(guān)系數(shù)據(jù)庫提供統(tǒng)一訪問。它由一組用Java語言編寫的類和接口組成。
JDBC只是Java提供的一些接口,而這些接口的實現(xiàn)類是由各個數(shù)據(jù)庫廠商提供的,稱之為數(shù)據(jù)庫連接驅(qū)動。這樣一來,我們就可以面向接口編程,以一種統(tǒng)一的方式訪問多種數(shù)據(jù)庫。
2. JDBC使用流程
- 下載對應(yīng)版本的數(shù)據(jù)庫連接驅(qū)動,并導(dǎo)入項目。
- 注冊驅(qū)動
通過Class.froName("Driver實現(xiàn)類路徑");
加載Driver驅(qū)動并注冊到DriverManager中。
對于MySQL而言,不同版本的 "Driver實現(xiàn)類路徑" 也是不同的:
5.x版本為:com.mysql.jdbc.Driver
8.x版本為:com.mysql.cj.jdbc.Driver
原理為: Driver類在靜態(tài)代碼塊中創(chuàng)建Driver對象,并注冊到DriverManager中。
**注意:**在JDK1.6中DriverManager明確指出,在JDBC4.0及之后版本中可以不用forName顯示加載驅(qū)動。這是因為在獲取連接時,可以根據(jù)url判斷并自動加載對應(yīng)的驅(qū)動。但是一般開發(fā)中還是 要寫forName,因為有些場合并不會自動加載,如Tomcat中,由于類加載器不同,省略forName就會找不到類。
- 創(chuàng)建連接
通過DriverManager
的getConnection方法獲取連接
Connection getConnection(url,userName,password);
- url:jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&serverTimezone=UTC
jdbc:mysql
是 協(xié)議 名,固定寫法localhost:3306
為服務(wù)器地址 及 端口號test
是要連接的數(shù)據(jù)庫名characterEncoding=UTF-8
的作用為,告訴mysql,將數(shù)據(jù)轉(zhuǎn)化為 utf-8 格式之后再傳給我的程序。serverTimezone=UTC
有時候不指定時區(qū)會報錯。 - userName 登錄MySQL的用戶名
- password 密碼
- 執(zhí)行SQL語句
通過connection對象的createStatement()方法獲取Statement對象,然后通過Statement對象的executeUpdate()
和executeQuery()
方法執(zhí)行SQL語句:executeUpdate()
:方法用于執(zhí)行insert update delete語句,返回受影響的行數(shù)。executeQuery()
:執(zhí)行select語句, 返回ResultSet結(jié)果集對象。 - 處理結(jié)果集
ResultSet的作用為 封裝查詢結(jié)果,然后通過其next方法和getXxx方法遍歷結(jié)果集。
方法
說明
boolean next()
1) 游標(biāo)向下一行
2) 返回 boolean 類型,如果還有下一條記錄,返回 true,否則返回 false
xxx getXxx( String or int)
1) 通過列名,參數(shù)是 String 類型。返回不同的類型
2) 通過列號,參數(shù)是整數(shù),從 1 開始。返回不同的類型
- 釋放資源
釋放原則為:先開的后關(guān),一次關(guān)閉 ResultSet對象,Statement對象,Connection對象 - 示例
@Test
public void test(){
String url = "jdbc:mysql://192.168.65.131:3306/test?characterEncoding=UTF-8&serverTimezone=UTC";
String userName = "mochen";
String password = "123456";
Connection conn = null;
Statement stmt = null;
ResultSet rs = null; // 查詢數(shù)據(jù)
try {
// 加載Driver類,并注冊到DriverManager
Class.forName("com.mysql.cj.jdbc.Driver");
// 獲取連接
conn = DriverManager.getConnection(url, userName, password);
// 獲取Statement對象
stmt = conn.createStatement();
// 執(zhí)行SQL語句
stmt.executeUpdate("insert into jdbc_user values(null,'王蛋','abcd','2014-05-12 13:21:00')"); // 插入數(shù)據(jù)
rs = stmt.executeQuery("select * from jdbc_user");
// 處理結(jié)果集
while(rs.next()){
System.out.println(rs.getString(1)+"\t\t"+rs.getString(2)
+"\t\t"+rs.getString(3)+"\t\t"
+rs.getString(4));
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally { // 釋放資源
if(null!=rs){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(null!=stmt){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(null!=conn){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
3. SQL注入 與 PreparedStatement
3.1 SQL注入
什么是SQL注入:
當(dāng)要執(zhí)行的SQL語句 是由用戶輸入的參數(shù) 拼接而來時,如果用戶可以輸入一些有特殊意義的內(nèi)容,就可以改變原有的SQL的意義,從而進行非法操作。這種情況就稱為SQL注入。
舉例:
如 驗證用戶登錄的SQL語句:
String sql = "select * from jdbc_user " +
"where username = " + " '" + name +"' " +" and password = " +" '" + pass +"'";
如果用戶輸入的密碼為:' or '1'=1
因為1=1肯定是真,or 又是有真即真,所以where條件的結(jié)果總為true。這就改變了SQL原有的含義。
真正的SQL注入可能要復(fù)雜的多,但原理應(yīng)該都是
3.2 PreparedStatement
1. 簡介
PreparedStatement 是 Statement 接口的子接口,繼承于父接口中所有的方法。它是一個預(yù)編譯的 SQL 語 句對象。預(yù)編譯:是指SQL 語句被預(yù)編譯,并存儲在 PreparedStatement 對象中。然后可以使用此對象多次高效地執(zhí)行該語句。
2. 使用
// 獲取 PreparedStatement
conn.prepareStatement(String sql); // Sql中可以使用 ? 作為參數(shù)的占位符,然后使用setXxx方法賦值
// 常用方法
int executeUpdate();
ResultSet executeQuery(); // 注意:這里不必填入SQL
3. 示例
PreparedStatement ps = conn.prepareStatement("select * from employee where id=? and name=?");
ps.setInt(1,1);
ps.setString(2,"孫悟空");
rs = ps.executeQuery();
while (rs.next()){
for(int i=0;i<6;i++){
System.out.print(rs.getObject(i+1)+"\t");
}
}
4. JDBC工具類
如何編寫:
- 用常量記錄 DRIVERNAME、URL 、USER 、PASSWORD ,也可以用配置文件
- 靜態(tài)代碼塊中注冊驅(qū)動(或者是創(chuàng)建數(shù)據(jù)源)
- 提供靜態(tài)方法 獲取連接、 關(guān)閉對象。
其他的方法(像直接執(zhí)行SQL的方法)就不要提供了,因為如果這樣調(diào)用者中就沒有Connection和Statement的引用,而這兩者又只能在處理結(jié)果后才能關(guān)閉,這就不是靜態(tài)方法能夠?qū)崿F(xiàn)的功能了。 - 工具類類一般都是提供靜態(tài)方法,不能創(chuàng)建對象。
我認(rèn)為所謂工具就是要足夠簡單,獨立,與主程序邏輯沒什么關(guān)系。拿來即用,用完就不需要有什么后續(xù)操作。所以要用靜態(tài)方法。為了保證它的絕對簡單,最好提供private 構(gòu)造方法,明確禁止創(chuàng)建對象。
示例:
/**
* JDBC 工具類
*/
public class JDBCUtils {
//1. 定義字符串常量, 記錄獲取連接所需要的信息
public static final String DRIVERNAME = "com.mysql.jdbc.Driver";
public static final String URL = "jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8";
public static final String USER = "root";
public static final String PASSWORD = "123456";
//2. 靜態(tài)代碼塊, 隨著類的加載而加載
static{
try {
//注冊驅(qū)動
Class.forName(DRIVERNAME);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//3.獲取連接的靜態(tài)方法
public static Connection getConnection(){
try {
//獲取連接對象
Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);
//返回連接對象
return connection;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
//關(guān)閉資源的方法
public static void close(Connection con, Statement st){
if(con != null && st != null){
try {
st.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection con, Statement st, ResultSet rs){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
close(con,st);
}
}
5. 事務(wù)控制
mysql的自動事務(wù)為每一條DML語句都開啟一個事務(wù)。JDBC可以通過關(guān)閉自動提交來打開事務(wù)。
相關(guān)方法:
void setAutoCommit(boolean autoCommit); // 設(shè)置為 false 關(guān)閉事務(wù)的自動提交
void commit(); // 事務(wù)提交
void rollback(); // 事務(wù)回滾
示例:
//1. 獲取連接
con = JDBCUtils.getConnection();
//2. 開啟事務(wù)
con.setAutoCommit(false);
//3. 獲取到 PreparedStatement 執(zhí)行兩次更新操作
//3.1 tom 賬戶 -500
ps = con.prepareStatement("update account set money = money - ? where name = ? ");
ps.setDouble(1,500.0);
ps.setString(2,"tom");
ps.executeUpdate();
//模擬tom轉(zhuǎn)賬后 出現(xiàn)異常
System.out.println(1 / 0);
//3.2 jack 賬戶 +500
ps = con.prepareStatement("update account set money = money + ? where name = ? ");
ps.setDouble(1,500.0);
ps.setString(2,"jack");
ps.executeUpdate();
//4. 正常情況下提交事務(wù)
con.commit();
System.out.println("轉(zhuǎn)賬成功!");
6. 批處理
6.1 簡介
批處理指的是一次操作中執(zhí)行多條SQL語句,批處理相比于一次一次執(zhí)行效率會提高很多。主要用途就是批量操作,如一鍵生成一千個用戶,從提交的文件中提取并導(dǎo)入大批量的數(shù)據(jù) 等。
6.2 實現(xiàn)
Statement和PreparedStatement都支持批處理操作。
相關(guān)方法:
void addBatch(); // 將給定的 SQL 命令添加到此 Statement 對象的當(dāng)前命令列表中。
int[] executeBatch(); // 提交一批命令到數(shù)據(jù)庫中執(zhí)行,返回的數(shù)組是每條命令所影響的行數(shù)
示例:
@Test // Statement 批處理 測試
public void test135() throws Exception{
Connection conn = DruidUtils.getConnection();
Statement stmt = conn.createStatement();
for(int i=0;i<10;i++) {
stmt.addBatch("insert into appdate values(123,'豬悟能')");
}
int[] ints = stmt.executeBatch();
System.out.println(Arrays.toString(ints));
}
@Test // PreparedStatement 批處理 測試
public void test150() throws Exception{
Connection conn = DruidUtils.getConnection();
PreparedStatement ps = conn.prepareStatement("insert into appdate values(?,?)");
for(int i=0;i<10;i++) {
ps.setInt(1, 123);
ps.setString(2,"豬悟能");
ps.addBatch();
}
int[] ints = ps.executeBatch();
System.out.println(Arrays.toString(ints));
}
**注意:**批處理中不能進行查詢
7. 獲取元數(shù)據(jù)
相關(guān)類:DatabaseMetaData、ResultSetMetaData
使用方法:
DatabaseMetaData:
@Test // DatabaseMetaData 測試
public void test165() throws Exception{
Connection conn = DruidUtils.getConnection();
// 通過 Connection 獲取 DatabaseMetaData 對象
DatabaseMetaData dmd = conn.getMetaData();
// 通過 DatabaseMetaData 對象(dmd) 獲取 元數(shù)據(jù)
System.out.println(dmd.getDriverName()); // 驅(qū)動器名,如:MySQL Connector/J
System.out.println(dmd.getURL()); // 就是連接是傳入的URL
System.out.println(dmd.getUserName()); // 登錄的用戶名,如:root@localhost
System.out.println(dmd.isReadOnly()); // 是否 只讀
System.out.println(dmd.getDatabaseProductName()); // 獲取數(shù)據(jù)庫軟件的名字,如:MySQL
System.out.println(dmd.getDatabaseProductVersion()); // 獲取數(shù)據(jù)庫軟件的版本,如:8.0.21
}
ResultSetMetaData
@Test // ResultSetMetaData 測試
public void test178() throws Exception{
Connection conn = DruidUtils.getConnection();
PreparedStatement ps = conn.prepareStatement("select * from employee");
ps.executeQuery();
// 同過 PreparedStatement 獲取 ResultSetMetaData 對象,注意 Statemet 不行。
ResultSetMetaData rsmd = ps.getMetaData();
// 通過 ResultsetMetaData 對象獲取元數(shù)據(jù)
System.out.println(rsmd.getColumnCount()); // 獲取結(jié)果集的 列數(shù)
System.out.println(rsmd.getColumnName(2)); // 獲取結(jié)果集 某一列的列名
System.out.println(rsmd.getColumnTypeName(2)); // 返回對應(yīng)列的 數(shù)據(jù)類型(MySQL類型)名字,如:VARCHAR
System.out.println(rsmd.getColumnClassName(2)); // 返回對應(yīng)列的 數(shù)據(jù)類型對應(yīng)的java類路徑,如:java.lang.String
}
8. 注意
- statement的方法使用說明
execute()
executeUpdate() 可以進行:
數(shù)據(jù)的 增 刪 改、
表的 增 刪 改
executeQuery() 可以查詢:
表中 數(shù)據(jù)
有哪些表 和 數(shù)據(jù)庫 show tables, show databases
當(dāng)前表名 select database()
表結(jié)構(gòu) desc 表名 返回 表結(jié)構(gòu)包括:Field、TYPE、NULL、Key、Default、Extra
暫時只能想到這些操作,其余的用到了在補充吧!
- Statement與PreparedStatement的選擇
當(dāng)一次同一SQL要執(zhí)行很多次時,選擇preparedStatement。
當(dāng)涉及到查詢參數(shù)時,使用PreparedStatement。
其余情況:
即 只是 簡單的 執(zhí)行單獨一條SQL語句,也不用有什么參數(shù)限制時,使用Statement即可。
姑且這么認(rèn)為吧,對不對的 總得先有個判別依據(jù),要不然太混亂了。