JDBC【數據庫鏈接池、DbUtils框架、分頁】

1.數據庫鏈接池

什麼是數據庫鏈接池

簡單來講:數據庫鏈接池就是提供鏈接的。。。java

爲何咱們要使用數據庫鏈接池

  • 數據庫的鏈接的創建和關閉是很是消耗資源的
  • 頻繁地打開、關閉鏈接形成系統性能低下

編寫鏈接池

  1. 編寫鏈接池需實現java.sql.DataSource接口
  2. 建立批量的Connection用LinkedList保存【既然是個池,固然用集合保存、、LinkedList底層是鏈表,對增刪性能較好】
  3. 實現getConnetion(),讓getConnection()每次調用,都是在LinkedList中取一個Connection返回給用戶
  4. 調用Connection.close()方法,Connction返回給LinkedList
private static LinkedList<Connection> list = new LinkedList<>();
    
    //獲取鏈接只須要一次就夠了,因此用static代碼塊
    static {
        //讀取文件配置
        InputStream inputStream = Demo1.class.getClassLoader().getResourceAsStream("db.properties");

        Properties properties = new Properties();
        try {
            properties.load(inputStream);
            String url = properties.getProperty("url");
            String username = properties.getProperty("username");
            String driver = properties.getProperty("driver");
            String password = properties.getProperty("password");

            //加載驅動
            Class.forName(driver);

            //獲取多個鏈接,保存在LinkedList集合中
            for (int i = 0; i < 10; i++) {
                Connection connection = DriverManager.getConnection(url, username, password);
                list.add(connection);
            }
            
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }

    //重寫Connection方法,用戶獲取鏈接應該從LinkedList中給他
    @Override
    public Connection getConnection() throws SQLException {
        System.out.println(list.size());
        System.out.println(list);

       //先判斷LinkedList是否存在鏈接
       return list.size() > 0 ? list.removeFirst() : null; 
    }



複製代碼

咱們已經完成前三步了,如今問題來了**。咱們調用Conncetion.close()方法,是把數據庫的物理鏈接關掉,而不是返回給LinkedList的**mysql

解決思路:算法

  1. 寫一個Connection子類,覆蓋close()方法
  2. 寫一個Connection包裝類,加強close()方法
  3. 用動態代理,返回一個代理對象出去,攔截close()方法的調用,對close()加強

分析第一個思路:sql

  • Connection是經過數據庫驅動加載的,保存了數據的信息。寫一個子類Connection,new出對象,子類的Connction沒法直接繼承父類的數據信息,也就是說子類的Connection是沒法鏈接數據庫的,更別談覆蓋close()方法了。

分析第二個思路:數據庫

  • 寫一個Connection包裝類。
    1. 寫一個類,實現與被加強對象的相同接口【Connection接口】
    2. 定義一個變量,指向被加強的對象
    3. 定義構造方法,接收被加強對象
    4. 覆蓋想加強的方法
    5. 對於不想加強的方法,直接調用被加強對象的方法
  • 這個思路自己是沒什麼毛病的,就是實現接口時,方法太多了!,因此咱們也不使用此方法

分析第三個思路代碼實現:數組

@Override
    public Connection getConnection() throws SQLException {

        if (list.size() > 0) {
            final Connection connection = list.removeFirst();

            //看看池的大小
            System.out.println(list.size());

            //返回一個動態代理對象
            return (Connection) Proxy.newProxyInstance(Demo1.class.getClassLoader(), connection.getClass().getInterfaces(), new InvocationHandler() {

                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                    //若是不是調用close方法,就按照正常的來調用
                    if (!method.getName().equals("close")) {
                        method.invoke(connection, args);
                    } else {

                        //進到這裏來,說明調用的是close方法
                        list.add(connection);

                        //再看看池的大小
                        System.out.println(list.size());

                    }
                    return null;
                }

            });
        }
        return null;
    }


複製代碼

