第二天:JDBC和DBUtils

一、eclipse的使用

1、修改註釋內容

選擇window---->>preferences

wKioL1naRVeiTogbAAAgMw9_xKU966.png

選擇Java---->>codestyle---->>code template---->>comments,然後雙擊types,修改裏面的內容即可!

 

e4e6d07a2abb714cee536b6388ab2929.png

2、修改Eclipse裏面的快捷鍵

舉例:修改複製行內容快捷鍵。

1.點擊window菜單->preferences子菜單->general->keys,進入快捷鍵管理界面

wKioL1naRXCzykM1AAIuxdyWYso353.png

 

 

2.在這裏可以查找所有功能的快捷鍵,需要修改或新增時,點擊需要修改或新增的命令,在binding裏設置快捷鍵

wKioL1naRXuQfT6tAAEm8M2xy4w637.png

 

3.設置完快捷鍵後,還需要設置在什麼時候可以使用該快捷鍵,eclipse提供各種場景供選擇,一般選擇In Windows(即在eclipse窗口激活狀態)即可

wKiom1naRdjyvl2KAADRGz-XyVY225.png

 

完成以上操作,點擊OK即完成設置

wKioL1naRZTjeJ-CAAE4aqc4KAA819.png

3、設置工作空間的編碼

 wKiom1naRSvjqbP_AADUznliK5I735.jpg

 

二、JUnit單元測試

1、介紹:JUnit是一個java語言的單元測試框架。屬於第三方工具,一般情況下需要導入jar包,不過多數java開發環境已經集成了JUnit作爲單元測試工具。

創建“day07”java項目,並創建“cn.itcast.a_junit”包 

 wKioL1na6gWzN9UMAABkcgph5b4375.png

2、編寫測試類,簡單理解可以用於取代java的main方法

3、在測試類方法上添加註解@Test

4、註解修飾方法要求:public void 方法名(){...},方法名自定義建議test開頭

63c5aa5adaa565d5f83c3389c11d366f.jpg

5、添加STS中集成的Junit庫,鼠標點擊“@Test”,使用快捷鍵“ctrl+1”,點擊“Add Junit...”

wKiom1na6xSx4-w5AAEcn_7cQG8080.png

結果

wKiom1na6yrQOFPOAADzXBHBl5k646.png

6、使用:選中方法右鍵,執行當前方法;選中類名右鍵,執行類中所有方法(方法必須標註@Test)

wKioL1na6yHTqph1AABqt70eYB0174.png

7、常用註解

    @Test,用於修飾需要執行的方法

    @Before,測試方法前執行的方法

    @After,測試方法後執行的方法

wKioL1na62myUqcVAAH4ZSF8VLs188.png

8、常見使用錯誤,如果沒有添加“@Test”,使用“Junit Test”進行運行,將拋異常

wKiom1na7eCBFl6GAAEoP4hX9MM265.png

三、JDBC

1、什麼是JDBC

JDBC(Java DataBase Connectivity)就是java數據庫連接,簡單地說就是用java語言來操作數據庫。原來我們操作數據庫是在控制檯使用SQL語句來操作數據庫,JDBC是用Java語言向數據庫發送SQL語句

<1>JDBC是一種用於執行SQL語句的java API

<2>JDBC可以爲多種關係數據庫提供統一訪問入口

<3>JDBC由一組java工具類和接口組成

2、JDBC原理

早期SUN公司的天才們想編寫一套可以連接天下所有數據庫的API,但是當他們剛剛開始時就發現這是不可完成的任務,因爲各個廠商的數據庫服務器差異太大了。後來SUN開始與數據庫廠商們討論,最終得出的結論是,由SUN提供一套訪問數據庫的規範(就是一組接口),並提供連接數據庫的協議標準,然後各個數據庫廠商會遵循SUN的規範提供一套訪問自己公司的數據庫服務器的API出現。SUN提供的規範命名爲JDBC。而各個廠商提供的,遵循了JDBC規範的,可以訪問自數據庫的API被稱之爲驅動。

fe3415571482502950d98cf0c77ad5bf.jpg

JDBC是接口,而JDBC驅動纔是接口的實現,沒有驅動無法完成數據庫連接!每個數據庫廠商都有自己的驅動,用來連接自己公司的數據庫。

當然還有第三方公司專門爲某一數據庫提供驅動,這樣的驅動往往不是開源免費的!

3、JDBC核心類(接口)介紹

JDBC中的核心類有:DriverManager、Connection、Statement和ResultSet

DriverManager(驅動管理器)的作用有兩個

<1>註冊驅動:這可以讓JDBC知道要使用的是哪個驅動;

<2>獲取Connection:如果可以獲取到Connection,那麼說明已經與數據庫連接上了。

    Connection對象表示連接,與數據庫的通訊都是通過這個對象展開的;

