Jdbc從入門到入土

程式設計師包子發表於2022-05-19

二刷jdbc

作者小結:從第一次大概幾天快速刷完jdbc,到如今的二刷,才發現自己對jdbc的理解有點太淺。到學習javaweb是建立資料庫層時的迷茫,到現在對這種設計模式的理解。我深有體會到了:實打實走好每一步的必要性!這篇筆記較為完整的展示了jdbc的發展脈絡,從原理到手動封裝,再到第三方庫,循序漸進。

## jdbc概述
  1. jdbc為訪問不同的資料庫提供了統一的介面。
  2. java程式設計師使用jdbc,可以連線任何提供了jdbc驅動程式的資料庫系統,從而完成對資料庫的各種操作
  3. jdbc的基本原理圖

image-20220519234042262

java程式通過制定一些介面,讓資料庫廠商實現這些介面

*************************模擬************************
//java制定的資料庫介面
Interface jdbcInterface{
    //連線
    public Object getConnection();
    //crud
    public void crud();
    //關閉連線
    public void close();
}

//mysql廠商繼承介面從而實現這些方法
public class MysqlJdbcImpl implements jdbcInterface{
    @Override
    public Object getConnetion() {
        System.out.println("mysql的實現");
        return null;
    }

    @Override
    public void crud() {

    }

    @Override
    public void close() {

    }
}

java程式使用

public class testjdbc{
    public static void main(String[] args) {
        //通過介面來呼叫實現類[動態繫結]
        jdbcInterface mysqlImpl = new mysqlImpl();
        //通過介面來呼叫實現類
        mysqlImpl.getConnetion();
        mysqlImpl.crud();
        mysqlImpl.close();
    }
}

通過介面來呼叫實現類的意義:(思考介面程式設計的好處)

​ 當使用者用其他資料庫廠商的實現類時只需動態繫結其他資料庫實現類

​ 例如上方:jdbcInterface mysqlImpl = new DB2Impl();

image-20220519234110459

jdbc程式編寫步驟

  1. 註冊驅動-載入Driver類
  2. 獲取連線-得到Connection
  3. 執行增刪改查-傳送sql給相應的資料庫執行
  4. 釋放資源-關閉相關的連線

簡單的步驟:

  1. 匯入對應的jar包,即驅動檔案

  2. 註冊驅動

    Derver driver=new new com.mysql.jdbc.Driver();

    -->簡寫為 Derver driver=new driver();

  3. String url 解讀

    String url="jdbc::mysql://ip:port/資料庫名"

    jdbc::mysql://規定好的,表示一個協議,通過jdbc的方式連線mysql。

    ip 連線到的主機名稱

    3306 表示mysql監聽的埠

    資料庫名 表示連線到mysql dbms的哪一個資料庫

    image-20220519234216519

​ mysql的連線本質就是socket連線

  1. 將使用者名稱和密碼放入到properties物件中

    Properties properties = new Properties();
            properties.setProperty("user","book");//使用者
            properties.setProperty("password","xxxx");//密碼
    
  2. 得到連線

    Connection conn=driver.connect(url,properties);
    
  3. 執行sql

    String sql="select * from xxx";
    

    Statement用於執行sql語句

    Statement statement=conn.createStatement();
    int i=statement.excuteUpdate(sql);
    //i表示受影響的行數
    
  4. 關閉資源

    statemen.close();
    conn.close();
    

    不關閉資源造成的影響:會造成連線不到mysql

    image-20220519234243330

獲取資料庫連線的五種方式

1.方式一 獲取Driver實現類物件

Driver driver=new com.mysql.jdbc.Driver();
String url="jdbc:mysql://ip:port/資料庫名";
Properties properties=new Properties();
properties.setProperty("user","name");
properties.setProperty("password","xxxx");
Connection conn=driver.connect(url,properties);

通過new了一個第三方的driver,第三方的dirver 是靜態載入,靈活性不高。

2.方式二 使用反射機制,動態載入。

//使用反射載入Driver類
        Class clazz=Class.forName("com.mysql.jdbc.Driver");
        Driver driver= (Driver) clazz.newInstance();
        String url="jdbc:mysql://ip:port/資料庫名";
        Properties properties=new Properties();
        properties.setProperty("user","name");
        properties.setProperty("password","xxxx");
        Connection conn=driver.connect(url,properties);

反射動態載入,更加靈活,減少依賴性。

3.方式三使用DriverManager進行統一管理

