傳統 JDBC 每次請求都經歷「載入驅動 → 建立連線 → 執行 SQL → 關閉連線」,連線建立/銷毀開銷極大,高併發下極易耗盡資料庫連線資源導致系統癱瘓。連線池核心思想:啟動時預建連線物件池,請求時借用、用畢歸還,實現複用。
| 參數 | 含義 | 建議值 |
| maxActive / maxTotal | 池中最大活躍連線數 | 應用伺服器執行緒數 × 1.5 |
| maxIdle | 池中最大閒置連線數 | ≈ maxActive × 0.3 |
| minIdle | 池中最小閒置連線數 | 預熱數量,避免冷啟動延遲 |
| maxWait / maxWaitMillis | 借用連線最長等待時間(毫秒) | 3000~10000,防止無限阻塞 |
| validationQuery | 驗證連線有效性的 SQL | MySQL: SELECT 1 / Oracle: SELECT 1 FROM DUAL |
| testOnBorrow / testWhileIdle | 借用/閒置時是否驗證 | 建議開啟 testWhileIdle + evictor 執行緒 |
方案一:Tomcat 原生 JNDI 資料源(web.xml + context.xml)
步驟 1:context.xml 定義 Resource
<Context>
<Resource name="jdbc/news"
auth="Container"
type="javax.sql.DataSource"
maxTotal="100"
maxIdle="30"
maxWaitMillis="10000"
username="sa"
password="120010"
driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver"
url="jdbc:sqlserver://localhost:1433;DatabaseName=news"
validationQuery="SELECT 1"
testOnBorrow="true"
testWhileIdle="true"
timeBetweenEvictionRunsMillis="30000"
minEvictableIdleTimeMillis="60000"/>
</Context>
關鍵點:
- JDBC 驅動 JAR(如
sqljdbc4.jar、ojdbc8.jar)必須放在 $CATALINA_HOME/lib,不可 放在專案 WEB-INF/lib(類別載入器隔離)
- 屬性值間以空格分隔,嚴禁包含中文註解
步驟 2:web.xml 宣告 resource-ref
<resource-ref>
<res-ref-name>jdbc/news</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
步驟 3:程式碼透過 JNDI 查找
Context initCtx = new InitialContext();
DataSource ds = (DataSource) initCtx.lookup("java:comp/env/jdbc/news");
Connection conn = ds.getConnection(); // 從池借用
try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM users");
ResultSet rs = ps.executeQuery()) {
// 業務邏輯
} finally {
if (conn != null) conn.close(); // 歸還池中,非真正關閉
}
方案二:Spring 管理資料源(委託 JNDI 或直接配置)
模式 A:Spring 透過 JNDI 查找 Tomcat 資料源
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/news"/>
<property name="proxyInterface" value="javax.sql.DataSource"/>
</bean>
模式 B:Spring 直接配置連線池(不依賴 Tomcat),以 HikariCP 為例
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/news?useSSL=false&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="secret"/>
<property name="maximumPoolSize" value="20"/>
<property name="minimumIdle" value="5"/>
<property name="idleTimeout" value="300000"/>
<property name="connectionTestQuery" value="SELECT 1"/>
</bean>
HikariCP 為目前效能最優、API 最簡潔的連線池,Spring Boot 2.x 預設即採用之。
DAO 整合 JdbcTemplate
@Repository
public class UserDao extends JdbcDaoSupport {
@Autowired
public void setDs(DataSource dataSource) { setDataSource(dataSource); }
public int countUsers() {
return getJdbcTemplate().queryForObject("SELECT COUNT(*) FROM users", Integer.class);
}
}
進階優化清單
- 啟用 PreparedStatement 快取:
poolPreparedStatements=true + maxPoolPreparedStatementPerConnectionSize=20
- 連線洩漏偵測:
logAbandoned=true + removeAbandonedTimeout=60
- 監控指標:整合
Micrometer + Prometheus 監控 hikaricp.connections.active/idle/pending
- 資料庫端配合:調整
max_connections、wait_timeout、interactive_timeout 避免資料庫主動斷開
常見坑點避雷
- 驅動 JAR 放錯位置 →
ClassNotFoundException 或 Cannot create JDBC driver
- JNDI 名稱前綴寫錯 → 必須為
java:comp/env/ + resource-ref 名稱
- 未關閉
ResultSet/Statement → 連線無法正確歸還,池耗盡
- 長事務持有連線 → 縮短事務邊界,業務邏輯與資料庫操作分離
- 上一篇文章: Java 字元編碼深度解析:ASCII、ISO-8859-1、Unicode、UTF-8、GB2312 演進脈絡
- 下一篇文章: Java 驗證碼生成實戰:支持中英數混合、隨機字體、干擾線、Session 綁定的完整方案
Copyright ©
程式碼之家 All Rights Reserved