Spring Boot 多環境配置完整指南:開發、測試、生產環境管理

🎯 為什麼需要多環境配置?

在現代軟體開發中,應用程式通常需要在多個環境中運行:

📋 常見環境類型與挑戰

開發環境 (Development)

  • 開發人員本地機器
  • 使用本地資料庫或輕量級資料庫
  • 詳細的日誌輸出便於除錯
  • 不需要 Redis 等快取服務

測試環境 (Staging/UAT)

  • 模擬生產環境的配置
  • 使用獨立的測試資料庫
  • 啟用效能監控
  • 測試與第三方服務的整合

生產環境 (Production)

  • 正式對外服務的環境
  • 高可用性資料庫叢集
  • 啟用 Redis 快取提升效能
  • 嚴格的安全性與日誌管理

🎯 核心需求分析

不同環境需要不同的配置:

  • 資料庫連接:開發環境用本地 MySQL,生產環境用 RDS
  • 快取服務:開發環境不用 Redis,生產環境必須啟用
  • 日誌級別:開發環境 DEBUG,生產環境 INFO/WARN
  • 安全設定:開發環境寬鬆,生產環境嚴格

🏗️ Spring Boot 多環境配置架構

🔧 Profile 機制原理

Spring Boot 使用 Profile 機制來管理不同環境的配置:

1src/main/resources/
2├── application.yml                    # 基礎配置(所有環境共用)
3├── application-dev.yml               # 開發環境專屬配置
4├── application-stage.yml             # 測試環境專屬配置
5└── application-prod.yml              # 生產環境專屬配置

配置優先級順序

1. application-{profile}.yml (最高優先級)
2. application.yml (基礎配置)
3. 環境變數
4. 命令列參數 (最高優先級,可覆蓋所有)

🎨 配置文件結構設計

基礎配置檔案 - application.yml

 1# ============================================
 2# 基礎配置 - 所有環境共用
 3# ============================================
 4spring:
 5  application:
 6    name: employee-management-system
 7
 8  # JPA 基礎配置
 9  jpa:
10    show-sql: false
11    properties:
12      hibernate:
13        format_sql: true
14        jdbc:
15          batch_size: 25
16
17  # 檔案上傳限制
18  servlet:
19    multipart:
20      max-file-size: 5MB
21      max-request-size: 5MB
22
23# Server 基礎配置
24server:
25  port: 8080
26  servlet:
27    context-path: /api
28
29# Actuator 監控端點
30management:
31  endpoints:
32    web:
33      exposure:
34        include: health,info,metrics
35  endpoint:
36    health:
37      show-details: when-authorized
38
39# 應用程式基本資訊
40info:
41  app:
42    name: ${spring.application.name}
43    version: 1.0.0
44    description: Employee Management System

開發環境配置 - application-dev.yml

 1# ============================================
 2# 開發環境配置
 3# ============================================
 4spring:
 5  # H2 記憶體資料庫(快速啟動,無需安裝)
 6  datasource:
 7    url: jdbc:h2:mem:devdb
 8    driver-class-name: org.h2.Driver
 9    username: sa
10    password:
11    hikari:
12      maximum-pool-size: 5
13      minimum-idle: 2
14
15  # JPA 開發設定
16  jpa:
17    hibernate:
18      ddl-auto: create-drop  # 每次啟動重建資料表
19    show-sql: true           # 顯示 SQL 語句
20    properties:
21      hibernate:
22        format_sql: true     # SQL 格式化
23
24  # H2 Console 啟用(方便查看資料庫)
25  h2:
26    console:
27      enabled: true
28      path: /h2-console
29
30  # Redis 停用(開發環境不需要)
31  cache:
32    type: simple             # 使用簡單的記憶體快取
33
34  # 開發環境 CORS 設定(允許前端跨域)
35  web:
36    cors:
37      allowed-origins: "http://localhost:3000,http://localhost:8080"
38      allowed-methods: "*"
39      allowed-headers: "*"
40
41# 日誌配置
42logging:
43  level:
44    root: INFO
45    com.employee.system: DEBUG        # 應用程式詳細日誌
46    org.springframework.web: DEBUG    # Spring Web 詳細日誌
47    org.springframework.security: DEBUG
48    org.hibernate.SQL: DEBUG          # SQL 語句日誌
49    org.hibernate.type.descriptor.sql.BasicBinder: TRACE  # SQL 參數值
50  pattern:
51    console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
52
53# JWT 開發配置(較短的有效期便於測試)
54jwt:
55  secret: dev-secret-key-change-in-production
56  expiration: 3600000      # 1 小時
57  refresh-expiration: 86400000  # 1 天
58
59# 檔案上傳路徑
60app:
61  upload:
62    dir: ./uploads/dev

測試環境配置 - application-stage.yml

 1# ============================================
 2# 測試環境配置
 3# ============================================
 4spring:
 5  # 測試用 MySQL 資料庫
 6  datasource:
 7    url: jdbc:mysql://${DB_HOST:localhost}:3306/${DB_NAME:employee_stage}?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
 8    driver-class-name: com.mysql.cj.jdbc.Driver
 9    username: ${DB_USER:stage_user}
