一、前言
1.1課程需要哪些前置技術
技術 | 版本 | 備註 |
idea | 2022.2 | 最新版本 |
jdk | 1.8 | |
mysql-jdbc驅動 | 8.0.27 | 8.0.25+ |
druid | 1.1.21 | |
mysql | 8.0.25 |
- 前置技術
- 需要軟件
- mysql軟件安裝(8+版本)
- mysql可視化工具安裝
- idea工具安裝(推薦2022版本)
- SQL語句
- 掌握數據庫連接命令
- 掌握基本的DDL,DQL,DML等命令
- 掌握數據庫事務概念
- Java基礎語法
- 多態機制
- 基本容器使用(集合和數組等)
- 泛型
- 反射等技術
- 需要軟件
1.2 課程學習路線設計
學 悟 行
二、全新JDBC技術概述
2.1 jdbc技術概念和理解
-
jdbc技術理解
-
jdbc概念總結
- jdbc是(Java Database Connectivity)單詞的縮寫,翻譯爲java連接數據庫
- jdbc是java程序連接數據庫的技術統稱
- jdbc由java語言的規範(接口)和各個數據庫廠商的實現驅動(jar)組成
- jdbc是一種典型的面向接口編程
- jdbc優勢
- 只需要學習jdbc規範接口的方法,即可操作所有的數據庫軟件
- 項目中期切換數據庫軟件,只需要更換對應的數據庫驅動jar包,不需要更改代碼
2.2 jdbc核心api和使用路線
-
jdbc技術組成
-
jdk下jdbc規範接口, 存儲在java.sql和javax.sql包中的api
爲了項目代碼的可移植性,可維護性,SUN公司從最初就制定了Java程序連接各種數據庫的統一接口規範。這樣的話,不管是連接哪一種DBMS軟件,Java代碼可以保持一致性。
-
各個數據庫廠商提供的驅動jar包
因爲各個數據庫廠商的DBMS軟件各有不同,那麼內部如何通過sql實現增、刪、改、查等管理數據,只有這個數據庫廠商自己更清楚,因此把接口規範的實現交給各個數據庫廠商自己實現。
jar包是什麼?
java程序打成的一種壓縮包格式,你可以將這些jar包引入你的項目中,然後你可以使用這個java程序中類和方法以及屬性了!
-
-
涉及具體核心類和接口
- DriverManager
- 將第三方數據庫廠商的實現驅動jar註冊到程序中
- 可以根據數據庫連接信息獲取connection
- Connection
- 和數據庫建立的連接,在連接對象上,可以多次執行數據庫curd動作
- 可以獲取statement和 preparedstatement,callablestatement對象
- Statement | PreparedStatement | CallableStatement
- 具體發送SQL語句到數據庫管理軟件的對象
- 不同發送方式稍有不同! **preparedstatement **使用爲重點!
- Result
- 面向對象思維的產物(抽象成數據庫的查詢結果表)
- 存儲DQL查詢數據庫結果的對象
- 需要我們進行解析,獲取具體的數據庫數據
- DriverManager
-
jdbc api使用路線
2.3爲什麼選擇全新 8+版本mysql-jdbc驅動?
- 支持8.0+版本mysql數據管理軟件
- mysql軟件知名版本迭代時間
版本號 | 迭代時間 | 大小 |
mysql-8.0.25 | 4月 30, 2021 | 435.7M |
mysql-5.7.25 | 1月 10, 2019 | 387.7M |
mysql-5.5.30 | 9月 19, 2012 | 201.5M |
-
mysql 8.x版本數據庫性能提升介紹
性能提升級。官方表示MySQL 8.0 的速度要比 MySQL 5.7 快 2 倍。
MySQL 8.0 在讀/寫工作負載、IO 密集型工作負載、以及高競爭工作負載時相比MySQL5.7有更好的性能。
-
支持java jdbc規範 4.2+版本新特性
-
java jdbc規範驅動版本和更新時間
Year JDBC Version JSR Specification JDK Implementation
2017 JDBC 4.3 JSR 221 Java SE 9
**2014 JDBC 4.2 JSR 221 ** Java SE 8
2011 JDBC 4.1 JSR 221 Java SE 7
2006 JDBC 4.0 JSR 221 Java SE 6
2001 JDBC 3.0 JSR 54 JDK 1.4
1999 JDBC 2.1 JDK 1.2
1997 JDBC 1.2 JDK 1.1
-
jdbc規範版本更新內容(瞭解)
-
**JDBC 4.3 中引入的主要新功能包括:**
添加了對分片的支持
添加了 java.sql.連接生成器接口
添加了 java.sql.ShardigKey 接口
添加了 java.sql.分片密鑰生成器接口
添加了.sql.XA 連接生成器接口
添加了 javax.sql.池連接生成器接口
**JDBC 4.2 中引入的主要新功能包括:**
添加了對引用光標的支持
添加了 java.sql.驅動程序操作接口
添加了.sql.SQLType 接口
添加 java.sql.JDBCType 枚舉
一些 JDBC 接口更改
**JDBC 4.1 中引入的主要新功能包括:**
添加了對“使用資源試用”語句的支持
增強的日期值和時間戳值
從 Java 對象到 JDBC 類型的其他映射
一些 JDBC 接口更改
**JDBC 4.0 中引入的主要新功能包括:**
自動加載爪哇.sql.驅動程序
數據類型支持
國家字符集轉換支持
支持
由於 JDBC 4.3 API 是向後兼容的,因此將 Java SE 9 或更高版本與 JDBC 4.2、4.1、4.0
或 3.0 驅動程序一起使用沒有問題,只要您不使用 JDBC 4.3 API 中引入的新方法或類。
-
支持 jdk1.8版本語法變更新特性
Connector/J 8.0是專門爲在Java 8平臺上運行而創建的。
衆所周知,Java8與早期的Java版本高度兼容,
但還是存在少量不兼容性,所以,驅動技術版本,儘量選擇支持jdk 8.0+!
-
支持全新的驅動api,增加自動時區選擇和默認utf-8編碼格式等配置
三、全新JDBC核心API
3.1 引入mysql-jdbc驅動jar
-
驅動jar版本選擇
我們選擇版本 8.0.27版本
mysql版本 | 推薦驅動版本 | 備註 |
mysql 5.5.x | 5.0.x | com.mysql.jdbc.Driver |
mysql 5.7.x | 5.1.x | com.mysql.jdbc.Driver |
msyql 8.x | 8.0.x | 建議: 8.0.25+省略時區設置com.mysql.cj.jdbc.Driver |
-
java工程導入依賴
-
項目創建lib文件夾
-
導入驅動依賴jar包
-
jar包右鍵-添加爲項目依賴
-
3.2 jdbc基本使用步驟分析(6步)
- 註冊驅動
- 獲取連接
- 創建發送sql語句對象
- 發送sql語句,並獲取返回結果
- 結果集解析
- 資源關閉
3.3基於statement演示查詢
- 準備數據庫數據
CREATE DATABASE atguigu;
USE atguigu;
CREATE TABLE t_user(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用戶主鍵',
account VARCHAR(20) NOT NULL UNIQUE COMMENT '賬號',
PASSWORD VARCHAR(64) NOT NULL COMMENT '密碼',
nickname VARCHAR(20) NOT NULL COMMENT '暱稱');
INSERT INTO t_user(account,PASSWORD,nickname) VALUES
('root','123456','經理'),('admin','666666','管理員');
-
查詢目標
查詢全部用戶信息,進行控制檯輸出
-
基於statement實現查詢(演示步驟)
/**
* Description: 利用jdbc技術,完成用戶數據查詢工作
*
* TODO: 步驟總結 (6步)
* 1. 註冊驅動
* 2. 獲取連接
* 3. 創建statement
* 4. 發送SQL語句,並獲取結果
* 5. 結果集解析
* 6. 關閉資源
*/
public class JdbcBasePart {
public static void main(String[] args) throws SQLException {
//1.註冊驅動
/**
* TODO: 注意
* Driver -> com.mysql.cj.jdbc.Driver
*/
DriverManager.registerDriver(new Driver());
//2.獲取連接
/**
* TODO: 注意
* 面向接口編程
* java.sql 接口 = 實現類
* connection 使用java.sql.Connection接口接收
*/
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/atguigu",
"root",
"root");
//3.創建小車
Statement statement = connection.createStatement();
//4.發送SQL語句
String sql = "select id,account,password,nickname from t_user ;";
ResultSet resultSet = statement.executeQuery(sql);
//5.結果集解析
while (resultSet.next()){
int id = resultSet.getInt("id");
String account = resultSet.getString("account");
String password = resultSet.getString("password");
String nickname = resultSet.getString("nickname");
System.out.println(id+"::"+account+"::"+password+"::"+nickname);
}
//6.關閉資源 【先開後關】
resultSet.close();
statement.close();
connection.close();
}
}
3.4基於statement方式問題
-
本案例目標
- 明確jdbc流程和詳細講解使用(註冊驅動,獲取連接,發送語句,結果解析)
- 發現問題,引出preparedstatement
-
準備數據庫數據
上個案例相同的數據庫
CREATE DATABASE atguigu;
USE atguigu;
CREATE TABLE t_user(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用戶主鍵',
account VARCHAR(20) NOT NULL UNIQUE COMMENT '賬號',
PASSWORD VARCHAR(64) NOT NULL COMMENT '密碼',
nickname VARCHAR(20) NOT NULL COMMENT '暱稱');
INSERT INTO t_user(account,PASSWORD,nickname) VALUES
('root','123456','經理'),('admin','666666','管理員');
-
演示目標
模擬登錄,控制檯輸入賬號和密碼,判斷是否登陸成功成功!
-
基於statement實現模擬登錄
/**
* Description: 輸入賬號密碼,模擬用戶登錄!
*/
public class JdbcStatementLoginPart {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1.輸入賬號和密碼
Scanner scanner = new Scanner(System.in);
String account = scanner.nextLine();
String password = scanner.nextLine();
scanner.close();
//2.jdbc的查詢使用
// 註冊驅動
// DriverManager.registerDriver(new Driver());
// 問題:註冊兩次驅動
// 1. DriverManager.registerDriver() 方法本身會註冊一次
// 2. Driver.static{ DriverManager.registerDriver(new Driver())} 靜態代碼塊也會註冊一次
// 解決:只想註冊一次驅動
// 只觸發靜態代碼塊即可!Driver
/**
* 類加載: java文件 -> 編譯 -> 【 class字節碼文件 --> 類加載 --> jvm虛擬中 --> Class對象】
* 類加載具體步驟: 加載 【class文件轉成對象加載到虛擬機中】->
* 連接 【驗證(檢查類文件) -> 準備 (靜態變量賦默認值) -> 解析 (調用靜態代碼塊) 】 ->
* 初始化 -> (賦真實值)
* 以下7種方式會觸發類加載:
* 1. new關鍵字
* 2. 調用靜態屬性
* 3. 調用靜態方法
* 4. 接口 包含1.8 新特性 default關鍵字
* 5. 反射 【Class.forName() 類名.class】
* 6. 子類調用會觸發父類的靜態代碼塊
* 7. 觸發類的入口方法main
*/
//註冊一次驅動
Class.forName("com.mysql.cj.jdbc.Driver");
/**
* 重寫: 爲了子類擴展父類的方法!父類也間接的規範了子類方法的參數和返回!
* 重載: 重載一般應用在第三方的工具類上,爲了方便用戶多種方式傳遞參數形式!簡化形式!
*/
/**
* 三個參數:
* String URL: 連接數據庫地址
* String user: 連接數據庫用戶名
* String password: 連接數據庫用戶對應的密碼
* 數據庫URL語法:
* JDBC:
* ip port
* jdbc:mysql | jdbc:oracle :// 127.0.0.1 | localhost : 3306 / 數據庫名
* jdbc:mysql://localhost:3306/day01
* 192.168.33.45
* jdbc:mysql://192.168.33.45/3306/day01
* 當前電腦的省略寫法! 注意:本機和端口3306
* jdbc:mysql://localhost:3306/day01 = jdbc:mysql:///day01
*
* 兩個參數:
* String URL : 寫法還是jdbc的路徑寫法!
* Properties : 就是一個參數封裝容器!至少要包含 user / password key!存儲連接賬號信息!
*
* 一個參數:
* String URL: URl可以攜帶目標地址,可以通過?分割,在後面key=value&key=value形式傳遞參數
* jdbc:mysql:///day01?user=root&password=123456
* 擴展路徑參數(瞭解):
* serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true
*
*/
//獲取連接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");
//固定方法固定劑
//創建statement
Statement statement = connection.createStatement();
//執行SQL語句 [動態SQL語句,需要字符串拼接]
String sql = "select * from t_user where account = '"+account+"' and password = '"+password+"' ;";
/**
* ResultSet 結果集對象 = executeQuery(DQL語句)
* int 響應行數 = executeUpdate(非DQL語句)
*/
ResultSet resultSet = statement.executeQuery(sql);
//ResultSet == 小海豚 你必須有面向對象的思維:Java是面向對象編程的語言 OOP!
/**
*
* TODO:1.需要理解ResultSet的數據結構和小海豚查詢出來的是一樣,需要在腦子裏構建結果表!
* TODO:2.有一個光標指向的操作數據行,默認指向第一行的上邊!我們需要移動光標,指向行,在獲取列即可!
* boolean = next()
* false: 沒有數據,也不移動了!
* true: 有更多行,並且移動到下一行!
* 推薦:推薦使用if 或者 while循環,嵌套next方法,循環和判斷體內獲取數據!
* if(next()){獲取列的數據!} || while(next()){獲取列的數據!}
*
*TODO:3.獲取當前行列的數據!
* get類型(int columnIndex | String columnLabel)
* 列名獲取 //lable 如果沒有別名,等於列名, 有別名label就是別名,他就是查詢結果的標識!
* 列的角標 //從左到右 從1開始! 數據庫全是從1開始!
*/
//進行結果集對象解析
if (resultSet.next()){
//只要向下移動,就是有數據 就是登錄成功!
System.out.println("登錄成功!");
}else{
System.out.println("登錄失敗!");
}
//關閉資源
resultSet.close();
statement.close();
connection.close();
}
}
-
存在問題
-
SQL語句需要字符串拼接,比較麻煩
-
只能拼接字符串類型,其他的數據庫類型無法處理
-
可能發生注入攻擊
動態值充當了SQL語句結構,影響了原有的查詢結果!
-
3.5 基於preparedStatement方式優化
利用preparedStatement解決上述案例注入攻擊和SQL語句拼接問題! (重點掌握)
/**
* @Author 趙偉風
* Description: 使用預編譯Statement解決注入攻擊問題
*/
public class JdbcPreparedStatementLoginPart {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1.輸入賬號和密碼
Scanner scanner = new Scanner(System.in);
String account = scanner.nextLine();
String password = scanner.nextLine();
scanner.close();
//2.jdbc的查詢使用
//註冊驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//獲取連接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");
//創建preparedStatement
//connection.createStatement();
//TODO 需要傳入SQL語句結構
//TODO 要的是SQL語句結構,動態值的部分使用 ? , 佔位符!
//TODO ? 不能加 '?' ? 只能替代值,不能替代關鍵字和容器名
String sql = "select * from t_user where account = ? and password = ? ;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//佔位符賦值
//給佔位符賦值! 從左到右,從1開始!
/**
* int 佔位符的下角標
* object 佔位符的值
*/
preparedStatement.setObject(2,password);
preparedStatement.setObject(1,account);
//這哥們內部完成SQL語句拼接!
//執行SQL語句即可
ResultSet resultSet = preparedStatement.executeQuery();
//preparedStatement.executeUpdate()
//進行結果集對象解析
if (resultSet.next()){
//只要向下移動,就是有數據 就是登錄成功!
System.out.println("登錄成功!");
}else{
System.out.println("登錄失敗!");
}
//關閉資源
resultSet.close();
preparedStatement.close();
connection.close();
}
}
3.6基於preparedStatement演示curd
- 數據庫數據插入
/**
* 插入一條用戶數據!
* 賬號: test
* 密碼: test
* 暱稱: 測試
*/
@Test
public void testInsert() throws Exception{
//註冊驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//獲取連接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");
//TODO: 切記, ? 只能代替 值!!!!! 不能代替關鍵字 特殊符號 容器名
String sql = "insert into t_user(account,password,nickname) values (?,?,?);";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//佔位符賦值
preparedStatement.setString(1, "test");
preparedStatement.setString(2, "test");
preparedStatement.setString(3, "測試");
//發送SQL語句
int rows = preparedStatement.executeUpdate();
//輸出結果
System.out.println(rows);
//關閉資源close
preparedStatement.close();
connection.close();
}
- 數據庫數據修改
/**
* 修改一條用戶數據!
* 修改賬號: test的用戶,將nickname改爲tomcat
*/
@Test
public void testUpdate() throws Exception{
//註冊驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//獲取連接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");
//TODO: 切記, ? 只能代替 值!!!!! 不能代替關鍵字 特殊符號 容器名
String sql = "update t_user set nickname = ? where account = ? ;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//佔位符賦值
preparedStatement.setString(1, "tomcat");
preparedStatement.setString(2, "test");
//發送SQL語句
int rows = preparedStatement.executeUpdate();
//輸出結果
System.out.println(rows);
//關閉資源close
preparedStatement.close();
connection.close();
}
- 數據庫數據刪除
/**
* 刪除一條用戶數據!
* 根據賬號: test
*/
@Test
public void testDelete() throws Exception{
//註冊驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//獲取連接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");
//TODO: 切記, ? 只能代替 值!!!!! 不能代替關鍵字 特殊符號 容器名
String sql = "delete from t_user where account = ? ;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//佔位符賦值
preparedStatement.setString(1, "test");
//發送SQL語句
int rows = preparedStatement.executeUpdate();
//輸出結果
System.out.println(rows);
//關閉資源close
preparedStatement.close();
connection.close();
}
- 數據庫數據查詢
/**
* 查詢全部數據!
* 將數據存到List<Map>中
* map -> 對應一行數據
* map key -> 數據庫列名或者別名
* map value -> 數據庫列的值
* TODO: 思路分析
* 1.先創建一個List<Map>集合
* 2.遍歷resultSet對象的行數據
* 3.將每一行數據存儲到一個map對象中!
* 4.將對象存到List<Map>中
* 5.最終返回
*
* TODO:
* 初體驗,結果存儲!
* 學習獲取結果表頭信息(列名和數量等信息)
*/
@Test
public void testQueryMap() throws Exception{
//註冊驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//獲取連接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");
//TODO: 切記, ? 只能代替 值!!!!! 不能代替關鍵字 特殊符號 容器名
String sql = "select id,account,password,nickname from t_user ;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//佔位符賦值 本次沒有佔位符,省略
//發送查詢語句
ResultSet resultSet = preparedStatement.executeQuery();
//創建一個集合
List<Map> mapList = new ArrayList<>();
//獲取列信息對象
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
while (resultSet.next()) {
Map map = new HashMap();
for (int i = 1; i <= columnCount; i++) {
map.put(metaData.getColumnLabel(i), resultSet.getObject(i));
}
mapList.add(map);
}
System.out.println(mapList);
//關閉資源close
preparedStatement.close();
connection.close();
resultSet.close();
}
3.7 preparedStatement使用方式總結
- 使用步驟總結
//1.註冊驅動
//2.獲取連接
//3.編寫SQL語句
//4.創建preparedstatement並且傳入SQL語句結構
//5.佔位符賦值
//6.發送SQL語句,並且獲取結果
//7.結果集解析
//8.關閉資源
- 使用API總結
//1.註冊驅動
方案1: 調用靜態方法,但是會註冊兩次
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
方案2: 反射觸發
Class.forName("com.mysql.cj.jdbc.Driver");
//2.獲取連接
Connection connection = DriverManager.getConnection();
3 (String url,String user,String password)
2 (String url,Properties info(user password))
1 (String url?user=賬號&password=密碼 )
//3.創建statement
//靜態
Statement statement = connection.createStatement();
//預編譯
PreparedStatement preparedstatement = connection.preparedStatement(sql語句結構);
//4.佔位符賦值
preparedstatement.setObject(?的位置 從左到右 從1開始,值)
//5.發送sql語句獲取結果
int rows = executeUpdate(); //非DQL
Resultset = executeQuery(); //DQL
//6.查詢結果集解析
//移動光標指向行數據 next(); if(next()) while(next())
//獲取列的數據即可 get類型(int 列的下角標 從1開始 | int 列的label (別名或者列名))
//獲取列的信息 getMetadata(); ResultsetMetaData對象 包含的就是列的信息
getColumnCount(); | getCloumnLebal(index)
//7.關閉資源
close();
四、全新JDBC擴展提升
4.1 自增長主鍵回顯實現
-
功能需求
- java程序獲取插入數據時mysql維護自增長維護的主鍵id值,這就是主鍵回顯
- 作用: 在多表關聯插入數據時,一般主表的主鍵都是自動生成的,所以在插入數據之前無法知道這條數據的主鍵,但是從表需要在插入數據之前就綁定主表的主鍵,這是可以使用主鍵回顯技術:
-
功能實現
繼續沿用之前的表數據
/**
* 返回插入的主鍵!
* 主鍵:數據庫幫助維護的自增長的整數主鍵!
* @throws Exception
*/
@Test
public void returnPrimaryKey() throws Exception{
//1.註冊驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//2.獲取連接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?user=root&password=root");
//3.編寫SQL語句結構
String sql = "insert into t_user (account,password,nickname) values (?,?,?);";
//4.創建預編譯的statement,傳入SQL語句結構
/**
* TODO: 第二個參數填入 1 | Statement.RETURN_GENERATED_KEYS
* 告訴statement攜帶回數據庫生成的主鍵!
*/
PreparedStatement statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
//5.佔位符賦值
statement.setObject(1,"towgog");
statement.setObject(2,"123456");
statement.setObject(3,"二狗子");
//6.執行SQL語句 【注意:不需要傳入SQL語句】 DML
int i = statement.executeUpdate();
//7.結果集解析
System.out.println("i = " + i);
//一行一列的數據!裏面就裝主鍵值!
ResultSet resultSet = statement.getGeneratedKeys();
resultSet.next();
int anInt = resultSet.getInt(1);
System.out.println("anInt = " + anInt);
//8.釋放資源
statement.close();
connection.close();
}
4.2 批量數據插入性能提升
- 功能需求
- 批量數據插入優化
- 提升大量數據插入效率
- 功能實現
/**
*
* 批量細節:
* 1.url?rewriteBatchedStatements=true
* 2.insert 語句必須使用 values
* 3.語句後面不能添加分號;
* 4.語句不能直接執行,每次需要裝貨 addBatch() 最後 executeBatch();
*
* 批量插入優化!
* @throws Exception
*/
@Test
public void batchInsertYH() throws Exception{
//1.註冊驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//2.獲取連接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?rewriteBatchedStatements=true",
"root","root");
//3.編寫SQL語句結構
String sql = "insert into t_user (account,password,nickname) values (?,?,?)";
//4.創建預編譯的statement,傳入SQL語句結構
/**
* TODO: 第二個參數填入 1 | Statement.RETURN_GENERATED_KEYS
* 告訴statement攜帶回數據庫生成的主鍵!
*/
long start = System.currentTimeMillis();
PreparedStatement statement = connection.prepareStatement(sql);
for (int i = 0; i < 10000; i++) {
//5.佔位符賦值
statement.setObject(1,"ergouzi"+i);
statement.setObject(2,"lvdandan");
statement.setObject(3,"驢蛋蛋"+i);
//6.裝車
statement.addBatch();
}
//發車! 批量操作!
statement.executeBatch();
long end = System.currentTimeMillis();
System.out.println("消耗時間:"+(end - start));
//7.結果集解析
//8.釋放資源
connection.close();
}
4.3 jdbc中數據庫事務實現
-
章節目標
使用jdbc代碼,添加數據庫事務動作!
開啓事務
事務提交 / 事務回滾
-
事務概念回顧
// 事務概念
數據庫事務就是一種SQL語句執行的緩存機制,不會單條執行完畢就更新數據庫數據,最終根據緩存內的多條語句執行結果統一判定!
一個事務內所有語句都成功及事務成功,我們可以觸發commit提交事務來結束事務,更新數據!
一個事務內任意一條語句失敗,及事務失敗,我們可以觸發rollback回滾結束事務,
數據回到事務之前狀態!
舉個例子:
臨近高考,你好喫懶做,偶爾還瞎花錢,父母也只會說'你等着!',待到高考完畢!
成績600+,翻篇,慶祝!
成績200+,翻舊賬,男女混合雙打!
//優勢
允許我們在失敗情況下,數據迴歸到業務之前的狀態!
//場景
**一個業務****涉及****多條修改****數據庫語句!**
例如: 經典的轉賬案例,轉賬業務(加錢和減錢)
批量刪除(涉及多個刪除)
批量添加(涉及多個插入)
// 事務特性
1. 原子性(Atomicity)原子性是指事務是一個不可分割的工作單位,事務中的操作要麼都發生,要麼都不發生。
2. 一致性(Consistency)事務必須使數據庫從一個一致性狀態變換到另外一個一致性狀態。
3. 隔離性(Isolation)事務的隔離性是指一個事務的執行不能被其他事務干擾,即一個事務內部的操作及使用的數據對併發的其他事務是隔離的,併發執行的各個事務之間不能互相干擾。
4. 持久性(Durability)持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來的其他操作和數據庫故障不應該對其有任何影響
// 事務類型
自動提交 : 每條語句自動存儲一個事務中,執行成功自動提交,執行失敗自動回滾! (MySQL)
手動提交: 手動開啓事務,添加語句,手動提交或者手動回滾即可!
// sql開啓事務方式
針對自動提交: 關閉自動提交即可,多條語句添加以後,最終手動提交或者回滾! (推薦)
SET autocommit = off; //關閉當前連接自動事務提交方式
# 只有當前連接有效
# 編寫SQL語句即可
SQL
SQL
SQL
#手動提交或者回滾 【結束當前的事務】
COMMIT / ROLLBACK ;
手動開啓事務: 開啓事務代碼,添加SQL語句,事務提交或者事務回滾! (不推薦)
// 呼應jdbc技術
try{
connection.setAutoCommit(false); //關閉自動提交了
//注意,只要當前connection對象,進行數據庫操作,都不會自動提交事務
//數據庫動作!
//statement - 單一的數據庫動作 c u r d
connection.commit();
}catch(Execption e){
connection.rollback();
}
- 數據庫表數據
-- 繼續在atguigu的庫中創建銀行表
CREATE TABLE t_bank(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '賬號主鍵',
account VARCHAR(20) NOT NULL UNIQUE COMMENT '賬號',
money INT UNSIGNED COMMENT '金額,不能爲負值') ;
INSERT INTO t_bank(account,money) VALUES
('ergouzi',1000),('lvdandan',1000);
-
代碼結構設計
-
jdbc事務實現
- 測試類
/**
* Description: 測試類
*/
public class BankTest {
@Test
public void testBank() throws Exception {
BankService bankService = new BankService();
bankService.transfer("ergouzi", "lvdandan",
500);
}
}
- BankService
/**
* Description: bank表業務類,添加轉賬業務
*/
public class BankService {
/**
* 轉賬業務方法
* @param addAccount 加錢賬號
* @param subAccount 減錢賬號
* @param money 金額
*/
public void transfer(String addAccount,String subAccount, int money) throws ClassNotFoundException, SQLException {
System.out.println("addAccount = " + addAccount + ", subAccount = " + subAccount + ", money = " + money);
//註冊驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//獲取連接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");
int flag = 0;
//利用try代碼塊,調用dao
try {
//開啓事務(關閉事務自動提交)
connection.setAutoCommit(false);
BankDao bankDao = new BankDao();
//調用加錢 和 減錢
bankDao.addMoney(addAccount,money,connection);
System.out.println("--------------");
bankDao.subMoney(subAccount,money,connection);
flag = 1;
//不報錯,提交事務
connection.commit();
}catch (Exception e){
//報錯回滾事務
connection.rollback();
throw e;
}finally {
connection.close();
}
if (flag == 1){
System.out.println("轉賬成功!");
}else{
System.out.println("轉賬失敗!");
}
}
}
- BankDao
/**
* Description: 數據庫訪問dao類
*/
public class BankDao {
/**
* 加錢方法
* @param account
* @param money
* @param connection 業務傳遞的connection和減錢是同一個! 纔可以在一個事務中!
* @return 影響行數
*/
public int addMoney(String account, int money,Connection connection) throws ClassNotFoundException, SQLException {
String sql = "update t_bank set money = money + ? where account = ? ;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//佔位符賦值
preparedStatement.setObject(1, money);
preparedStatement.setString(2, account);
//發送SQL語句
int rows = preparedStatement.executeUpdate();
//輸出結果
System.out.println("加錢執行完畢!");
//關閉資源close
preparedStatement.close();
return rows;
}
/**
* 減錢方法
* @param account
* @param money
* @param connection 業務傳遞的connection和加錢是同一個! 纔可以在一個事務中!
* @return 影響行數
*/
public int subMoney(String account, int money,Connection connection) throws ClassNotFoundException, SQLException {
String sql = "update t_bank set money = money - ? where account = ? ;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//佔位符賦值
preparedStatement.setObject(1, money);
preparedStatement.setString(2, account);
//發送SQL語句
int rows = preparedStatement.executeUpdate();
//輸出結果
System.out.println("減錢執行完畢!");
//關閉資源close
preparedStatement.close();
return rows;
}
}
五、國貨之光Druid連接池技術使用
5.1 連接性能消耗問題分析
**貪污和浪費是極大的犯罪 **
- 毛爺爺
connection可以複用! (年紀輕輕你就範了 浪費連接的罪啊!)
5.2 數據庫連接池作用
總結缺點:
(1)不使用數據庫連接池,每次都通過DriverManager獲取新連接,用完直接拋棄斷開,連接的利用率太低,太浪費。
(2)對於數據庫服務器來說,壓力太大了。我們數據庫服務器和Java程序對連接數也無法控制,很容易導致數據庫服務器崩潰。
我們就希望能管理連接。
- 我們可以建立一個連接池,這個池中可以容納一定數量的連接對象,一開始,我們可以先替用戶先創建好一些連接對象,等用戶要拿連接對象時,就直接從池中拿,不用新建了,這樣也可以節省時間。然後用戶用完後,放回去,別人可以接着用。
- 可以提高連接的使用率。當池中的現有的連接都用完了,那麼連接池可以向服務器申請新的連接放到池中。
- 直到池中的連接達到“最大連接數”,就不能在申請新的連接了,如果沒有拿到連接的用戶只能等待。
5.3 市面常見連接池產品和對比
JDBC 的數據庫連接池使用 javax.sql.DataSource接口進行規範,所有的第三方連接池都實現此接口,自行添加具體實現!也就是說, 所有連接池獲取連接的和回收連接方法都一樣,不同的只有性能和擴展功能!
- DBCP 是Apache提供的數據庫連接池,速度相對c3p0較快,但因自身存在BUG
- C3P0 是一個開源組織提供的一個數據庫連接池,速度相對較慢,穩定性還可以
- Proxool 是sourceforge下的一個開源項目數據庫連接池,有監控連接池狀態的功能,穩定性較c3p0差一點
- Druid 是阿里提供的數據庫連接池,據說是集DBCP 、C3P0 、Proxool 優點於一身的數據庫連接池,妥妥國貨之光!!!!
5.4國貨之光druid連接池使用
記得導入druid工具類jar
- 硬編碼方式(瞭解,不推薦)
/**
* 創建druid連接池對象,使用硬編碼進行核心參數設置!
* 必須參數: 賬號
* 密碼
* url
* driverClass
* 非必須參數:
* 初始化個數
* 最大數量等等 不推薦設置
*/
@Test
public void druidHard() throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
//設置四個必須參數
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setUrl("jdbc:mysql:///day01");
//獲取連接
Connection connection = dataSource.getConnection();
// JDBC的步驟
//回收連接
connection.close();
}
-
軟編碼方式
-
外部配置
存放在src/druid.properties
-
# druid連接池需要的配置參數,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///atguigu
- druid聲明代碼
/**
* 不直接在java代碼編寫配置文件!
* 利用工廠模式,傳入配置文件對象,創建連接池!
* @throws Exception
*/
@Test
public void druidSoft() throws Exception {
Properties properties = new Properties();
InputStream ips = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(ips);
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
}
- druid配置(瞭解)
配置 | 缺省 | 說明 |
---|---|---|
name | 配置這個屬性的意義在於,如果存在多個數據源,監控的時候可以通過名字來區分開來。 如果沒有配置,將會生成一個名字,格式是:”DataSource-” + System.identityHashCode(this) | |
jdbcUrl | 連接數據庫的url,不同數據庫不一樣。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto | |
username | 連接數據庫的用戶名 | |
password | 連接數據庫的密碼。如果你不希望密碼直接寫在配置文件中,可以使用ConfigFilter。詳細看這裏:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter | |
driverClassName | 根據url自動識別 這一項可配可不配,如果不配置druid會根據url自動識別dbType,然後選擇相應的driverClassName(建議配置下) | |
initialSize | 0 | 初始化時建立物理連接的個數。初始化發生在顯示調用init方法,或者第一次getConnection時 |
maxActive | 8 | 最大連接池數量 |
maxIdle | 8 | 已經不再使用,配置了也沒效果 |
minIdle | 最小連接池數量 | |
maxWait | 獲取連接時最大等待時間,單位毫秒。配置了maxWait之後,缺省啓用公平鎖,併發效率會有所下降,如果需要可以通過配置useUnfairLock屬性爲true使用非公平鎖。 | |
poolPreparedStatements | false | 是否緩存preparedStatement,也就是PSCache。PSCache對支持遊標的數據庫性能提升巨大,比如說oracle。在mysql下建議關閉。 |
maxOpenPreparedStatements | -1 | 要啓用PSCache,必須配置大於0,當大於0時,poolPreparedStatements自動觸發修改爲true。在Druid中,不會存在Oracle下PSCache佔用內存過多的問題,可以把這個數值配置大一些,比如說100 |
validationQuery | 用來檢測連接是否有效的sql,要求是一個查詢語句。如果validationQuery爲null,testOnBorrow、testOnReturn、testWhileIdle都不會其作用。 | |
testOnBorrow | true | 申請連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。 |
testOnReturn | false | 歸還連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能 |
testWhileIdle | false | 建議配置爲true,不影響性能,並且保證安全性。申請連接的時候檢測,如果空閒時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效。 |
timeBetweenEvictionRunsMillis | 有兩個含義: 1)Destroy線程會檢測連接的間隔時間2)testWhileIdle的判斷依據,詳細看testWhileIdle屬性的說明 | |
numTestsPerEvictionRun | 不再使用,一個DruidDataSource只支持一個EvictionRun | |
minEvictableIdleTimeMillis | ||
connectionInitSqls | 物理連接初始化的時候執行的sql | |
exceptionSorter | 根據dbType自動識別 當數據庫拋出一些不可恢復的異常時,拋棄連接 | |
filters | 屬性類型是字符串,通過別名的方式配置擴展插件,常用的插件有: 監控統計用的filter:stat日誌用的filter:log4j防禦sql注入的filter:wall | |
proxyFilters | 類型是List,如果同時配置了filters和proxyFilters,是組合關係,並非替換關係 |
六、全新JDBC使用優化以及工具類封裝
6.1 jdbc工具類封裝v1.0
我們封裝一個工具類,內部包含連接池對象,同時對外提供連接的方法和回收連接的方法!
外部配置文件
位置: src/druid.properties
# druid連接池需要的配置參數,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///atguigu
工具類代碼
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCToolsVersion1 {
private static DataSource ds;
static{//靜態代碼塊,JDBCToolsVersion1類初始化執行
try {
Properties pro = new Properties();
pro.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return ds.getConnection();//這麼寫,不能保證同一個線程,兩次getConnection()得到的是同一個Connection對象
//如果不能保證是同一個連接對象,就無法保證事務的管理
}
public static void free(Connection conn) throws SQLException {
conn.setAutoCommit(true);
conn.close();//還給連接池
}
}
6.2 jdbc工具類封裝v.2.0
優化工具類v1.0版本,考慮事務的情況下!如何一個線程的不同方法獲取同一個連接!
ThreadLocal的介紹:
JDK 1.2的版本中就提供java.lang.ThreadLocal,爲解決多線程程序的併發問題提供了一種新的思路。
使用這個工具類可以很簡潔地編寫出優美的多線程程序。通常用來在在多線程中管理共享數據庫連接、Session等
ThreadLocal用於保存某個線程共享變量,原因是在Java中,每一個線程對象中都有一個ThreadLocalMap<ThreadLocal, Object>,其key就是一個ThreadLocal,而Object即爲該線程的共享變量。而這個map是通過ThreadLocal的set和get方法操作的。對於同一個static ThreadLocal,不同線程只能從中get,set,remove自己的變量,而不會影響其他線程的變量。
1、ThreadLocal對象.get: 獲取ThreadLocal中當前線程共享變量的值。
2、ThreadLocal對象.set: 設置ThreadLocal中當前線程共享變量的值。
3、ThreadLocal對象.remove: 移除ThreadLocal中當前線程共享變量的值。
v2.0版本工具類
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/*
這個工具類的作用就是用來給所有的SQL操作提供“連接”,和釋放連接。
這裏使用ThreadLocal的目的是爲了讓同一個線程,在多個地方getConnection得到的是同一個連接。
這裏使用DataSource的目的是爲了(1)限制服務器的連接的上限(2)連接的重用性等
*/
public class JDBCTools {
private static DataSource ds;
private static ThreadLocal<Connection> tl = new ThreadLocal<>();
static{//靜態代碼塊,JDBCToolsVersion1類初始化執行
try {
Properties pro = new Properties();
pro.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
Connection connection = tl.get();
if(connection == null){//當前線程還沒有拿過連接,就給它從數據庫連接池拿一個
connection = ds.getConnection();
tl.set(connection);
}
return connection;
}
public static void free() throws SQLException {
Connection connection = tl.get();
if(connection != null){
tl.remove();
connection.setAutoCommit(true);//避免還給數據庫連接池的連接不是自動提交模式(建議)
connection.close();
}
}
}
**注意: **修改轉賬業務,使用此工具類
6.3 高級應用層封裝BaseDao
基本上每一個數據表都應該有一個對應的DAO接口及其實現類,發現對所有表的操作(增、刪、改、查)代碼重複度很高,所以可以抽取公共代碼,給這些DAO的實現類可以抽取一個公共的父類,我們稱爲BaseDao
public abstract class BaseDao {
/*
通用的增、刪、改的方法
String sql:sql
Object... args:給sql中的?設置的值列表,可以是0~n
*/
protected int update(String sql,Object... args) throws SQLException {
// 創建PreparedStatement對象,對sql預編譯
Connection connection = JDBCTools.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
//設置?的值
if(args != null && args.length>0){
for(int i=0; i<args.length; i++) {
ps.setObject(i+1, args[i]);//?的編號從1開始,不是從0開始,數組的下標是從0開始
}
}
//執行sql
int len = ps.executeUpdate();
ps.close();
//這裏檢查下是否開啓事務,開啓不關閉連接,業務方法關閉!
//沒有開啓事務的話,直接回收關閉即可!
if (connection.getAutoCommit()) {
//回收
JDBCTools.free();
}
return len;
}
/*
通用的查詢多個Javabean對象的方法,例如:多個員工對象,多個部門對象等
這裏的clazz接收的是T類型的Class對象,
如果查詢員工信息,clazz代表Employee.class,
如果查詢部門信息,clazz代表Department.class,
*/
protected <T> ArrayList<T> query(Class<T> clazz,String sql, Object... args) throws Exception {
// 創建PreparedStatement對象,對sql預編譯
Connection connection = JDBCTools.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
//設置?的值
if(args != null && args.length>0){
for(int i=0; i<args.length; i++) {
ps.setObject(i+1, args[i]);//?的編號從1開始,不是從0開始,數組的下標是從0開始
}
}
ArrayList<T> list = new ArrayList<>();
ResultSet res = ps.executeQuery();
/*
獲取結果集的元數據對象。
元數據對象中有該結果集一共有幾列、列名稱是什麼等信息
*/
ResultSetMetaData metaData = res.getMetaData();
int columnCount = metaData.getColumnCount();//獲取結果集列數
//遍歷結果集ResultSet,把查詢結果中的一條一條記錄,變成一個一個T 對象,放到list中。
while(res.next()){
//循環一次代表有一行,代表有一個T對象
T t = clazz.newInstance();//要求這個類型必須有公共的無參構造
//把這條記錄的每一個單元格的值取出來,設置到t對象對應的屬性中。
for(int i=1; i<=columnCount; i++){
//for循環一次,代表取某一行的1個單元格的值
Object value = res.getObject(i);
//這個值應該是t對象的某個屬性值
//獲取該屬性對應的Field對象
// String columnName = metaData.getColumnName(i);//獲取第i列的字段名
String columnName = metaData.getColumnLabel(i);//獲取第i列的字段名或字段的別名
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);//這麼做可以操作private的屬性
field.set(t, value);
}
list.add(t);
}
res.close();
ps.close();
//這裏檢查下是否開啓事務,開啓不關閉連接,業務方法關閉!
//沒有開啓事務的話,直接回收關閉即可!
if (connection.getAutoCommit()) {
//回收
JDBCTools.free();
}
return list;
}
protected <T> T queryBean(Class<T> clazz,String sql, Object... args) throws Exception {
ArrayList<T> list = query(clazz, sql, args);
if(list == null || list.size() == 0){
return null;
}
return list.get(0);
}
}
七、基於CMS項目JDBC實戰練習
7.1 cms項目介紹和導入
-
項目介紹
利用JavaSE技術,進行控制檯輸出的客戶管理系統! 主要功能讓包含客戶展示,客戶刪除,客戶添加,客戶修改,退出系統!
添加客戶
修改客戶
展示客戶列表
刪除客戶
退出系統
-
項目導入
-
打開項目
-
配置jdk
-
7.2基於cms項目添加數據庫相關配置
- 準備數據庫腳本
-- 員工表
CREATE TABLE t_customer(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '客戶主鍵',
NAME VARCHAR(20) COMMENT '客戶名稱',
gender VARCHAR(4) COMMENT '客戶性別',
age INT COMMENT '客戶年齡',
salary DOUBLE(8,1) COMMENT '客戶工資',
phone VARCHAR(11) COMMENT '客戶電話')
-
添加配置文件
位置: src下, druid.properties
# druid連接池需要的配置參數,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///atguigu
- 導入jdbcv2.0工具類
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/*
這個工具類的作用就是用來給所有的SQL操作提供“連接”,和釋放連接。
這裏使用ThreadLocal的目的是爲了讓同一個線程,在多個地方getConnection得到的是同一個連接。
這裏使用DataSource的目的是爲了(1)限制服務器的連接的上限(2)連接的重用性等
*/
public class JDBCTools {
private static DataSource ds;
private static ThreadLocal<Connection> tl = new ThreadLocal<>();
static{//靜態代碼塊,JDBCToolsVersion1類初始化執行
try {
Properties pro = new Properties();
pro.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
Connection connection = tl.get();
if(connection == null){//當前線程還沒有拿過連接,就給它從數據庫連接池拿一個
connection = ds.getConnection();
tl.set(connection);
}
return connection;
}
public static void free() throws SQLException {
Connection connection = tl.get();
if(connection != null){
tl.remove();
connection.setAutoCommit(true);//避免還給數據庫連接池的連接不是自動提交模式(建議)
connection.close();
}
}
}
- 導入baseDao工具類
public abstract class BaseDao {
/*
通用的增、刪、改的方法
String sql:sql
Object... args:給sql中的?設置的值列表,可以是0~n
*/
protected int update(String sql,Object... args) throws SQLException {
// 創建PreparedStatement對象,對sql預編譯
Connection connection = JDBCTools.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
//設置?的值
if(args != null && args.length>0){
for(int i=0; i<args.length; i++) {
ps.setObject(i+1, args[i]);//?的編號從1開始,不是從0開始,數組的下標是從0開始
}
}
//執行sql
int len = ps.executeUpdate();
ps.close();
//這裏檢查下是否開啓事務,開啓不關閉連接,業務方法關閉!
//沒有開啓事務的話,直接回收關閉即可!
if (connection.getAutoCommit()) {
//回收
JDBCTools.free();
}
return len;
}
/*
通用的查詢多個Javabean對象的方法,例如:多個員工對象,多個部門對象等
這裏的clazz接收的是T類型的Class對象,
如果查詢員工信息,clazz代表Employee.class,
如果查詢部門信息,clazz代表Department.class,
*/
protected <T> ArrayList<T> query(Class<T> clazz,String sql, Object... args) throws Exception {
// 創建PreparedStatement對象,對sql預編譯
Connection connection = JDBCTools.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
//設置?的值
if(args != null && args.length>0){
for(int i=0; i<args.length; i++) {
ps.setObject(i+1, args[i]);//?的編號從1開始,不是從0開始,數組的下標是從0開始
}
}
ArrayList<T> list = new ArrayList<>();
ResultSet res = ps.executeQuery();
/*
獲取結果集的元數據對象。
元數據對象中有該結果集一共有幾列、列名稱是什麼等信息
*/
ResultSetMetaData metaData = res.getMetaData();
int columnCount = metaData.getColumnCount();//獲取結果集列數
//遍歷結果集ResultSet,把查詢結果中的一條一條記錄,變成一個一個T 對象,放到list中。
while(res.next()){
//循環一次代表有一行,代表有一個T對象
T t = clazz.newInstance();//要求這個類型必須有公共的無參構造
//把這條記錄的每一個單元格的值取出來,設置到t對象對應的屬性中。
for(int i=1; i<=columnCount; i++){
//for循環一次,代表取某一行的1個單元格的值
Object value = res.getObject(i);
//這個值應該是t對象的某個屬性值
//獲取該屬性對應的Field對象
// String columnName = metaData.getColumnName(i);//獲取第i列的字段名
String columnName = metaData.getColumnLabel(i);//獲取第i列的字段名或字段的別名
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);//這麼做可以操作private的屬性
field.set(t, value);
}
list.add(t);
}
res.close();
ps.close();
//這裏檢查下是否開啓事務,開啓不關閉連接,業務方法關閉!
//沒有開啓事務的話,直接回收關閉即可!
if (connection.getAutoCommit()) {
//回收
JDBCTools.free();
}
return list;
}
protected <T> T queryBean(Class<T> clazz,String sql, Object... args) throws Exception {
ArrayList<T> list = query(clazz, sql, args);
if(list == null || list.size() == 0){
return null;
}
return list.get(0);
}
}
7.3 基於cms項目實戰
- customerService
package com.atguigu.cms.service;
import com.atguigu.cms.dao.CustomerDao;
import com.atguigu.cms.javabean.Customer;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* 這是一個具有管理功能的功能類. 內部數據不允許外部隨意修改, 具有更好的封裝性.
*/
public class CustomerService {
private CustomerDao customerDao = new CustomerDao();
/**
* 用途:返回所有客戶對象
* 返回:集合
*/
public List<Customer> getList() {
try {
return customerDao.queryList();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 用途:添加新客戶
* 參數:customer指定要添加的客戶對象
*/
public void addCustomer(Customer customer) {
try {
customerDao.insertCustomer(customer);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 用途:返回指定id的客戶對象記錄
* 參數: id 就是要獲取的客戶的id號.
* 返回:封裝了客戶信息的Customer對象
*/
public Customer getCustomer(int id) {
try {
return customerDao.queryById(id);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 修改指定id號的客戶對象的信息
* @param id 客戶id
* @param cust 對象
* @return 修改成功返回true, false表明指定id的客戶未找到
*/
public boolean modifyCustomer(int id, Customer cust) {
int rows = 0;
try {
rows = customerDao.updateCustomer(cust);
} catch (SQLException e) {
throw new RuntimeException(e);
}
return rows > 0;
}
/**
* 用途:刪除指定id號的的客戶對象記錄
* 參數: id 要刪除的客戶的id號
* 返回:刪除成功返回true;false表示沒有找到
*/
public boolean removeCustomer(int id) {
int rows = 0;
try {
rows = customerDao.deleteCustomer(id);
} catch (SQLException e) {
throw new RuntimeException(e);
}
return rows > 0;
}
}
- customerDao
package com.atguigu.cms.dao;
import com.atguigu.cms.javabean.Customer;
import com.atguigu.cms.utils.BaseDao;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* Description: 客戶進行數據庫操作的類
*/
public class CustomerDao extends BaseDao {
public List<Customer> queryList() throws Exception {
ArrayList<Customer> list = query(Customer.class, "select * from t_customer");
return list;
}
public void insertCustomer(Customer customer) throws SQLException {
int rows = update("insert into t_customer(name,gender,age,salary,phone) values (?,?,?,?,?)",
customer.getName(), customer.getGender(),customer.getAge(),customer.getSalary(),customer.getPhone());
}
public Customer queryById(int id) throws Exception {
Customer customer = queryBean(Customer.class, "select * from t_customer where id = ?", id);
return customer;
}
public int deleteCustomer(int id) throws SQLException {
return update("delete from t_customer where id =?", id);
}
public int updateCustomer(Customer cust) throws SQLException {
return update("update t_customer set name = ? , gender = ? , age = ? ," +
"salary = ? , phone = ? where id = ? ;", cust.getName(), cust.getGender(),cust.getAge(), cust.getSalary(), cust.getPhone(), cust.getId());
}
}