<3>Connection最爲重要的一個方法就是用來獲取Statement對象;

<4>Statement是用來向數據庫發送SQL語句的,這樣數據庫會執行發送過來的的SQL語句

<5>void executeUpdate(String sql):執行更新操作(insert、update、delete等);

<6>ResultSet executeQuery(String sql):執行查詢操作,數據庫在執行查詢後會返回查詢結果,查詢    結果就是ResultSer;

    ResultSet對象表示查詢結果集,只有在執行查詢操作後纔會有結果集的產生。結果集是一個人二維     的表格,有行有列。操作結果集要學習移動ResultSet內部的“行光標”,以及獲取當前行上的數據

<7>boolean next():使“行光標”移動到下一行,並返回移動後的行是否存在;

<8>xxx getxxx(int col):獲取當前行指定列上的值,參數就是列數,列數從1開始,而不是0

4、JDBC開發步驟

4.1 導入mysql數據庫的驅動jar包:

<1>創建lib目錄,用於存放當前項目需要的所有jar包

<2>選擇jar包,右鍵執行build path/Add to Build Path

b28265e7078e8412efc83b3b1a03c29c.jpg


4.2 註冊驅動

註冊驅動就只有一句話:Class.forName("com.mysql.jdbc.Driver"),

下面的內容都是對這句代碼的解釋。今後我們的代碼中,與註冊驅動相關的代碼只有這一句。

<1>分析步驟1:JDBC規範定義驅動接口:java.sql.Driver,

               MySql驅動包提供了實現類:com.mysql.jdbc.Driver 
<2>分析步驟2:DriverManager工具類,提供註冊驅動的方法registerDriver(),

               方法的參數是java.sql.Driver,所以我們可以通過如下語句進行註冊

               DriverManager.registerDriver(new com.mysql.jdbc.Driver());

               以上代碼不推薦使用,存在兩方面不足:

               1.硬編碼,後期不易於程序擴展和維護

               2.驅動被註冊兩次

<3>分析步驟3:通常開發我們使用Class.forName()加載一個使用字符串描述的驅動類。

               如果使用Class.forName()將加載到內存,該類的靜態代碼將自動執行

               通過查詢com.mysql.jdbc.Driver源碼,我們發現Driver類“主動”將自己進行註冊 

DriverManager類的registerDriver()方法的參數是java.sql.Driver,但java.sql.Driver是一個接口,實現類由mysql驅動來提供,mysql驅動中的java.sql.Driver接口的實現類爲com.mysql.Driver那麼註冊驅動的代碼如下:

    DriverManager.registerDriver(new com.mysql.jdbc.Driver());

    上面的代碼雖然可以註冊驅動,但是出現硬編碼(代碼依賴mysql驅動jar包),如果將來想連接Oracle數據庫,那麼必須要修改代碼。並且其實這種註冊驅動的方式是註冊了兩次驅動!

    JDBC中規定,驅動類在被加載時,需要自己“主動”把自己註冊到DriverManager中,下面來看看com.mysql.jdbc.Driver類的源代碼:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch(SQLException E) {
            throw newRuntimeException("Can't register driver!");
        }
    }
……
}

com.mysql.jdbc.Driver類中的static塊會創建本類對象,並註冊到DriverManager中,這說明只要去加載com.mysql.jdbc.Driver類,那麼就會執行這個static塊,從而也就會把com.mysql.jdbc.Driver註冊到DriverManager中,所以可以把註冊驅動類的代碼修改爲加載驅動類。

Class.forName("com.mysql.jdbc.Driver");    


4.3 獲取連接

代碼:Connection con=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydbl","root","root")

獲取連接需要兩步,一是使用DriverManager來註冊驅動,二是使用DriverManager來獲取Connection對象。

    獲取連接的也只有一句話代碼:

    DriverManager.getConnection(url,username,password);

    其中username和password是登錄數據庫的用戶名和密碼

    url相對複雜一點,它是用來找到要連接數據庫的“網址”,就好比你要在瀏覽器中查找百度時,也需要提供一個url。下面是mysql的url:

    jdbc:mysql://localhost:3306/mydbl

    JDBC規定url的格式由三部分組成,每個部分中間使用冒號分割。

    <1>第一部分是jdbc,這是固定的;

    <2>第二部分是數據庫名稱,那麼連接mysql數據庫,第二部分當然是mysql了;

    <3>第三部分是由數據庫廠商規定的,我們需要了解每個數據庫廠商的要求,mysql的第三部分分別      由數據庫服務器的IP地址(localhost)、端口號(3306),以及database名稱(mybdl)組成

    下面是獲取連接的語句: 

    Connection con=DriverManager.getConnection("jdbc:mysql://localhost:3306/web08","root","root");

     還可以在url中提供參數:

    jdbc:mysql://localhost:3306/web08?useUnicode=true&characterEncoding=UTF8

    useUnicode參數指定這個連接數據庫的過程中,使用的字節集是Unicode字節集;

    characterEncoding參數指定java程序連接數據庫的過程中,使用的字符集編碼爲UTF-8編碼。注意,mysql中指定UTF-8編碼給出的是UTF8,而不是UTF-8