//使用反射載入Driver
Class clazz=Class.forName("com.mysql.jdbc.Driver");
Driver driver= (Driver) clazz.newInstance();
//建立url user password
String url="jdbc:mysql://ip:port/資料庫名";
String user="root";
String password="xxxx";
//註冊Driver驅動
DriverManager.registerDriver(driver);
Connection conn=DriverManager.getConnection(url,user,password);

DriverManagaer用於管理一組jdbc驅動程式的基本服務

4.方式四使用forName()自動完成註冊驅動,簡化程式碼--推薦使用

//使用反射載入Driver
Class clazz=Class.forName("com.mysql.jdbc.Driver");
//建立url user password
String url="jdbc:mysql://ip:port/資料庫名";
String user="root";
String password="xxxx";
Connection conn=DriverManager.getConnection(url,user,password);

Class.forName 在載入Driver類時自動完成了註冊

image-20220519234303076

tip:沒用顯示呼叫Class.forName("com.mysql.jdbc.Driver")仍然可以拿到資料庫的連線。建議寫上,更加明確

image-20220519234332516

image-20220519234346973

5.方式五通過寫配置檔案,讓連線更加靈活

//通過Peoperties物件獲取配置檔案的資訊
        Properties properties=new Properties();
        properties.load(new FileInputStream("com\\mysql.properties"));
        //通過key獲取相關的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driver = properties.getProperty("driver");

        Class.forName(driver);
        DriverManager.getConnection(url,user,password);

在方式四的基礎上,增加配置檔案,讓連線更加靈活。

配置檔案:

#key=value
user=root
password=xxx
url=jdbc:mysql://ip:port/資料庫名
driver=com.mysql.jdbc.Driver

ResultSet結果集--底層(?)

概述:表示資料庫結果集的資料表,通常通過查詢資料庫的語句生成。ResultSet物件保持一個游標指向其當前的資料行,最初游標位於第一行,next方法將游標移動到下一行,並且由於在ResultSet物件中沒有更多行時返回false。類似於迭代器。

		//得到Statemen
        Statement statement = connection.createStatement();
        //sql語句
        String sql="SELECT * FROM xxx";
        //執行給定的sql語句,該語句返回單個ResultSet物件即為一張表
        java.sql.ResultSet resultSet = statement.executeQuery(sql);
        //迴圈取出
        while(resultSet.next()){//讓游標向後移動,如果沒有更多行,則返回false
            resultSet.getInt(1);//獲取改行的第一列資料
            resultSet.getString(2);//獲取該行第二列
        }
        //關閉資源
        resultSet.close();
        statement.close();
        connection.close();

statement--存在sql注入問題

概述:用於執行靜態的sql語句並返回其生成的結果的物件

statement是一個介面需要不同的資料庫廠商實現

解決方案:使用preperdStatement

程式碼實現:

		//得到Statemen
        Statement statement = connection.createStatement();
        //sql語句xx
        String sql="SELECT * FROM xxx";
        //執行給定的sql語句,該語句返回單個ResultSet物件即為一張表
        java.sql.ResultSet resultSet = statement.executeQuery(sql);

如果將使用者輸入改成next()也可以防止sql注入,next遇到空格會停止。

PreperdStatement

概述:預處理Statement,是一個介面。

用法:

1.PreperdStatement執行的sql語句中的引數用問號(?)來表示,呼叫PreperdStatement物件的setXXX()方法來設定這些引數。setXXX()方法有兩個引數,第一個引數是要設定的sql語句中的引數的索引(即第幾個問號),第二個設定的是引數的值。image-20220518193746580

2.呼叫executeQuery(),返回ResultSet物件

3.呼叫excuteUpdate(),執行crud。返回影響行數

預處理好處:

​ 不在使用+拼接sql語句,減少語法錯誤,有效解決了sql注入問題,減少了編譯次數,效率較高。

預處理就是再執行sql之前就已經完成對sql的賦值。

程式碼實現:

//sql語句xx,設定問號
String sql="SELECT * FROM xxx WHERE name=?and password=?";
//得到PreparedStatemen
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//給問號賦值
preparedStatement.setString(1,user_name);
preparedStatement.setString(2,user_pass);
//執行注意執行的時候不需要再填sql
preparedStatement.executeQuery();

新增記錄dml

String sql="INSERT INTO xxx VALUES(?,?)";

API小結

image-20220518201735923

image-20220518202015875

封裝Utils類

簡介:在jdbc操作中,獲取資料庫連線和釋放資源是經常使用到的可以將其封裝為JDBC連線的工具類Utils。