10    password: ${DB_PASSWORD:stage_password}
11    hikari:
12      maximum-pool-size: 10
13      minimum-idle: 5
14      connection-timeout: 20000
15      idle-timeout: 300000
16      leak-detection-threshold: 60000
17
18  # JPA 測試設定
19  jpa:
20    hibernate:
21      ddl-auto: validate     # 驗證資料表結構,不自動建立
22    show-sql: false
23    properties:
24      hibernate:
25        format_sql: false
26
27  # Redis 部分啟用(測試快取功能)
28  redis:
29    host: ${REDIS_HOST:localhost}
30    port: ${REDIS_PORT:6379}
31    password: ${REDIS_PASSWORD:}
32    lettuce:
33      pool:
34        max-active: 8
35        max-idle: 8
36        min-idle: 2
37
38  cache:
39    type: redis
40    redis:
41      time-to-live: 600000   # 10 分鐘快取
42
43# 日誌配置(平衡詳細度與效能)
44logging:
45  level:
46    root: INFO
47    com.employee.system: INFO
48    org.springframework.web: INFO
49    org.springframework.security: WARN
50    org.hibernate.SQL: DEBUG
51  file:
52    name: ./logs/stage/application.log
53    max-size: 10MB
54    max-history: 7
55
56# JWT 測試配置
57jwt:
58  secret: ${JWT_SECRET:stage-secret-key-please-change}
59  expiration: 7200000      # 2 小時
60  refresh-expiration: 604800000  # 7 天
61
62# 檔案上傳配置
63app:
64  upload:
65    dir: ${UPLOAD_DIR:/app/uploads/stage}
66  cors:
67    allowed-origins: ${CORS_ORIGINS:http://stage.example.com}

生產環境配置 - application-prod.yml

  1# ============================================
  2# 生產環境配置
  3# ============================================
  4spring:
  5  # 生產 MySQL 資料庫(使用環境變數)
  6  datasource:
  7    url: jdbc:mysql://${DB_HOST}:${DB_PORT:3306}/${DB_NAME}?useSSL=true&requireSSL=true&serverTimezone=UTC
  8    driver-class-name: com.mysql.cj.jdbc.Driver
  9    username: ${DB_USER}
 10    password: ${DB_PASSWORD}
 11    hikari:
 12      maximum-pool-size: 20      # 較大的連接池
 13      minimum-idle: 10
 14      connection-timeout: 30000
 15      idle-timeout: 600000
 16      max-lifetime: 1800000
 17      leak-detection-threshold: 60000
 18
 19  # JPA 生產設定
 20  jpa:
 21    hibernate:
 22      ddl-auto: validate         # 僅驗證,絕不自動修改
 23    show-sql: false              # 不顯示 SQL(效能考量)
 24    properties:
 25      hibernate:
 26        format_sql: false
 27        jdbc:
 28          batch_size: 50         # 批次處理提升效能
 29        order_inserts: true
 30        order_updates: true
 31
 32  # Redis 完整啟用(生產環境必須)
 33  redis:
 34    host: ${REDIS_HOST}
 35    port: ${REDIS_PORT:6379}
 36    password: ${REDIS_PASSWORD}
 37    ssl: true                    # 啟用 SSL
 38    timeout: 2000ms
 39    lettuce:
 40      pool:
 41        max-active: 20
 42        max-idle: 10
 43        min-idle: 5
 44        max-wait: 2000ms
 45      shutdown-timeout: 100ms
 46
 47  cache:
 48    type: redis
 49    redis:
 50      time-to-live: 1800000      # 30 分鐘快取
 51      cache-null-values: false   # 不快取 null 值
 52
 53  # 安全的 CORS 設定
 54  web:
 55    cors:
 56      allowed-origins: ${CORS_ORIGINS}  # 必須從環境變數設定
 57      allowed-methods: GET,POST,PUT,DELETE
 58      allowed-headers: Authorization,Content-Type
 59      allow-credentials: true
 60      max-age: 3600
 61
 62# 生產日誌配置(僅記錄重要資訊)
 63logging:
 64  level:
 65    root: WARN
 66    com.employee.system: INFO
 67    org.springframework.web: WARN
 68    org.springframework.security: WARN
 69    org.hibernate.SQL: WARN
 70  file:
 71    name: /var/log/employee-system/application.log
 72    max-size: 100MB
 73    max-history: 30
 74  pattern:
 75    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
 76
 77# JWT 生產配置(強安全性)
 78jwt:
 79  secret: ${JWT_SECRET}          # 必須從環境變數或密鑰管理服務讀取
 80  expiration: 3600000            # 1 小時
 81  refresh-expiration: 2592000000 # 30 天
 82
 83# 檔案上傳配置
 84app:
 85  upload:
 86    dir: ${UPLOAD_DIR:/app/uploads/prod}
 87    max-size: 5242880            # 5MB
 88    allowed-extensions: jpg,jpeg,png,pdf
 89
 90# Actuator 安全配置
 91management:
 92  endpoints:
 93    web:
 94      exposure:
 95        include: health,info     # 僅暴露必要端點
 96  endpoint:
 97    health:
 98      show-details: never        # 不暴露詳細健康資訊
 99
100# Server 生產配置
101server:
102  port: ${SERVER_PORT:8080}
103  error:
104    include-stacktrace: never    # 不暴露堆疊追蹤
105    include-message: never       # 不暴露錯誤訊息
106  tomcat:
107    threads:
108      max: 200                   # 最大執行緒數
109      min-spare: 10
110    max-connections: 8192        # 最大連接數
111    accept-count: 100

💻 Java 程式碼實作

🔧 配置類別設計

1. 資料庫配置類別 - DatabaseConfig.java

  1package com.employee.system.config;
  2
  3import com.zaxxer.hikari.HikariConfig;
  4import com.zaxxer.hikari.HikariDataSource;
  5import lombok.extern.slf4j.Slf4j;
  6import org.springframework.beans.factory.annotation.Value;
  7import org.springframework.context.annotation.Bean;
  8import org.springframework.context.annotation.Configuration;
  9import org.springframework.context.annotation.Profile;
 10
 11import javax.sql.DataSource;
 12
 13/**
 14 * 資料庫配置類別
 15 * 根據不同環境提供不同的資料庫配置
 16 */
 17@Configuration
 18@Slf4j
 19public class DatabaseConfig {
 20
 21    @Value("${spring.datasource.url}")
 22    private String jdbcUrl;
 23
 24    @Value("${spring.datasource.username}")
 25    private String username;
 26
 27    @Value("${spring.datasource.password}")
 28    private String password;
 29
 30    @Value("${spring.datasource.driver-class-name}")
 31    private String driverClassName;
 32
 33    /**
 34     * 開發環境資料源(H2 記憶體資料庫)
 35     */
 36    @Bean
 37    @Profile("dev")
 38    public DataSource devDataSource() {
 39        log.info("====================================");
 40        log.info("初始化開發環境資料源 (H2 Database)");
 41        log.info("JDBC URL: {}", jdbcUrl);
 42        log.info("====================================");
 43
 44        HikariConfig config = new HikariConfig();
 45        config.setJdbcUrl(jdbcUrl);
 46        config.setUsername(username);
 47        config.setPassword(password);
 48        config.setDriverClassName(driverClassName);
 49
 50        // 開發環境較小的連接池
 51        config.setMaximumPoolSize(5);
 52        config.setMinimumIdle(2);
 53        config.setConnectionTimeout(20000);
 54
 55        return new HikariDataSource(config);
 56    }
 57
 58    /**
 59     * 測試環境資料源(MySQL)
 60     */
 61    @Bean
 62    @Profile("stage")
 63    public DataSource stageDataSource() {
 64        log.info("====================================");
 65        log.info("初始化測試環境資料源 (MySQL)");
 66        log.info("JDBC URL: {}", jdbcUrl);
 67        log.info("====================================");
 68
 69        HikariConfig config = new HikariConfig();
 70        config.setJdbcUrl(jdbcUrl);
 71        config.setUsername(username);
 72        config.setPassword(password);
 73        config.setDriverClassName(driverClassName);
 74
 75        // 測試環境中等連接池
 76        config.setMaximumPoolSize(10);
 77        config.setMinimumIdle(5);
 78        config.setConnectionTimeout(20000);
 79        config.setLeakDetectionThreshold(60000);
 80
 81        return new HikariDataSource(config);
 82    }
 83
 84    /**
 85     * 生產環境資料源(MySQL with 優化配置)
 86     */
 87    @Bean
 88    @Profile("prod")
 89    public DataSource prodDataSource() {
 90        log.info("====================================");
 91        log.info("初始化生產環境資料源 (MySQL Production)");
 92        log.info("JDBC URL: {}", maskPassword(jdbcUrl));
 93        log.info("====================================");
 94
 95        HikariConfig config = new HikariConfig();
 96        config.setJdbcUrl(jdbcUrl);
 97        config.setUsername(username);
 98        config.setPassword(password);
 99        config.setDriverClassName(driverClassName);
100
101        // 生產環境較大的連接池
102        config.setMaximumPoolSize(20);
103        config.setMinimumIdle(10);
104        config.setConnectionTimeout(30000);
105        config.setIdleTimeout(600000);
106        config.setMaxLifetime(1800000);
107        config.setLeakDetectionThreshold(60000);
108
109        // 生產環境額外配置
110        config.addDataSourceProperty("cachePrepStmts", "true");
111        config.addDataSourceProperty("prepStmtCacheSize", "250");
112        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
113        config.addDataSourceProperty("useServerPrepStmts", "true");
114
115        return new HikariDataSource(config);
116    }
117
118    /**
119     * 遮蔽密碼資訊(安全性)
120     */
121    private String maskPassword(String url) {
122        if (url.contains("password=")) {
123            return url.replaceAll("password=[^&]*", "password=****");
124        }
125        return url;
126    }
127}

2. Redis 配置類別 - RedisConfig.java

  1package com.employee.system.config;
  2
  3import com.fasterxml.jackson.annotation.JsonAutoDetect;
  4import com.fasterxml.jackson.annotation.PropertyAccessor;
  5import com.fasterxml.jackson.databind.ObjectMapper;
  6import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
  7import lombok.extern.slf4j.Slf4j;
  8import org.springframework.beans.factory.annotation.Value;
  9import org.springframework.cache.CacheManager;
 10import org.springframework.cache.annotation.EnableCaching;
 11import org.springframework.context.annotation.Bean;
 12import org.springframework.context.annotation.Configuration;
 13import org.springframework.context.annotation.Profile;
 14import org.springframework.data.redis.cache.RedisCacheConfiguration;
 15import org.springframework.data.redis.cache.RedisCacheManager;
 16import org.springframework.data.redis.connection.RedisConnectionFactory;
 17import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
 18import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
 19import org.springframework.data.redis.core.RedisTemplate;
 20import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
 21import org.springframework.data.redis.serializer.RedisSerializationContext;
 22import org.springframework.data.redis.serializer.StringRedisSerializer;
 23
 24import java.time.Duration;
 25import java.util.HashMap;
 26import java.util.Map;
 27
 28/**
 29 * Redis 快取配置
 30 * 開發環境停用,測試/生產環境啟用
 31 */
 32@Configuration
 33@EnableCaching
 34@Slf4j
 35public class RedisConfig {
 36
 37    @Value("${spring.redis.host:localhost}")
 38    private String redisHost;
 39
 40    @Value("${spring.redis.port:6379}")
 41    private int redisPort;
 42
 43    @Value("${spring.redis.password:}")
 44    private String redisPassword;
 45
 46    /**
 47     * 開發環境:使用簡單的記憶體快取(不需要 Redis)
 48     */
 49    @Bean
 50    @Profile("dev")
 51    public CacheManager devCacheManager() {
 52        log.info("====================================");
 53        log.info("開發環境:使用簡單記憶體快取(無 Redis)");
 54        log.info("====================================");
 55        return new org.springframework.cache.concurrent.ConcurrentMapCacheManager();
 56    }
 57
 58    /**
 59     * 測試/生產環境:使用 Redis
 60     */
 61    @Bean
 62    @Profile({"stage", "prod"})
 63    public LettuceConnectionFactory redisConnectionFactory() {
 64        log.info("====================================");
 65        log.info("初始化 Redis 連接");
 66        log.info("Redis Host: {}", redisHost);
 67        log.info("Redis Port: {}", redisPort);
 68        log.info("====================================");
 69
 70        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
 71        config.setHostName(redisHost);
 72        config.setPort(redisPort);
 73
 74        if (redisPassword != null && !redisPassword.isEmpty()) {
 75            config.setPassword(redisPassword);
 76        }
 77
 78        return new LettuceConnectionFactory(config);
 79    }
 80
 81    /**
 82     * Redis Template 配置
 83     */
 84    @Bean
 85    @Profile({"stage", "prod"})
 86    public RedisTemplate<String, Object> redisTemplate(
 87            RedisConnectionFactory connectionFactory) {
 88
 89        RedisTemplate<String, Object> template = new RedisTemplate<>();
 90        template.setConnectionFactory(connectionFactory);
 91
 92        // Jackson 序列化配置
 93        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
 94            new Jackson2JsonRedisSerializer<>(Object.class);
 95
 96        ObjectMapper objectMapper = new ObjectMapper();
 97        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
 98        objectMapper.activateDefaultTyping(
 99            LaissezFaireSubTypeValidator.instance,
100            ObjectMapper.DefaultTyping.NON_FINAL
101        );
102
103        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
104
105        // String 序列化
106        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
107
108        // Key 使用 String 序列化
109        template.setKeySerializer(stringRedisSerializer);
110        template.setHashKeySerializer(stringRedisSerializer);
111
112        // Value 使用 JSON 序列化
113        template.setValueSerializer(jackson2JsonRedisSerializer);
114        template.setHashValueSerializer(jackson2JsonRedisSerializer);
115
116        template.afterPropertiesSet();
117
118        return template;
119    }
120
121    /**
122     * Redis Cache Manager 配置
123     */
124    @Bean
125    @Profile({"stage", "prod"})
126    public CacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
127        log.info("====================================");
128        log.info("初始化 Redis Cache Manager");
129        log.info("====================================");
130
131        // 預設快取配置
132        RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
133            .entryTtl(Duration.ofMinutes(30))  // 預設 30 分鐘過期
134            .serializeKeysWith(
135                RedisSerializationContext.SerializationPair.fromSerializer(
136                    new StringRedisSerializer()))
137            .serializeValuesWith(
138                RedisSerializationContext.SerializationPair.fromSerializer(
139                    new Jackson2JsonRedisSerializer<>(Object.class)))
140            .disableCachingNullValues();  // 不快取 null 值
141
142        // 不同快取的個別配置
143        Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
144
145        // 員工資料快取:1 小時
146        cacheConfigurations.put("employees",
147            defaultConfig.entryTtl(Duration.ofHours(1)));
148
149        // 部門資料快取:2 小時(較少變動)
150        cacheConfigurations.put("departments",
151            defaultConfig.entryTtl(Duration.ofHours(2)));
152
153        // 使用者資料快取:30 分鐘
154        cacheConfigurations.put("users",
155            defaultConfig.entryTtl(Duration.ofMinutes(30)));
156
157        return RedisCacheManager.builder(connectionFactory)
158            .cacheDefaults(defaultConfig)
159            .withInitialCacheConfigurations(cacheConfigurations)
160            .transactionAware()
161            .build();
162    }
163}

