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分析語義實現的。