image-20220518203730303

使用步驟:

  1. 定義相關的屬性(4個),因為只需要一份,所以用static修飾
  2. 在static程式碼塊初始化
  3. 通過配置檔案讀取相關的屬性值
  4. 寫連線函式,推薦使用DriverManager
  5. 寫釋放資源函式

程式碼實現:

//定義相關的屬性(4個),因為只需要一份,所以用static修飾
    private static String user;//使用者名稱
    private static String password;//密碼
    private static String url;//資料庫url
    private static String driver;//驅動名

    //在static程式碼塊初始化
    static{

        try {
            Properties properties=new Properties();
            properties.load(new FileInputStream("com\\mysql.properties"));
            //讀取相關的屬性值
            user = properties.getProperty("user");
            password=properties.getProperty("password");
            url=properties.getProperty("url");
            driver=properties.getProperty("driver");
        } catch (IOException e) {
            //在實際開發中,常常轉為執行異常丟擲
            //將編譯異常轉為執行異常,呼叫者可以選擇捕獲該異常,也可以選擇預設處理該異常,比較方便。
            throw new RuntimeException(e);
        }
    }
    //連線資料庫,返回Connection
    public static Connection getConnection() throws SQLException, ClassNotFoundException {
        Class.forName(driver);
        return DriverManager.getConnection(url,user,password);
    }
    //關閉相應資源
    /*
        可能關閉的資源
        1.ResultSet結果集
        2.Statement和preparedStatement
        3.connection
        4.如果需要關閉資源,則傳入物件,否則傳入null
     */
    //用statement來接受因為statement是preparedStatement的父介面,都可以接收
    /*
    當一個物件被當作引數傳遞到一個方法後,此方法可改變這個物件的屬性,並可返回變化後的結果,那麼這裡到底是值傳遞還是引用傳遞?
    Java 程式語言只有值傳遞引數。當一個物件例項作為一個引數被傳遞到方法中時,引數的值就是對該物件的引用。物件的內容可以在被呼叫的方法中改變,但物件的引用是永遠不會改變的。
     */
    public static void close(ResultSet resultSet, Statement state,Connection conn) throws SQLException {
            if(resultSet!=null){
                resultSet.close();
            }
            if(state!=null){
                state.close();
            }
            if(conn!=null){
                state.close();
            }
    }

實際開發過程中異常處理:

在實際開發中,常常轉為執行異常丟擲,將編譯異常轉為執行異常,呼叫者可以選擇捕獲該異常,也可以選擇預設處理該異常,比較方便。throw new RuntimeException(e);

Java是值傳遞:

Java 程式語言只有值傳遞引數。當一個物件例項作為一個引數被傳遞到方法中時,引數的值就是對該物件的引用。物件的內容可以在被呼叫的方法中改變,但物件的引用是永遠不會改變的。

Utils使用

使用步驟:

  1. 得到連線。
  2. 組織一個sql語句。
  3. 建立一個PreparedStatement物件。
  4. 執行sql語句。
  5. 釋放資源呼叫close()。

程式碼實現:

public class use_utils {
    public void use_ut() throws SQLException {
        Connection conn=null;
        String sql="SELECT * FROM xxx";
        PreparedStatement preparedStatement=null;
        try {
            //得到連線
            conn=jdbcutils.getConnection();
            //建立PreparedStaement
            preparedStatement= conn.prepareStatement(sql);
            preparedStatement.executeQuery();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            jdbcutils.close(null,preparedStatement,conn);
        }
    }
}

事務

概述:Jdbc程式中當一個Connection物件建立時,預設情況下是自動提交事務,不能回滾。並且jdbc程式中為了讓多個SQL語句作為一個整體執行,需要使用事務,呼叫Connection的setAutoCommit(false)可以取消自動提交事務,當所有的sql語句都執行後,呼叫Commit()方法即可提交事務,在其中某個操作失敗或出現異常時,呼叫rollback()方法即可回滾事務。

預設情況下,Connection物件是自動提交的。

應用例項:經典的轉賬業務。

程式碼實現: "未使用事務"

Connection conn=null;
        String sql="update account set balance=balance-100 where id=1";
        String sql2="update account set balance=balance+100 where id=2";
        PreparedStatement preparedStatement=null;
        try {
            //得到連線
            conn=jdbcutils.getConnection();
            //建立PreparedStaement
            preparedStatement= conn.prepareStatement(sql);
            preparedStatement.executeQuery();//執行第一條sql
            int i=1/0;//丟擲異常
            preparedStatement=conn.prepareStatement(sql2);
            preparedStatement.executeQuery();//執行第二條SQL
    }