3. 環境感知服務類別 - EnvironmentService.java

 1package com.employee.system.service;
 2
 3import lombok.extern.slf4j.Slf4j;
 4import org.springframework.beans.factory.annotation.Autowired;
 5import org.springframework.beans.factory.annotation.Value;
 6import org.springframework.core.env.Environment;
 7import org.springframework.stereotype.Service;
 8
 9import javax.annotation.PostConstruct;
10import java.util.Arrays;
11
12/**
13 * 環境資訊服務
14 * 提供當前運行環境的資訊
15 */
16@Service
17@Slf4j
18public class EnvironmentService {
19
20    @Autowired
21    private Environment environment;
22
23    @Value("${spring.datasource.url:N/A}")
24    private String datasourceUrl;
25
26    @Value("${spring.redis.host:N/A}")
27    private String redisHost;
28
29    @Value("${spring.cache.type:N/A}")
30    private String cacheType;
31
32    /**
33     * 應用程式啟動時顯示環境資訊
34     */
35    @PostConstruct
36    public void displayEnvironmentInfo() {
37        String[] activeProfiles = environment.getActiveProfiles();
38        String profile = activeProfiles.length > 0 ? activeProfiles[0] : "default";
39
40        log.info("========================================");
41        log.info("🚀 應用程式環境資訊");
42        log.info("========================================");
43        log.info("當前環境: {}", profile.toUpperCase());
44        log.info("資料庫 URL: {}", maskSensitiveInfo(datasourceUrl));
45        log.info("快取類型: {}", cacheType);
46        log.info("Redis 主機: {}", redisHost);
47        log.info("========================================");
48    }
49
50    /**
51     * 檢查是否為開發環境
52     */
53    public boolean isDevelopment() {
54        return Arrays.asList(environment.getActiveProfiles()).contains("dev");
55    }
56
57    /**
58     * 檢查是否為測試環境
59     */
60    public boolean isStaging() {
61        return Arrays.asList(environment.getActiveProfiles()).contains("stage");
62    }
63
64    /**
65     * 檢查是否為生產環境
66     */
67    public boolean isProduction() {
68        return Arrays.asList(environment.getActiveProfiles()).contains("prod");
69    }
70
71    /**
72     * 檢查 Redis 是否啟用
73     */
74    public boolean isRedisEnabled() {
75        return "redis".equalsIgnoreCase(cacheType);
76    }
77
78    /**
79     * 取得當前環境名稱
80     */
81    public String getCurrentEnvironment() {
82        String[] profiles = environment.getActiveProfiles();
83        return profiles.length > 0 ? profiles[0] : "default";
84    }
85
86    /**
87     * 遮蔽敏感資訊
88     */
89    private String maskSensitiveInfo(String info) {
90        if (info.contains("password=")) {
91            info = info.replaceAll("password=[^&]*", "password=****");
92        }
93        return info;
94    }
95}

