一文學會JDBC實現java和mySQL的數據連接(尚矽谷學習課程代碼+筆記+思路總結)

来源:https://www.cnblogs.com/jing15/p/18066603
-Advertisement-
Play Games

JDBC是指資料庫連接技術,用於java連接mySQL等資料庫。本文詳細介紹了尚矽谷課程中JDBC的學習內容和補充知識。 概述 java語言只提供規範介面,存在於java.sql.javax.sql包下,然後資料庫軟體根據java提供的規範實現具體的驅動代碼(jar) jar包是java程式打成的一 ...


JDBC是指資料庫連接技術,用於java連接mySQL等資料庫。本文詳細介紹了尚矽谷課程中JDBC的學習內容和補充知識。

概述

  • java語言只提供規範介面,存在於java.sql.javax.sql包下,然後資料庫軟體根據java提供的規範實現具體的驅動代碼(jar)
  • jar包是java程式打成的一種壓縮包格式,只要導入就可以使用對應方法

學習思路:(可以學完再看)

  1. 六大基本步驟獲取連接,包括直接輸入字元串的Statement和改進版的PreparedStatement(通過占位符解決了容易SQL攻擊的問題)

  2. JDBC的增刪改查,其中插入數據需要考慮主鍵自增長批量插入效率低的問題

  3. 建立資料庫事務(基本特征是關閉了自動提交,要同時完成多個操作後再一起提交,如轉賬過程先增加A賬戶的錢,再扣除B賬戶的錢,一定要一起提交,防止出現只成功一件事)

  4. 連接池,優化建立連接的過程(每件事都去連接很繁瑣,浪費資源,Druid連接池)

    • 硬編碼:通過設置參數的方式
    • 軟編碼:通過讀取外部配置文件的方式(讀取方法使用了類載入器,補充說明在末尾)
  5. 封裝連接工具類(通過靜態代碼塊的方式保證連接池只被創建一次,而不是每次調用方法都創建一個新的連接池對象。)

    • V1.0:不同方法調用拿到的是不同連接對象
    • V2.0:利用線程本地量的方法,保證同一個線程不同方法拿到的是同一個連接
  6. 完整工具類封裝(連接池優化了建立連接,那麼把增刪改查操作也封裝一下吧)

    • executeUpdate(實現數據的更新操作,包括增,刪,改)
    • executeQuery(實現數據的查詢操作,通過反射機制,輸入模板對象,返回查詢出的對象列表)

基本步驟

  1. 註冊驅動,將依賴的jar包進行安裝
  2. 建立連接 connection
  3. 創建發送SQL語句的對象statement
  4. statement對象去發送SQL數據到資料庫獲取返回結果
  5. 解析結果集
  6. 銷毀資源

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();


    }
}