4.4 獲得語句執行

String sql="insert into category(cid,cname) values('c007','分類')";

Statement stmt=con.createStatement();

在得到Connection之後,說明已經與數據庫連接上了,下面是通過Connection獲取Statement對象的代碼:

    Statement stmt=con.createStatement();

    Statement是用來向數據庫發送要執行的SQL語句的

常用方法:

執行SQL語句:

    <1>int executeUpdate(String sql);---執行insert update delete語句.(DML語句)

    <2>ResultSet executeQuery(String sql);---執行select語句(DQL語句)

    <3>boolean execute(String sql);---執行select返回true,執行其他的語句返回false 

        如果返回true,需要使用getResultSet()獲得查詢結果

        如果返回false,需要使用getUpdateCount()獲得影響行數

執行批處理:(可選)

    addBatch(String sql);

    clearBatch();

    executeBatch();


4.5 預處理對象

使用處理對象時,建議每條sql語句所有的實際參數,都使用逗號分隔

String sql="insert into category(cid,cname) values(?,?)";

PreparedStatement psmt=con.prepareStatement(sql);

常用方法:

執行SQL語句:

    <1>int executeUpdate(String sql);---執行insert update delete語句.(DML語句)

    <2>ResultSet executeQuery(String sql);---執行select語句(DQL語句)

    <3>boolean execute(String sql);---執行select返回true,執行其他的語句返回false 

設置實際參數:

    setxxx(int,T)通過setter方法將?佔位符替換成實際參數

    例如:setString()實際參數類型爲hi字符串

執行批處理:(可選)

    addBatch; 添加批處理的實際參數,需要調方法前執行對應setter方法

    clearBatch();

    executeBatch();


4.6 處理結果集

ResultSet就是一張二維的表格,它內部有一個“行光標”,光標默認位置在“第一行上方”,我們可以調用rs對象的next()方法把“行光標”向下移動一行,當第一次調用next()方法時,“行光標”就到了第一行記錄的位置,這時就可以使用ResultSet提供的getxxx(int col)方法來獲取指定列的數據了:

    rs.next();//光標移動到第一行

    rs.getInt(1);//光標移動到第一列的數據

wKioL1nbK0zzPLiyAABislzVNhQ045.jpg

    當你使用rs.getInt(1)方法時,你必須可以肯定第1列的數據類型就是int類型,如果你不能肯定,那麼最好使用rs.getObject(1)。在ResultSet類中提供了一系列的getxxx()方法,比較常用的方法有:

    Object getObject(int col)

    String getString(int col)

    int getInt(int col)

    double getDouble(int col)


4.7 釋放資源

與IO流一樣,使用後的東西都需要關閉,關閉的順序是先得到的後關閉,後得到的先關閉

    rs.close();

    stmt.close();

    con.close;


4.8 完成查詢操作代碼

    public static Connection getConnection() throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        String url ="jdbc:mysql://localhost:3306/web08";
        return DriverManager.getConnection(url, "root", "root");
    }
    @Test
    public void query() throws Exception{
        Connectioncon = getConnection();
        Statementstmt = con.createStatement();
        String sql ="select * from user";
        ResultSet rs= stmt.executeQuery(sql);
        while(rs.next()){
            String username = rs.getString(1);
            String password = rs.getString(2);
            System.out.println(username+ ", " + password);
        }
    }


4.9 規範化代碼

所謂規範化代碼就是無論是否出現異常,都要關閉ResultSet、Statement,以及Connection

    @Test
    public void query() {
        Connection con = null;
        Statement stmt = null;
        ResultSet rs= null;
        try {
            con = getConnection();
            stmt =con.createStatement();
            String sql = "select * from user";
            rs =stmt.executeQuery(sql);
            while(rs.next()){
                String username = rs.getString(1);
                String password = rs.getString(2);
                System.out.println(username+ ", " + password);
            }
        } catch(Exceptione) {
            throw newRuntimeException(e);
        } finally {
            try {
                if(rs != null)rs.close();
                if(stmt != null) stmt.close();
                if(con != null)con.close();
            } catch(SQLExceptione) {}
        }
    }

4.10 案例:查詢所有


wKioL1nbLYOx_SQCAAELYP8GbJc958.jpg

wKiom1nbLdTgu5ubAAAlV9df4JA721.jpg


5、JDBC對象介紹

5.1JDBC中的主要類(接口)

<1>DriverManager

<2>Connection

<3>Statement