4. Service 層使用快取 - EmployeeService.java

  1package com.employee.system.service;
  2
  3import com.employee.system.dto.EmployeeDto;
  4import com.employee.system.entity.Employee;
  5import com.employee.system.repository.EmployeeRepository;
  6import lombok.extern.slf4j.Slf4j;
  7import org.springframework.beans.factory.annotation.Autowired;
  8import org.springframework.cache.annotation.CacheEvict;
  9import org.springframework.cache.annotation.CachePut;
 10import org.springframework.cache.annotation.Cacheable;
 11import org.springframework.stereotype.Service;
 12import org.springframework.transaction.annotation.Transactional;
 13
 14import java.util.List;
 15import java.util.stream.Collectors;
 16
 17/**
 18 * 員工服務類別
 19 * 展示如何使用快取註解
 20 */
 21@Service
 22@Slf4j
 23@Transactional
 24public class EmployeeService {
 25
 26    @Autowired
 27    private EmployeeRepository employeeRepository;
 28
 29    @Autowired
 30    private EnvironmentService environmentService;
 31
 32    /**
 33     * 根據 ID 查詢員工(使用快取)
 34     * 開發環境:記憶體快取
 35     * 測試/生產環境:Redis 快取
 36     */
 37    @Cacheable(value = "employees", key = "#id", unless = "#result == null")
 38    public EmployeeDto getEmployeeById(Long id) {
 39        log.info("從資料庫查詢員工 ID: {} (環境: {})",
 40                 id, environmentService.getCurrentEnvironment());
 41
 42        Employee employee = employeeRepository.findById(id)
 43            .orElseThrow(() -> new RuntimeException("員工不存在: " + id));
 44
 45        return convertToDto(employee);
 46    }
 47
 48    /**
 49     * 查詢所有員工(使用快取)
 50     */
 51    @Cacheable(value = "employees", key = "'all'")
 52    public List<EmployeeDto> getAllEmployees() {
 53        log.info("從資料庫查詢所有員工 (環境: {})",
 54                 environmentService.getCurrentEnvironment());
 55
 56        return employeeRepository.findAll()
 57            .stream()
 58            .map(this::convertToDto)
 59            .collect(Collectors.toList());
 60    }
 61
 62    /**
 63     * 更新員工資料(更新快取)
 64     */
 65    @CachePut(value = "employees", key = "#id")
 66    public EmployeeDto updateEmployee(Long id, EmployeeDto employeeDto) {
 67        log.info("更新員工 ID: {} (環境: {})",
 68                 id, environmentService.getCurrentEnvironment());
 69
 70        Employee employee = employeeRepository.findById(id)
 71            .orElseThrow(() -> new RuntimeException("員工不存在: " + id));
 72
 73        // 更新邏輯...
 74        employee.setFirstName(employeeDto.getFirstName());
 75        employee.setLastName(employeeDto.getLastName());
 76        employee.setEmail(employeeDto.getEmail());
 77
 78        Employee updated = employeeRepository.save(employee);
 79
 80        // 清除所有員工的快取
 81        evictAllEmployeesCache();
 82
 83        return convertToDto(updated);
 84    }
 85
 86    /**
 87     * 刪除員工(清除快取)
 88     */
 89    @CacheEvict(value = "employees", key = "#id")
 90    public void deleteEmployee(Long id) {
 91        log.info("刪除員工 ID: {} (環境: {})",
 92                 id, environmentService.getCurrentEnvironment());
 93
 94        employeeRepository.deleteById(id);
 95
 96        // 清除所有員工的快取
 97        evictAllEmployeesCache();
 98    }
 99
100    /**
101     * 清除所有員工快取
102     */
103    @CacheEvict(value = "employees", key = "'all'")
104    public void evictAllEmployeesCache() {
105        log.info("清除所有員工快取 (環境: {})",
106                 environmentService.getCurrentEnvironment());
107    }
108
109    /**
110     * Entity 轉 DTO
111     */
112    private EmployeeDto convertToDto(Employee employee) {
113        return EmployeeDto.builder()
114            .id(employee.getId())
115            .firstName(employee.getFirstName())
116            .lastName(employee.getLastName())
117            .email(employee.getEmail())
118            .build();
119    }
120}