上述程式碼,在執行sql時如果沒用開啟事務,會造成第一條sql執行成功,而第二條sql未執行便被捕獲異常,在轉賬問題方面就會出現問題。

程式碼實現: 開啟事務

//得到連線
      conn=jdbcutils.getConnection()            //建立PreparedStaement
preparedStatement= conn.prepareStatement(sql);
/*得到連線後將conn設定為不自動提交*/
conn.setAutoCommit(false);
preparedStatement.executeQuery();//執行第一條sql
            int i=1/0;//丟擲異常
            preparedStatement=conn.prepareStatement(sql2);
            preparedStatement.executeQuery();//執行第二條SQL
/*在catch中即可處理異常,撤消先前已經執行的sql*/
				/*即回滾*/
catch(Exception e){
    conn.rollback();
}

rollback預設回滾到事務開啟的地方。

批處理

概述:當需要成批插入或者更新資料時,可以採用Java批量更新機制,這一機制允許將多條語句一次性提交給資料庫批量處理。

批處理步驟:

  1. 如果使用批處理時,需要在url中新增引數:

    ?rewriteBatchedStatements=true

  2. addBatch():新增需要批量處理的SQL語句或引數

  3. excuteBatch():執行批量處理的語句

  4. clearBatch():清空批處理包的語句

批處理優勢:批處理往往和PreparedStatement一起搭配使用,既可以減少編譯次數,又減少執行次數,讓效率提高。

程式碼實現:

傳統程式碼

public void nobatch() throws SQLException, ClassNotFoundException {
        Connection connection = jdbcutils.getConnection();
        String sql="insert into xxx values(null,?,?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        for (int i = 0; i < 5000; i++) {
            preparedStatement.setString(1,"tom"+i);
            preparedStatement.setString(2,"xxx");
            preparedStatement.executeUpdate();
        }
        //關閉連線
        jdbcutils.close(null,preparedStatement,connection);
 }

批處理程式碼

public void batch_() throws SQLException, ClassNotFoundException {
        Connection connection = jdbcutils.getConnection();
        String sql="insert into xxx values(null,?,?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        for (int i = 0; i < 5000; i++) {
            preparedStatement.setString(1,"tom"+i);
            preparedStatement.setString(2,"xxx");
            /*preparedStatement.executeUpdate();*/
            //將sql語句加入到批處理包中 ->
            preparedStatement.addBatch();
            //當有1000條資料時,在批量執行
            if((i+1)%1000==0){
                //滿1000條
                preparedStatement.executeBatch();
                //清空
                preparedStatement.clearBatch();
            }
        }
        //關閉連線
        jdbcutils.close(null,preparedStatement,connection);
    }

關鍵程式碼:

preparedStatement.addBatch();
//當有1000條資料時,在批量執行
if((i+1)%1000==0){
//滿1000條
preparedStatement.executeBatch();
//清空
preparedStatement.clearBatch();
}

注:原始碼未了解!

資料庫連線池

概述:傳統方式連線資料庫過多,由於沒用的連線資源未被及時斷開會造成,連線不上數s據庫,資料庫連線池就誕生了,資料庫連線池可以合理分配連線資源。

實現方式:

1.預先在緩衝池中放入一定數量的連線,當需要建立資料庫連線時,只需要從 "緩衝池"中取出一個,使用完畢之後再放回去。

2.資料庫連線池負責分配,管理和釋放資料庫連線,它允許應用程式重複使用一個現有的資料庫連線,而不是重寫建立一個

3.當應用程式向連線池請求的連線數超過最大連線數量時,這些請求將被加入到等待佇列中。

image-20220518233952426

image-20220518234252208

資料庫連線池種類:

1.Jdbc的資料庫連線池使用java.sql.dateSource來表示,DateSource只是一個介面,該介面由第三方提供實現。

2.C3P0, DBCP, Proxool, BoneCP, Druid

C3P0

實現步驟:

一、傳統方式

1.建立一個資料來源物件。

2.通過配置檔案獲取相關的資訊user,url...

3.給資料來源ComboPooledDataSource(c3p0)設定相關的引數url,user...setInitialPoolSize()方法設定初始化連線數,setMaxPoolSize()方法設定最大連線數。

4.得到連線。

5.關閉連線--即放回到連線池中。

二、使用配置檔案模板

概述:c3p0設計者提供了一個xml檔案,方便配置