<4>ResultSet


5.2 DriverManager

我們今後只需要會用DriverManager的getConnection()方法即可:

<1>Class.forName("com.mysql.jdbc.Driver");

<2>String url="jdbc:mysql://localhost:3306/web08";

<3>String username="root"

<4>String password="root"

<5>Connection con=DriverManager.getConnection(url,username,password);


注意:上面代碼可能出現的兩種異常:

<1>ClassNotFoundException:這個異常是在第1句上出現的,出現這個異常有兩個可能:

    (1)你沒有給出mysql的jar包;

    (2)你把類名打錯了,查看類名是不是com.mysql.jdbc.Driver。

<2>SQLException:這個異常出現在第5句,出現這個異常就是三個參數的問題,往往username和password一般不會出錯,所以需要認真查看url是否打錯

    對於DriverManager.registerDriver()方法瞭解即可,因爲我們今後註冊驅動只會用                 Class.forName(),而不會使用這個方法

5.3 Connection

    Connection最爲重要的方法就是獲取Statement:

    (1)Statement stmt=con.createStatement();

    後面在學習ResultSet方法時,還要學習一下下面的方法

    (2)Statement stmt=con.createStatement(int,int);

5.4 Statement

    Statement最爲重要的方法是:

    <1>int executeUpdate(String sql):執行更新操作,即執行insert、update、delete語句,其實

    這個方法也可以執行create table、alter table,以及drop table等語句,但我們很少會使JDBC

    來執行這些語句;

    <2>ResultSet executeQuery(String sql):執行查詢語句,執行查詢操作會返回ResultSet,即結果集;

    <3>boolean execute():Statement還有一個boolean execute()方法,這個方法可以用來執行       增、刪、改、查所有的SQL語句,該方法返回的是boolean類型,表示SQL語句是否執行成功。

    如果使用execute()方法執行的是更新語句,那麼還要調用int getUpdateCount()來獲取insert、       update、delete語句所影響的行數

    如果使用execute()方法執行的是查詢語句,那麼還要調用ResultSet、getResultSet()來獲取         select語句的查詢結果

5.5 ResultSet之滾動結果集(瞭解)

    ResultSet表示結果集,它是一個二維的表格!ResultSet內部維護一個行光標(遊標),ResultSet提供了一系列的方法來移動遊標:

    <1>void beforeFirst():把光標放到第一行的前面,這也是光標默認的位置;

    <2>void afterLast():把光標放到最後一行的後面;

    <3>boolean first():把光標放到第一行的位置上,返回值表示調控光標是否成功;

    <4>boolean last():把光標放到最後一行的位置上;返回值表示調控光標是否成功

    <5>boolean isBeforeFirst():當前光標位置是否在第一行前面;

    <6>boolean isAfterLast():當前光標位置是否在最後一行的後面;

    <7>boolean isFirst():當前光標位置是否在第一行上;

    <8>boolean isLast:當前光標位置是否在最後一行上;

    <9>boolean next():把光標向下挪一行;

    <10>boolean relative(int row):相對位移,當row爲正數時,表示向下移動row行,爲負數時     表示向上移動row行;

    <11>boolean absolute(int row):絕對位移,把光標移動到指定行上;

    <12>int getRow():返回當前光標所有行


    上面方法分爲兩類,一類用來判斷遊標位置的,另一類是用來移動遊標的。如果結果集是不可滾動的,那麼只能使用next()方法來移動遊標。而beforeFirst()、afterLast()、first()、last()、previous()、relative()方法都不能使用!

    結果集是否支持滾動,要從Connection類的createStatement()方法說起,也就是說創建的Statement決定了使用Statement創建的ResultSet是否支持滾動

    Statement createStatement(int resultSetType,int resultSetConcurrency)

    resultSetType的可選值:

    (1)ResultSet.TYPE_FORWARD_ONLY:不滾動結果集

    (2)ResultSet.TYPE_SCROLL_INSENSITIVE:滾動結果集,但結果集數據不會再跟隨數據庫而變      化;

    (3)ResultSet.TYPE_SCROLL_SENSITIVE:滾動結果集,但結果集數據不會在跟隨數據庫而變化


    可以看出,如果想使用滾動的結果集,我們應該選擇TYPE_SCROLL_INSENSITIVE!其實很少有數據庫驅動會支持TYPE_SCROLL_SENSITIVE的特性!通常我們也不需要查詢到的結果集再受到數據庫變化的影響

    resultSetConcurrency的可選值:

    (1)CONCUR_READ_ONLY:結果集是隻讀的,不能通過修改結果集而反向影響數據庫;

    (2)CONCUR_UPDATABLE:結果集是可更新的,對結果集的更新可以反向影響數據庫;


    通常可更新結果集這一“高級特性”我們也是不需要的!


    獲取滾動結果集的代碼如下:

    Connection con=Statement stmt=con.createStatement(ResultSet.TYPE.SCROLL.INSENSITIVE,CONCUR_READ_ONLY);

    String sql=....//查詢語句

   ResultSet rs=stmt.executeQuery(sql);//這個結果集是可滾動的

