1. 程式人生 > >Java資料庫連線池

Java資料庫連線池

1.簡介

Java 獲取資料庫的基本方式有兩種:1,通過DriverManager;2,通過DataSource,這裡主要講述第二種。

1.1 DriverManager跟DataSource獲取getConnection 的區別

DriverManager是通過註冊的驅動直接和資料庫程式互動。而DataSource感覺是在Java應用和資料庫程式之間的一箇中間層,幫你管理資料庫連線並做了一定的優化。

public interface DataSource extends CommonDataSource, Wrapper:(A factory for connections to the physical data source that this DataSource object represents):
該工廠用於提供到此 DataSource 物件所表示的物理資料來源的連線。作為 DriverManager 工具的替代項,DataSource 物件是獲取連線的首選方法。實現 DataSource 介面的物件通常在基於 JavaTM Naming and Directory Interface (JNDI) API 的命名服務中註冊。 

DataSource 介面由驅動程式供應商實現。共有三種類型的實現:
基本實現 - 生成標準的 Connection 物件 
 
連線池實現 - 生成自動參與連線池的 Connection 物件。此實現與中間層連線池管理器一起使用。 

分散式事務實現 - 生成一個 Connection 物件,該物件可用於分散式事務,大多數情況下總是參與連線池。此實現與中間層事務管理器一起使用,大多數情況下總是與連線池管理器一起使用。

 DataSource 物件的屬性在必要時可以修改。例如,如果將資料來源移動到另一個伺服器,則可更改與伺服器相關的屬性。其優點在於,由於可以更改資料來源的屬性,所以任何訪問該資料來源的程式碼都無需更改。 通過 DataSource 物件訪問的驅動程式本身不會向 DriverManager 註冊。通過查詢操作獲取 DataSource 物件,然後使用該物件建立 Connection 物件。使用基本的實現,通過 DataSource 物件獲取的連線與通過 DriverManager 設施獲取的連線相同。 

1.2 例項:使用DriverManger的例子

基本步驟:

  • 裝載資料庫驅動程式;
  • 建立資料庫連線;
  • 建立資料庫操作物件 訪問資料庫,執行sql語句;
  • 處理返回結果集
  • 斷開資料庫連線。 程式碼:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class test {
	public static void main(String[] args) {
		Connection con;
		Statement stmt;
		ResultSet rs;

		try {
			// 1,裝載資料庫驅動程式
			Class.forName("com.mysql.jdbc.Driver")
			// 2,建立資料庫連線
			con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
			// 3,建立資料庫操作物件
			stmt = con.createStatement();
			// 4,執行sql語句
			rs = stmt.executeQuery("select * from  test");
			// 5,處理返回結果集
			while (rs.next()) {
				int num = rs.getInt("id");
				String name = rs.getString("name");
				String des = rs.getString("description");
				System.out.println(num + " " + name + " " + des);
			}
			// 6,斷開資料庫連線
			stmt.close();
			con.close();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("連線失敗");
		}
	}
}

1.3 例項:使用DataSource的例子

當使用DataSource操作資料庫時,不需要手動的載入驅動,在對DataSource初始化後,直接通過DataSource.getConnection()方法獲取連線使用,使用完畢後,通過DataSource.close()方法釋放該連線即可。但是使用的DataSource不同,則對DataSource例項的配置、初始化、獲取方式不同。

這裡以c3po為例。

關於c3po:C3P0是一個開源的JDBC連線池,它實現了資料來源和JNDI繫結,支援JDBC3規範和JDBC2的標準擴充套件。目前使用它的開源專案有Hibernate,Spring等。C3P0資料來源在專案開發中使用得比較多。

1.2.1 配置

  • 匯入相關jar包
  • 在類目錄下增加並填寫配置檔案c3p0-config.xml。
<?xml version="1.0" encoding="UTF-8"?>
<!--
c3p0-config.xml必須位於類路徑下面
private static ComboPooledDataSource ds;
static{
    try {
        ds = new ComboPooledDataSource("MySQL");
    } catch (Exception e) {
        throw new ExceptionInInitializerError(e);
    }
}
-->
 