🐳 Docker 容器化部署

📦 Dockerfile 設計

多階段建置 Dockerfile

 1# ============================================
 2# Stage 1: Build Stage (Maven 編譯)
 3# ============================================
 4FROM maven:3.8.6-eclipse-temurin-17 AS build
 5
 6WORKDIR /app
 7
 8# 複製 pom.xml 並下載依賴(利用 Docker 快取)
 9COPY pom.xml .
10RUN mvn dependency:go-offline -B
11
12# 複製原始碼並編譯
13COPY src ./src
14RUN mvn clean package -DskipTests -B
15
16# ============================================
17# Stage 2: Runtime Stage (執行環境)
18# ============================================
19FROM eclipse-temurin:17-jre-alpine
20
21WORKDIR /app
22
23# 建立非 root 使用者(安全性)
24RUN addgroup -g 1001 appuser && \
25    adduser -D -u 1001 -G appuser appuser
26
27# 複製編譯好的 JAR 檔案
28COPY --from=build /app/target/*.jar app.jar
29
30# 建立日誌和上傳目錄
31RUN mkdir -p /app/logs /app/uploads && \
32    chown -R appuser:appuser /app
33
34# 切換到非 root 使用者
35USER appuser
36
37# 暴露埠號
38EXPOSE 8080
39
40# 健康檢查
41HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
42  CMD wget --no-verbose --tries=1 --spider http://localhost:8080/api/actuator/health || exit 1
43
44# 啟動指令(使用環境變數指定 Profile)
45ENTRYPOINT ["java", \
46            "-Djava.security.egd=file:/dev/./urandom", \
47            "-Dspring.profiles.active=${SPRING_PROFILE:prod}", \
48            "-jar", \
49            "app.jar"]

🎯 Docker Compose 配置

完整的多環境 Docker Compose 配置

docker-compose.dev.yml - 開發環境

 1version: '3.8'
 2
 3services:
 4  # Spring Boot 應用(開發環境)
 5  app:
 6    build:
 7      context: .
 8      dockerfile: Dockerfile
 9    container_name: employee-system-dev
10    environment:
11      - SPRING_PROFILE=dev
12    ports:
13      - "8080:8080"
14    volumes:
15      - ./logs:/app/logs
16      - ./uploads:/app/uploads
17    networks:
18      - employee-network
19    restart: unless-stopped
20    healthcheck:
21      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/api/actuator/health"]
22      interval: 30s
23      timeout: 10s
24      retries: 3
25
26networks:
27  employee-network:
28    driver: bridge

docker-compose.stage.yml - 測試環境

 1version: '3.8'
 2
 3services:
 4  # MySQL 資料庫
 5  mysql:
 6    image: mysql:8.0
 7    container_name: employee-mysql-stage
 8    environment:
 9      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
10      MYSQL_DATABASE: ${DB_NAME:-employee_stage}
11      MYSQL_USER: ${DB_USER:-stage_user}
12      MYSQL_PASSWORD: ${DB_PASSWORD}
13    ports:
14      - "3306:3306"
15    volumes:
16      - mysql_stage_data:/var/lib/mysql
17      - ./sql/init:/docker-entrypoint-initdb.d
18    networks:
19      - employee-network
20    restart: unless-stopped
21    healthcheck:
22      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
23      timeout: 10s
24      retries: 5
25
26  # Redis 快取
27  redis:
28    image: redis:7-alpine
29    container_name: employee-redis-stage
30    command: redis-server --requirepass ${REDIS_PASSWORD}
31    ports:
32      - "6379:6379"
33    volumes:
34      - redis_stage_data:/data
35    networks:
36      - employee-network
37    restart: unless-stopped
38    healthcheck:
39      test: ["CMD", "redis-cli", "ping"]
40      interval: 10s
41      timeout: 3s
42      retries: 5
43
44  # Spring Boot 應用(測試環境)
45  app:
46    build:
47      context: .
48      dockerfile: Dockerfile
49    container_name: employee-system-stage
50    environment:
51      - SPRING_PROFILE=stage
52      - DB_HOST=mysql
53      - DB_PORT=3306
54      - DB_NAME=${DB_NAME:-employee_stage}
55      - DB_USER=${DB_USER:-stage_user}
56      - DB_PASSWORD=${DB_PASSWORD}
57      - REDIS_HOST=redis
58      - REDIS_PORT=6379
59      - REDIS_PASSWORD=${REDIS_PASSWORD}
60      - JWT_SECRET=${JWT_SECRET}
61    ports:
62      - "8080:8080"
63    depends_on:
64      mysql:
65        condition: service_healthy
66      redis:
67        condition: service_healthy
68    volumes:
69      - ./logs:/app/logs
70      - ./uploads:/app/uploads
71    networks:
72      - employee-network
73    restart: unless-stopped
74    healthcheck:
75      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/api/actuator/health"]
76      interval: 30s
77      timeout: 10s
78      retries: 3
79
80volumes:
81  mysql_stage_data:
82    driver: local
83  redis_stage_data:
84    driver: local
85
86networks:
87  employee-network:
88    driver: bridge

docker-compose.prod.yml - 生產環境

  1version: '3.8'
  2
  3services:
  4  # MySQL 資料庫(生產配置)
  5  mysql:
  6    image: mysql:8.0
  7    container_name: employee-mysql-prod
  8    environment:
  9      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
 10      MYSQL_DATABASE: ${DB_NAME}
 11      MYSQL_USER: ${DB_USER}
 12      MYSQL_PASSWORD: ${DB_PASSWORD}
 13    ports:
 14      - "3306:3306"
 15    volumes:
 16      - mysql_prod_data:/var/lib/mysql
 17      - ./sql/init:/docker-entrypoint-initdb.d
 18      - ./mysql/conf:/etc/mysql/conf.d  # 自定義 MySQL 配置
 19    command:
 20      - --character-set-server=utf8mb4
 21      - --collation-server=utf8mb4_unicode_ci
 22      - --max_connections=500
 23      - --innodb_buffer_pool_size=2G
 24    networks:
 25      - employee-network
 26    restart: always
 27    healthcheck:
 28      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
 29      timeout: 10s
 30      retries: 5
 31
 32  # Redis 快取(生產配置)
 33  redis:
 34    image: redis:7-alpine
 35    container_name: employee-redis-prod
 36    command: >
 37      redis-server
 38      --requirepass ${REDIS_PASSWORD}
 39      --maxmemory 512mb
 40      --maxmemory-policy allkeys-lru
 41      --save 900 1
 42      --save 300 10
 43      --save 60 10000      
 44    ports:
 45      - "6379:6379"
 46    volumes:
 47      - redis_prod_data:/data
 48      - ./redis/redis.conf:/usr/local/etc/redis/redis.conf  # 自定義 Redis 配置
 49    networks:
 50      - employee-network
 51    restart: always
 52    healthcheck:
 53      test: ["CMD", "redis-cli", "--pass", "${REDIS_PASSWORD}", "ping"]
 54      interval: 10s
 55      timeout: 3s
 56      retries: 5
 57
 58  # Spring Boot 應用(生產環境)
 59  app:
 60    build:
 61      context: .
 62      dockerfile: Dockerfile
 63    container_name: employee-system-prod
 64    environment:
 65      - SPRING_PROFILE=prod
 66      - DB_HOST=mysql
 67      - DB_PORT=3306
 68      - DB_NAME=${DB_NAME}
 69      - DB_USER=${DB_USER}
 70      - DB_PASSWORD=${DB_PASSWORD}
 71      - REDIS_HOST=redis
 72      - REDIS_PORT=6379
 73      - REDIS_PASSWORD=${REDIS_PASSWORD}
 74      - JWT_SECRET=${JWT_SECRET}
 75      - UPLOAD_DIR=/app/uploads/prod
 76      - CORS_ORIGINS=${CORS_ORIGINS}
 77      - JAVA_OPTS=-Xms512m -Xmx2048m -XX:+UseG1GC -XX:MaxGCPauseMillis=200
 78    ports:
 79      - "8080:8080"
 80    depends_on:
 81      mysql:
 82        condition: service_healthy
 83      redis:
 84        condition: service_healthy
 85    volumes:
 86      - ./logs:/var/log/employee-system
 87      - uploads_prod_data:/app/uploads/prod
 88    networks:
 89      - employee-network
 90    restart: always
 91    healthcheck:
 92      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/api/actuator/health"]
 93      interval: 30s
 94      timeout: 10s
 95      retries: 3
 96      start_period: 60s
 97    deploy:
 98      resources:
 99        limits:
100          cpus: '2'
101          memory: 2048M
102        reservations:
103          cpus: '1'
104          memory: 512M
105
106  # Nginx 反向代理
107  nginx:
108    image: nginx:alpine
109    container_name: employee-nginx-prod
110    ports:
111      - "80:80"
112      - "443:443"
113    volumes:
114      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
115      - ./nginx/ssl:/etc/nginx/ssl:ro
116      - uploads_prod_data:/var/www/uploads:ro
117    depends_on:
118      - app
119    networks:
120      - employee-network
121    restart: always
122
123volumes:
124  mysql_prod_data:
125    driver: local
126  redis_prod_data:
127    driver: local
128  uploads_prod_data:
129    driver: local
130
131networks:
132  employee-network:
133    driver: bridge

🔐 環境變數管理

.env.dev - 開發環境變數

1# 開發環境配置
2SPRING_PROFILE=dev

.env.stage - 測試環境變數

 1# 測試環境配置
 2SPRING_PROFILE=stage
 3
 4# 資料庫配置
 5DB_ROOT_PASSWORD=stage_root_password
 6DB_NAME=employee_stage
 7DB_USER=stage_user
 8DB_PASSWORD=stage_db_password
 9
10# Redis 配置
11REDIS_PASSWORD=stage_redis_password
12
13# JWT 配置
14JWT_SECRET=stage_jwt_secret_key_minimum_256_bits
15
16# CORS 配置
17CORS_ORIGINS=http://stage-frontend.example.com

.env.prod - 生產環境變數

 1# 生產環境配置
 2SPRING_PROFILE=prod
 3
 4# 資料庫配置(使用強密碼)
 5DB_ROOT_PASSWORD=<strong-root-password>
 6DB_NAME=employee_prod
 7DB_USER=prod_user
 8DB_PASSWORD=<strong-db-password>
 9
10# Redis 配置
11REDIS_PASSWORD=<strong-redis-password>
12
13# JWT 配置(使用密鑰管理服務)
14JWT_SECRET=<strong-jwt-secret-minimum-256-bits>
15
16# CORS 配置
17CORS_ORIGINS=https://app.example.com,https://www.example.com
18
19# 檔案上傳配置
20UPLOAD_DIR=/app/uploads/prod
21
22# Java 記憶體配置
23JAVA_OPTS=-Xms1024m -Xmx2048m -XX:+UseG1GC

🚀 Docker 部署指令

開發環境啟動

1# 使用開發環境配置啟動
2docker-compose -f docker-compose.dev.yml --env-file .env.dev up -d
3
4# 查看日誌
5docker-compose -f docker-compose.dev.yml logs -f app
6
7# 停止
8docker-compose -f docker-compose.dev.yml down

測試環境啟動

 1# 使用測試環境配置啟動
 2docker-compose -f docker-compose.stage.yml --env-file .env.stage up -d
 3
 4# 查看所有服務狀態
 5docker-compose -f docker-compose.stage.yml ps
 6
 7# 查看特定服務日誌
 8docker-compose -f docker-compose.stage.yml logs -f app
 9
10# 重新建置並啟動
11docker-compose -f docker-compose.stage.yml up -d --build
12
13# 停止並刪除資料卷(慎用)
14docker-compose -f docker-compose.stage.yml down -v

生產環境啟動

 1# 使用生產環境配置啟動
 2docker-compose -f docker-compose.prod.yml --env-file .env.prod up -d
 3
 4# 檢查健康狀態
 5docker-compose -f docker-compose.prod.yml ps
 6
 7# 查看資源使用情況
 8docker stats
 9
10# 查看應用程式日誌(不使用 -f 避免阻塞)
11docker-compose -f docker-compose.prod.yml logs --tail=100 app
12
13# 執行資料庫備份
14docker exec employee-mysql-prod mysqldump -u root -p${DB_ROOT_PASSWORD} ${DB_NAME} > backup.sql
15
16# 滾動更新(零停機)
17docker-compose -f docker-compose.prod.yml up -d --no-deps --build app
18
19# 停止(保留資料卷)
20docker-compose -f docker-compose.prod.yml down

🔄 Docker Build 參數化

使用 Build Args 指定環境

 1# 建置開發環境映像
 2docker build \
 3  --build-arg SPRING_PROFILE=dev \
 4  -t employee-system:dev \
 5  .
 6
 7# 建置測試環境映像
 8docker build \
 9  --build-arg SPRING_PROFILE=stage \
10  -t employee-system:stage \
11  .
12
13# 建置生產環境映像
14docker build \
15  --build-arg SPRING_PROFILE=prod \
16  -t employee-system:prod \
17  .
18
19# 執行特定環境的容器
20docker run -d \
21  --name employee-system-prod \
22  -p 8080:8080 \
23  --env-file .env.prod \
24  employee-system:prod

📊 監控與驗證

🔍 驗證配置載入

建立監控端點 - ConfigController.java

 1package com.employee.system.controller;
 2
 3import com.employee.system.service.EnvironmentService;
 4import lombok.AllArgsConstructor;
 5import lombok.Data;
 6import org.springframework.beans.factory.annotation.Autowired;
 7import org.springframework.beans.factory.annotation.Value;
 8import org.springframework.core.env.Environment;
 9import org.springframework.web.bind.annotation.GetMapping;
10import org.springframework.web.bind.annotation.RequestMapping;
11import org.springframework.web.bind.annotation.RestController;
12
13import java.util.Arrays;
14import java.util.HashMap;
15import java.util.Map;
16
17/**
18 * 配置資訊監控端點
19 * 僅在開發/測試環境啟用
20 */
21@RestController
22@RequestMapping("/api/config")
23public class ConfigController {
24
25    @Autowired
26    private Environment environment;
27
28    @Autowired
29    private EnvironmentService environmentService;
30
31    @Value("${spring.datasource.url:N/A}")
32    private String datasourceUrl;
33
34    @Value("${spring.cache.type:N/A}")
35    private String cacheType;
36
37    /**
38     * 取得當前配置資訊
39     */
40    @GetMapping("/info")
41    public ConfigInfo getConfigInfo() {
42        // 生產環境不暴露配置資訊
43        if (environmentService.isProduction()) {
44            throw new RuntimeException("生產環境不允許存取配置資訊");
45        }
46
47        ConfigInfo info = new ConfigInfo();
48        info.setEnvironment(environmentService.getCurrentEnvironment());
49        info.setActiveProfiles(Arrays.asList(environment.getActiveProfiles()));
50        info.setDatasourceUrl(maskSensitiveInfo(datasourceUrl));
51        info.setCacheType(cacheType);
52        info.setRedisEnabled(environmentService.isRedisEnabled());
53
54        return info;
55    }
56
57    /**
58     * 健康檢查端點
59     */
60    @GetMapping("/health")
61    public Map<String, Object> healthCheck() {
62        Map<String, Object> health = new HashMap<>();
63        health.put("status", "UP");
64        health.put("environment", environmentService.getCurrentEnvironment());
65        health.put("database", "UP");
66        health.put("cache", environmentService.isRedisEnabled() ? "Redis" : "Simple");
67
68        return health;
69    }
70
71    /**
72     * 遮蔽敏感資訊
73     */
74    private String maskSensitiveInfo(String info) {
75        if (info.contains("password=")) {
76            info = info.replaceAll("password=[^&]*", "password=****");
77        }
78        return info;
79    }
80
81    @Data
82    @AllArgsConstructor
83    private static class ConfigInfo {
84        private String environment;
85        private java.util.List<String> activeProfiles;
86        private String datasourceUrl;
87        private String cacheType;
88        private boolean redisEnabled;
89
90        public ConfigInfo() {}
91    }
92}

