JDBC技術: java數據庫連接技術!
接口:
Connection: 連接對象
Statement: 執行命令對象: 把SQL語句發送到數據庫執行
ResultSet: (在線式)結果集接口, 必須要保持與數據庫的連接!
開發步驟:
1. 建項目,引入數據庫驅動包
2. 加載驅動
Class.forName(..);
3. 獲取連接對象
4. 創建執行sql語句的stmt對象; 寫sql
5. 執行sql
a) 更新 delete/insert/update
i. executeUpdate();
b) 查詢 select
i. executeQuery();
6. 關閉/異常
一: 預編譯sql處理(防止sql注入)
-- 創建數據庫
CREATE DATABASE jdbc_demo DEFAULT CHARACTER SET utf8;i
-- 創建表
USE jdbc_demo;
CREATE TABLE admin(
id INT PRIMARY KEY AUTO_INCREMENT,
userName VARCHAR(20),
pwd VARCHAR(20)
)
-Statement 執行SQL命令
|-- CallableStatement, 執行存儲過程
|-- PreparedStatement 預編譯SQL語句執行
使用預編譯SQL語句的命令對象,好處:
1. 避免了頻繁sql拼接 (可以使用佔位符)
2. 可以防止sql注入
public class App {
// 連接參數
//private String url = "jdbc:mysql://localhost:3306/jdbc_demo";
private String url = "jdbc:mysql:///jdbc_demo";
private String user = "root";
private String password = "root";
private Connection con;
private Statement stmt;
private PreparedStatement pstmt;
private ResultSet rs;
// 1. 沒有使用防止sql注入的案例
@Test
public void testLogin() {
// 1.0 模擬登陸的用戶名,密碼
String userName = "tom";
//String pwd = "8881";
String pwd = " ' or 1=1 -- ";
// SQL語句
String sql = "select * from admin where userName='"+userName+"' and pwd='"+pwd+"' ";
System.out.println(sql);
try {
// 1.1 加載驅動,創建連接
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection(url, user, password);
// 1.2 創建stmt對象
stmt = con.createStatement();
// 1.3 執行查詢
rs = stmt.executeQuery(sql);
// 業務判斷
if (rs.next()) {
System.out.println("登陸成功, 編號:" + rs.getInt("id"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 1.4 關閉
try {
rs.close();
stmt.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 2. 使用PreparedStatement, 防止sql注入
@Test
public void testLogin2() {
// 1.0 模擬登陸的用戶名,密碼
String userName = "tom";
//String pwd = "8881";
String pwd = " ' or 1=1 -- ";
// SQL語句
String sql = "select * from admin where userName=? and pwd=? ";
try {
// 1.1 加載驅動,創建連接
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection(url, user, password);
// 1.2 創建pstmt對象
pstmt = con.prepareStatement(sql); // 對sql語句預編譯
// 設置佔位符值
pstmt.setString(1, userName);
pstmt.setString(2, pwd);
// 1.3 執行
rs = pstmt.executeQuery();
if (rs.next()) {
System.out.println("登陸成功," + rs.getInt("id"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 1.4 關閉
try {
rs.close();
pstmt.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
二:存儲過程
-- 定義分隔符
DELIMITER $$
CREATE PROCEDURE proc_login()
BEGIN
SELECT * FROM admin;
END $$
-- 調用
CALL proc_login;
public class App_call {
// 全局參數
private Connection con;
private Statement stmt;
private PreparedStatement pstmt;
private CallableStatement cstmt; // 存儲過程
private ResultSet rs;
// 程序中調用存儲過程
@Test
public void testCall() throws Exception {
try {
//1 . 創建連接
con = JdbcUtil.getConnection();
//2. 創建執行存儲過程的stmt對象
CallableStatement cstmt = con.prepareCall("CALL proc_login");
//3. 執行(存儲過程)
rs = cstmt.executeQuery();
// 遍歷結果,測試
if (rs.next()) {
String name = rs.getString("userName");
String pwd = rs.getString("pwd");
// 測試
System.out.println(name + pwd);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
三:批處理
很多時候,需要批量執行sql語句!
需求:批量保存信息!
設計:
AdminDao
Public void save(List<Admin list){ // 目前用這種方式
// 循環
// 保存 (批量保存)
}
技術:
|-- Statement
批處理相關方法
void addBatch(String sql) 添加批處理
void clearBatch() 清空批處理
int[] executeBatch() 執行批處理
實現:
Admin.java 實體類封裝數據
AdminDao.java 封裝所有的與數據庫的操作
App.java 測試
public class Admin {
private String userName;
private String pwd;
public class App {
// 測試批處理操作
@Test
public void testBatch() throws Exception {
// 模擬數據
List<Admin> list = new ArrayList<Admin>();
for (int i=1; i<21; i++) {
Admin admin = new Admin();
admin.setUserName("Jack" + i);
admin.setPwd("888" + i);
list.add(admin);
}
// 保存
AdminDao dao = new AdminDao();
dao.save(list);
}
}
// 封裝所有的與數據庫的操作
public class AdminDao {
// 全局參數
private Connection con;
private PreparedStatement pstmt;
private ResultSet rs;
// 批量保存管理員
public void save(List<Admin> list) {
// SQL
String sql = "INSERT INTO admin(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);
}
}
}
四:插入數據,獲取自增長值
部門與員工,
一對多的關係
ü 設計數據庫:
員工表 (外鍵表) 【員工表有一個外鍵字段,引用了部門表的主鍵】
部門表(主鍵表)
總體思路:
保存員工及其對應的部門!
步驟:
1. 先保存部門
2. 再得到部門主鍵,再保存員工
部門
CREATE TABLE dept(
deptId INT PRIMARY KEY AUTO_INCREMENT,
deptName VARCHAR(20)
);
-- 員工
CREATE TABLE employee(
empId INT PRIMARY KEY AUTO_INCREMENT,
empName VARCHAR(20),
dept_id INT -- 外鍵字段
);
-- 給員工表添加外鍵約束
ALTER TABLE employee ADD CONSTRAINT FK_employee_dept_deptId
FOREIGN KEY(dept_id) REFERENCES dept(deptId) ;
public class EmpDao {
private Connection con;
private PreparedStatement pstmt;
private ResultSet rs;
// 保存員工,同時保存關聯的部門
public void save(Employee emp){
// 保存部門
String sql_dept = "insert into dept(deptName) values(?)";
// 保存員工
String sql_emp = "INSERT INTO employee (empName,dept_id) VALUES (?,?)";
// 部門id
int deptId = 0;
try {
// 連接
con = JdbcUtil.getConnection();
/*****保存部門,獲取自增長*******/
// 【一、需要指定返回自增長標記】
pstmt = con.prepareStatement(sql_dept,Statement.RETURN_GENERATED_KEYS);
// 設置參數
pstmt.setString(1, emp.getDept().getDeptName());
// 執行
pstmt.executeUpdate();
// 【二、獲取上面保存的部門子增長的主鍵】
rs = pstmt.getGeneratedKeys();
// 得到返回的自增長字段
if (rs.next()) {
deptId = rs.getInt(1);
}
/*****保存員工*********/
pstmt = con.prepareStatement(sql_emp);
// 設置參數
pstmt.setString(1, emp.getEmpName());
pstmt.setInt(2, deptId);
pstmt.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.closeAll(con, pstmt, rs);
}
}
}
五:事物
事務使指一組最小邏輯操作單元,裏面有多個操作組成。 組成事務的每一部分必須要同時提交成功,如果有一個操作失敗,整個操作就回滾。
事務ACID特性
l原子性(Atomicity)
原子性是指事務是一個不可分割的工作單位,事務中的操作要麼都發生,要麼都不發生。
l一致性(Consistency)
事務必須使數據庫從一個一致性狀態變換到另外一個一致性狀態。
l 隔離性(Isolation)
事務的隔離性是多個用戶併發訪問數據庫時,數據庫爲每一個用戶開啓的事務,不能被其他事務的操作數據所幹擾,多個併發事務之間要相互隔離。
l 持久性(Durability)
持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即使數據庫發生故障也不應該對其有任何影響
事務的特性:
原子性,是一個最小邏輯操作單元 !
一致性,事務過程中,數據處於一致狀態。
持久性, 事務一旦提交成功,對數據的更改會反映到數據庫中。
隔離性, 事務與事務之間是隔離的。
事物所需的方法:
void setAutoCommit(boolean autoCommit) ; 設置事務是否自動提交,如果設置爲false,表示手動提交事務。
void commit() (); 手動提交事務
void rollback() ; 回滾(出現異常時候,所有已經執行成功的代碼需要回退到事務開始前的狀態。)
Savepoint setSavepoint(String name)
賬戶表
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
accountName VARCHAR(20),
money DOUBLE
);
-- 轉賬
UPDATE account SET money=money-1000 WHERE accountName='張三';
UPDATE account SET money=money+1000 WHERE accountName='李四';
public class AccountDao {
// 全局參數
private Connection con;
private PreparedStatement pstmt;
// 1. 轉賬,沒有使用事務
public void trans1() {
String sql_zs = "UPDATE account SET money=money-1000 WHERE accountName='張三';";
String sql_ls = "UPDATE account SET money=money+1000 WHERE accountName='李四';";
try {
con = JdbcUtil.getConnection(); // 默認開啓的隱士事務
con.setAutoCommit(true);
/*** 第一次執行SQL ***/
pstmt = con.prepareStatement(sql_zs);
pstmt.executeUpdate();
/*** 第二次執行SQL ***/
pstmt = con.prepareStatement(sql_ls);
pstmt.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.closeAll(con, pstmt, null);
}
}
// 2. 轉賬,使用事務
public void trans2() {
String sql_zs = "UPDATE account SET money=money-1000 WHERE accountName='張三';";
String sql_ls = "UPDATE1 account SET money=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) {
}
}
}
// 3. 轉賬,使用事務, 回滾到指定的代碼段
public void trans() {
// 定義個標記
Savepoint sp = null;
// 第一次轉賬
String sql_zs1 = "UPDATE account SET money=money-1000 WHERE accountName='張三';";
String sql_ls1 = "UPDATE account SET money=money+1000 WHERE accountName='李四';";
// 第二次轉賬
String sql_zs2 = "UPDATE account SET money=money-500 WHERE accountName='張三';";
String sql_ls2 = "UPDATE1 account SET money=money+500 WHERE accountName='李四';";
try {
con = JdbcUtil.getConnection(); // 默認開啓的隱士事務
con.setAutoCommit(false); // 設置事務手動提交
/*** 第一次轉賬 ***/
pstmt = con.prepareStatement(sql_zs1);
pstmt.executeUpdate();
pstmt = con.prepareStatement(sql_ls1);
pstmt.executeUpdate();
// 回滾到這個位置?
sp = con.setSavepoint();
/*** 第二次轉賬 ***/
pstmt = con.prepareStatement(sql_zs2);
pstmt.executeUpdate();
pstmt = con.prepareStatement(sql_ls2);
pstmt.executeUpdate();
} catch (Exception e) {
try {
// 回滾 (回滾到指定的代碼段)
con.rollback(sp);
} catch (SQLException e1) {
}
e.printStackTrace();
} finally {
try {
// 提交
con.commit();
} catch (SQLException e) {
}
JdbcUtil.closeAll(con, pstmt, null);
}
}
}