<c3p0-config>
    <!--
    C3P0的預設(預設)配置,
    如果在程式碼中“ComboPooledDataSource ds = new ComboPooledDataSource();”這樣寫就表示使用的是C3P0的預設(預設)配置資訊來建立資料來源
    -->
    <default-config>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy</property>
        <property name="user">root</property>
        <property name="password">XDP</property>
        
        <property name="acquireIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property>
    </default-config>
 
    <!--
    C3P0的命名配置,
    如果在程式碼中“ComboPooledDataSource ds = new ComboPooledDataSource("MySQL");”這樣寫就表示使用的是name是MySQL的配置資訊來建立資料來源
    -->
    <named-config name="MySQL">
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy</property>
        <property name="user">root</property>
        <property name="password">XDP</property>
        
        <property name="acquireIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property>
    </named-config>
 
</c3p0-config>

1.2.2 DataSource初始化並獲取連線

package me.gacl.util;
 
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.mchange.v2.c3p0.ComboPooledDataSource;
 
/**
* @ClassName: JdbcUtils_C3P0
* @Description: 資料庫連線工具類
* @author: 孤傲蒼狼
* @date: 2014-10-4 下午6:04:36
*
*/
public class JdbcUtils_C3P0 {
    
    private static ComboPooledDataSource ds = null;
    //在靜態程式碼塊中建立資料庫連線池
    static{
        try{
            //通過程式碼建立C3P0資料庫連線池
            /*ds = new ComboPooledDataSource();
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbcstudy");
            ds.setUser("root");
            ds.setPassword("XDP");
            ds.setInitialPoolSize(10);
            ds.setMinPoolSize(5);
            ds.setMaxPoolSize(20);*/
            
            //通過讀取C3P0的xml配置檔案建立資料來源,C3P0的xml配置檔案c3p0-config.xml必須放在src目錄下
            //ds = new ComboPooledDataSource();//使用C3P0的預設配置來建立資料來源
            ds = new ComboPooledDataSource("MySQL");//使用C3P0的命名配置來建立資料來源
            
        }catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    
    /**
    * @Method: getConnection
    * @Description: 從資料來源中獲取資料庫連線
    * @Anthor:孤傲蒼狼
    * @return Connection
    * @throws SQLException
    */
    public static Connection getConnection() throws SQLException{
        //從資料來源中獲取資料庫連線
        return ds.getConnection();
    }
    
    /**
    * @Method: release
    * @Description: 釋放資源,
    * 釋放的資源包括Connection資料庫連線物件,負責執行SQL命令的Statement物件,儲存查詢結果的ResultSet物件
    * @Anthor:孤傲蒼狼
    *
    * @param conn
    * @param st
    * @param rs
    */
    public static void release(Connection conn,Statement st,ResultSet rs){
        if(rs!=null){
            try{
                //關閉儲存查詢結果的ResultSet物件
                rs.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
            rs = null;
        }
        if(st!=null){
            try{
                //關閉負責執行SQL命令的Statement物件
                st.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        if(conn!=null){
            try{
                //將Connection連線物件還給資料庫連線池
                conn.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

1.2.3 通過connection進行資料庫操作

package me.gacl.test;
 
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import org.junit.Test;
import me.gacl.util.JdbcUtils_C3P0;
import me.gacl.util.JdbcUtils_DBCP;
 
public class DataSourceTest {
    
    @Test
    public void c3p0DataSourceTest() {
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        try{
            //獲取資料庫連線
            conn = JdbcUtils_C3P0.getConnection();
            String sql = "insert into test1(name) values(?)";
            st = conn.prepareStatement(sql);
            st.setString(1, "gacl");
            st.executeUpdate();
            //獲取資料庫自動生成的主鍵
            rs = st.getGeneratedKeys();
            if(rs.next()){
                System.out.println(rs.getInt(1));
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally{
            //釋放資源
            JdbcUtils_C3P0.release(conn, st, rs);
        }
    }
}

2.DataSource的簡單實現

package me.gacl.demo;

import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.Properties;
import javax.sql.DataSource;

/**
* @ClassName: JdbcPool
* @Description:編寫資料庫連線池
* @author: 孤傲蒼狼
* @date: 2014-9-30 下午11:07:23
*
*/ 
public class JdbcPool implements DataSource{

    /**
    * @Field: listConnections
    *         使用LinkedList集合來存放資料庫連結,
    *        由於要頻繁讀寫List集合,所以這裡使用LinkedList儲存資料庫連線比較合適
    */ 
    private static LinkedList<Connection> listConnections = new LinkedList<Connection>();
    
    static{
        //在靜態程式碼塊中載入db.properties資料庫配置檔案
        InputStream in = JdbcPool.class.getClassLoader().getResourceAsStream("db.properties");
        Properties prop = new Properties();
        try {
            prop.load(in);
            String driver = prop.getProperty("driver");
            String url = prop.getProperty("url");
            String username = prop.getProperty("username");
            String password = prop.getProperty("password");
            //資料庫連線池的初始化連線數大小
            int jdbcPoolInitSize =Integer.parseInt(prop.getProperty("jdbcPoolInitSize"));
            //載入資料庫驅動
            Class.forName(driver);
            for (int i = 0; i < jdbcPoolInitSize; i++) {
                Connection conn = DriverManager.getConnection(url, username, password);
                System.out.println("獲取到了連結" + conn);
                //將獲取到的資料庫連線加入到listConnections集合中,listConnections集合此時就是一個存放了資料庫連線的連線池
                listConnections.add(conn);
            }
            
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    
    @Override
    public PrintWriter getLogWriter() throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {
        // TODO Auto-generated method stub
        
    }

    @Override
    public int getLoginTimeout() throws SQLException {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        // TODO Auto-generated method stub
        return false;
    }

    /* 獲取資料庫連線
     * @see javax.sql.DataSource#getConnection()
     */
    @Override
    public Connection getConnection() throws SQLException {
        //如果資料庫連線池中的連線物件的個數大於0
        if (listConnections.size()>0) {
            //從listConnections集合中獲取一個數據庫連線
            final Connection conn = listConnections.removeFirst();
            System.out.println("listConnections資料庫連線池大小是" + listConnections.size());
            //返回Connection物件的代理物件
            return (Connection) Proxy.newProxyInstance(JdbcPool.class.getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler(){
                @Override
                public Object invoke(Object proxy, Method method, Object[] args)
                        throws Throwable {
                    if(!method.getName().equals("close")){
                        return method.invoke(conn, args);
                    }else{
                        //如果呼叫的是Connection物件的close方法,就把conn還給資料庫連線池
                        listConnections.add(conn);
                        System.out.println(conn + "被還給listConnections資料庫連線池了!!");
                        System.out.println("listConnections資料庫連線池大小為" + listConnections.size());
                        return null;
                    }
                }
            });
        }else {
            throw new RuntimeException("對不起,資料庫忙");
        }
    }

    @Override
    public Connection getConnection(String username, String password)
            throws SQLException {
        return null;
    }
}

3.常用資料庫連線池

在Java中開源的常用的資料庫連線池有以下幾種

3.1 DBCP

DBCP是一個依賴Jakarta commons-pool物件池機制的資料庫連線池.DBCP可以直接的在應用程式中使用,Tomcat的資料來源使用的就是DBCP。

3.2 c3p0

c3p0是一個開放原始碼的JDBC連線池,它在lib目錄中與Hibernate一起釋出,包括了實現jdbc3和jdbc2擴充套件規範說明的Connection 和Statement 池的DataSources 物件。

3.3 Druid

阿里出品,淘寶和支付寶專用資料庫連線池,但它不僅僅是一個數據庫連線池,它還包含一個ProxyDriver,一系列內建的JDBC元件庫,一個SQL Parser。支援所有JDBC相容的資料庫,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等等。 Druid針對Oracle和MySql做了特別優化,比如Oracle的PS Cache記憶體佔用優化,MySql的ping檢測優化。Druid提供了MySql、Oracle、Postgresql、SQL-92的SQL的完整支援,這是一個手寫的高效能SQL Parser,支援Visitor模式,使得分析SQL的抽象語法樹很方便。簡單SQL語句用時10微秒以內,複雜SQL用時30微秒。通過Druid提供的SQL Parser可以在JDBC層攔截SQL做相應處理,比如說分庫分表、審計等。Druid防禦SQL注入攻擊的WallFilter就是通過Druid的SQL Parser分析語義實現的。