1.將C3P0提供的配置檔案 c3p0.config.xml拷貝到src目錄下

2.建立一個資料來源物件,引數即為c3p0.config.xml檔案中的

3.得到連線

4.關閉連線--即放回到連線池中。

C3P0:

image-20220518235502696

配置檔案:c3p0.config.xml

<c3p0-config>
    <!-- 資料來源名稱代表連線池-->
    <name-config name="xxx">
        <!--  連線引數 -->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/web</property>
        <property name="user">root</property>
        <property name="password">ROOT</property>

        <!-- 連線池引數 -->
        <!-- 每次增長連線池可供連線數 -->
        <property name="acquireIncrement">10</property>
        <!-- 初始連線數 -->
        <property name="initialPoolSize">5</property>
        <!-- 最大連線數 -->
        <property name="maxPoolSize">10</property>
        <!-- 最大等待時間 -->
        <property name="checkoutTimeout">2000</property>
        <!-- 最大空閒回收時間 -->
        <property name="maxIdleTime">1000</property>
</c3p0-config>

程式碼實現:

傳統方式

    public void testc3p0() throws IOException, PropertyVetoException, SQLException {
        //1.建立一個資料來源物件
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        //2.通過配置檔案獲取相關的資訊
        Properties properties=new Properties();
        properties.load(new FileInputStream("com\\mysql.properties"));
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driver = properties.getProperty("driver");
        //給資料來源 comboPooledDataSource設定相關的引數。
        //我們連線的管理是由comboPooledDataSource來管理的。
        comboPooledDataSource.setDriverClass(driver);
        comboPooledDataSource.setUser(user);
        comboPooledDataSource.setPassword(password);
        comboPooledDataSource.setJdbcUrl(url);
        //設定連線數--初始化連線數
        comboPooledDataSource.setInitialPoolSize(10);
        //最大連線數
        comboPooledDataSource.setMaxPoolSize(50);

        Connection connection = comboPooledDataSource.getConnection();//這個方法就是從DateSource介面實現的
        connection.close();
    }

xml配置檔案的方式

public void test04() throws SQLException {
        //1.將配置檔案匯入src目錄下

        //2.建立一個資料來源物件,引數即為c3p0.config.xml檔案中的 <name-config name="nihao">
        ////資料來源會根據資料來源名稱讀取xml檔案中的內容
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("nihao");

        Connection connection = comboPooledDataSource.getConnection();

        connection.close();
    }

:資料來源名稱不能寫錯,並且xml檔案的名稱是固定的,資料來源會根據資料來源名稱讀取xml檔案中的內容,自動完成配置。

Druid

概述:

Druid連線池是阿里實現的,獲取連線的速度比較快。

實現步驟:

1.新增jar包,和properties配置檔案,將配置檔案拷貝到專案的src目錄下。

​ driverClassName 底層用這個欄位來讀取資料庫驅動。

​ minIdle 空閒時候的連線數量

2.建立Properties物件來讀取配置檔案

3.建立一個指定引數的資料庫連線池

4.得到連線

5.釋放連線

程式碼實現:

public void druidx() throws Exception {
        /*
        1.新增jar包,和properties配置檔案,將配置檔案拷貝到專案的src目錄下。
		driverClassName 底層用這個欄位來讀取資料庫驅動。
		minIdle 空閒時候的連線數量

        2.建立Properties物件來讀取配置檔案
         */
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\druid.properties"));
        //建立一個指定引數的資料庫連線池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    	//得到連線
        Connection connection = dataSource.getConnection();
        connection.close();
    }

配置檔案: druid.properties

driverClassName=com.mysql.jdbc.Driver //驅動載入
url=jdbc:mysql://127.0.0.1:3306/student?characterEncoding=utf-8 //註冊驅動
username=root //連線資料庫的使用者名稱
password=sjw58586 //連線資料庫的密碼。
filters=stat //屬性型別的字串,通過別名的方式配置擴充套件外掛, 監控統計用的stat 日誌用log4j 防禦sql注入:wall
initialSize=2 //初始化時池中建立的物理連線個數。
maxActive=300 //最大的可活躍的連線池數量
maxWait=60000 //獲取連線時最大等待時間,單位毫秒,超過連線就會失效。配置了maxWait之後,預設啟用公平鎖,併發效率會有所下降, 如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖。
timeBetweenEvictionRunsMillis=60000 // 連線回收器的執行週期時間,時間到了清理池中空閒的連線,testWhileIdle根據這個判斷
minEvictableIdleTimeMillis=300000
validationQuery=SELECT 1 //用來檢測連線是否有效的sql,要求是一個查詢語句。
testWhileIdle=true //建議配置為true,不影響效能,並且保證安全性。 申請連線的時候檢測,如果空閒時間大於timeBetweenEvictionRunsMillis, 執行validationQuery檢測連線是否有效。
testOnBorrow=false //申請連線時執行validationQuery檢測連線是否有效,做了這個配置會降低效能。設定為false
testOnReturn=false //歸還連線時執行validationQuery檢測連線是否有效,做了這個配置會降低效能,設定為flase
poolPreparedStatements=false //是否快取preparedStatement,也就是PSCache。
maxPoolPreparedStatementPerConnectionSize=200 // 池中能夠緩衝的preparedStatements語句數量