咱們上面已經可以簡單編寫一個線程池了。下面咱們來使用一下開源數據庫鏈接池tomcat

DBCP

使用DBCP數據源的步驟:服務器

  1. 導入兩個jar包【Commons-dbcp.jar和Commons-pool.jar】
  2. 讀取配置文件
  3. 獲取BasicDataSourceFactory對象
  4. 建立DataSource對象
private static DataSource dataSource = null;

    static {
        try {
            //讀取配置文件
            InputStream inputStream = Demo3.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
            Properties properties = new Properties();
            properties.load(inputStream);

            //獲取工廠對象
            BasicDataSourceFactory basicDataSourceFactory = new BasicDataSourceFactory();
            dataSource = basicDataSourceFactory.createDataSource(properties);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();

    }

    //這裏釋放資源不是把數據庫的物理鏈接釋放了,是把鏈接歸還給鏈接池【鏈接池的Connection內部本身作好了】
    public static void release(Connection conn, Statement st, ResultSet rs) {

        if (rs != null) {
            try {
                rs.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            rs = null;
        }
        if (st != null) {
            try {
                st.close();
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        if (conn != null) {
            try {
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }


複製代碼

C3P0

C3P0數據源的性能更勝一籌,而且它能夠使用XML配置文件配置信息!微信

步驟:oracle

  1. 導入開發包【c3p0-0.9.2-pre1.jar】和【mchange-commons-0.2.jar】
  2. 導入XML配置文件【能夠在程序中本身一個一個配,C3P0的doc中的Configuration有XML文件的事例】
  3. new出ComboPooledDataSource對象
private static ComboPooledDataSource comboPooledDataSource = null;

    static {
        //若是我什麼都不指定,就是使用XML默認的配置,這裏我指定的是oracle的
        comboPooledDataSource = new ComboPooledDataSource("oracle");
    }

    public static Connection getConnection() throws SQLException {
        return comboPooledDataSource.getConnection();
    }

複製代碼

Tomcat數據源

Tomcat服務器也給咱們提供了鏈接池,內部其實就是DBCP

步驟:

  1. 在META-INF目錄下配置context.xml文件【文件內容能夠在tomcat默認頁面的 JNDI Resources下Configure Tomcat's Resource Factory找到】
  2. 導入Mysql或oracle開發包到tomcat的lib目錄下
  3. 初始化JNDI->獲取JNDI容器->檢索以XXX爲名字在JNDI容器存放的鏈接池

context.xml文件的配置:

<Context>

  <Resource name="jdbc/EmployeeDB"
            auth="Container"
            type="javax.sql.DataSource"
            
            username="root"
            password="root"
            driverClassName="com.mysql.jdbc.Driver"
            url="jdbc:mysql://localhost:3306/zhongfucheng"
            maxActive="8"
            maxIdle="4"/>
</Context>

複製代碼
try {

			//初始化JNDI容器
            Context initCtx = new InitialContext();

			//獲取到JNDI容器
            Context envCtx = (Context) initCtx.lookup("java:comp/env");

			//掃描以jdbc/EmployeeDB名字綁定在JNDI容器下的鏈接池
            DataSource ds = (DataSource)
                    envCtx.lookup("jdbc/EmployeeDB");

            Connection conn = ds.getConnection();
            System.out.println(conn);

        } 


複製代碼

使用dbutils框架

dbutils它是對JDBC的簡單封裝,極大簡化jdbc編碼的工做量

DbUtils類

提供了關閉鏈接,裝載JDBC驅動,回滾提交事務等方法的工具類【比較少使用,由於咱們學了鏈接池,就應該使用鏈接池鏈接數據庫】

QueryRunner類

該類簡化了SQL查詢,配合ResultSetHandler使用,能夠完成大部分的數據庫操做,重載了許多的查詢,更新,批處理方法。大大減小了代碼量

ResultSetHandler接口

該接口規範了對ResultSet的操做,要對結果集進行什麼操做,傳入ResultSetHandler接口的實現類便可。

  • ArrayHandler:把結果集中的第一行數據轉成對象數組。
  • ArrayListHandler:把結果集中的每一行數據都轉成一個數組,再存放到List中。
  • BeanHandler:將結果集中的第一行數據封裝到一個對應的JavaBean實例中。
  • BeanListHandler:將結果集中的每一行數據都封裝到一個對應的JavaBean實例中,存放到List裏。
  • ColumnListHandler:將結果集中某一列的數據存放到List中。
  • KeyedHandler(name):將結果集中的每一行數據都封裝到一個Map裏,再把這些map再存到一個map裏,其key爲指定的key。
  • MapHandler:將結果集中的第一行數據封裝到一個Map裏,key是列名,value就是對應的值。
  • MapListHandler:將結果集中的每一行數據都封裝到一個Map裏,而後再存放到List
  • ScalarHandler 將ResultSet的一個列到一個對象中。

使用DbUtils框架對數據庫的CRUD

/* * 使用DbUtils框架對數據庫的CRUD * 批處理 * * */
public class Test {

    @org.junit.Test public void add() throws SQLException {

        //建立出QueryRunner對象
        QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "INSERT INTO student (id,name) VALUES(?,?)";

        //咱們發現query()方法有的須要傳入Connection對象,有的不須要傳入
        //區別:你傳入Connection對象是須要你來銷燬該Connection,你不傳入,由程序幫你把Connection放回到鏈接池中
        queryRunner.update(sql, new Object[]{"100", "zhongfucheng"});

    }

    @org.junit.Test public void query()throws SQLException {

        //建立出QueryRunner對象
        QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "SELECT * FROM student";

        List list = (List) queryRunner.query(sql, new BeanListHandler(Student.class));
        System.out.println(list.size());

    }

    @org.junit.Test public void delete() throws SQLException {
        //建立出QueryRunner對象
        QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "DELETE FROM student WHERE id='100'";

        queryRunner.update(sql);
    }

    @org.junit.Test public void update() throws SQLException {
        //建立出QueryRunner對象
        QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "UPDATE student SET name=? WHERE id=?";

        queryRunner.update(sql, new Object[]{"zhongfuchengaaa", 1});
    }

    @org.junit.Test public void batch() throws SQLException {
        //建立出QueryRunner對象
        QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "INSERT INTO student (name,id) VALUES(?,?)";

        Object[][] objects = new Object[10][];
        for (int i = 0; i < 10; i++) {
            objects[i] = new Object[]{"aaa", i + 300};
        }
        queryRunner.batch(sql, objects);
    }

}


複製代碼

分頁

分頁技術是很是常見的,在搜索引擎下搜索頁面,不可能把所有數據都顯示在一個頁面裏邊。因此咱們用到了分頁技術。

Oracle實現分頁

/* Oracle分頁語法: @lineSize---每頁顯示數據行數 @currentPage----當前所在頁 */
	SELECT *FROM (
	    SELECT 列名,列名,ROWNUM rn
	    FROM 表名
	    WHERE ROWNUM<=(currentPage*lineSize)) temp
	
	WHERE temp.rn>(currentPage-1)*lineSize;


複製代碼

Oracle分頁原理簡單解釋

/* Oracle分頁: Oracle的分頁依賴於ROWNUM這個僞列,ROWNUM主要做用就是產生行號。 分頁原理: 1:子查詢查出前n行數據,ROWNUM產生前N行的行號 2:使用子查詢產生ROWNUM的行號,經過外部的篩選出想要的數據 例子: 我如今規定每頁顯示5行數據【lineSize=5】,我要查詢第2頁的數據【currentPage=2】 注:【對照着語法來看】 實現: 1:子查詢查出前10條數據【ROWNUM<=10】 2:外部篩選出後面5條數據【ROWNUM>5】 3:這樣咱們就取到了後面5條的數據 */

複製代碼

Mysql實現分頁

/* Mysql分頁語法: @start---偏移量,不設置就是從0開始【也就是(currentPage-1)*lineSize】 @length---長度,取多少行數據 */
	SELECT *
	FROM 表名
	LIMIT [START], length;
	
	/* 例子: 我如今規定每頁顯示5行數據,我要查詢第2頁的數據 分析: 1:第2頁的數據其實就是從第6條數據開始,取5條 實現: 1:start爲5【偏移量從0開始】 2:length爲5 */

複製代碼

總結:

  • Mysql從(currentPage-1)*lineSize開始取數據,取lineSize條數據
  • Oracle先獲取currentPage*lineSize條數據,從(currentPage-1)*lineSize開始取數據

使用JDBC鏈接數據庫實現分頁

下面是常見的分頁圖片


配合圖片,看下咱們的需求是什麼:

  1. 算出有多少頁的數據,顯示在頁面上
  2. 根據頁碼,從數據庫顯示相對應的數據。

分析:

  1. 算出有多少頁數據這是很是簡單的【在數據庫中查詢有多少條記錄,你每頁顯示多少條記錄,就能夠算出有多少頁數據了】
  2. 使用Mysql或Oracle的分頁語法便可

經過上面分析,咱們會發現須要用到4個變量

  • currentPage--當前頁【由用戶決定的】
  • totalRecord--總數據數【查詢表可知】
  • lineSize--每頁顯示數據的數量【由咱們開發人員決定】
  • pageCount--頁數【totalRecord和lineSize決定】
//每頁顯示3條數據
        int lineSize = 3;

        //總記錄數
        int totalRecord = getTotalRecord();

        //假設用戶指定的是第2頁
        int currentPage = 2;

        //一共有多少頁
        int pageCount = getPageCount(totalRecord, lineSize);

        //使用什麼數據庫進行分頁,記得要在JdbcUtils中改配置
        List<Person> list = getPageData2(currentPage, lineSize);
        for (Person person : list) {
            System.out.println(person);
        }

    }

    //使用JDBC鏈接Mysql數據庫實現分頁
    public static List<Person> getPageData(int currentPage, int lineSize) throws SQLException {

        //從哪一個位置開始取數據
        int start = (currentPage - 1) * lineSize;

        QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "SELECT name,address FROM person LIMIT ?,?";

        List<Person> persons = (List<Person>) queryRunner.query(sql, new BeanListHandler(Person.class), new Object[]{start, lineSize});
        return persons;

    }

    //使用JDBC鏈接Oracle數據庫實現分頁
    public static List<Person> getPageData2(int currentPage, int lineSize) throws SQLException {

        //從哪一個位置開始取數據
        int start = (currentPage - 1) * lineSize;

        //讀取前N條數據
        int end = currentPage * lineSize;

        QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "SELECT " +
                " name, " +
                " address " +
                "FROM ( " +
                " SELECT " +
                " name, " +
                " address , " +
                " ROWNUM rn " +
                " FROM person " +
                " WHERE ROWNUM <= ? " +
                ")temp WHERE temp.rn>?";

        List<Person> persons = (List<Person>) queryRunner.query(sql, new BeanListHandler(Person.class), new Object[]{end, start});
        return persons;

    }

    public static int getPageCount(int totalRecord, int lineSize) {

        //簡單算法
        //return (totalRecord - 1) / lineSize + 1;

        //此算法比較好理解,把數據代代進去就知道了。
        return totalRecord % lineSize == 0 ? (totalRecord / lineSize) : (totalRecord / lineSize) + 1;

    }


    public static int getTotalRecord() throws SQLException {

        //使用DbUtils框架查詢數據庫表中有多少條數據
        QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "SELECT COUNT(*) FROM person";

        Object o = queryRunner.query(sql, new ScalarHandler());

        String ss = o.toString();
        int  s = Integer.parseInt(ss);
        return s;
    }


複製代碼

若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章的同窗,能夠關注微信公衆號:Java3y。

相關文章
相關標籤/搜索