📈 測試不同環境

測試腳本 - test-environments.sh

 1#!/bin/bash
 2
 3# 顏色定義
 4GREEN='\033[0;32m'
 5YELLOW='\033[1;33m'
 6RED='\033[0;31m'
 7NC='\033[0m' # No Color
 8
 9echo -e "${YELLOW}========================================${NC}"
10echo -e "${YELLOW}Spring Boot 多環境配置測試${NC}"
11echo -e "${YELLOW}========================================${NC}"
12
13# 測試開發環境
14echo -e "\n${GREEN}1. 測試開發環境 (dev)${NC}"
15docker-compose -f docker-compose.dev.yml --env-file .env.dev up -d
16sleep 10
17
18echo -e "檢查配置..."
19curl -s http://localhost:8080/api/config/info | jq '.'
20
21echo -e "檢查健康狀態..."
22curl -s http://localhost:8080/api/config/health | jq '.'
23
24docker-compose -f docker-compose.dev.yml down
25
26# 測試測試環境
27echo -e "\n${GREEN}2. 測試測試環境 (stage)${NC}"
28docker-compose -f docker-compose.stage.yml --env-file .env.stage up -d
29sleep 30
30
31echo -e "檢查配置..."
32curl -s http://localhost:8080/api/config/info | jq '.'
33
34echo -e "檢查 Redis 連接..."
35docker exec employee-redis-stage redis-cli -a stage_redis_password ping
36
37docker-compose -f docker-compose.stage.yml down
38
39# 測試生產環境
40echo -e "\n${GREEN}3. 測試生產環境 (prod)${NC}"
41docker-compose -f docker-compose.prod.yml --env-file .env.prod up -d
42sleep 30
43
44echo -e "檢查健康狀態..."
45curl -s http://localhost:8080/api/actuator/health | jq '.'
46
47echo -e "檢查 Redis 連接..."
48docker exec employee-redis-prod redis-cli -a ${REDIS_PASSWORD} ping
49
50docker-compose -f docker-compose.prod.yml down
51
52echo -e "\n${YELLOW}========================================${NC}"
53echo -e "${YELLOW}所有環境測試完成!${NC}"
54echo -e "${YELLOW}========================================${NC}"