將Jdbc工具類改成druid實現

程式碼實現:

 private static DataSource ds;
    //在靜態程式碼塊完成ds初始化
    static{
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("src\\druid.properties"));
            ds= DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            new RuntimeException(e);
        }
    }
    //得到連線方法
    public Connection getConnection() throws SQLException {
        return ds.getConnection();
    }
    //釋放資源,Connection放回連線池,此是的close是資料庫連線池實現的close方法。
    public void close(ResultSet rs, Connection conn, Statement st) throws SQLException {
            if(rs!=null){
                rs.close();
            }
            if (conn!=null){
                conn.close();
            }
            if(st!=null){
                st.close();
            }
    }

注:此時呼叫的close方法,是連線池實現的close()方法並不會真正的關閉連線,而是將連線放回到資料庫連線池。

Bean,Domain,POJO

問題引出:

1.java程式使用Connection連線,Connection和ResultSet關聯,當關閉Connection無法再使用ResultSet。

2.如果一個程式返回ResultSet物件,這是已經關閉Connection,仍然無法拿到結果集物件,導致結果集只能使用一次。

解決方案:

寫一個Java類---->常被叫做Bean,Domain,POJO,該類中有查詢到的表中的欄位屬性,讓一個Actor物件對應查詢到的一條記錄,將結果集封裝到ArrayList中。

image-20220519180317108

ApachDBUtils

概述:面對ResultSet問題,ApachDBUtils工具類完美解決了這個問題。

傳統方式解決(土方法封裝)

實現步驟:

1.新建一個POJO類,用於封裝查詢到的表的記錄。

2.類中定義表中相對應的欄位,建構函式,setter,getter方法。

​ 注:一定要給一個預設建構函式[反射需要]。

3.在java程式中用ArrayList 來存貯資料。

4.在ResultSet遍歷的時候,封裝資料,儲存到集合中。

程式碼實現:

Actor---POJO

import java.util.Date;

public class Actor {//POJO
    //和表的欄位相對應
    //細節建議用Integer裝箱
    private Integer id;
    private String name;
    private String sex;
    //Date用util包下的。
    private Date bornDate;
    private String phone;
    //一定要給一個無參構造器[反射會需要]
    public Actor(){

    }

    public Actor(Integer id, String name, String sex, Date bornDate, String phone) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.bornDate = bornDate;
        this.phone = phone;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public void setBornDate(Date bornDate) {
        this.bornDate = bornDate;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public Integer getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getSex() {
        return sex;
    }

    public Date getBornDate() {
        return bornDate;
    }

    public String getPhone() {
        return phone;
    }
}

java程式:

public void testSelecttoArraylist() throws Exception {
        Properties properties=new Properties();
        properties.load(new FileInputStream("com\\mysql.properties"));
        //通過key獲取相關的值
        String user_name;
        String user_pass;
        Scanner scanner = new Scanner(System.in);
        user_name=scanner.nextLine();
        user_pass=scanner.nextLine();
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driver = properties.getProperty("driver");
        //建立ArrayList物件,存放Actor物件
        ArrayList<Actor> list=new ArrayList<>();
        //註冊驅動
        Class.forName(driver);
        //得到連線
        Connection connection = DriverManager.getConnection(url, user, password);
        //sql語句xx,設定問號
        String sql="SELECT * FROM xxx WHERE name=?and password=?";
        //得到PreparedStatemen
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //給問號賦值
        preparedStatement.setString(1,user_name);
        preparedStatement.setString(2,user_pass);
        //執行給定的sql語句,該語句返回單個ResultSet物件即為一張表
        java.sql.ResultSet resultSet = preparedStatement.executeQuery(sql);
        //迴圈取出
        while(resultSet.next()){//讓游標向後移動,如果沒有更多行,則返回false
            int id=resultSet.getInt("id");
            String name = resultSet.getString("name");
            String sex = resultSet.getString("sex");
            Date borndate = resultSet.getDate("borndate");
            String phone = resultSet.getString("phone");
            //把得到的resultSet記錄封裝到Actor物件,放入到list集合。
            list.add(new Actor(id,name,sex,borndate,phone));
        }
        //關閉資源
        resultSet.close();
        preparedStatement.close();
        connection.close();


    }
}