5.6 ResultSet之獲取列數據

    可以通過next()方法使ResultSet的遊標向下移動,當遊標移動到你需要的行時,就需要來獲取該行的數據了,ResultSet提供了一系列的獲取列數據的方法:

    (1)String getString(int columnIndex):獲取指定列的String類型數據;

    (2)int getInt(int columnIndex):獲取指定列的int類型數據

    (3)double getDouble(int columnIndex):獲取指定列的double類型數據;

    (4)boolean getBoolean(int columnIndex):獲取指定列的boolean類型數據

    (5)Object getObject(int columnIndex):獲取指定列的Object類型的數據


    上面的方法中,參數columnIndex表示列的索引,列索引從1開始,而不是0,這第一點與數組不同,如果你清楚當前列的數據類型,那麼可以使用getInt()之類的方法來獲取,如果你不清楚列的類型,那麼你應該使用getObject()方法類獲取

    ResultSet還提供了一套通過列名稱來獲取列數據的方法:

    (1)String getSting(String columnName):獲取名稱爲columnName的列的String數據;

    (2)int getInt(String columnName):獲取名稱爲columnName的列的int數據

    (3)double getDouble(String columnName):獲取名稱爲columnName的列的double數據;

    (4)boolean getBoolean(String columnName):獲取名稱爲columnName的列的boolean數據;

    (5)Object getObject(String columnName):獲取名稱爲columnName的列的Object數據

6、SQL注入

6.1 什麼是SQL***

    在需要用戶輸入的地方,用戶輸入的是SQL語句的片段,最終用戶輸入的SQL片段與我們dao中寫的SQL語句合成一個完整的SQL語句!例如用戶在登錄時輸入的用戶名和密碼都是爲SQL語句的片段

6.2 演示SQL*** 

    首先我們需要創建一張用戶表,用來存儲用戶的信息

    CREATE TABLE user(
    uid CHAR(32)PRIMARY KEY,
    username   VARCHAR(30)UNIQUE KEY NOT NULL,
    PASSWORD   VARCHAR(30)
    );
 
    INSERT INTO userVALUES('U_1001', 'zs', 'zs');
    SELECT * FROM user;

    現在用戶表中只有一行記錄,就是zs。

    下面我們寫一個login()方法

    public void login(String username, String password) {
        Connectioncon = null;
        Statementstmt = null;
        ResultSet rs= null;
        try {
            con =JdbcUtils.getConnection();
            stmt =con.createStatement();
            String sql = "SELECT * FROM user WHERE " +
                    "username='" +username + 
                    "'and password='" + password + "'";
            rs =stmt.executeQuery(sql);
            if(rs.next()){
                System.out.println("歡迎" +rs.getString("username"));
            } else {
                System.out.println("用戶名或密碼錯誤!");
            }
        } catch(Exception e) {
            throw newRuntimeException(e);
        } finally {
            JdbcUtils.close(con,stmt, rs);
        }       
    }

    下面是調用這個方法的代碼:

login("a' or 'a'='a", "a'or 'a'='a");

    這行當前會使我們登錄成功,因爲輸入的用戶名和密碼是SQL語句片段,最終與我們的login()方法中的SQL語句組合在一起,我們來看看組合在一起的SQL語句

SELECT * FROM tab_user WHERE username='a'or 'a'='a' and password='a' or 'a'='a'

6.3防止SQL***

    過濾用戶輸入的數據中是否包含非法字符;

    分步校驗,先使用用戶名來查詢用戶,如果查找到了,再比較密碼;

    使用PreparedStatement

6.4 PreparedStatement是什麼?

    PreparedStatement叫預編譯聲明

    PreparedStatement是Statement的子接口,你可以使用PreparedStatement來替換Statement

    PreparedStatement的好處:

    (1)防止SQL***

    (2)提高代碼的可讀性,以及可維護性

    (3)提高效率

6.5 PreparedStatement的使用

    使用Connection的prepareStatement(String sql):即創建它時就讓它與一條SQL模板綁定;

    調用PreparedStatement的setXXX()系列方法爲問號設置值;

    調用executeUpdate()或executeQuery()方法,但要注意,調用沒有參數的方法;

