25、連線池(DBCP、C3P0)、動態代理與分頁技術

guanhang89發表於2016-06-11

連線池

思考:

程式中連線如何管理?
1.  連線資源寶貴;需要對連線管理
2.  連線:
    a)  運算元據庫,建立連線
    b)  操作結束,  關閉!
分析:
涉及頻繁的連線的開啟、關閉,影響程式的執行效率!
連線管理:
預先建立一組連線,有的時候每次取出一個; 用完後,放回;

學習連線池:

a.  自定義一個連線池
b.  學習優秀的連線池元件
a)  DBCP
b)  C3P0

動態代理

Java提供了一個Proxy類,呼叫它的newInstance方法可以生成某個物件的代理物件,使用該方法生成代理物件時,需要三個引數:

1.生成代理物件使用哪個類裝載器
2.生成哪個物件的代理物件,通過介面指定
3.生成的代理物件的方法裡幹什麼事,由開發人員編寫handler介面的實現來指定。

初學者必須理解,或不理解必須記住的2件事情:

Proxy類負責建立代理物件時,如果指定了handler(處理器),那麼不管使用者呼叫代理物件的什麼方法,該方法都是呼叫處理器的invoke方法(相當於攔截了)。
由於invoke方法被呼叫需要三個引數:代理物件、方法、方法的引數,因此不管代理物件哪個方法呼叫處理器的invoke方法,都必須把自己所在的物件、自己(呼叫invoke方法的方法)、方法的引數傳遞進來。

自定義連線池

代理:

如果對某個介面中的某個指定的方法的功能進行擴充套件,而不想實現介面裡所有方法,可以使用(動態)代理模式!
Java中代理模式:靜態/動態/Cglib代理(spring)
使用動態代理,可以監測介面中方法的執行!

如何對Connection物件,生成一個代理物件

|--Proxy
    static Object newProxyInstance(
ClassLoader loader,    當前使用的類載入器
Class<?>[] interfaces,   目標物件(Connection)實現的介面型別
InvocationHandler h    事件處理器:當執行上面介面中的方法的時候,就會自動觸發事件處理器程式碼
把當前執行的方法(method)作為引數傳入。)

/**
 * 自定義連線池, 管理連線
 * 程式碼實現:
    1.  MyPool.java  連線池類,   
    2.  指定全域性引數:  初始化數目、最大連線數、當前連線、   連線池集合
    3.  建構函式:迴圈建立3個連線
    4.  寫一個建立連線的方法
    5.  獲取連線
    ------>  判斷: 池中有連線, 直接拿
     ------>                池中沒有連線,
    ------>                 判斷,是否達到最大連線數; 達到,丟擲異常;沒有達到最大連線數,
            建立新的連線
    6. 釋放連線
     ------->  連線放回集合中(..)
 *
 */
public class MyPool {

    private int init_count = 3;     // 初始化連線數目
    private int max_count = 6;      // 最大連線數
    private int current_count = 0;  // 記錄當前使用連線數
    // 連線池 (存放所有的初始化連線)
    private LinkedList<Connection> pool = new LinkedList<Connection>();


    //1.  建構函式中,初始化連線放入連線池
    public MyPool() {
        // 初始化連線
        for (int i=0; i<init_count; i++){
            // 記錄當前連線數目
            current_count++;
            // 建立原始的連線物件
            Connection con = createConnection();
            // 把連線加入連線池
            pool.addLast(con);
        }
    }

