承接上文,本文聚焦 **Context 級資料源** 配置細節,展示如何為單一 WebApp 獨立定義 DataSource,實現多應用部署時的資料庫隔離、配置解耦與安全管控。
<Context antiResourceLocking="true">
<!-- 核心 DataSource 定義 -->
<Resource
name="jdbc/myapp"
auth="Container"
type="javax.sql.DataSource"
driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://${DB_HOST}:3306/${DB_NAME}?useSSL=false&serverTimezone=Asia/Taipei&allowPublicKeyRetrieval=true"
username="${DB_USER}"
password="${DB_PASS}"
maxTotal="30"
maxIdle="15"
minIdle="3"
maxWaitMillis="5000"
validationQuery="SELECT 1"
testOnBorrow="true"
testWhileIdle="true"
timeBetweenEvictionRunsMillis="30000"
minEvictableIdleTimeMillis="60000"
/>
<!-- 可選:第二個資料源(如唯讀從庫) -->
<Resource
name="jdbc/myapp-ro"
auth="Container"
type="javax.sql.DataSource"
driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://${DB_RO_HOST}:3306/${DB_NAME}?useSSL=false&serverTimezone=Asia/Taipei"
username="${DB_RO_USER}"
password="${DB_RO_PASS}"
maxTotal="20"
maxIdle="10"
minIdle="2"
maxWaitMillis="3000"
validationQuery="SELECT 1"
testOnBorrow="true"
/>
</Context>
| 屬性 | 說明 | 建議值 |
|---|---|---|
| antiResourceLocking | 防止 JAR 鎖定導致熱部署失敗(Windows 必開,Linux 可選) | true |
| name | JNDI 名稱,代碼中用 `java:comp/env/` + name 查找 | jdbc/<app-name> |
| driverClassName | MySQL 8.0+ 用 `com.mysql.cj.jdbc.Driver`,5.1 用 `com.mysql.jdbc.Driver` | 對應驅動版本 |
| url | JDBC URL,建議用環境變數佔位符,啟動腳本替換 | 含時區、SSL、公開金鑰參數 |
| maxTotal | 最大連線數,核心參數 | 壓測決定,通常 20-100 |
| validationQuery | 驗證查詢,MySQL 用 `SELECT 1` | SELECT 1 |
| testOnBorrow | 借用前驗證,防斷線 | true |
<resource-ref>
<description>MySQL Primary DataSource</description>
<res-ref-name>jdbc/myapp</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<resource-ref>
<description>MySQL ReadOnly DataSource</description>
<res-ref-name>jdbc/myapp-ro</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
// 純 JNDI 查找
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
DataSource ds = (DataSource) envCtx.lookup("jdbc/myapp");
try (Connection conn = ds.getConnection()) {
// 使用連線
}
// Spring 注入
@Resource(name = "jdbc/myapp")
private DataSource dataSource;
// Spring Boot 2.x/3.x application.yml
spring:
datasource:
jndi-name: java:comp/env/jdbc/myapp
為了不將密碼寫入原始碼倉庫,採用 **啟動腳本 + 環境變數 + 模板替換** 方案:
#!/bin/bash
# bin/start-tomcat.sh
export DB_HOST="prod-db.internal"
export DB_NAME="myapp_prod"
export DB_USER="app_user"
export DB_PASS="$(vault kv get -field=password secret/myapp/db)"
# 替換 context.xml 中的佔位符
envsubst < /opt/tomcat/webapps/myapp/META-INF/context.xml.template \
> /opt/tomcat/webapps/myapp/META-INF/context.xml
exec /opt/tomcat/bin/catalina.sh run
`context.xml.template` 中使用 `${DB_HOST}` 等語法,`envsubst` (GNU gettext) 會自動替換環境變數值。
| 環境 | 配置來源 | 部署方式 |
|---|---|---|
| 開發 | 開發者本地 `.env` / Docker Compose `environment` | IDE 熱部署 / `mvn tomcat7:run` |
| 測試 | CI/CD 變數 / K8s ConfigMap | Helm chart `values.yaml` 注入 |
| 預發/生產 | Vault / 參數存儲 / K8s Secret | ArgoCD / Helm + SealedSecrets |
Context 級資料源是 Java Web 標準部署模式的基石。掌握其配置、注入、環境化流程,能讓應用在開發、測試、生產環境間無縫流轉,同時守住安全底線。