String sql = “select * from tab_studentwhere s_number=?”;
PreparedStatement pstmt =con.prepareStatement(sql);
pstmt.setString(1, “S_1001”);
ResultSet rs = pstmt.executeQuery();
rs.close();
pstmt.clearParameters();
pstmt.setString(1, “S_1002”);
rs= pstmt.executeQuery();

   在使用Connection創建PreparedStatement對象時需要給出一個SQL模板,所謂SQL模板就是有“?”的SQL語句,其中“?”就是參數

    在得到PreparedStatement對象後,調用它的setxxx()方法爲“?”賦值,這樣就可以得到把模板變成一條完整的SQL語句,然後再調用PreparedStatement對象的executeQuery()方法獲取ResultSet對象

    注意:PreparedStatement對象獨有的executeQuery()方法是沒有參數的,而Statement的executeQuery()是需要參數(SQL語句)的。因爲在創建PreparedStatement對象時已經讓它與一條SQL模板綁定在一起了,所以在調用它的executeQuery()和executeUpdate()方法時就不再需要參數了。

    PreparedStatement最大的好處就是在於重複使用同一模板,給予其不同的參數來重複的使用它。這纔是真正提高效率的原因

    所依,在以後的開發中,無論什麼情況,都去使用PreparedStatement,而不是使用Statement.


7、使用JDBC完成分類表CRUD(增刪改查)的操作

7.1 案例分析

使用JDBC對分類表category進行增刪改查操作


7.2 工具類

“獲得連接”和“釋放資源”兩次代碼將在之後的增刪改查所有功能中都存在,開發中一般遇到此種情況,將採用工具類的方法進行抽取,從而達到代碼的重複利用

wKiom1nbM3uy1XEWAAAXNSmRr2s732.jpg


7.3 獲得連接

wKiom1nbNInTxassAAANljNn57o779.jpg

wKioL1nbNDjCf_4TAABFwdjd5ZY572.jpg


7.4 釋放資源

如果釋放資源採用依次關閉三個對象,那麼第一個對象關閉時拋出了異常,後面兩個對象將無法成功釋放資源,通常我們使用try-catch塊進行處理

方法1:多個try-catch塊,將資源釋放,容易理解

wKioL1nbNX2gl_VsAABjstkCVFE134.jpg

wKioL1nbNX3ThFwMAAANxBPM2B8245.jpg

方法2:try-catch-finally嵌套,資源釋放時如果出錯,將通知調用者

wKioL1nbNhzxcusRAACHgzc7qQs103.jpg


7.5 使用properties配置文件

開發中獲得連接的4個參數(驅動、URL、用戶名、密碼)通常都存在配置文件中,方便後期維護,程序如果需要更換數據庫,只需要修改配置文件即可

通常情況下,我們習慣使用properties文件,此文件我們將做如下要求:

<1>文件位置:任意,建議src下

<2>文件名稱:任意,擴展名爲properties

<3>文件內容:一行一組數據,格式是“key=value”

    (1)key命名自定義,如果是多個單詞,習慣使用點分割。例如:jdbc.driver

    (2)value值不支持中文,如果需要使用非英文字符,將進行unicode轉換


7.5.1 創建配置文件

右鍵/New/File,輸入“db.properties”文件名

wKiom1nbdcaRxffAAAAW65UFElY023.jpg

a436c5e5f64c9148b1878484911bc248.jpg


7.5.2 加載配置文件:ResourceBundle對象

ResourceBundle提供getBundle()方法用於只提供properties文件即可,之後使用getString(key)通過key獲得value的值

1fbec71d81ebda03deb382de739dda2f.jpg

wKioL1nbd4SBsj5CAABjD788yB0331.jpg

wKiom1nbd9bwQtFVAAAmlUcPBjA469.jpg

7.5.3 獲得連接

wKiom1nbeNCxz1m2AABRO3RvHyU381.jpg


7.5.4 加載配置文件:Properties對象(可選)

對應properties文件處理,開發中也會使用Properties對象進行。在v3版本中我們將採用加載properties文件獲得流,然後使用Properties對象進行處理

wKioL1nbeXjj0-E8AAAfsC9FlTM936.jpg

wKiom1nbehXg-DcUAABE91zBRKg459.jpg

wKiom1nbehXg6x5cAACMKJVFM-k512.jpg

7.6 實現

7.6.1 模板


wKiom1nbeuzhqNQqAAA9XA-6G1Q328.jpg

wKioL1nbepujnmw1AAAEnliaNYk902.jpg

7.6.2 添加:insert into

wKiom1nbe8bjfjheAAB7Y4o2i1U616.jpg

7.6.3 更新:update...set

wKiom1nbfCbxVom-AAArHoAkJG4873.jpg

wKioL1nbe-SBz8tfAAA72rKEtl8340.jpg

7.6.4 刪除:delete

wKioL1nbfIKyv9UhAABx0eCFAG4549.jpg

7.6.5 通過ID查詢

wKiom1nbfTrAfLQVAACiRNuHjzA956.jpg

四、JDBC連接池

1、案例分析

實際開發中“獲得連接”或“釋放資源”是非常消耗系統資源的兩個過程,爲了解決此類性能問題,通常情況我們採用連接池技術,來共享連接Connection