    //2. 建立一個新的連線的方法
    private Connection createConnection(){
        try {
            Class.forName("com.mysql.jdbc.Driver");
            // 原始的目標物件
            final Connection con = DriverManager.getConnection("jdbc:mysql:///jdbc_demo", "root", "root");

            /**********對con物件代理**************/

            // 對con建立其代理物件
            Connection proxy = (Connection) Proxy.newProxyInstance(

                    con.getClass().getClassLoader(),    // 類載入器
                    //con.getClass().getInterfaces(),   // 當目標物件是一個具體的類的時候 
                    new Class[]{Connection.class},      // 目標物件實現的介面

                    new InvocationHandler() {           // 當呼叫con物件方法的時候, 自動觸發事務處理器
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args)
                                throws Throwable {
                            // 方法返回值
                            Object result = null;
                            // 當前執行的方法的方法名
                            String methodName = method.getName();

                            // 判斷當執行了close方法的時候,把連線放入連線池
                            if ("close".equals(methodName)) {
                                System.out.println("begin:當前執行close方法開始!");
                                // 連線放入連線池
                                pool.addLast(con);
                                System.out.println("end: 當前連線已經放入連線池了!");
                            } else {
                                // 呼叫目標物件方法
                                result = method.invoke(con, args);
                            }
                            return result;
                        }
                    }
            );
            return proxy;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    //3. 獲取連線
    public Connection getConnection(){

        // 3.1 判斷連線池中是否有連線, 如果有連線,就直接從連線池取出
        if (pool.size() > 0){
            current_count++;
            return pool.removeFirst();
        }

        // 3.2 連線池中沒有連線: 判斷,如果沒有達到最大連線數,建立;
        if (current_count < max_count) {
            // 記錄當前使用的連線數
            current_count++;
            // 建立連線
            return createConnection();
        }

        // 3.3 如果當前已經達到最大連線數,丟擲異常
        throw new RuntimeException("當前連線已經達到最大連線數目 !");
    }


    //4. 釋放連線
    public void realeaseConnection(Connection con) {
        // 4.1 判斷: 池的數目如果小於初始化連線,就放入池中
        if (pool.size() < init_count){
            pool.addLast(con);
        } else {
            try {
                // 4.2 關閉 
                current_count--;
                con.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) throws SQLException {
        MyPool pool = new MyPool();
        System.out.println("當前連線: " + pool.current_count);  // 3

        // 使用連線
        pool.getConnection();
        pool.getConnection();
        Connection con4 = pool.getConnection();
        Connection con3 = pool.getConnection();
        Connection con2 = pool.getConnection();
        Connection con1 = pool.getConnection();

        // 釋放連線, 連線放回連線池
//      pool.realeaseConnection(con1);
        /*
         * 希望:當關閉連線的時候,要把連線放入連線池!【當呼叫Connection介面的close方法時候,希望觸發pool.addLast(con);操作】
         *                                                                          把連線放入連線池
         * 解決1:實現Connection介面,重寫close方法
         * 解決2:動態代理
         */
        con1.close();

        // 再獲取
        pool.getConnection();

        System.out.println("連線池:" + pool.pool.size());      // 0
        System.out.println("當前連線: " + pool.current_count);  // 3
    }

}

代理的總結:

使用代理,可以在不實現介面的情況,對介面的方法進行擴充套件,新增額外的使用者需要的業務邏輯!

DBCP連線池

概述:

Sun公司約定: 如果是連線池技術,需要實現一個介面!
javax.sql.DataSource;

連線池:

DBCP
C3P0

DBCP連線池:

DBCP 是 Apache 軟體基金組織下的開源連線池實現,使用DBCP資料來源,應用程式應在系統中增加如下兩個 jar 檔案:

•   Commons-dbcp.jar:連線池的實現
•   Commons-pool.jar:連線池實現的依賴庫

Tomcat 的連線池正是採用該連線池來實現的。該資料庫連線池既可以與應用伺服器整合使用,也可由應用程式獨立使用。

核心類:

BasicDataSource

使用步驟

•   引入jar檔案
commons-dbcp-1.4.jar
commons-pool-1.5.6.jar

配置方式實現DBCP連線池, 配置檔案中的key與BaseDataSouce中的屬性一樣:

db.properties
url=jdbc:mysql:///jdbc_demo
driverClassName=com.mysql.jdbc.Driver
username=root
password=root
initialSize=3
maxActive=6
maxIdle=3000

使用:

public class App_DBCP {

    // 1. 硬編碼方式實現連線池
    @Test
    public void testDbcp() throws Exception {
        // DBCP連線池核心類
        BasicDataSource dataSouce = new BasicDataSource();
        // 連線池引數配置:初始化連線數、最大連線數 / 連線字串、驅動、使用者、密碼
        dataSouce.setUrl("jdbc:mysql:///jdbc_demo");            //資料庫連線字串
        dataSouce.setDriverClassName("com.mysql.jdbc.Driver");  //資料庫驅動
        dataSouce.setUsername("root");                          //資料庫連線使用者
        dataSouce.setPassword("root");                          //資料庫連線密碼
        dataSouce.setInitialSize(3);  // 初始化連線
        dataSouce.setMaxActive(6);    // 最大連線
        dataSouce.setMaxIdle(3000);   // 最大空閒時間

        // 獲取連線
        Connection con = dataSouce.getConnection();
        con.prepareStatement("delete from admin where id=3").executeUpdate();
        // 關閉
        con.close();
    }

    @Test
    // 2. 【推薦】配置方式實現連線池  ,  便於維護
    public void testProp() throws Exception {
        // 載入prop配置檔案
        Properties prop = new Properties();
        // 獲取檔案流
        InputStream inStream = App_DBCP.class.getResourceAsStream("db.properties");
        // 載入屬性配置檔案
        prop.load(inStream);
        // 根據prop配置,直接建立資料來源物件
        DataSource dataSouce = BasicDataSourceFactory.createDataSource(prop);

        // 獲取連線
        Connection con = dataSouce.getConnection();
        con.prepareStatement("delete from admin where id=4").executeUpdate();
        // 關閉
        con.close();
    }
}

C3P0

C3P0連線池:

最常用的連線池技術!Spring框架,預設支援C3P0連線池技術!

C3P0連線池,核心類:

CombopooledDataSource ds;

使用:

1.  下載,引入jar檔案:  c3p0-0.9.1.2.jar
2.  使用連線池,建立連線
    a)  硬編碼方式
    b)  配置方式(xml)

案例:

public class App {

