JDBC是指資料庫連線技術,用於java連線mySQL等資料庫。本文詳細介紹了尚矽谷課程中JDBC的學習內容和補充知識。
概述
- java語言只提供規範介面,存在於java.sql.javax.sql包下,然後資料庫軟體根據java提供的規範實現具體的驅動程式碼(jar)
- jar包是java程式打成的一種壓縮包格式,只要匯入就可以使用對應方法
學習思路:(可以學完再看)
-
六大基本步驟獲取連線,包括直接輸入字串的Statement和改進版的PreparedStatement(透過佔位符解決了容易SQL攻擊的問題)
-
JDBC的增刪改查,其中插入資料需要考慮主鍵自增長、批次插入效率低的問題
-
建立資料庫事務(基本特徵是關閉了自動提交,要同時完成多個操作後再一起提交,如轉賬過程先增加A賬戶的錢,再扣除B賬戶的錢,一定要一起提交,防止出現只成功一件事)
-
連線池,最佳化建立連線的過程(每件事都去連線很繁瑣,浪費資源,Druid連線池)
- 硬編碼:透過設定引數的方式
- 軟編碼:透過讀取外部配置檔案的方式(讀取方法使用了類載入器,補充說明在末尾)
-
封裝連線工具類(透過靜態程式碼塊的方式保證連線池只被建立一次,而不是每次呼叫方法都建立一個新的連線池物件。)
- V1.0:不同方法呼叫拿到的是不同連線物件
- V2.0:利用執行緒本地量的方法,保證同一個執行緒不同方法拿到的是同一個連線
-
完整工具類封裝(連線池最佳化了建立連線,那麼把增刪改查操作也封裝一下吧)
- executeUpdate(實現資料的更新操作,包括增,刪,改)
- executeQuery(實現資料的查詢操作,透過反射機制,輸入模板物件,返回查詢出的物件列表)
基本步驟
- 註冊驅動,將依賴的jar包進行安裝
- 建立連線 connection
- 建立傳送SQL語句的物件statement
- statement物件去傳送SQL資料到資料庫獲取返回結果
- 解析結果集
- 銷燬資源
mySQL端 建立t_user表
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','管理員');
java端獲取資料
public class StatementQuery {
public static void main(String[] args) throws SQLException {
//1.註冊驅動
DriverManager.registerDriver(new Driver());//8+驅動要選擇帶cj的
//2. 獲取連線,需要資料庫ip地址(127.0.0.1),資料庫埠號(3306),賬號(root),密碼,連線資料庫的名稱(atguigu)
//jdbc:資料庫廠商名//ip:埠號/資料庫名
Connection connection=DriverManager.
getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
//3. 建立statement
Statement statement = connection.createStatement();
//4. 傳送SQL語句
String sql="select * 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();
}
}
模擬使用者登入
- 模擬使用者登入
- 鍵盤輸入事件收集賬號密碼資訊
- 輸入賬號密碼進行查詢驗證是否登入成功
public class SatetementUserLoginPart {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//0. 獲取使用者輸入資訊
Scanner scanner = new Scanner(System.in);
System.out.println("請輸入賬號");
String account = scanner.nextLine();
System.out.println("請輸入密碼");
String password = scanner.nextLine();
//1.註冊驅動,有兩種方案
/*方案一--------------
//DriverManager.registerDriver(new Driver());//會註冊兩次驅動,可以考慮僅觸發靜態程式碼塊
//類載入機制:類記載時刻會觸發靜態程式碼塊,可以透過new,靜態方法,靜態屬性等觸發
*/
//方案二-------------
Class.forName("com.mysql.cj.jdbc.Driver");//觸發類載入,觸發靜態程式碼塊的呼叫
//優點是字串可以提取到外部的配置檔案,便於更換資料驅動,會更加靈活
//2. 獲取資料庫連線,共有三種方法
//該方法是一個過載方法,允許開發者用不同的形式傳入資料庫連線的核心引數
//三個引數-------------
Connection connection=DriverManager.
getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
//jdbc:mysql:///atguigu 如果本機和3306可以省略為///
//兩個引數--------------
/*
Properties info=new Properties();
info.put("user","root");
info.put("password","root");
DriverManager.getConnection("jdbc:mysql:///atguigu",info);
//一個引數--------------
//jdbc:資料庫廠商名//ip:埠號/資料庫名?user=root&password=root
DriverManager.getConnection("jdbc:mysql:///atguigu?user=root&password=root");
//其他可選資訊,如果是sql8.0以後的版本可以省略serverTimezone=Asia/Shanghai&UseUnicode=true&characterEncoding=utf8&useSSL=true
*/
//3. 建立傳送SQL語句的Statetment物件
Statement statement = connection.createStatement();
//4. 傳送SQL語句
String sql="SELECT * FROM t_user WHERE account='"+account+"'AND PASSWORD='"+password+"';";
//executeUpdate用於更新資料,返回影響的函式,executeQuery負責返回查詢結果,返回結果封裝物件
ResultSet resultSet = statement.executeQuery(sql);
//5.查詢結果集解析,resultSet給出的是逐行的物件,使用next可以逐行提取下一個,遍歷完畢時返回false
/*僅查詢的方案
while(resultSet.next()){
//游標已經移動,此時利用getInt\getString讀取該行的資料
//getString輸入可以是index(列的下角標,從左向右從1開始),可以是列名(有別名寫別名)
int id = resultSet.getInt("id");
String account1 = resultSet.getString("account");
String password1 = resultSet.getString("password");
String nickname = resultSet.getString("nickname");
System.out.println(id+"--"+account+"--"+password+"--"+nickname);
}
*/
//考慮實際需求,只需要有一行資料證明就可以登入成功
if(resultSet.next()){
System.out.println("登入成功");
}else {
System.out.println("登入失敗,使用者名稱或密碼錯誤");
}
//6. 關閉資源
resultSet.close();
statement.close();
connection.close();
}
}
存在幾個問題
-
SQL語句需要字串拼接比較麻煩
-
只能拼接字串型別,其他的資料庫型別無法處理
-
可能發生注入攻擊(動態值充當了SQL語句結構)
模擬使用者登入(prepare改進)
public class SPUserLoginPart {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//0. 獲取使用者輸入資訊
Scanner scanner = new Scanner(System.in);
System.out.println("請輸入賬號");
String account = scanner.nextLine();
System.out.println("請輸入密碼");
String password = scanner.nextLine();
//1.註冊驅動,有兩種方案
Class.forName("com.mysql.cj.jdbc.Driver");//觸發類載入,觸發靜態程式碼塊的呼叫
//2. 獲取資料庫連線
Connection connection= DriverManager.
getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
//3. 建立傳送SQL語句的preparedstatetment物件
//(1). 編寫SQL語句結構,動態值部分使用佔位符?替代(2).建立preparedstatetment,傳入動態值
//(3). 動態值 佔位符 賦值? 單獨賦值即可 (4). 傳送
String sql="SELECT * FROM t_user where account=? and password=?;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
/*
引數一:index佔位符的位置,從1開始
引數二:Object佔位符的值,可以是任何型別
*/
preparedStatement.setObject(1,account);
preparedStatement.setObject(2,password);
ResultSet resultSet = preparedStatement.executeQuery();
//5. 結果集解析
if(resultSet.next()){
System.out.println("登入成功");
}else {
System.out.println("登入失敗,使用者名稱或密碼錯誤");
}
//6. 關閉資源
resultSet.close();
preparedStatement.close();
connection.close();
}
}
JDBC實現增刪改查
public class PSCURDPart {
@Test
public void testInsert() throws ClassNotFoundException, SQLException {
//1.註冊驅動,有兩種方案
Class.forName("com.mysql.cj.jdbc.Driver");//觸發類載入,觸發靜態程式碼塊的呼叫
//2. 獲取資料庫連線
Connection connection= DriverManager.
getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
//3. 建立傳送SQL語句的preparedstatetment物件
String sql="insert into t_user(account,password,nickname) values(?,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1,"test");
preparedStatement.setObject(2,"test");
preparedStatement.setObject(3,"二狗");
int rows = preparedStatement.executeUpdate();
//5. 結果集解析
if(rows>0){
System.out.println("插入成功");
}else {
System.out.println("插入失敗");
}
//6. 關閉資源
preparedStatement.close();
connection.close();
}
@Test
public void testUpdate() throws ClassNotFoundException, SQLException {
//1.註冊驅動,有兩種方案
Class.forName("com.mysql.cj.jdbc.Driver");//觸發類載入,觸發靜態程式碼塊的呼叫
//2. 獲取資料庫連線
Connection connection= DriverManager.
getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
//3. 建立傳送SQL語句的preparedstatetment物件
String sql="update t_user set nickname=? where id=?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1,"張三");
preparedStatement.setObject(2,3);
int rows = preparedStatement.executeUpdate();
//5. 結果集解析
if(rows>0){
System.out.println("修改成功");
}else {
System.out.println("修改失敗");
}
//6. 關閉資源
preparedStatement.close();
connection.close();
}
@Test
public void testDelete() throws ClassNotFoundException, SQLException {
//1.註冊驅動,有兩種方案
Class.forName("com.mysql.cj.jdbc.Driver");//觸發類載入,觸發靜態程式碼塊的呼叫
//2. 獲取資料庫連線
Connection connection= DriverManager.
getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
//3. 建立傳送SQL語句的preparedstatetment物件
String sql="delete from t_user where id=?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1,3);
int rows = preparedStatement.executeUpdate();
//5. 結果集解析
if(rows>0){
System.out.println("刪除成功");
}else {
System.out.println("刪除失敗");
}
//6. 關閉資源
preparedStatement.close();
connection.close();
}
//查詢所有使用者資料,並封裝到一個List<Map> 集合中,key=列名,value=列的內容
@Test
public void testSelect() throws ClassNotFoundException, SQLException {
//1.註冊驅動,有兩種方案
Class.forName("com.mysql.cj.jdbc.Driver");//觸發類載入,觸發靜態程式碼塊的呼叫
//2. 獲取資料庫連線
Connection connection= DriverManager.
getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
//3. 建立傳送SQL語句的preparedstatetment物件
String sql="SELECT id,account,password,nickname FROM t_user;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
ResultSet resultSet = preparedStatement.executeQuery();
//5. 結果集解析
List<Map> list=new ArrayList<>();
ResultSetMetaData metaData = resultSet.getMetaData();//獲取當前結果集列的資訊物件
int columnCount = metaData.getColumnCount();//為了水平遍歷
while(resultSet.next()){
Map map=new HashMap();
for (int i = 1; i <columnCount ; i++) {
Object value = resultSet.getObject(i);
String columnLabel = metaData.getColumnLabel(i);//getlabel可以得到別名,name只能別名
map.put(columnLabel,value);
}
list.add(map);
}
System.out.println(list);
//6. 關閉資源
preparedStatement.close();
connection.close();
}
}
靈活插入
主鍵回顯
在多表關聯插入資料時,一般主表的主鍵是自動生成的,所以插入資料前無法獲取主鍵,但是從表需要再插入資料之前就繫結主表的主鍵,這時可以使用主鍵回顯技術
public class PSOthePart {
//t_user 插入一條資料,並獲取資料庫自增長的主鍵
@Test
public void returnPrimaryKey() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection= DriverManager.
getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
String sql="Insert into t_user(account,password,nickname) values(?,?,?);";
//插入資料同時獲取返回主鍵
PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
preparedStatement.setObject(1,"test1");
preparedStatement.setObject(2,"123456");
preparedStatement.setObject(3,"蛋");
int i=preparedStatement.executeUpdate();
if(i>0){
System.out.println("插入成功");
//獲取主鍵的結果集物件,一行一列,id=值
ResultSet resultSet = preparedStatement.getGeneratedKeys();
resultSet.next();
int id=resultSet.getInt(1);
System.out.println("id="+id);
}else{
System.out.println("插入失敗");
}
preparedStatement.close();
connection.close();
}
}
批次插入
public void returnPrimaryKey2() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
// 1.路徑後面新增允許批次操作
Connection connection= DriverManager.
getConnection("jdbc:mysql:///atguigu?rewriteBatchedStatements=true","root","mypassword");
String sql="Insert into t_user(account,password,nickname) values(?,?,?);";
PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
long start=System.currentTimeMillis();
for (int i = 1; i < 10000; i++) {
preparedStatement.setObject(1,"dd"+i);
preparedStatement.setObject(2,"dd"+i);
preparedStatement.setObject(3,"蛋"+i);
preparedStatement.addBatch();//2. 不執行,追加到values後面,批次新增addBatch
}
preparedStatement.executeBatch();//3. 執行批次操作
long end = System.currentTimeMillis();
preparedStatement.close();
connection.close();
}
資料庫事務
-
允許在失敗情況下,資料迴歸到業務之前的狀態!
-
具有原子性(不可切分)、一致性(狀態不變)、隔離性(互不干擾)、永續性(永久性改變)
-
手動提交
實現目標:實現兩個賬戶之間的安全轉賬,如果一方餘額不足不會出現轉賬錯誤(即一方賬戶增加了另一方沒有減少)
業務表的建立
CREATE TABLE t_bank(
id INT PRIMARY KEY AUTO_INCREMENT,
account VARCHAR(20) NOT NULL UNIQUE COMMENT '賬戶',
money INT UNSIGNED COMMENT '金額,不能為負值'
);
INSERT INTO t_bank(account,money) VALUES
('ergouz1',1000),('lvdandan',1000);
BankService.java(實現業務操作,即轉賬)
public class BankService {
@Test
public void start() throws SQLException, ClassNotFoundException {
transfer("lvdandan","ergouz1",500);
}
public void transfer(String addAccount,String subAccount,int money) throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.cj.jdbc.Driver");
// 1.路徑後面新增允許批次操作
Connection connection= DriverManager.
getConnection("jdbc:mysql:///atguigu","root","mypassword");
BankDao bankDao=new BankDao();
try{
connection.setAutoCommit(false);//關閉自動提交,開啟手動提交
bankDao.add(addAccount,money,connection);
System.out.println("----");
bankDao.sub(subAccount,money,connection);
connection.commit();//事務提交
}catch (Exception e){
connection.rollback();
throw e;
}finally {
connection.close();
}
}
}
BankDao.java(業務實現細節,即賬戶錢增加和減少)
public class BankDao {
public void add(String account,int money,Connection connection) throws ClassNotFoundException, SQLException {
String sql="Update t_bank set money=money+? where account=?;";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setObject(1,money);
statement.setObject(2,account);
statement.executeUpdate();
statement.close();
}
public void sub(String account,int money,Connection connection) throws ClassNotFoundException, SQLException {
String sql="Update t_bank set money=money-? where account=?;";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setObject(1,money);
statement.setObject(2,account);
statement.executeUpdate();
statement.close();
}
}
連線池
- 每次獲取新的連線消耗很大,為了節約建立和銷燬連線的效能消耗
- javax.sql.DataSource介面,規範了連線池獲取連線的方法,規範了連線池回收連線的方法,連線池有多種,但都實現了該介面
- 硬編碼:透過設定引數的方式
- 軟編碼:透過讀取外部配置檔案的方式(讀取方法使用了類載入器,補充說明在末尾)
public class DruidUsePart {
//直接使用程式碼設定連線池連線引數等方式
@Test
public void testHard() throws SQLException {
DruidDataSource dataSource=new DruidDataSource();
//連線資料庫驅動類的全限定符 註冊驅動
dataSource.setUrl("jdbc:mysql:///atguigu");
dataSource.setUsername("root");
dataSource.setPassword("mypassword");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setInitialSize(5);//初始化連線數量
dataSource.setMaxActive(10);//最大的數量
DruidPooledConnection connection = dataSource.getConnection();//獲取連線
//資料庫操作
//
connection.close();
}
//讀取外部配置檔案的方法例項化連線物件
public void testSoft() throws Exception {
Properties properties = new Properties();
InputStream ips = DruidUsePart.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(ips);
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
//資料庫操作
//
connection.close();
}
}
druid.properties
#key = value => java properties讀取
#druid的key必須固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=123456
url=jdbc:mysql://127.0.0.1:3306/atguigu
連線工具類封裝
透過靜態程式碼塊的方式保證連線池只被建立一次,而不是每次呼叫方法都建立一個新的連線池物件
V1.0
public class JdbcUtils {
private static DataSource dataSource = null;//連線池物件
static{
//初始化連線池物件
Properties properties = new Properties();
InputStream is = JdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties");
try {
properties.load(is);
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void freeConnection(Connection connection) throws SQLException {
connection.close();//連線池的連線執行回收
}
}
問題:不同方法呼叫拿到的是不同物件,考慮如何同一個執行緒不同方法拿到的是同一個連線
V2.0(執行緒本地量)
利用執行緒本地變數,儲存連線資訊 確保一個執行緒的多個方法可以獲取同一個connection
-
優勢:事務操作的時候server 和 dao 屬於同一個執行緒,不用再傳遞引數
-
大家都可以呼叫getConnection自動獲取的是相同的連線池
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
V2版本程式碼
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class JdbcUtilsV2 {
private static DataSource dataSource = null;//連線池物件
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
static{
//初始化連線池物件
Properties properties = new Properties();
InputStream is = JdbcUtilsV2.class.getClassLoader().getResourceAsStream("druid.properties");
try {
properties.load(is);
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Connection getConnection() throws SQLException {
//先檢視執行緒本地變數中是否存在
Connection connection = threadLocal.get();
if(connection == null){
//執行緒本地變數沒有,連線池獲取
connection = dataSource.getConnection();
threadLocal.set(connection);
}
return dataSource.getConnection();
}
public static void freeConnection() throws SQLException {
Connection connection = threadLocal.get();
if(connection != null){
threadLocal.remove();//清空本地變數資料
connection.setAutoCommit(true);
connection.close();//連線池的連線執行回收
}
}
}
此時的封裝只針對了獲取連線部分,還不夠完善,可以建立一個更加完善的工具類,包括資料庫的增刪改查
完整工具類(修改和查詢)
類程式碼
package utils;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* Description:封裝dao層資料庫重複程式碼
* TODO:封裝兩個方法
* 一個簡化非DQL
* 一個簡化DQL(查詢語句)
*/
public class BaseDao {
/**
* 封裝簡化非DQL語句
* @param sql 帶佔位符的SQL語句
* @param params 佔位符的值 注意:傳入的佔位符的值,必須等於SQL語句?位置的值
* @return 執行影響的行數
*/
public int executeUpdate(String sql, Object... params) throws SQLException {//Object...可變引數傳引數形式,在程式碼中可以作為陣列使用
//獲取連線
Connection connection = JdbcUtilsV2.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//可變引數可當作陣列使用
for (int i = 1; i <= params.length; i++) {
preparedStatement.setObject(i+1, params[i-1]);
}
int rows = preparedStatement.executeUpdate();
preparedStatement.close();
//是否回收連線,需要考慮是不是事務,如果不是事務,則直接回收連線
if (connection.getAutoCommit()) {
//true -> 沒有開啟事物,正常回收連線
JdbcUtilsV2.freeConnection();
}
return rows;
}
/**
* 將查結果封裝到一個實體類集合
* @param clazz 要接值的實體類集合的模板物件
* @param sql 查詢語句,要求類名或者別名等於實體類的屬性名 u_id as uId => uId
* @param params 佔位符的值 要和 ? 位置物件對應
* @return 查詢的實體類集合
* @param <T> 宣告的結果的泛型
* @throws SQLException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws NoSuchFieldException
*/
//第一個<T>是方法泛型,需要呼叫時確定
// 第三個T是指使用反射技術賦值
public <T> List<T> executeQuery(Class<T> clazz,String sql,Object... params) throws SQLException, InstantiationException, IllegalAccessException, NoSuchFieldException {
Connection connection = JdbcUtilsV2.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
if(params != null && params.length != 0) {
for (int i = 1; i <= params.length; i++) {
preparedStatement.setObject(i,params[i-1]);
}
}
ResultSet resultSet = preparedStatement.executeQuery();
List<T> list = new ArrayList<>();
//獲取列的資訊
//TODO:metadata裝的是列的資訊(可以獲取列的名稱根據下角標,可以獲取列的數量)
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
while (resultSet.next()) {
T t = clazz.newInstance();//呼叫類的無參建構函式例項化物件
//自動遍歷列 注意:要從1開始,等於總列數
for (int i = 1; i <= columnCount; i++) {
//獲取指定列下角標的值
Object value = resultSet.getObject(i);
//獲取指定列下角標列的名稱
//getColumnLabel: 會獲取別名,如果沒有定義別名,獲取列名 不要使用getColumnName:只會獲取列名
String key = metaData.getColumnLabel(i);
//反射,給物件的屬性值賦值
Field field = clazz.getDeclaredField(key);//獲取屬性key
field.setAccessible(true);//屬性可以設定,打破private限制
field.set(t,value);//給物件賦值(賦值物件,屬性值),如果是靜態屬性,第一個引數可以為null
}
list.add(t);
}
resultSet.close();
preparedStatement.close();
if(connection.getAutoCommit()){
JdbcUtilsV2.freeConnection();
}
return list;
}
}
測試程式碼
package utils;
import org.junit.Test;
import java.util.List;
/**
* @create 2024-03-{DAY}-{TIME}
*/
public class JdbcCurdPart extends BaseDao {
@Test
public void testInsert() throws Exception {
/**
* t_user表插入一條資料
* account test
* password test
* nickname 二狗子
*/
String sql = "insert into t_user(account,password,nickname) values(?,?,?)";
int i = executeUpdate(sql, "測試333", "333", "ergouzi");
if (i > 0) {
System.out.println("插入成功");
} else {
System.out.println("插入失敗");
}
}
@Test
public void testUpdate() throws Exception {
String sql = "update t_user set nickname = ? where id = ?;";
executeUpdate(sql, "新的nickname", "4");
}
@Test
public void testDelete() throws Exception {
String sql = "delete from t_user where id = ?;";
executeUpdate(sql, 4);
}
@Test
public void testSelect() throws Exception {
String sql = "select id,account,money from t_bank;";
List<Bank> banks = executeQuery(Bank.class, sql);
for (Bank bank : banks) {
System.out.println(bank);
}
}
@Test
public void testSelect1() throws Exception {
String sql = "select * from t_bank;";
executeQuery(Bank.class,sql);
}
}
class Bank{
int id;
String account;
int money;
}
附加知識
這部分內容用於反射、泛型不熟練的進一步理解程式碼
獲取類class物件
目的是執行類的靜態程式碼塊
- Class.forName(類名可以不固定)
使用 Class.forName("com.mysql.cj.jdbc.Driver")
語句是為了載入MySQL JDBC驅動程式。在Java中,類的載入是透過類載入器(ClassLoader)來完成的,Class.forName
方法是一種觸發類載入的方式。這種載入方式主要用於載入類的靜態程式碼塊。
- 動態載入驅動程式: 使用
Class.forName
允許在執行時根據需要載入特定的驅動程式類。這使得你可以在不修改程式碼的情況下,透過配置檔案或其他方式動態更改要使用的資料庫驅動。 - 提高靈活性: 透過將驅動程式類名硬編碼在程式碼中,你的程式碼將直接依賴於特定的資料庫驅動。使用反射可以將驅動程式類的選擇推遲到執行時,使得程式碼更加靈活,更容易適應變化。
總之,Class.forName("com.mysql.cj.jdbc.Driver")
的目的是為了載入並註冊MySQL資料庫驅動程式,使用反射的方式允許在執行時動態選擇和載入驅動程式,提高了程式碼的靈活性和可配置性。
- 固定類名
類字面常量是在Java中引入的一種語法,用於獲取類的Class
物件。它是在編譯時就被解析的,因此具有更好的型別安全性。類字面常量的語法形式為在類名後面新增.class
。
以下是類字面常量的示例:
javaClass<MyClass> myClass = MyClass.class;
這裡,MyClass.class
表示 MyClass
類的 Class
物件。與使用 Class.forName()
方法不同,類字面常量在編譯時就會受到檢查,如果類名不存在或有語法錯誤,編譯器將會報錯。
這種方式的優點包括:
- 型別安全: 編譯器會在編譯時檢查類名的正確性,避免了在執行時可能出現的 ClassNotFoundException。
- 更簡潔: 語法更加簡潔清晰,不需要字串形式的類名。
- 更高效: 類字面常量的方式更高效,因為它是在編譯時就進行解析的,不需要在執行時動態載入類。
總體而言,如果在編譯時就知道要載入的類,而且希望程式碼更加型別安全,類字面常量是一個更好的選擇。如果類名是在執行時動態確定的,那麼可以考慮使用 Class.forName()
方法。
讀取配置檔案
InputStream ips = DruidUsePart.class.getClassLoader().getResourceAsStream("druid.properties");
這行程式碼是透過類載入器(ClassLoader)獲取資原始檔的輸入流。這種方式通常用於從類路徑中載入配置檔案。讓我解釋一下為什麼這樣獲取配置檔案是常見的做法:
- 相對路徑的問題: 使用類載入器可以避免相對路徑的問題。當你使用相對路徑載入檔案時,具體的相對位置可能會受到呼叫程式碼的影響,可能導致找不到檔案。而使用類載入器可以相對於類路徑來查詢資源,這樣不受呼叫位置的具體影響。
- 支援打包為JAR檔案: 如果你的應用被打包為JAR檔案,直接使用相對路徑可能會導致找不到檔案。類載入器能夠在JAR檔案中查詢資源,並將其作為輸入流返回。
- 類載入器的一致性: 類載入器提供了一種一致的方式來載入資源,無論是從檔案系統、網路還是JAR檔案中。這樣的一致性可以簡化程式碼,並使其在不同環境中都能夠正常工作。
- getResourceAsStream方法:
getResourceAsStream
方法是ClassLoader
類的一個方法,它返回指定資源的輸入流。這使得你可以直接使用輸入流來讀取資源,而不需要關心其具體的物理路徑。
在你的例子中,透過 DruidUsePart.class.getClassLoader().getResourceAsStream("druid.properties")
獲取了名為 "druid.properties" 的配置檔案的輸入流。
泛型方法
public <T> List<T> executeQuery(Class<T> clazz, String sql, Object... params)
- 這是一個泛型方法的定義,
<T>
表示該方法是泛型的,返回型別為List<T>
,其中T
是在呼叫時確定的型別引數。這意味著,呼叫該方法時,需要提供一個具體的型別,例如Bank.class
,以告訴方法返回什麼型別的物件列表。 Class<T> clazz
:這個參數列示需要執行查詢的物件型別。透過傳入clazz
引數,方法可以瞭解需要構建的物件型別,並使用反射機制來建立物件或者設定物件的屬性。Object... params
:這個引數是可變引數,用於傳遞查詢中需要的引數。
如何透過反射獲取模板類的屬性呢?
Field field = clazz.getDeclaredField(key);//獲取屬性key
field.setAccessible(true);//屬性可以設定,打破private限制
field.set(t,value);//給物件賦值(賦值物件,屬性值),如果是靜態屬性,第一個引數可以為null
Field field = clazz.getDeclaredField(key)
:這行程式碼使用了反射技術。clazz.getDeclaredField(key)
用於獲取指定名稱的欄位。在這裡,key
應該是你查詢中的一個列名或者屬性名。field.setAccessible(true)
:這行程式碼打破了欄位的封裝性,使得即使欄位是私有的,也可以透過反射來訪問或者修改它的值。field.set(t, value)
:這行程式碼將指定物件t
的屬性field
的值設定為value
。這裡的t
是一個物件例項,value
是要設定的值。