2、連接池概述

2.1 概念

用池來管理Connection,這樣可以重複使用Connection。有了池,所以我們就不用自己來創建Connection,而是通過池來獲取Connection對象。當使用完Connection後,調用Connection的close()方法也不會真的關閉Connection,而是把Connection"歸還"給池。池就可以再利用這個Connection對象了。

wKioL1nbgsTQbuDOAAA2ws3uKIY821.jpg

2.2 規範

java爲數據庫連接池提供了公共的接口:javax.sql.DataSource,各個廠商需要讓自己的連接池實現這個接口。這樣應用程序纔可以方便的切換不同廠商的連接池!

常見的連接池:DBCP、C3P0


3、自定義連接池

3.1 案例分析

我們編寫自定義連接池,需要完成一下步驟

3.1.1 創建連接池實現(數據源),並實現接口javax.sql.DataSource。因爲我們只使用該接口中getConnection()方法,簡化本案例,我們將自己提供方法1,而沒有實現接口

3.1.2 提供一個集合,用於存放連接,因爲移除/添加操作過多,所以選擇LinkedList

3.1.3 本案例在靜態代碼塊中,爲連接池初始化3個連接

3.1.4 之後程序如果需要連接,調用實現類的getConnection(),本方法將從連接池(容器List)獲得連接。爲了保證當前連接只能提供給一個線程使用,所以我們將連接先從連接池中移除

3.1.5 當用戶使用完連接,釋放資源時,不執行close()方法,而是將連接添加到連接池中


3.2 案例實現

wKiom1nbiCSDuUcpAAAUP7S9eEo057.jpg

3.2.1 提供容器及初始化

wKioL1nbiEPCDouYAACAInwYc_0726.jpg

3.2.2 獲得連接

wKioL1nbihXxuvW0AABEROAieAI019.jpg

wKiom1nbimfzgSCwAAAlparid1g613.jpg

3.2.3 歸還連接

wKioL1nbjMPgIM40AABfOg6izv4308.jpg

3.2.4 測試使用

爲了體現連接池優勢,我們將採用多線程併發訪問,使同一個連接在不同的時段,被不同的線程使用

wKiom1nbjjziiDC0AAAqxUpWLoA936.jpg

wKioL1nbjeviIbR3AABM8H1Hwp4564.jpg

3.3 自定義連接池:方法增強

3.3.1 需求

自定義連接池中存在嚴重問題,用戶調用getConnection()獲得連接後,必須使用release()方法進行連接的歸還,如果用戶調用conn.close()將連接真正的釋放,連接池中將出現無連接可用。

此時我們希望,即使用戶調用了close()方法,連接仍歸還給連接池。close()方法原有功能釋放資源,期望功能:將當前連接歸還連接池。說明close()方法沒有我們希望的功能,我們將對close()方法進行增強,從而實現將連接歸還給連接池的功能

3.3.2 方法增強總結

<1>繼承,子類繼承父類,將父類的方法進行腹複寫,從而進行增強

   使用前提:必須有父類,且存在繼承關係

<2>裝飾者設計模式,此設計模式專門用於增強方法

   使用前提:必須有接口

   缺點:需要將接口的所有方法都實現

<3>動態代理:在運行時動態的創建代理類,完成增強操作。與裝飾者相似

   使用前提:必須有接口

   難點:需要反射技術

<4>字節碼增強,運行時創建目標類子類,從而進行增強

   常見第三方框架:cglib、javassist等

3.3.3 裝飾者設計模式

設計模式:專門爲解決一類問題,而編寫的固定格式的代碼。

裝飾者固定結構:接口A,已知實現類C,需要裝飾者創建代理類B

<1>創建類B,並實現接口A

<2>提供類B的構造方法,參數類型爲A,用於接收A接口的其他實現類(C)

<3>給類B添加類型爲A成員變量,用於存放A接口的其他實現類

<4>增強需要的方法

<5>實現不需要增強的方法,方法體重調用成員變量存放的其他實現類對應的方法

edda48f5c6060f1d9d57dccd0715f3d3.jpg

3.3.4 實現

3.3.4.1 裝飾類

e82261f2740b6cd1b6b1153b5b22d83d.jpg

97bcc76c0cc1e7e7b704159922b7a6d8.jpg

3.3.4.2 使用裝飾者(包裝類)

將由DriverManager創建的連接,使用裝飾類包裝一下,然後添加到連接池中,構造方法中將容器pool傳遞進去,方便連接的歸還

82ba1d8169c62322c9e572b3d5c684e1.jpg

3.3.4.3 使用連接

54055b1e037141e2b2e9be37f03add60.jpg


4、C3P0連接池