    @Test
    //1. 硬編碼方式,使用C3P0連線池管理連線
    public void testCode() throws Exception {
        // 建立連線池核心工具類
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        // 設定連線引數:url、驅動、使用者密碼、初始連線數、最大連線數
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/jdbc_demo");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setInitialPoolSize(3);
        dataSource.setMaxPoolSize(6);
        dataSource.setMaxIdleTime(1000);

        // ---> 從連線池物件中,獲取連線物件
        Connection con = dataSource.getConnection();
        // 執行更新
        con.prepareStatement("delete from admin where id=7").executeUpdate();
        // 關閉
        con.close();
    }

    @Test
    //2. XML配置方式,使用C3P0連線池管理連線
    public void testXML() throws Exception {
        // 建立c3p0連線池核心工具類
        // 自動載入src下c3p0的配置檔案【c3p0-config.xml】
        ComboPooledDataSource dataSource = new ComboPooledDataSource();// 使用預設的配置

        // 獲取連線
        Connection con = dataSource.getConnection();
        // 執行更新
        con.prepareStatement("delete from admin where id=5").executeUpdate();
        // 關閉
        con.close();

    }
}

配置方式:

<c3p0-config>
    <default-config>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc_demo
        </property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="user">root</property>
        <property name="password">root</property>
        <property name="initialPoolSize">3</property>
        <property name="maxPoolSize">6</property>
        <property name="maxIdleTime">1000</property>
    </default-config>

    <named-config name="oracle_config">
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc_demo</property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="user">root</property>
        <property name="password">root</property>
        <property name="initialPoolSize">3</property>
        <property name="maxPoolSize">6</property>
        <property name="maxIdleTime">1000</property>
    </named-config>
</c3p0-config>

重寫Utils

public class JdbcUtils {

    /**
     *  1. 初始化C3P0連線池
     */
    private static  DataSource dataSource;
    static {
        dataSource = new ComboPooledDataSource();
    }

