一、應用程序直接獲取數據庫連接的缺點
用戶每次請求都需要向數據庫獲得鏈接,而數據庫創建連接通常需要消耗相對較大的資源,創建時間也較長。假設網站一天10萬訪問量,數據庫服務器就需要創建10萬次連接,極大的浪費數據庫的資源,並且極易造成數據庫服務器內存溢出、拓機。如下圖所示:
二、使用數據庫連接池優化程序性能
2.1、數據庫連接池的基本概念
數據庫連接是一種關鍵的有限的昂貴的資源,這一點在多用戶的網頁應用程序中體現的尤爲突出.對數據庫連接的管理能顯著影響到整個應用程序的伸縮性和健壯性,影響到程序的性能指標.數據庫連接池正式針對這個問題提出來的.數據庫連接池負責分配,管理和釋放數據庫連接,它允許應用程序重複使用一個現有的數據庫連接,而不是重新建立一個。如下圖所示:
數據庫連接池在初始化時將創建一定數量的數據庫連接放到連接池中, 這些數據庫連接的數量是由最小數據庫連接數來設定的.無論這些數據庫連接是否被使用,連接池都將一直保證至少擁有這麼多的連接數量.連接池的最大數據庫連接數量限定了這個連接池能佔有的最大連接數,當應用程序向連接池請求的連接數超過最大連接數量時,這些請求將被加入到等待隊列中.
數據庫連接池的最小連接數和最大連接數的設置要考慮到以下幾個因素:
最小連接數:是連接池一直保持的數據庫連接,所以如果應用程序對數據庫連接的使用量不大,將會有大量的數據庫連接資源被浪費.
最大連接數:是連接池能申請的最大連接數,如果數據庫連接請求超過次數,後面的數據庫連接請求將被加入到等待隊列中,這會影響以後的數據庫操作
如果最小連接數與最大連接數相差很大:那麼最先連接請求將會獲利,之後超過最小連接數量的連接請求等價於建立一個新的數據庫連接.不過,這些大於最小連接數的數據庫連接在使用完不會馬上被釋放,他將被放到連接池中等待重複使用或是空間超時後被釋放.
2.2、編寫數據庫連接池
編寫連接池需實現java.sql.DataSource接口。DataSource接口中定義了兩個重載的getConnection方法:
Connection getConnection()
Connection getConnection(String username, String password)
實現DataSource接口,並實現連接池功能的步驟:
在DataSource構造函數中批量創建與數據庫的連接,並把創建的連接加入LinkedList對象中。
實現getConnection方法,讓getConnection方法每次調用時,從LinkedList中取一個Connection返回給用戶。
當用戶使用完Connection,調用Connection.close()方法時,Collection對象應保證將自己返回到LinkedList中,而不要把conn還給數據庫。Collection保證將自己返回到LinkedList中是此處編程的難點。
數據庫連接池核心代碼
使用動態代理技術構建連接池中的connection
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;
}
}
db.properties配置文件如下:
1 driver=com.mysql.jdbc.Driver
2 url=jdbc:mysql://localhost:3306/jdbcStudy
3 username=root
4 password=XDP
5
6 jdbcPoolInitSize=10
jdbc 數據庫連接池的測試
package me.gacl.utils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import me.gacl.demo.JdbcPool;
public class JdbcUtil {
/**
* @Field: pool
* 數據庫連接池
*/
private static JdbcPool pool = new JdbcPool();
/**
* @Method: getConnection
* @Description: 從數據庫連接池中獲取數據庫連接對象
* @Anthor:孤傲蒼狼
* @return Connection數據庫連接對象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException{
return pool.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();
}
}
}
}
三、開源數據庫連接池
現在很多WEB服務器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的實現,即連接池的實現。通常我們把DataSource的實現,按其英文含義稱之爲數據源,數據源中都包含了數據庫連接池的實現。
也有一些開源組織提供了數據源的獨立實現:
DBCP 數據庫連接池
C3P0 數據庫連接池
在使用了數據庫連接池之後,在項目的實際開發中就不需要編寫連接數據庫的代碼了,直接從數據源獲得數據庫的連接。
3.1、DBCP數據源
DBCP 是 Apache 軟件基金組織下的開源連接池實現,要使用DBCP數據源,需要應用程序應在系統中增加如下兩個 jar 文件:
Commons-dbcp.jar:連接池的實現
Commons-pool.jar:連接池實現的依賴庫
Tomcat 的連接池正是採用該連接池來實現的。該數據庫連接池既可以與應用服務器整合使用,也可由應用程序獨立使用。
3.2、在應用程序中加入dbcp連接池
1.導入相關jar包
commons-dbcp-1.2.2.jar、commons-pool.jar
2、在類目錄下加入dbcp的配置文件:dbcpconfig.properties
dbcpconfig.properties的配置信息如下:
#連接設置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy
username=root
password=XDP
#<!-- 初始化連接 -->
initialSize=10
#最大連接數量
maxActive=50
#<!-- 最大空閒連接 -->
maxIdle=20
#<!-- 最小空閒連接 -->
minIdle=5
#<!-- 超時等待時間以毫秒爲單位 6000毫秒/1000等於60秒 -->
maxWait=60000
#JDBC驅動建立連接時附帶的連接屬性屬性的格式必須爲這樣:[屬性名=property;]
#注意:"user" 與 "password" 兩個屬性會被明確地傳遞,因此這裏不需要包含他們。
connectionProperties=useUnicode=true;characterEncoding=UTF8
#指定由連接池所創建的連接的自動提交(auto-commit)狀態。
defaultAutoCommit=true
#driver default 指定由連接池所創建的連接的只讀(read-only)狀態。
#如果沒有設置該值,則“setReadOnly”方法將不被調用。(某些驅動並不支持只讀模式,如:Informix)
defaultReadOnly=
#driver default 指定由連接池所創建的連接的事務級別(TransactionIsolation)。
#可用值爲下列之一:(詳情可見javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
package me.gacl.util;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
/**
* @ClassName: JdbcUtils_DBCP
* @Description: 數據庫連接工具類
* @author: 孤傲蒼狼
* @date: 2014-10-4 下午6:04:36
*
*/
public class JdbcUtils_DBCP {
/**
* 在java中,編寫數據庫連接池需實現java.sql.DataSource接口,每一種數據庫連接池都是DataSource接口的實現
* DBCP連接池就是java.sql.DataSource接口的一個具體實現
*/
private static DataSource ds = null;
//在靜態代碼塊中創建數據庫連接池
static{
try{
//加載dbcpconfig.properties配置文件
InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
Properties prop = new Properties();
prop.load(in);
//創建數據源
ds = BasicDataSourceFactory.createDataSource(prop);
}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();
}
}
}
}
測試dbcp數據庫連接池
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_DBCP;
public class DataSourceTest {
@Test
public void dbcpDataSourceTest() {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
//獲取數據庫連接
conn = JdbcUtils_DBCP.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_DBCP.release(conn, st, rs);
}
}
}