JDBC概述
-
什么是JDBC
JDBC (Java Data Base Connectivity) 是 Java 訪問數(shù)據(jù)庫的標準規(guī)范。是一種用于執(zhí)行 SQL 語句的 Java API,可以為多種關(guān)系數(shù)據(jù)庫提供統(tǒng)一訪問,它由一組用 Java 語言編寫的類和接口組成。是 Java 訪問數(shù)據(jù)庫的標準規(guī)范。
-
JDBC原理
JDBC 是接口,驅(qū)動是接口的實現(xiàn),沒有驅(qū)動將無法完成數(shù)據(jù)庫連接,從而不能操作數(shù)據(jù)庫。每個數(shù)據(jù)庫廠商都需要提供自己的驅(qū)動,用來連接自己公司的數(shù)據(jù)庫,也就是說驅(qū)動一般都由數(shù)據(jù)庫生產(chǎn)廠商提供。
JDBC 就是由 sun 公司定義的一套操作所有關(guān)系型數(shù)據(jù)庫的規(guī)則(接口),而數(shù)據(jù)庫廠商需要實現(xiàn)這套接口,提供數(shù)據(jù)庫驅(qū)動 jar 包,我們可以使用這套接口編程,真正執(zhí)行的代碼是對應(yīng)驅(qū)動包中的實現(xiàn)類。
JDBC開發(fā)
-
數(shù)據(jù)準備
在 MySQL 中準備好以下數(shù)據(jù)
-- 創(chuàng)建 jdbc_user表
CREATE TABLE jdbc_user (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50),
PASSWORD VARCHAR(50),
birthday DATE
);
-- 添加數(shù)據(jù)
INSERT INTO jdbc_user (username, PASSWORD,birthday)
VALUES
('admin1', '123','1991/12/24'),
('admin2','123','1995/12/24'),
('test1', '123','1998/12/24'),
('test2', '123','2000/12/24');
-
MySql驅(qū)動包
首先將 MySQL 驅(qū)動包添加到 jar 包庫文件夾 myJar 中,它用于存放當前項目需要的所有 jar 包。然后在 IDEA 的項目中配置 jar 包庫的位置。最后創(chuàng)建一個新的模塊 jdbc_task01 并添加 jar 包庫的依賴。
步驟分析:
1 . 獲取驅(qū)動 (可以省略)
2 . 獲取連接
3 . 獲取 Statement 對象
4 . 處理結(jié)果集 (只在查詢時處理)
5 . 釋放資源
1. 注冊驅(qū)動
??JDBC規(guī)范定義驅(qū)動接口: java.sql.Driver
??MySql驅(qū)動包提供了實現(xiàn)類: com.mysql.jdbc.Driver
從 JDBC 3 開始,目前已經(jīng)普遍使用的版本,可以不用注冊驅(qū)動而直接使用。
使用反射方法 Class.forName 加載 Driver 類的時候會自動執(zhí)行 Driver 類的靜態(tài)代碼塊里面的注冊驅(qū)動的代碼。
public class JDBCDemo01 {
public static void main(String[] args) throws ClassNotFoundException {
// 注冊驅(qū)動 forName 方法執(zhí)行將類進行初始化
Class.forName("com.mysql.jdbc.Driver");
}
}
2. 獲得連接
解決插入中文亂碼問題:characterEncoding=UTF-8 指定字符的編碼、解碼格式。
public class JDBCDemo02 {
public static void main(String[] args) throws Exception {
// 注冊驅(qū)動
Class.forName("com.mysql.jdbc.Driver");
// 獲取連接 url, 用戶名, 密碼
String url = "jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8";
Connection con = DriverManager.getConnection(url, "root", "123456");
}
}
3. 獲取語句執(zhí)行平臺
??Statement createStatement(); -- 創(chuàng)建 SQL 語句執(zhí)行對象。
??int executeUpdate(String sql); -- 執(zhí)行增刪改語句,返回 int 類型,代表受影響的行數(shù)。
public class JDBCDemo03 {
public static void main(String[] args) throws Exception {
// 注冊驅(qū)動
Class.forName("com.mysql.jdbc.Driver");
// 獲取連接 url,用戶名, 密碼
String url = "jdbc:mysql://localhost:3306/db4";
Connection con = DriverManager.getConnection(url, "root", "123456");
// 獲取 Statement 對象
Statement statement = con.createStatement();
// 執(zhí)行創(chuàng)建表操作
String sql = "create table test01(id int, name varchar(20), age int);";
// 增刪改操作
int i = statement.executeUpdate(sql);
// 返回值是受影響的函數(shù)
System.out.println(i);
// 關(guān)閉流
statement.close();
con.close();
}
}
4. 處理結(jié)果集
ResultSet executeQuery(String sql); -- 執(zhí)行查詢語句,返回 ResultSet 結(jié)果集對象。
只有在進行查詢操作的時候,才會處理結(jié)果集。
ResultSet 接口作用:封裝數(shù)據(jù)庫查詢的結(jié)果集,對結(jié)果集進行遍歷,取出每一條記錄。
public class JDBCDemo04 {
public static void main(String[] args) throws SQLException {
// 注冊驅(qū)動可以省略
// 獲取連接
String url = "jdbc:mysql://localhost:3306/db4";
Connection con = DriverManager.getConnection(url, "root", "123456");
// 獲取 Statement對象
Statement statement = con.createStatement();
String sql = "select * from jdbc_user";
// 執(zhí)行查詢操作, 返回的是一個 ResultSet 結(jié)果對象
ResultSet resultSet = statement.executeQuery(sql);
// 處理結(jié)果集 resultSet
while(resultSet.next()){
//獲取id
int id = resultSet.getInt("id");
//獲取姓名
String username = resultSet.getString("username");
//獲取生日
Date birthday = resultSet.getDate("birthday");
System.out.println(id + " = " +username + " : " + birthday);
}
//關(guān)閉連接
resultSet.close();
statement.close();
con.close();
}
}
5. 釋放資源
釋放原則:先開的后關(guān),后開的先關(guān)。
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 注冊驅(qū)動(省略)
// 獲取連接
String url = "jdbc:mysql://localhost:3306/db4";
connection = DriverManager.getConnection(url, "root", "123456");
// 獲取 Statement對象
statement = connection.createStatement();
String sql = "select * from jdbc_user";
resultSet = statement.executeQuery(sql);
} catch (SQLException e) {
e.printStackTrace();
} finally {
/**
* 關(guān)閉的順序是先開后關(guān), 先得到的后關(guān)閉,后得到的先關(guān)閉
*/
try {
connection.close();
resultSet.close();
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
JDBC實現(xiàn)增刪改查
-
JDBC工具類
如果一個功能經(jīng)常要用到,建議把這個功能做成一個工具類,可以在不同的地方重用。
“獲得數(shù)據(jù)庫連接”操作,將在以后的增刪改查所有功能中都存在,可以封裝工具類 JDBCUtils:1. 把幾個字符串定義成常量:用戶名,密碼,URL,驅(qū)動類;2. 得到數(shù)據(jù)庫的連接:getConnection();3. 關(guān)閉所有打開的資源。
/**
* JDBC 工具類
*/
public class JDBCUtils {
// 定義字符串常量, 記錄獲取連接所需要的信息
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";
// 靜態(tài)代碼塊, 隨著類的加載而加載
static{
try {
//注冊驅(qū)動
Class.forName(DRIVERNAME);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// 獲取連接的靜態(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);
}
}
插入記錄
/**
* 插入數(shù)據(jù)
* @throws SQLException
*/
@Test
public void testInsert() throws SQLException {
// 通過工具類獲取連接
Connection connection = JDBCUtils.getConnection();
// 獲取 Statement
Statement statement = connection.createStatement();
// 編寫 SQL
String sql = "insert into jdbc_user values(null,'布萊爾','123','2020/1/1')";
// 執(zhí)行 SQL
int i = statement.executeUpdate(sql);
System.out.println(i);
// 關(guān)閉流
JDBCUtils.close(connection,statement);
}
更新記錄
/**
* 修改 id 為 1 的用戶名為張三
*/
@Test
public void testUpdate() throws SQLException {
Connection connection = JDBCUtils.getConnection();
Statement statement = connection.createStatement();
String sql = "update jdbc_user set username = '張三' where id = 1";
statement.executeUpdate(sql);
JDBCUtils.close(connection, statement);
}
刪除記錄
/**
* 刪除 id 為 3 和 4 的記錄
* @throws SQLException
*/
@Test
public void testDelete() throws SQLException {
Connection connection = JDBCUtils.getConnection();
Statement statement = connection.createStatement();
statement.executeUpdate("delete from jdbc_user where id in(3,4)");
JDBCUtils.close(connection,statement);
}
查詢記錄
/**
* 查詢姓名為張三的一條記錄
* @throws SQLException
*/
@Test
public void testDelete() throws SQLException {
// 獲取連接對象
Connection connection = JDBCUtils.getConnection();
// 獲取Statement對象
Statement statement = connection.createStatement();
String sql = "SELECT * FROM jdbc_user WHERE username = '張三';";
ResultSet resultSet = statement.executeQuery(sql);
// 處理結(jié)果集
while(resultSet.next()){
// 通過列名 獲取字段信息
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
String birthday = resultSet.getString("birthday");
System.out.println(id+" "+username+" " + password +" " + birthday);
}
// 釋放資源
JDBCUtils.close(connection,statement,resultSet);
}
SQL注入問題
-
什么是SQL注入?
SQL 注入:比如用戶輸入的密碼和 SQL 語句進行字符串拼接時,用戶輸入的內(nèi)容作為了 SQL 語句語法的一部分,改變了原有 SQL 真正的意義。
-
Example
在語句 select * from jdbc_user where username = 'abc' and password = 'abc' 后面加上 or '1'='1',那么無論 username 和 password 的值時什么,都會查詢了所有記錄,然后就會成功登錄。
public class TestLogin01 {
/**
* 用戶登錄案例
* 使用 Statement 字符串拼接的方式完成查詢
* @param args
*/
public static void main(String[] args) throws SQLException {
// 獲取連接
Connection connection = JDBCUtils.getConnection();
// 獲取 Statement對象
Statement statement = connection.createStatement();
// 獲取用戶輸入的用戶名和密碼
Scanner sc = new Scanner(System.in);
System.out.println("請輸入用戶名: ");
String name = sc.nextLine();
System.out.println("請輸入密碼: ");
String pass = sc.nextLine();
System.out.println(pass);
// 拼接 SQL,執(zhí)行查詢
String sql = "select * from jdbc_user " + "where username = " + " '" + name +"' " +" and password = " +" '" + pass +"'";
System.out.println(sql);
ResultSet resultSet = statement.executeQuery(sql);
// 處理結(jié)果集,判斷結(jié)果集是否為空
if(resultSet.next()){
System.out.println("登錄成功! 歡迎您: " + name);
}else {
System.out.println("登錄失敗!");
}
// 釋放資源
JDBCUtils.close(connection, statement, resultSet);
}
}

-
如何解決
要解決 SQL 注入就不能讓用戶輸入的密碼和 SQL 語句進行簡單的字符串拼接。
預(yù)處理對象
-
PreparedStatement 接口
預(yù)處理對象:因為有預(yù)先編譯的功能,可以提高 SQL 的執(zhí)行效率,還可以有效的防止 SQL 注入的問題,安全性更高。
PreparedStatement 接口是 Statement 接口的子接口,繼承于父接口中所有的方法。它是一個預(yù)編譯的 SQL 語句對象。
預(yù)編譯:是指 SQL 語句被預(yù)編譯,并存儲在 PreparedStatement 對象中。然后可以使用此對象多次高效地執(zhí)行該語句。
public class TestLogin02 {
/**
* 使用預(yù)編譯對象 PrepareStatement 完成登錄案例
* @param args
* @throws SQLException
*/
public static void main(String[] args) throws SQLException {
// 獲取連接
Connection connection = JDBCUtils.getConnection();
// 獲取用戶輸入的用戶名和密碼
Scanner sc = new Scanner(System.in);
System.out.println("請輸入用戶名: ");
String name = sc.nextLine();
System.out.println("請輸入密碼: ");
String pass = sc.nextLine();
System.out.println(pass);
/*
* 1) 編寫 SQL 語句,未知內(nèi)容使用?占位,
* 2) 獲得 PreparedStatement 對象
* 3) 設(shè)置實際參數(shù):setXxx( 占位符的位置, 真實的值)
* 4) 執(zhí)行參數(shù)化 SQL 語句
* 5)關(guān)閉資源
*
*/
// 編寫 SQL 使用 ? 占位符方式
String sql = "select * from jdbc_user where username = ? and password = ?";
// 1. 獲取 PrepareStatement 預(yù)編譯對象
PreparedStatement ps = connection.prepareStatement(sql);
// 2. 設(shè)置占位符參數(shù)
ps.setString(1, name);
ps.setString(2, pass);
// 3. 執(zhí)行查詢并處理結(jié)果集
ResultSet resultSet = ps.executeQuery();
if(resultSet.next()){
System.out.println("登錄成功,歡迎您: " + name);
}else{
System.out.println("登錄失敗!");
}
// 釋放資源
JDBCUtils.close(connection, ps, resultSet);
}
}
-
Statement 與 PreparedStatement的區(qū)別?
Statement 用于執(zhí)行靜態(tài) SQL 語句,在執(zhí)行時,必須指定一個事先準備好的SQL語句。
PrepareStatement 是預(yù)編譯的 SQL 語句對象,語句中可以包含動態(tài)參數(shù) “?”,在執(zhí)行時可以為 “?” 動態(tài)設(shè)置參數(shù)值。
PrepareStatement 可以減少編譯次數(shù)提高數(shù)據(jù)庫性能。

JDBC 控制事務(wù)
-
數(shù)據(jù)準備
在 MySQL 中準備好以下數(shù)據(jù)
-- 創(chuàng)建賬戶表
CREATE TABLE account(
-- 主鍵
id INT PRIMARY KEY AUTO_INCREMENT,
-- 姓名
NAME VARCHAR(10),
-- 轉(zhuǎn)賬金額
money DOUBLE
);
-- 添加兩個用戶
INSERT INTO account (NAME, money) VALUES ('tom', 1000), ('jack', 1000);
步驟
- 獲取連接
- 開啟事務(wù) setAutoCommit(false)
- 獲取到 PreparedStatement,執(zhí)行兩次更新操作
- 正常情況下提交事務(wù) commit()
- 出現(xiàn)異常回滾事務(wù) rollback()
- 最后關(guān)閉資源
public class JDBCTransaction {
public static void main(String[] args) {
Connection con = null;
PreparedStatement ps = null;
try {
// 獲取連接
con = JDBCUtils.getConnection();
// 開啟事務(wù)
con.setAutoCommit(false);
// 獲取到 PreparedStatement 執(zhí)行兩次更新操作
// 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);
// jack 賬戶增加 500
ps = con.prepareStatement("update account set money = money + ? where name = ? ");
ps.setDouble(1, 500.0);
ps.setString(2, "jack");
ps.executeUpdate();
// 正常情況下提交事務(wù)
con.commit();
System.out.println("轉(zhuǎn)賬成功");
} catch (SQLException e) {
e.printStackTrace();
try {
// 出現(xiàn)異?;貪L事務(wù)
con.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
// 最后關(guān)閉資源
JDBCUtils.close(con,ps);
}
}
}