    /**
     * 2. 建立DbUtils核心工具類物件
     */
    public static QueryRunner getQueryRuner(){
        // 建立QueryRunner物件,傳入連線池物件
        // 在建立QueryRunner物件的時候,如果傳入了資料來源物件;
        // 那麼在使用QueryRunner物件方法的時候,就不需要傳入連線物件;
        // 會自動從資料來源中獲取連線(不用關閉連線)
        return new QueryRunner(dataSource);
    }
}

分頁技術實踐

分頁技術:
JSP頁面,用來顯示資料! 如果資料有1000條,分頁顯示,每頁顯示10條,共100頁; 好處: 利於頁面佈局,且顯示的效率高!

分頁關鍵點:

  1. 分頁SQL語句;
  2. 後臺處理: dao/service/servlet/JSP

實現步驟:

0.  環境準備
    a)  引入jar檔案及引入配置檔案
        i.  資料庫驅動包
        ii. C3P0連線池jar檔案 及 配置檔案
        iii.    DbUtis元件:    QueryRunner qr = new QueryRuner(dataSouce);
        qr.update(sql);
b)  公用類: JdbcUtils.java
    1.  先設計:PageBean.java
    2.  Dao介面設計/實現:   2個方法
    3.  Service/servlet
    4.  JSP

entity:

public class Employee {

    private int empId;          // 員工id
    private String empName;     // 員工名稱
    private int dept_id;        // 部門id

    public int getEmpId() {
        return empId;
    }
    public void setEmpId(int empId) {
        this.empId = empId;
    }
    public String getEmpName() {
        return empName;
    }
    public void setEmpName(String empName) {
        this.empName = empName;
    }
    public int getDept_id() {
        return dept_id;
    }
    public void setDept_id(int deptId) {
        dept_id = deptId;
    }       
}

Dao:

public interface IEmployeeDao {

    /**
     * 分頁查詢資料
     */
    public void getAll(PageBean<Employee> pb);

    /**
     * 查詢總記錄數
     */
    public int getTotalCount();
}

DaoImpl:

public class EmployeeDao implements IEmployeeDao {