C3P0是開源免費的連接池,目前使用它的開源項目有:Spring、Hibernate等。使用第三方工具需要導入jar包,C3P0使用時還需要添加配置文件c3p0-config.xml

4.1 導入jar包

我們使用的0.9.2版本,需要導入2個jar包

b1378f23242c0278306d300082e42051.jpg

4.2 配置文件

<1>配置文件名稱:c3p0-config.xml(固定)

<2>配置文件位置:src(類路徑)

<3>配置文件內容:命名配置

e94a139a725bb45fb1e37fd5d1070f79.jpg

配置文件內容:默認配置

29c445d762671af666377e78ec36f4fc.jpg

76e136c5e8949d4410153ed21fa88cff.jpg

4.3 常見配置項

b53f2e6d00f1f3240a0c80bd31ca32b9.jpg

033809efa132a901baf13e41d908f952.jpg

4.4 編寫工具類

C3P0提供核心工具類:ComboPooledDataSource,如果要使用連接池,必須創建該類的實例對象。

765cec635b96f2ef0ce60ea746cbe3cc.jpg

aaa1b457e43998f702090e29f7bacc9a.jpg

f3d80589f4be6a56cc2218f8c26034e2.jpg

5、DBCP連接池

DBCP也是一個開源的連接池,是Apache Common成員之一,在企業開發中也比較常見,tomcat內置的連接池

5.1 導入jar包

f1d97daeaba501ce923c5b4d5840f419.jpg


5.2 配置文件

<1>配置文件名稱:*.properties

<2>配置文件位置:任意,建議src(classpath/類路徑)

<3>配置文件內容:properties不能編寫中文,不支持在STS中修改,必須使用記事本修改內容,否則中文註釋就亂碼了

b94b2b8af9add4a4ca218c4e42c76e83.jpg

5.3 常見配置項

9b4a2e82f281825cd3bf1532865f6a6d.jpg

a1646ff0a05358dd6eae125bb0a00939.jpg

5.4 編寫工具類

0cf16fcd99c601130cda0ea7ec335282.jpg

c1a5771b66bf165216e80df949a883fc.jpg

五、使用DBUtils增刪改查的操作

1、案例分析

如果只使用JDBC進行開發,我們會發現冗餘代碼過多,爲了簡化JDBC開發,我們將採用apache commons組件一個成員:DEUtils。

DBUtils就是JDBC的簡化開發工具包。需要使用技術:連接池(獲得連接),SQL語句都沒有少

2、案例相關知識

2.1、JavaBean組件

javaBean就是一個類,在開發中常用於封裝數據。具有如下特性

<1>需要實現接口:java.io.Serializable,通常偷懶省略了。

<2>提供私有字段:private類型 字段名;

<3>提供getter/setter方法

<4>提供無參構造

9e85e05c2bb29b0251a43ef39fec819b.jpg

a41807b8ab51a54d7b9933d9bdffb901.jpg

3、DBUtils完成CRUD

3.1 概述

DBUtils是java編程中的數據庫操作實用工具,小巧簡單實用

DBUtils封裝了對JDBC的操作,簡化了JDBC操作,可以少寫代碼

DBUtils三個核心功能介紹

<1>QueryRunner中提供對sql語句操作的API

<2>ResultSetHandler接口,用於定義select操作後,怎樣封裝結果集

<3>DBUtils類,它就是一工具類,定義了關閉資源與事務處理的方法

3.2 QueryRunner核心類

<1>QueryRunner(DataSource ds),提供數據源(連接池),DBUtils底層自動維護連接connection

<2>update(String sql,Object...params),執行更新數據

<3>query(String sql,ResultSetHandler<T>rsh,Object...params),執行查詢

3.3 ResultSetHandler結果集處理類

5835699d49beb5918427a3a4a04389b9.jpg1df73375ebfb2b5430a4b66f40b4ebcc.jpg

3.4 DBUtils工具類

closeQuietly(Connection conn) 關閉連接,如果有異常,try後不拋

commitAndCloseQuietly(Connection conn) 提交併關閉連接

rollbackAndCloseQuietly(Connection conn) 回滾並關閉連接

3.5 實現

3.5.1 添加

40be82f58b692e1a36c23e2819b81536.jpg

8c7850de9c07638d86ae3ccdcd441218.jpg

3.5.2 更新

2dc650496b3949f4c7234a9b29fd92fa.jpg

3.5.3 刪除

954f25e5e16ea6ba7e8494bd82a4bbc3.jpg62b3cab72468595500857006ab48e0df.jpg

3.5.4 通過id查詢

05c4011b4677373337f51da28fff981d.jpg

3.5.5 查詢所有

4958afef8de5b8b4143f554008635ddb.jpg

44a0126294c0f385f9a2acaf8009a9e9.jpg

3.5.6 總記錄數

a569a0191884dd08369114eeb490b367.jpg

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章