存在幾個問題

  1. SQL語句需要字元串拼接比較麻煩

  2. 只能拼接字元串類型,其他的資料庫類型無法處理

  3. 可能發生註入攻擊(動態值充當了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對象

目的是執行類的靜態代碼塊

  1. Class.forName(類名可以不固定)

使用 Class.forName("com.mysql.cj.jdbc.Driver") 語句是為了載入MySQL JDBC驅動程式。在Java中,類的載入是通過類載入器(ClassLoader)來完成的,Class.forName方法是一種觸發類載入的方式。這種載入方式主要用於載入類的靜態代碼塊。

  1. 動態載入驅動程式: 使用 Class.forName 允許在運行時根據需要載入特定的驅動程式類。這使得你可以在不修改代碼的情況下,通過配置文件或其他方式動態更改要使用的資料庫驅動。
  2. 提高靈活性: 通過將驅動程式類名硬編碼在代碼中,你的代碼將直接依賴於特定的資料庫驅動。使用反射可以將驅動程式類的選擇推遲到運行時,使得代碼更加靈活,更容易適應變化。

總之,Class.forName("com.mysql.cj.jdbc.Driver") 的目的是為了載入並註冊MySQL資料庫驅動程式,使用反射的方式允許在運行時動態選擇和載入驅動程式,提高了代碼的靈活性和可配置性。

  1. 固定類名
    類字面常量是在Java中引入的一種語法,用於獲取類的 Class 對象。它是在編譯時就被解析的,因此具有更好的類型安全性。類字面常量的語法形式為在類名後面添加 .class

以下是類字面常量的示例:

javaClass<MyClass> myClass = MyClass.class;

這裡,MyClass.class 表示 MyClass 類的 Class 對象。與使用 Class.forName() 方法不同,類字面常量在編譯時就會受到檢查,如果類名不存在或有語法錯誤,編譯器將會報錯。

這種方式的優點包括:

  1. 類型安全: 編譯器會在編譯時檢查類名的正確性,避免了在運行時可能出現的 ClassNotFoundException。
  2. 更簡潔: 語法更加簡潔清晰,不需要字元串形式的類名。
  3. 更高效: 類字面常量的方式更高效,因為它是在編譯時就進行解析的,不需要在運行時動態載入類。

總體而言,如果在編譯時就知道要載入的類,而且希望代碼更加類型安全,類字面常量是一個更好的選擇。如果類名是在運行時動態確定的,那麼可以考慮使用 Class.forName() 方法。

讀取配置文件

InputStream ips = DruidUsePart.class.getClassLoader().getResourceAsStream("druid.properties");

這行代碼是通過類載入器(ClassLoader)獲取資源文件的輸入流。這種方式通常用於從類路徑中載入配置文件。讓我解釋一下為什麼這樣獲取配置文件是常見的做法:

  1. 相對路徑的問題: 使用類載入器可以避免相對路徑的問題。當你使用相對路徑載入文件時,具體的相對位置可能會受到調用代碼的影響,可能導致找不到文件。而使用類載入器可以相對於類路徑來查找資源,這樣不受調用位置的具體影響。
  2. 支持打包為JAR文件: 如果你的應用被打包為JAR文件,直接使用相對路徑可能會導致找不到文件。類載入器能夠在JAR文件中查找資源,並將其作為輸入流返回。
  3. 類載入器的一致性: 類載入器提供了一種一致的方式來載入資源,無論是從文件系統、網路還是JAR文件中。這樣的一致性可以簡化代碼,並使其在不同環境中都能夠正常工作。
  4. 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
  1. Field field = clazz.getDeclaredField(key):這行代碼使用了反射技術。clazz.getDeclaredField(key) 用於獲取指定名稱的欄位。在這裡,key 應該是你查詢中的一個列名或者屬性名。
  2. field.setAccessible(true):這行代碼打破了欄位的封裝性,使得即使欄位是私有的,也可以通過反射來訪問或者修改它的值。
  3. field.set(t, value):這行代碼將指定對象 t 的屬性 field 的值設置為 value。這裡的 t 是一個對象實例,value 是要設置的值。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 過濾器和攔截器的辨析 介紹 過濾器和攔截器都是為了在請求到達目標處理器(Servlet或Controller)之前或者之後插入自定義的處理邏輯 過濾器: 遵循AOP(面向切麵編程)思想實現,基於Servlet規範提供的Filter介面,它是位於客戶端請求與伺服器響應之間的一個組件,依賴於Servle ...
  • 什麼是函數回調? 介紹 函數回調是一種編程概念,它描述的是這樣一個過程:一個函數(稱為回調函數)作為參數傳遞給另一個函數(稱為調用函數),當滿足一定條件或者在某個特定時刻,調用函數會調用傳遞過來的回調函數。這種機制允許程式員在編寫代碼時,能夠在不同的上下文中重用函數,同時也能實現非同步處理、事件驅動編 ...
  • 是的,\t 是指製表符(tab),它通常用作欄位分隔符在 TSV(Tab-Separated Values)格式的文件中。TSV是一種簡單的文本格式,它使用製表符來分隔每一列中的值,而每一行則代表一個數據記錄。 TSV文件例: ID\tName\tAge\tCity 1\tJohn Doe\t28\ ...
  • 一、基礎知識 1、Lucene 是什麼 Lucene 是一個本地全文搜索引擎,Solr 和 ElasticSearch 都是基於 Lucene 的封裝 Lucene 適合那種輕量級的全文搜索,我就是伺服器資源不夠,如果上 ES 的話會很占用伺服器資源,所有就選擇了 Lucene 搜索引擎 2、倒排索 ...
  • Java HashMap HashMap 是一種哈希表,它存儲鍵值對。鍵用於查找值,就像數組中的索引一樣。HashMap 的優勢在於它可以使用任何類型作為鍵,並且查找速度很快。 創建 HashMap // 導入 HashMap 類 import java.util.HashMap; public c ...
  • 一、什麼是 Java Java 是 Sun Microsystems 於 1995 年首次發佈的一種編程語言和計算平臺。編程語言還是比較好理解的,什麼是計算平臺呢? 計算平臺是電腦中運行應用程式(軟體的環境),包括硬體環境和軟體環境。一般系統平臺包括一臺電腦的硬體體繫結構,操作系統、運行時庫。 Ja ...
  • 哈嘍大家好,我是鹹魚。 今天鹹魚列出了一些大家在初學 Python 的時候容易踩的一些坑,看看你有沒有中招過。 原文:https://www.bitecode.dev/p/unexpected-python-traps-for-beginners 不明顯的字元串拼接 Python 在詞法分析的時候會 ...
  • InLine_Function How to Write it? example-> inline void func(); inline int sum(int v1 , int v2 ); inline void func(){ std::cout<<"func()"<<std::endl; } ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...