14-数据库连接池和jdbc优化
一、數據庫連接池
1. 什么是連接池
傳統的開發模式下,Servlet處理用戶的請求,找Dao查詢數據,dao會創建與數據庫之間的連接,完成數據查詢后會關閉數據庫的鏈接。
這樣的方式會導致用戶每次請求都要向數據庫建立鏈接而數據庫創建連接通常需要消耗相對較大的資源,創建時間也較長。假設網站一天10萬訪問量,數據庫服務器就需要創建10萬次連接,極大的浪費數據庫的資源,并且極易造成數據庫服務器內存溢出、宕機。
?
解決方案就是數據庫連接池
連接池就是數據庫連接對象的一個緩沖池
我們可以先創建10個數據庫連接緩存在連接池中,當用戶有請求過來的時候,dao不必創建數據庫連接,而是從數據庫連接池中獲取一個,用完了也不必關閉連接,而是將連接換回池子當中,繼續緩存
?
使用數據庫連接池可以極大地提高系統的性能
?
2. 實現數據庫連接池
jdbc統一了數據庫的操作? 定義了規范
jdbc針對數據庫連接池也定義的接口java.sql.DataSource,所有的數據庫連接池實現都要實現該接口
該接口中定義了兩個重載的方法
Connection getConnection()
Connection getConnection(String?username, String?password)
?
數據庫連接池實現思路
1) 定義一個類實現java.sql.DataSource接口
2) 定義一個集合用于保存Connection對象,由于頻繁地增刪操作,用LinkedList比較好
3) 實現getConnection方法,在方法中取出LinkedList集合中的一個連接對象返回
注意:
??? 返回的Connection對象不是從集合中獲得,而是刪除
??? 用戶用完Connection,會調用close方法釋放資源,此時要保證連接換回連接池,而不是關閉連接
??? 重寫close方法是難點,解決方案: 裝飾設計模式、動態代理
?
二、 數據源
通常我們把DataSource的實現,按其英文含義稱之為數據源,數據源中都包含了數據庫連接池的實現。
?
一些開源組織提供了數據源的獨立實現,常用的有:
DBCP 數據庫連接池
C3P0 數據庫連接池
1.? DBCP 數據源
介紹
DBCP 是 Apache 軟件基金組織下的開源連接池實現
tomcat服務器就是使用DBCP作為數據庫連接池
使用DBCP數據源,需要導入兩個jar包
Commons-dbcp.jar:連接池的實現
Commons-pool.jar:連接池實現的依賴庫
?
DBCP核心 API
BasicDataSource
數據源實現
BasicDataSourceFactory
用于創建數據源的工廠類
?
dbcp 創建連接池
方法1:? 直接創建對象,設置參數
BasicDataSource bds = new BasicDataSource();
?
// 設置連接數據庫需要的配置信息
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUrl("jdbc:mysql://localhost:3306/jdbc3");
bds.setUsername("root");
bds.setPassword("root");
?
// 設置連接池的參數
bds.setInitialSize(5);
bds.setMaxActive(10);
?
ds = bds
?
方法2: 通過工廠類創建對象,讀取配置文件
try {
?? Properties prop = new Properties();
?? // 讀配置文件
?? InputStream in =
?? ?? JdbcUtils.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
?? prop.load(in);
?? ds = BasicDataSourceFactory.createDataSource(prop);
}catch (Exception e) {
?? throw new ExceptionInInitializerError(e);
}
?
配置文件為dbcpconfig.properties
#連接設置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc3
username=root
password=root
?
#<!-- 初始化連接 -->
initialSize=5
?
#最大連接數量
maxActive=10
?
#<!-- 最大空閑連接 -->
maxIdle=10
?
?
#<!-- 超時等待時間以毫秒為單位 6000毫秒/1000等于60秒 -->
maxWait=60000
?
?
#JDBC驅動建立連接時附帶的連接屬性屬性的格式必須為這樣:[屬性名=property;]
#注意:"user" 與 "password" 兩個屬性會被明確地傳遞,因此這里不需要包含他們。
connectionProperties=useUnicode=true;characterEncoding=gbk
?
#指定由連接池所創建的連接的自動提交(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
2.? C3P0 數據源
介紹
c3p0是一個開源的jdbc連接池,我們熟悉的 Hibernate和 Sprint 框架使用的都是該數據源
?
創建連接池對象
方法1:直接創建對象,設置參數
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbc3");
cpds.setUser("root");
cpds.setPassword("root");
cpds.setInitialPoolSize(5);
cpds.setMaxPoolSize(15);
方法2:讀取配置文件
ComboPooledDataSource cpds = new ComboPooledDataSource("itcast");
配置文件為c3p0-config.xml 該文件需要放在類路徑下
<c3p0-config>
?
?? <default-config>
?? ?? <!—- 默認配置 –->
????? <property name="initialPoolSize">5</property>
????? <property name="maxPoolSize">15</property>
????? <property name="driverClass">com.mysql.jdbc.Driver</property>
????? <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc3</property>
????? <property name="user">root</property>
????? <property name="password">root</property>
?? </default-config>
?? <named-config name="xwh">
????? <property name="initialPoolSize">5</property>
????? <property name="maxPoolSize">15</property>
????? <property name="driverClass">com.mysql.jdbc.Driver</property>
????? <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc3</property>
????? <property name="user">root</property>
????? <property name="password">root</property>
?? </named-config>
</c3p0-config>
?
三、ResultSetMetaData對象
元數據,可以理解為描述數據的數據
jdbc中的元數據是指數據庫、表、列的定義信息
?
ResultSetMetaData對象表示結果集 ResultSet對象的元數據
獲得該對象:
ResultSetMetaData metaData = rs.getMetaData();
?
常用方法:
getColumnCount() ?返回resultset對象的列數
getColumnName(int?column) ?獲得指定列的名稱
getColumnTypeName(int?column) 獲得指定列的類型
?
四、jdbc優化
使用jdbc對數據庫進行crud操作時,會有很多重復的代碼,仔細分析不難發現其實變化的只是其中幾行代碼
?
對于 cud(增刪改) 操作,代碼幾乎完全一樣, 唯一的區別就是sql語句不同,我們完全可以把相同的代碼抽取出來定義在一個工具方法中,然后定義一個參數來接收sql語句
?
對于 r(查詢) 操作,除SQL語句不同之外,根據操作的實體不同,對ResultSet結果集的處理也有所不相同,因此可義一個query方法,除以參數形式接收變化的SQL語句外,可以使用策略模式由qurey方法的調用者決定如何把ResultSet中的數據映射到實體對象中
?
優化后的工具類 JdbcUtils
// 通用的增刪改方法
publicstaticint update(String sql, Object[] params) throws SQLException {
?? Connection conn = null;
?? PreparedStatement pstmt = null;
?? ResultSet rs = null;
?? try {
????? // 獲得連接
????? conn = getConnection();
????? // 預編譯sql
????? pstmt = conn.prepareStatement(sql);
????? // 將參數設置進去
????? for(int i=0;? params!=null&&i<params.length; i++) {
???????? pstmt.setObject(i+1, params[i]);
????? }
????? // 發送sql
????? int num = pstmt.executeUpdate();
????? return num;
?? } finally {
????? // 釋放資源
????? release(conn, pstmt, rs);
?? }
}
?
// 優化查詢
publicstatic Object query(String sql, Object[] params, ResultSetHandler rsh) throws SQLException {
?? Connection conn = null;
?? PreparedStatement pstmt = null;
?? ResultSet rs = null;
?? try {
????? // 獲得連接
????? conn = getConnection();
????? // 預編譯sql
????? pstmt = conn.prepareStatement(sql);
????? // 將參數設置進去
????? for(int i=0; params!=null&&i<params.length; i++) {
???????? pstmt.setObject(i+1, params[i]);
????? }
????? // 發送sql
????? rs = pstmt.executeQuery();
????? // 不知道別人想如何處理結果集
????? // 干脆想別人所要一個結果集的處理器
????? // 為了讓當前代碼繼續,定義一個結果集處理器接口
????? // 策略模式,規定算法,具體的算法留給將來的調用者實現
????? Object obj = rsh.handle(rs);
????? return obj;
?? } finally {
????? // 釋放資源
????? release(conn, pstmt, rs);
?? }
}
?
?
結果集處理器接口
public interface ResultSetHandler {
?? // 處理結果集的方法
?? public Object handle(ResultSet rs);
}
?
實現類:
BeanListHandler
public class BeanListHandler implements ResultSetHandler {
?
?? private Class clazz;
?? public BeanListHandler(Class clazz) {
????? this.clazz = clazz;
?? }
?? public Object handle(ResultSet rs) {
????? try {
???????? // 取出結果集所有的記錄,封裝到bean,存入list返回
???????? List list = new ArrayList();
???????? while (rs.next()) {
??????????? Object bean = clazz.newInstance();
??????????? // 獲得元數據
??????????? ResultSetMetaData metaData = rs.getMetaData();
??????????? // 獲得列的數量
??????????? int count = metaData.getColumnCount();
??????????? // 遍歷列
??????????? for(int i=1; i<=count; i++) {
??????????????? // 取列名
??????????????? String columnName = metaData.getColumnName(i);
??????????????? // 取這列的值
??????????????? Object value = rs.getObject(columnName);
??????????????? // 反射出屬性
??????????????? Field field = clazz.getDeclaredField(columnName);
??????????????? // 設置屬性
??????????????? field.setAccessible(true);
??????????????? field.set(bean, value);
??????????? }
??????????? // 加入list
??????????? list.add(bean);
???????? }
???????? return list;
????? } catch (Exception e) {
???????? throw new RuntimeException(e);
????? }
?? }
?
}
?
BeanHandler
public class BeanHandler implements ResultSetHandler {
?? private Class clazz;
?? public BeanHandler(Class clazz) {
????? this.clazz = clazz;
?? }
?? public Object handle(ResultSet rs) {
????? // 不知道有幾列數據,不知道列名,不知道封裝到什么樣的bean
????? // 表的列明和javabean的字段名一致
????? try {
???????? if(rs.next()) {
??????????? // 創建bean
??????????? Object bean = clazz.newInstance();
??????????? // 封裝數據
??????????? // 獲得結果集的元數據
??????????? ResultSetMetaData metaData = rs.getMetaData();
??????????? int count = metaData.getColumnCount();
??????????? // 迭代取每一列的數據
??????????? for(int i=1; i<=count; i++) {
??????????????? // 獲得列名? username
??????????????? String columnName = metaData.getColumnName(i);
??????????????? // 獲得數據 ddd
??????????????? Object value = rs.getObject(columnName);
??????????????? // 根據列名反射出映射的屬性 username
??????????????? Field field = clazz.getDeclaredField(columnName);
??????????????? // 為屬性賦值
??????????????? field.setAccessible(true);
??????????????? field.set(bean, value);
??????????? }
??????????? return bean;
???????? }
???????? return null;
????? } catch (Exception e) {
???????? throw new RuntimeException(e);
????? }
?? }
?
}
?
ArrayHandler
// 取出第一行的所有記錄存入一個Object數組
public class ArrayHandler implements ResultSetHandler {
?
?? public Object handle(ResultSet rs) {
????? try {
???????? if (rs.next()) {
??????????? // 指向了第一行的記錄
??????????? // 獲得元數據
??????????? ResultSetMetaData metaData = rs.getMetaData();
??????????? // 獲得列數
??????????? int count = metaData.getColumnCount();
??????????? // 創建數組
??????????? Object[] arr = new Object[count];
??????????? // 迭代所有列的值,存入數組
??????????? for(int i=1; i<=count; i++) {
??????????????? Object value = rs.getObject(i);?? // 獲得指定列的值
??????????????? arr[i-1] = value;
??????????? }
??????????? return arr;
???????? }
???????? return null;
????? } catch (Exception e) {
???????? throw new RuntimeException(e);
????? }
?? }
?
}
?
批處理
?
處理大數據
Clob Character large Object
text
?
Blob binary? large object
?
?
?
?
?
總結
以上是生活随笔為你收集整理的14-数据库连接池和jdbc优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 库尔斯克坦克大会战:二个硬汉的生死决斗
- 下一篇: 三种情况可能导致普京动用核武器