    public void getAll(PageBean<Employee> pb) {

        //2. 查詢總記錄數;  設定到pb物件中
        int totalCount = this.getTotalCount();
        pb.setTotalCount(totalCount);

        /*
         * 問題: jsp頁面,如果當前頁為首頁,再點選上一頁報錯!
         *              如果當前頁為末頁,再點下一頁顯示有問題!
         * 解決:
         *     1. 如果當前頁 <= 0;       當前頁設定當前頁為1;
         *     2. 如果當前頁 > 最大頁數;  當前頁設定為最大頁數
         */
        // 判斷
        if (pb.getCurrentPage() <=0) {
            pb.setCurrentPage(1);                       // 把當前頁設定為1
        } else if (pb.getCurrentPage() > pb.getTotalPage()){
            pb.setCurrentPage(pb.getTotalPage());       // 把當前頁設定為最大頁數
        }

        //1. 獲取當前頁: 計算查詢的起始行、返回的行數
        int currentPage = pb.getCurrentPage();
        int index = (currentPage -1 ) * pb.getPageCount();      // 查詢的起始行
        int count = pb.getPageCount();                          // 查詢返回的行數


        //3. 分頁查詢資料;  把查詢到的資料設定到pb物件中
        String sql = "select * from employee limit ?,?";

        try {
            // 得到Queryrunner物件
            QueryRunner qr = JdbcUtils.getQueryRuner();
            // 根據當前頁,查詢當前頁資料(一頁資料)
            List<Employee> pageData = qr.query(sql, new BeanListHandler<Employee>(Employee.class), index, count);
            // 設定到pb物件中
            pb.setPageData(pageData);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

service:

public interface IEmployeeService {

    /**
     * 分頁查詢資料
     */
    public void getAll(PageBean<Employee> pb);
}

serviceimpl:

public class EmployeeService implements IEmployeeService {

    // 建立Dao例項
    private IEmployeeDao employeeDao = new EmployeeDao();

    public void getAll(PageBean<Employee> pb) {
        try {
            employeeDao.getAll(pb);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

servlet:

public class IndexServlet extends HttpServlet {
    // 建立Service例項
    private IEmployeeService employeeService = new EmployeeService();
    // 跳轉資源
    private String uri;

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        try {
            //1. 獲取“當前頁”引數;  (第一次訪問當前頁為null) 
            String currPage = request.getParameter("currentPage");
            // 判斷
            if (currPage == null || "".equals(currPage.trim())){
                currPage = "1";     // 第一次訪問,設定當前頁為1;
            }
            // 轉換
            int currentPage = Integer.parseInt(currPage);

            //2. 建立PageBean物件,設定當前頁引數; 傳入service方法引數
            PageBean<Employee> pageBean = new PageBean<Employee>();
            pageBean.setCurrentPage(currentPage);

            //3. 呼叫service  
            employeeService.getAll(pageBean);    // 【pageBean已經被dao填充了資料】

            //4. 儲存pageBean物件,到request域中
            request.setAttribute("pageBean", pageBean);

            //5. 跳轉 
            uri = "/WEB-INF/list.jsp";
        } catch (Exception e) {
            e.printStackTrace();  // 測試使用
            // 出現錯誤,跳轉到錯誤頁面;給使用者友好提示
            uri = "/error/error.jsp";
        }
        request.getRequestDispatcher(uri).forward(request, response);

    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doGet(request, response);
    }

}

PageBean:

public class PageBean<T> {
    private int currentPage = 1; // 當前頁, 預設顯示第一頁
    private int pageCount = 4;   // 每頁顯示的行數(查詢返回的行數), 預設每頁顯示4行
    private int totalCount;      // 總記錄數
    private int totalPage;       // 總頁數 = 總記錄數 / 每頁顯示的行數  (+ 1)
    private List<T> pageData;       // 分頁查詢到的資料

    // 返回總頁數
    public int getTotalPage() {
        if (totalCount % pageCount == 0) {
            totalPage = totalCount / pageCount;
        } else {
            totalPage = totalCount / pageCount + 1;
        }
        return totalPage;
    }
    public void setTotalPage(int totalPage) {
        this.totalPage = totalPage;
    }

    public int getCurrentPage() {
        return currentPage;
    }
    public void setCurrentPage(int currentPage) {
        this.currentPage = currentPage;
    }
    public int getPageCount() {
        return pageCount;
    }
    public void setPageCount(int pageCount) {
        this.pageCount = pageCount;
    }
    public int getTotalCount() {
        return totalCount;
    }
    public void setTotalCount(int totalCount) {
        this.totalCount = totalCount;
    }

    public List<T> getPageData() {
        return pageData;
    }
    public void setPageData(List<T> pageData) {
        this.pageData = pageData;
    }
}

jsp:

<html>
  <head>

    <title>分頁查詢資料</title>
    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="expires" content="0">    
  </head>

  <body>
    <table border="1" width="80%" align="center" cellpadding="5" cellspacing="0">
        <tr>
            <td>序號</td>
            <td>員工編號</td>
            <td>員工姓名</td>
        </tr>
        <!-- 迭代資料 -->
        <c:choose>
            <c:when test="${not empty requestScope.pageBean.pageData}">
                <c:forEach var="emp" items="${requestScope.pageBean.pageData}" varStatus="vs">
                    <tr>
                        <td>${vs.count }</td>
                        <td>${emp.empId }</td>
                        <td>${emp.empName }</td>
                    </tr>
                </c:forEach>
            </c:when>
            <c:otherwise>
                <tr>
                    <td colspan="3">對不起,沒有你要找的資料</td>
                </tr>
            </c:otherwise>
        </c:choose>

        <tr>
            <td colspan="3" align="center">
                當前${requestScope.pageBean.currentPage }/${requestScope.pageBean.totalPage }頁     &nbsp;&nbsp;

                <a href="${pageContext.request.contextPath }/index?currentPage=1">首頁</a>
                <a href="${pageContext.request.contextPath }/index?currentPage=${requestScope.pageBean.currentPage-1}">上一頁 </a>
                <a href="${pageContext.request.contextPath }/index?currentPage=${requestScope.pageBean.currentPage+1}">下一頁 </a>
                <a href="${pageContext.request.contextPath }/index?currentPage=${requestScope.pageBean.totalPage}">末頁</a>
            </td>
        </tr>

    </table>
  </body>
</html>

相關文章