ApachDBUtils解決

概述:commons-dbutils是Apache組織提供的一個開源的JDBC工具類庫,它是對JDBC的封裝,使用dbutils能極大簡化jdbc編碼的工作量。

dbutils常用類和介面:

  1. QueryRunner類:該類封裝了SQl的執行,是執行緒安全的,可以實現增刪改查,批處理。
  2. ResultSetHandler介面:該介面用於處理java.sql.ResultSet,將資料按要求轉換為另一種形式。

ArrayHandler: 把結果集中的第一行資料轉成物件陣列
ArrayListHandler: 把結果集中的每一行資料都轉成一個陣列,再存放到List中。
BeanHandler: 將結果集中的第一行資料封裝到一個對應的JavaBean例項中。
BeanListHandler: 將結果集中的每一行資料都封裝到一個對應的JavaBean: 例項中,存放到List裡。
ColumnListHandler: 將結果集中某一列的資料存放到List中。
KeyedHandler(name): 將結果集中的每行資料都封裝到Map裡,再把這些map再存到一個map裡,其key為指定的key。
MapHandler: 將結果集中的第一行資料封裝到一個Map裡,key是列名,vaue就是對應的值。
MapListHandler: 將結果集中的每一行資料都封裝到一個Map裡,然後再存放到List

使用步驟:druid+dbutils

  1. 引入commons jar包

  2. 使用自己封裝的DruidUtils得到連線

  3. 建立QueryRunner

  4. 呼叫QueryRunner的query方法執行sql返回ArrayList集合

    注:sql語句也可以查詢部分列

    BeanListHandler<>(Actor.class):在將ResultSet->Actor 物件->封裝到ArrayList

    1: 傳給sql中的問號的,可以有多個後邊是可變引數Object...params

    底層得到的ResultSet會在query執行後關閉,PreparedStatement也會自動關閉

    引數列表
    (connection, sql, new BeanListHandler<>(Actor.class), 1)
    
  5. 關閉連線。

程式碼實現:

public void querytest() throws SQLException {
        //得到連線(druid)
        Connection connection = DruidUtils.getConnection();
        //使用DbUtils類和介面,引入相應的jar檔案。
        //建立QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        //QueryRunner就可以執行相關的方法,返回ArrayList結果集
        String sql="SELECT * FROM xxx WHERE id>=?";
        //sql語句也可以查詢部分列
        /*
         queryRunner.query方法就是執行一個sql語句得到ResultSet,--封裝到ArrayList集合中
         然後返回集合。
         引數Connection  連線
         sql: 執行的sql語句
         BeanListHandler<>(Actor.class):在將ResultSet->Actor 物件->封裝到ArrayList
         底層使用反射機制獲取Actor屬性進行封裝
         1: 傳給sql中的問號的,可以有多個後邊是可變引數Object...params
         底層得到的ResultSet會在query執行後關閉,PreparedStatement也會自動關閉
        * */
        List<Actor> list = queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
        DruidUtils.close(null,connection,null);
    }

dbutils查詢單行記錄(單個物件)

解決方案:返回單行記錄,單個物件使用的Hander是BeanHandler

new BeanHandler<>(Actor.class)

單行單列:返回的是一個Object,物件使用Handers是ScalarHandler()

new ScalarHandler()

dbutils+druid實現crud

執行dml操作使用queryRunner.update()

返回值是受影響的行數。

程式碼實現:

public void testcrud() throws Exception{
        Connection connection = DruidUtils.getConnection();
        QueryRunner queryRunner = new QueryRunner();
        String sql="update actor set name=? where id=?";
        int affectedrows = queryRunner.update(connection, sql, "niuma", 4);

    }

對應關係:

注: java中全部對應包裝類。

image-20220519215702289

BasicDAO

問題分析:

dbutils和druid簡化了JDBC開發,但有不足

1.Sql語句是固定,不能通過引數傳入,通用性不好,需要進行該進,更方便執行crud

2.對於select操作,如果有返回值,返回值型別不能固定,需要使用泛型