🎯 最佳實踐與注意事項

✅ 配置管理最佳實踐

1. 安全性

  • 絕對不要將敏感資訊(密碼、金鑰)寫在配置檔案中
  • 必須使用環境變數或密鑰管理服務
  • ✅ 生產環境使用 AWS Secrets Manager 或 HashiCorp Vault
  • .env 檔案加入 .gitignore

2. 配置分離

  • ✅ 基礎配置放在 application.yml
  • ✅ 環境特定配置放在 application-{profile}.yml
  • ✅ 敏感配置使用環境變數
  • ✅ 使用 @Value@ConfigurationProperties 注入配置

3. 資料庫管理

  • ✅ 開發環境使用 H2/SQLite 記憶體資料庫
  • ✅ 測試環境使用獨立的測試資料庫
  • ✅ 生產環境使用 RDS 或資料庫叢集
  • ✅ 使用 Flyway/Liquibase 管理資料庫版本

4. 快取策略

  • ✅ 開發環境不使用 Redis(減少依賴)
  • ✅ 測試環境使用 Redis 測試快取功能
  • ✅ 生產環境使用 Redis Cluster(高可用)
  • ✅ 設定合理的快取過期時間

⚠️ 常見陷阱與解決方案

問題 1:Profile 沒有正確載入