3.將來的表很多,業務需求複雜,不可能只靠一個Java類完成

設計理念:各司其職。一張表對應一個DAO

DAO:訪問資料的物件

image-20220519222455869

image-20220519223149361

BasicDAO將所有的DAO共有的部分提取出來,讓子類去繼承簡化程式碼。

設計步驟:

1.第一個包 放utils工具類

2.第二個包 javaBean/domain

3.第三個包 放xxxDAO和BasicDAO

4.第四個包 寫測試類

目錄結構:

image-20220519231821656

程式碼:

BasicDAO:

package com.basic_.dao;

import com.basic_.utils.DruidUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import javax.management.Query;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

//開發basicDAO
//新增泛型將來是操作domain的
public class BasicDAO<T> {
    private QueryRunner qr=new QueryRunner();

    //開發通用的dml方法,針對任意的表
    public int update(String sql,Object...prameters) throws SQLException {
        Connection conn=null;
        conn= DruidUtils.getConnection();
        int affectedRows = qr.update(conn, sql, prameters);
        return affectedRows;
    }
    //返回多個物件(即查詢的結果是多行的),針對任意表
    //sql語句可以有問號,佔位符
    //clazz傳入一個類的Class物件,底層通過反射實現domain 比如Actor.class
    //prameters傳入問號具體的值。
    public List<T> queryMulti(String sql, Class<T> clazz, Object...prameters) throws SQLException {
        Connection conn=null;
        conn=DruidUtils.getConnection();
        List<T> list = qr.query(conn, sql, new BeanListHandler<>(clazz), prameters);
        DruidUtils.close(null,conn,null);
        return list;
    }
    //查詢單行結果通用方法   T可能是Actor..不同的表。
    public T querySingle(String sql,Class<T> clazz,Object...prameters) throws SQLException {
        Connection conn=null;
        conn= DruidUtils.getConnection();
        T rs = qr.query(conn, sql, new BeanHandler<>(clazz), prameters);
        DruidUtils.close(null,conn,null);
        return rs;
    }
    //查詢單行單列即返回單值的方法
    public Object queryScalar(String sql,Object...prameters) throws SQLException {
        Connection conn=null;
        conn= DruidUtils.getConnection();
        Object rs = qr.query(conn, sql, new ScalarHandler(), prameters);
        DruidUtils.close(null,conn,null);
        return rs;
    }

}

ActorDAO

public class ActorDAO extends BasicDAO<Actor>{
    //Actor有BasicDAO 的方法
    //根據業務需求,可以編寫特有的方法
}

domain

public class Actor {//POJO
    //和表的欄位相對應
    //細節建議用Integer裝箱
    private Integer id;
    private String name;
    private String sex;
    //Date用util包下的。
    private Date bornDate;
    private String phone;
    //一定要給一個無參構造器[反射會需要]
    public Actor(){

    }

    public Actor(Integer id, String name, String sex, Date bornDate, String phone) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.bornDate = bornDate;
        this.phone = phone;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public void setBornDate(Date bornDate) {
        this.bornDate = bornDate;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public Integer getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getSex() {
        return sex;
    }

    public Date getBornDate() {
        return bornDate;
    }

    public String getPhone() {
        return phone;
    }
}

utils

    private static DataSource ds;
    //在靜態程式碼塊完成ds初始化
    static{
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("src\\druid.properties"));
            ds= DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            new RuntimeException(e);
        }
    }
    //得到連線方法
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }
    //釋放資源,Connection放回連線池,此是的close是資料庫連線池實現的close方法。
    public static void close(ResultSet rs, Connection conn, Statement st) throws SQLException {
            if(rs!=null){
                rs.close();
            }
            if (conn!=null){
                conn.close();
            }
            if(st!=null){
                st.close();
            }
    }

測試使用

//測試ActorDao對actor表crud操作
    public void testActorDao() throws SQLException {
        ActorDAO actorDAO = new ActorDAO();
        //1.查詢
        List<Actor> actors = actorDAO.queryMulti("select *from actor where id>?", Actor.class, 1);
        //2.查詢單行
        Actor actor01 = actorDAO.querySingle("select *from actor where id=?", Actor.class, 1);
        //3.查詢單行單列
        Object o = actorDAO.queryScalar("select name from actor where id=?", 6);
        //4.dml操作
        //新增資料是按照什麼順序呢?
        int afftedRows = actorDAO.update("insert into actor values(null,?,?)", "nihao", "niuma");

    }

相關文章