1# 解決方案:明確指定 Profile
2java -jar app.jar --spring.profiles.active=prod
3
4# Docker 環境
5docker run -e SPRING_PROFILES_ACTIVE=prod app:latest

問題 2:環境變數沒有生效

1# 錯誤寫法
2spring:
3  datasource:
4    url: jdbc:mysql://localhost:3306/db
5
6# 正確寫法(使用環境變數)
7spring:
8  datasource:
9    url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:db}

問題 3:Redis 連接失敗

1// 解決方案:使用 @ConditionalOnProperty 條件化 Bean
2@Configuration
3@ConditionalOnProperty(name = "spring.cache.type", havingValue = "redis")
4public class RedisConfig {
5    // Redis 配置...
6}

問題 4:Docker 容器內連接資料庫失敗

1# 錯誤:使用 localhost
2DB_HOST=localhost
3
4# 正確:使用 Docker Compose 服務名稱
5DB_HOST=mysql

🎉 總結

📊 多環境配置架構優勢

1. 開發效率提升

  • 開發人員無需安裝 MySQL、Redis 等服務
  • 使用 H2 記憶體資料庫快速啟動
  • 詳細的日誌輸出便於除錯

2. 測試環境隔離

  • 獨立的測試資料庫避免污染生產資料
  • 模擬生產環境配置提前發現問題
  • 支援自動化測試和持續整合

3. 生產環境安全

  • 敏感資訊完全隔離
  • 效能優化配置(連接池、快取)
  • 完整的監控和日誌管理

4. 維護成本降低

  • 統一的配置管理方式
  • 清晰的環境區分
  • 容易的新環境建立

🚀 延伸學習

進階主題

  1. Spring Cloud Config:集中化配置管理
  2. Kubernetes ConfigMap:容器編排環境配置
  3. AWS Parameter Store:雲端配置管理
  4. Vault Integration:密鑰管理整合

相關資源

💡 核心要點回顧

  1. ✅ 使用 Spring Profile 管理不同環境配置
  2. ✅ 敏感資訊必須使用環境變數
  3. ✅ 開發環境簡化配置,生產環境強化安全
  4. ✅ Docker Compose 統一管理容器化部署
  5. ✅ Redis 快取視環境需求啟用/停用
  6. ✅ 完整的健康檢查和監控機制

透過本文的完整指南,你可以建立一個專業、安全、易維護的 Spring Boot 多環境配置架構,無論是本地開發、測試環境還是生產部署,都能游刃有餘!


🔗 相關資源

資源連結
📂 完整範例程式碼GitHub - SpringPlayground
📖 Spring Boot 文檔官方文檔
🐳 Docker 文檔Docker 官方文檔
📚 相關文章Spring Boot 系列文章
Yen

Yen

Yen