SpringBoot with Java
Spring Boot with Java 包括的技術ガイド
目次
- はじめに
- アーキテクチャの全体像
- Spring Boot の概要とアーキテクチャ
- 自動構成(Auto-Configuration)の仕組み
- Spring Boot Starter
- 設定管理とプロファイル
- Web 開発(Spring MVC / WebFlux)
- データアクセス(Spring Data JPA / JDBC)
- セキュリティ(Spring Security)
- テスト戦略
- Actuator とモニタリング
- メッセージングとイベント駆動
- キャッシュと性能最適化
- マイクロサービスアーキテクチャと Spring Cloud
- コンテナ化とデプロイメント
- GraalVM Native Image との統合
- ベストプラクティスとアンチパターン
- まとめ
1. はじめに
1.1 Spring Boot とは
Spring Boot は、Spring Framework をベースとしたアプリケーション開発フレームワークであり、本番環境に対応したスタンドアロンの Spring ベースアプリケーションを迅速に構築するために設計されている。2014年に Pivotal(現 VMware Tanzu / Broadcom)によって初めてリリースされ、以降 Java エコシステムにおけるデファクトスタンダードのフレームワークとして広く普及している。
Spring Boot の核心的な設計思想は「Convention over Configuration(設定より規約)」であり、開発者が最小限の設定で生産性の高い開発を行えるようにすることを目指している。従来の Spring Framework では XML ベースの大量の設定ファイルが必要であったが、Spring Boot はこれを自動設定(Auto-Configuration)メカニズムによって劇的に簡素化した。
1.2 Spring Boot のバージョン体系
| バージョン | リリース年 | Java 最低要件 | 主な特徴 |
|---|---|---|---|
| Spring Boot 2.7.x | 2022 | Java 8 | Spring Framework 5.3.x ベース、LTS |
| Spring Boot 3.0.x | 2022 | Java 17 | Jakarta EE 9+、Spring Framework 6.0 |
| Spring Boot 3.1.x | 2023 | Java 17 | Docker Compose サポート、テスト用コンテナ |
| Spring Boot 3.2.x | 2023 | Java 17 | Virtual Threads、JdbcClient |
| Spring Boot 3.3.x | 2024 | Java 17 | CDS サポート強化、Base64 プロパティ |
| Spring Boot 3.4.x | 2024 | Java 17 | 構造化ログ、改善されたコンテナイメージ |
Spring Boot 3.x 系列における最大の変更点は、javax.* パッケージから jakarta.* パッケージへの全面的な移行である。
1.3 Spring Boot がもたらす価値
- 迅速なプロジェクト立ち上げ: Spring Initializr を通じて数秒でプロジェクトの雛形を生成できる
- 自動設定: クラスパス上のライブラリを検出し、適切なデフォルト設定を自動的に適用する
- 組み込みサーバー: Tomcat、Jetty、Undertow などの組み込みサーバーを内蔵しており、WAR デプロイが不要
- 本番環境対応機能: Actuator によるヘルスチェック、メトリクス、監視機能を標準で提供
- 豊富なスターター:
spring-boot-starter-*による依存関係管理の大幅な簡素化 - 外部設定の柔軟性: プロファイルベースの設定管理により環境ごとの設定切り替えが容易
- テスト支援:
@SpringBootTestをはじめとする包括的なテスト基盤を提供 - クラウドネイティブ対応: GraalVM ネイティブイメージ、コンテナ最適化を標準サポート
1.4 Spring Framework との関係
Spring Boot は Spring Framework を「置き換える」ものではなく、Spring Framework の上に構築された「追加レイヤー」である。
Spring Boot = Spring Framework + 自動設定 + 組み込みサーバー + 本番対応機能 + スターター
2. アーキテクチャの全体像
2.1 レイヤードアーキテクチャ
┌──────────────────────────────────────────────────────────────┐
│ プレゼンテーション層 │
│ Controller / RestController / View Template │
├──────────────────────────────────────────────────────────────┤
│ サービス層 │
│ ビジネスロジック / トランザクション管理 │
├──────────────────────────────────────────────────────────────┤
│ データアクセス層 │
│ Repository / JPA Entity / DAO │
├──────────────────────────────────────────────────────────────┤
│ インフラ層 │
│ DataSource / Cache / Messaging / External APIs │
└──────────────────────────────────────────────────────────────┘
2.2 起動プロセスの詳細フロー
1. main() メソッド実行
└── SpringApplication.run() 呼び出し
├── 2. SpringApplication インスタンス生成
│ ├── WebApplicationType の推定 (SERVLET / REACTIVE / NONE)
│ ├── ApplicationContextInitializer の検出
│ └── ApplicationListener の検出
├── 3. SpringApplicationRunListeners の通知
├── 4. 環境(Environment)の準備
│ ├── application.properties / .yml の読み込み
│ ├── 環境変数の読み込み
│ └── プロファイルのアクティベーション
├── 5. ApplicationContext の生成
├── 6. ApplicationContext の準備
├── 7. ApplicationContext のリフレッシュ
│ ├── Auto-Configuration の適用
│ ├── Bean のインスタンス化と依存性注入
│ └── 組み込みサーバーの起動
└── 8. Runner の実行
├── ApplicationRunner / CommandLineRunner の実行
└── ApplicationReadyEvent 発行
3. Spring Boot の概要とアーキテクチャ
3.1 DI(Dependency Injection)/ IoC(Inversion of Control)
Spring Framework の中核は DI/IoC コンテナである。IoC とは「制御の反転」を意味し、オブジェクトの生成やライフサイクル管理をフレームワーク側が担うデザインパターンである。
@Service
public class OrderServiceImpl implements OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
// コンストラクタインジェクション(推奨)
public OrderServiceImpl(OrderRepository orderRepository,
PaymentService paymentService) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
}
@Override
@Transactional
public Order createOrder(OrderRequest request) {
Order order = new Order(request);
orderRepository.save(order);
paymentService.processPayment(order);
return order;
}
}
DI の方式比較
| 方式 | 推奨度 | 説明 |
|---|---|---|
| コンストラクタインジェクション | 最も推奨 | 不変性保証、テスト容易性 |
| セッターインジェクション | 条件付き | オプショナルな依存に使用 |
| フィールドインジェクション | 非推奨 | テスト困難、不変性なし |
3.2 Bean のスコープ
| スコープ | 説明 | 用途 |
|---|---|---|
singleton | アプリケーション全体で1つのインスタンス(デフォルト) | ステートレスなサービス |
prototype | 取得ごとに新しいインスタンス | ステートフルなオブジェクト |
request | HTTPリクエストごとに1つ | Webアプリケーション |
session | HTTPセッションごとに1つ | ユーザーセッション管理 |
3.3 AOP(Aspect-Oriented Programming)
@Aspect
@Component
public class PerformanceMonitorAspect {
private static final Logger log = LoggerFactory.getLogger(PerformanceMonitorAspect.class);
@Around("@annotation(Monitored)")
public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().toShortString();
long startTime = System.nanoTime();
try {
Object result = joinPoint.proceed();
long duration = (System.nanoTime() - startTime) / 1_000_000;
log.info("Method {} executed in {} ms", methodName, duration);
return result;
} catch (Exception e) {
long duration = (System.nanoTime() - startTime) / 1_000_000;
log.error("Method {} failed after {} ms: {}", methodName, duration, e.getMessage());
throw e;
}
}
}
3.4 Spring のイベントモデル
// イベント発行
@Service
public class OrderService {
private final ApplicationEventPublisher eventPublisher;
public Order createOrder(OrderRequest request) {
Order order = // ... 注文作成ロジック
eventPublisher.publishEvent(new OrderCreatedEvent(this, order));
return order;
}
}
// イベントリスナー
@Component
public class OrderEventListener {
@EventListener
@Async
public void handleOrderCreated(OrderCreatedEvent event) {
sendConfirmationEmail(event.getOrder());
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleOrderAfterCommit(OrderCreatedEvent event) {
updateAnalytics(event.getOrder());
}
}
4. 自動構成(Auto-Configuration)の仕組み
4.1 Auto-Configuration の原理
Spring Boot の自動構成は、クラスパス上のライブラリを検出し、適切な Bean を自動的に設定する仕組みである。
@EnableAutoConfigurationがMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsを読み込む- 各 Auto-Configuration クラスの条件アノテーションを評価
- 条件を満たす設定クラスが有効化され、Bean が登録される
4.2 条件アノテーション(@Conditional)
| アノテーション | 条件 |
|---|---|
@ConditionalOnClass | 指定クラスがクラスパスに存在する場合 |
@ConditionalOnMissingClass | 指定クラスがクラスパスに存在しない場合 |
@ConditionalOnBean | 指定Beanが存在する場合 |
@ConditionalOnMissingBean | 指定Beanが存在しない場合 |
@ConditionalOnProperty | 指定プロパティが特定の値の場合 |
@ConditionalOnResource | 指定リソースが存在する場合 |
@ConditionalOnWebApplication | Webアプリケーションの場合 |
4.3 Auto-Configuration のカスタム実装例
@AutoConfiguration
@ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(prefix = "app.datasource.monitoring", name = "enabled", havingValue = "true")
@EnableConfigurationProperties(DataSourceMonitoringProperties.class)
public class DataSourceMonitoringAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DataSourceMonitor dataSourceMonitor(
DataSource dataSource,
DataSourceMonitoringProperties properties) {
return new DataSourceMonitor(dataSource, properties.getInterval());
}
}
@ConfigurationProperties(prefix = "app.datasource.monitoring")
public class DataSourceMonitoringProperties {
private boolean enabled = false;
private Duration interval = Duration.ofSeconds(30);
// getter / setter
}
4.4 自動構成の確認とデバッグ
debug: true # 起動時にAutoConfigurationレポートを表示
management:
endpoints:
web:
exposure:
include: conditions
5. Spring Boot Starter
5.1 pom.xml の設定例(Maven)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.0</version>
</parent>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
5.2 build.gradle の設定例(Gradle)
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.0'
id 'io.spring.dependency-management' version '1.1.5'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
runtimeOnly 'org.postgresql:postgresql'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
6. 設定管理とプロファイル
6.1 application.yml の包括的な設定例
spring:
application:
name: order-service
datasource:
url: jdbc:postgresql://localhost:5432/orderdb
username: ${DB_USERNAME:order_user}
password: ${DB_PASSWORD:}
hikari:
maximum-pool-size: 20
minimum-idle: 5
idle-timeout: 300000
max-lifetime: 1800000
connection-timeout: 30000
leak-detection-threshold: 60000
jpa:
hibernate:
ddl-auto: validate
open-in-view: false
properties:
hibernate:
format_sql: true
jdbc:
batch_size: 50
order_inserts: true
order_updates: true
default_batch_fetch_size: 16
flyway:
enabled: true
locations: classpath:db/migration
jackson:
default-property-inclusion: non_null
serialization:
write-dates-as-timestamps: false
deserialization:
fail-on-unknown-properties: false
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
lettuce:
pool:
max-active: 16
max-idle: 8
kafka:
bootstrap-servers: ${KAFKA_BROKERS:localhost:9092}
consumer:
group-id: order-service-group
auto-offset-reset: earliest
enable-auto-commit: false
producer:
retries: 3
acks: all
server:
port: 8080
shutdown: graceful
tomcat:
max-threads: 200
min-spare-threads: 10
compression:
enabled: true
mime-types: application/json,application/xml
min-response-size: 1024
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus,env,loggers
endpoint:
health:
show-details: when-authorized
probes:
enabled: true
metrics:
tags:
application: ${spring.application.name}
distribution:
percentiles-histogram:
http.server.requests: true
logging:
level:
root: INFO
com.example.myapp: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId:-},%X{spanId:-}] %logger{36} - %msg%n"
6.2 プロファイル(Profile)
application.yml <- ベース設定(常に読み込み)
↓ マージ
application-{profile}.yml <- プロファイル固有設定(上書き)
↓ マージ
環境変数 <- さらに上書き
↓ マージ
コマンドライン引数 <- 最高優先度
# application-dev.yml(開発環境)
spring:
datasource:
url: jdbc:h2:mem:devdb
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
# application-prod.yml(本番環境)
spring:
datasource:
url: jdbc:postgresql://${DB_HOST}:5432/orderdb
hikari:
maximum-pool-size: 30
jpa:
show-sql: false
server:
port: 8443
ssl:
enabled: true
6.3 外部設定のバインディング
@ConfigurationProperties(prefix = "app.order")
@Validated
public class OrderProperties {
@NotNull
private Integer maxItemsPerOrder = 100;
@NotBlank
private String defaultCurrency = "JPY";
private Duration expiryDuration = Duration.ofMinutes(30);
private Retry retry = new Retry();
public static class Retry {
private int maxAttempts = 3;
private Duration delay = Duration.ofSeconds(1);
private double multiplier = 2.0;
}
}
6.4 設定の優先順位(高い順)
- コマンドライン引数 (
--server.port=9090) - Java システムプロパティ (
-Dserver.port=9090) - OS 環境変数 (
SERVER_PORT=9090) - プロファイル固有の
application-{profile}.yml - パッケージ外の
application.yml - パッケージ内の
application.yml @PropertySourceで指定されたファイル- デフォルト値
7. Web 開発(Spring MVC / WebFlux)
7.1 REST Controller の実装例
@RestController
@RequestMapping("/api/v1/orders")
@Validated
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping
public ResponseEntity<Page<OrderResponse>> getOrders(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) OrderStatus status) {
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
return ResponseEntity.ok(orderService.getOrders(status, pageable));
}
@GetMapping("/{orderId}")
public ResponseEntity<OrderResponse> getOrder(@PathVariable @Positive Long orderId) {
return ResponseEntity.ok(orderService.getOrderById(orderId));
}
@PostMapping
public ResponseEntity<OrderResponse> createOrder(
@RequestBody @Valid CreateOrderRequest request,
@AuthenticationPrincipal UserDetails userDetails) {
OrderResponse order = orderService.createOrder(request, userDetails.getUsername());
URI location = ServletUriComponentsBuilder
.fromCurrentRequest().path("/{id}")
.buildAndExpand(order.getId()).toUri();
return ResponseEntity.created(location).body(order);
}
@DeleteMapping("/{orderId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void cancelOrder(@PathVariable @Positive Long orderId) {
orderService.cancelOrder(orderId);
}
}
7.2 DTO(Data Transfer Object)
public record CreateOrderRequest(
@NotBlank String customerId,
@NotEmpty @Size(max = 100) List<@Valid OrderItemRequest> items,
@NotNull String shippingAddress,
String notes
) {}
public record OrderResponse(
Long id, String customerId, OrderStatus status,
List<OrderItemResponse> items, BigDecimal totalAmount,
String shippingAddress, LocalDateTime createdAt, LocalDateTime updatedAt
) {}
7.3 グローバル例外ハンドラー
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse(404, "NOT_FOUND", ex.getMessage(), LocalDateTime.now()));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationErrors(MethodArgumentNotValidException ex) {
List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors().stream()
.map(fe -> new FieldError(fe.getField(), fe.getDefaultMessage())).toList();
return ResponseEntity.badRequest()
.body(new ErrorResponse(400, "VALIDATION_ERROR", "入力値が不正です", LocalDateTime.now(), fieldErrors));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneral(Exception ex) {
log.error("Unexpected error occurred", ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse(500, "INTERNAL_ERROR", "内部エラーが発生しました", LocalDateTime.now()));
}
}
7.4 Spring WebFlux(リアクティブ)
@RestController
@RequestMapping("/api/v1/reactive/orders")
public class ReactiveOrderController {
private final ReactiveOrderService orderService;
@GetMapping("/{orderId}")
public Mono<OrderResponse> getOrder(@PathVariable Long orderId) {
return orderService.getOrderById(orderId)
.switchIfEmpty(Mono.error(new ResourceNotFoundException("Order not found")));
}
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<OrderEvent> streamOrderEvents() {
return orderService.getOrderEventStream();
}
}
7.5 Spring MVC と WebFlux の比較
| 特性 | Spring MVC | Spring WebFlux |
|---|---|---|
| プログラミングモデル | 同期・ブロッキング | 非同期・ノンブロッキング |
| 基盤 | Servlet API | Reactive Streams |
| サーバー | Tomcat, Jetty, Undertow | Netty, Tomcat, Jetty |
| スレッドモデル | Thread-per-request | Event Loop |
| 戻り値 | 通常のオブジェクト | Mono<T> / Flux<T> |
| ユースケース | 一般的なCRUDアプリ | 高並行性、ストリーミング |
| DB アクセス | JDBC / JPA | R2DBC |
7.6 フィルターとインターセプター
@Component
@Order(1)
public class RequestLoggingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String requestId = UUID.randomUUID().toString();
MDC.put("requestId", requestId);
response.setHeader("X-Request-Id", requestId);
long startTime = System.currentTimeMillis();
try {
filterChain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - startTime;
log.info("Request: {} {} - Status: {} - Duration: {}ms",
request.getMethod(), request.getRequestURI(), response.getStatus(), duration);
MDC.clear();
}
}
}
8. データアクセス(Spring Data JPA / JDBC)
8.1 エンティティ定義
@Entity
@Table(name = "orders", indexes = {
@Index(name = "idx_orders_customer_id", columnList = "customerId"),
@Index(name = "idx_orders_status", columnList = "status")
})
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 50)
private String customerId;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private OrderStatus status = OrderStatus.PENDING;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> items = new ArrayList<>();
@Column(nullable = false, precision = 12, scale = 2)
private BigDecimal totalAmount = BigDecimal.ZERO;
@CreatedDate
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(nullable = false)
private LocalDateTime updatedAt;
@Version
private Long version; // 楽観的ロック
}
8.2 リポジトリ定義
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByCustomerIdOrderByCreatedAtDesc(String customerId);
Page<Order> findByStatus(OrderStatus status, Pageable pageable);
long countByCustomerIdAndStatus(String customerId, OrderStatus status);
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id")
Optional<Order> findByIdWithItems(@Param("id") Long id);
@Query(value = """
SELECT o.customer_id, COUNT(*) as order_count, SUM(o.total_amount) as total_spent
FROM orders o WHERE o.created_at >= :since
GROUP BY o.customer_id HAVING COUNT(*) >= :minOrders
ORDER BY total_spent DESC
""", nativeQuery = true)
List<Object[]> findTopCustomers(@Param("since") LocalDateTime since, @Param("minOrders") int minOrders);
@Modifying
@Query("UPDATE Order o SET o.status = :status WHERE o.id = :id")
int updateStatus(@Param("id") Long id, @Param("status") OrderStatus status);
}
8.3 Specification による動的クエリ
public class OrderSpecifications {
public static Specification<Order> hasStatus(OrderStatus status) {
return (root, query, cb) -> status == null ? null : cb.equal(root.get("status"), status);
}
public static Specification<Order> hasCustomerId(String customerId) {
return (root, query, cb) -> customerId == null ? null : cb.equal(root.get("customerId"), customerId);
}
public static Specification<Order> createdBetween(LocalDateTime start, LocalDateTime end) {
return (root, query, cb) -> {
if (start == null && end == null) return null;
if (start != null && end != null) return cb.between(root.get("createdAt"), start, end);
if (start != null) return cb.greaterThanOrEqualTo(root.get("createdAt"), start);
return cb.lessThanOrEqualTo(root.get("createdAt"), end);
};
}
}
8.4 トランザクション管理
@Service
@Transactional(readOnly = true)
public class OrderService {
@Transactional
public OrderResponse createOrder(CreateOrderRequest request) {
Order order = new Order(request);
inventoryService.reserveItems(order.getItems());
orderRepository.save(order);
return OrderResponse.from(order);
}
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED,
timeout = 30, rollbackFor = {BusinessException.class})
public void processOrder(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new ResourceNotFoundException("Order not found"));
paymentService.processPayment(order);
order.setStatus(OrderStatus.PROCESSING);
orderRepository.save(order);
}
}
8.5 データベースマイグレーション(Flyway)
-- V1__create_orders_table.sql
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
customer_id VARCHAR(50) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
total_amount DECIMAL(12, 2) NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
version BIGINT NOT NULL DEFAULT 0
);
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
CREATE INDEX idx_orders_status ON orders(status);
9. セキュリティ(Spring Security)
9.1 Spring Security アーキテクチャ
HTTP Request → Security Filter Chain → DispatcherServlet → Controller
├── CorsFilter
├── CsrfFilter
├── BasicAuthenticationFilter
├── BearerTokenAuthFilter
├── AuthorizationFilter
└── ExceptionTranslationFilter
9.2 セキュリティ設定(Spring Boot 3.x / Spring Security 6.x)
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/auth/**").permitAll()
.requestMatchers("/actuator/health").permitAll()
.requestMatchers(HttpMethod.DELETE, "/api/v1/orders/**").hasRole("ADMIN")
.anyRequest().authenticated())
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
UsernamePasswordAuthenticationFilter.class)
.headers(headers -> headers
.frameOptions(frame -> frame.deny())
.httpStrictTransportSecurity(hsts -> hsts.maxAgeInSeconds(31536000).includeSubDomains(true)))
.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
}
9.3 JWT 認証
@Component
public class JwtTokenProvider {
@Value("${app.jwt.secret}")
private String jwtSecret;
public String generateAccessToken(UserDetails userDetails) {
return Jwts.builder()
.claims(Map.of("roles", userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority).toList()))
.subject(userDetails.getUsername())
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + jwtExpirationMs))
.signWith(getSigningKey())
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parser().verifyWith(getSigningKey()).build().parseSignedClaims(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}
9.4 メソッドレベルセキュリティ
@PreAuthorize("hasRole('ADMIN') or #customerId == authentication.name")
public List<Order> getOrdersByCustomer(String customerId) { ... }
@PostAuthorize("returnObject.customerId == authentication.name or hasRole('ADMIN')")
public Order getOrder(Long orderId) { ... }
9.5 OAuth 2.0 Resource Server 設定
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth.example.com/realms/my-realm
10. テスト戦略
10.1 単体テスト
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock private OrderRepository orderRepository;
@Mock private PaymentService paymentService;
@InjectMocks private OrderService orderService;
@Test
@DisplayName("正常な注文作成が成功する")
void createOrder_withValidRequest_shouldSucceed() {
when(orderRepository.save(any(Order.class))).thenReturn(savedOrder);
OrderResponse response = orderService.createOrder(request, "customer-1");
assertThat(response).isNotNull();
assertThat(response.status()).isEqualTo(OrderStatus.PENDING);
verify(orderRepository).save(any(Order.class));
}
}
10.2 統合テスト
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@ActiveProfiles("test")
@Transactional
class OrderControllerIntegrationTest {
@Autowired private MockMvc mockMvc;
@Autowired private ObjectMapper objectMapper;
@Test
@WithMockUser(roles = "USER")
void createOrder_shouldReturn201() throws Exception {
mockMvc.perform(post("/api/v1/orders")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.status").value("PENDING"));
}
}
10.3 スライステスト
@WebMvcTest(OrderController.class) // Controller 層のみ
@DataJpaTest // Repository 層のみ
10.4 Testcontainers
@SpringBootTest
@Testcontainers
class OrderServiceWithDatabaseTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
}
11. Actuator とモニタリング
11.1 主要エンドポイント
| エンドポイント | 説明 |
|---|---|
/actuator/health | ヘルスチェック |
/actuator/info | アプリケーション情報 |
/actuator/metrics | メトリクス一覧 |
/actuator/prometheus | Prometheus 形式メトリクス |
/actuator/env | 環境プロパティ |
/actuator/loggers | ログレベル管理 |
/actuator/threaddump | スレッドダンプ |
11.2 カスタムメトリクス(Micrometer)
@Component
public class OrderMetrics {
private final Counter orderCreatedCounter;
private final Timer orderProcessingTimer;
public OrderMetrics(MeterRegistry registry) {
this.orderCreatedCounter = Counter.builder("orders.created")
.description("Number of orders created").register(registry);
this.orderProcessingTimer = Timer.builder("orders.processing.duration")
.publishPercentiles(0.5, 0.95, 0.99)
.publishPercentileHistogram().register(registry);
}
}
11.3 Kubernetes プローブ設定
management:
endpoint:
health:
probes:
enabled: true
group:
readiness:
include: db, redis, kafka
liveness:
include: livenessState
12. メッセージングとイベント駆動
12.1 Apache Kafka との統合
// Producer
@Service
public class OrderEventProducer {
private final KafkaTemplate<String, OrderEvent> kafkaTemplate;
public CompletableFuture<SendResult<String, OrderEvent>> publishOrderCreated(Order order) {
OrderEvent event = new OrderEvent(UUID.randomUUID().toString(), "ORDER_CREATED",
order.getId(), order.getCustomerId(), order.getTotalAmount(), Instant.now());
return kafkaTemplate.send("order-events", order.getId().toString(), event);
}
}
// Consumer
@Component
public class OrderEventConsumer {
@KafkaListener(topics = "order-events", groupId = "analytics-service-group")
@RetryableTopic(attempts = "3", backoff = @Backoff(delay = 1000, multiplier = 2.0))
public void handleOrderEvent(@Payload OrderEvent event, Acknowledgment ack) {
analyticsService.processOrderEvent(event);
ack.acknowledge();
}
@DltHandler
public void handleDlt(OrderEvent event) {
log.error("Message exhausted retries: {}", event.eventId());
}
}
12.2 RabbitMQ との統合
@Configuration
public class RabbitMQConfig {
@Bean
public Queue orderQueue() {
return QueueBuilder.durable("order.queue")
.withArgument("x-dead-letter-exchange", "dlx.exchange").build();
}
@Bean
public TopicExchange orderExchange() {
return new TopicExchange("order.exchange");
}
}
13. キャッシュと性能最適化
13.1 Spring Cache Abstraction
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)).disableCachingNullValues();
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(defaultConfig)
.withInitialCacheConfigurations(Map.of(
"orders", defaultConfig.entryTtl(Duration.ofMinutes(10)),
"products", defaultConfig.entryTtl(Duration.ofHours(1))))
.build();
}
}
@Service
public class ProductService {
@Cacheable(value = "products", key = "#productId", unless = "#result == null")
public Product getProduct(String productId) { ... }
@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) { ... }
@CacheEvict(value = "products", key = "#productId")
public void deleteProduct(String productId) { ... }
}
13.2 非同期処理
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
13.3 HTTP クライアント(Spring Boot 3.2+)
@HttpExchange("/api/v1")
public interface ExternalOrderClient {
@GetExchange("/orders/{id}")
OrderResponse getOrder(@PathVariable String id);
@PostExchange("/orders")
OrderResponse createOrder(@RequestBody CreateOrderRequest request);
}
14. マイクロサービスアーキテクチャと Spring Cloud
14.1 Spring Cloud エコシステム
| コンポーネント | 役割 |
|---|---|
| Service Discovery (Eureka/Consul) | サービス登録・発見 |
| API Gateway (Spring Cloud Gateway) | ルーティング、レート制限 |
| Circuit Breaker (Resilience4j) | 障害分離 |
| Config Server | 設定の一元管理 |
| Distributed Tracing (Micrometer/OTEL) | 分散トレーシング |
| Load Balancer (Spring Cloud LB) | クライアントサイド負荷分散 |
| Stream (Kafka/RabbitMQ) | イベントストリーミング |
| OpenFeign | 宣言的 HTTP クライアント |
14.2 サーキットブレーカー(Resilience4j)
@Service
public class PaymentServiceClient {
@CircuitBreaker(name = "paymentService", fallbackMethod = "paymentFallback")
@Retry(name = "paymentService")
@Bulkhead(name = "paymentService")
public PaymentResult processPayment(PaymentRequest request) {
return restClient.post().uri("/api/v1/payments").body(request)
.retrieve().body(PaymentResult.class);
}
private PaymentResult paymentFallback(PaymentRequest request, Throwable t) {
return new PaymentResult("PENDING", "Payment queued for retry");
}
}
resilience4j:
circuitbreaker:
instances:
paymentService:
sliding-window-size: 10
failure-rate-threshold: 50
wait-duration-in-open-state: 30s
permitted-number-of-calls-in-half-open-state: 3
retry:
instances:
paymentService:
max-attempts: 3
wait-duration: 1s
exponential-backoff-multiplier: 2
14.3 分散トレーシング
management:
tracing:
sampling:
probability: 1.0
logging:
pattern:
level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"
15. コンテナ化とデプロイメント
15.1 Dockerfile(マルチステージビルド)
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:go-offline -B
COPY src ./src
RUN ./mvnw clean package -DskipTests -B
FROM eclipse-temurin:21-jdk-alpine AS extractor
WORKDIR /app
COPY --from=builder /app/target/*.jar application.jar
RUN java -Djarmode=layertools -jar application.jar extract
FROM eclipse-temurin:21-jre-alpine
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
WORKDIR /app
COPY --from=extractor /app/dependencies/ ./
COPY --from=extractor /app/spring-boot-loader/ ./
COPY --from=extractor /app/snapshot-dependencies/ ./
COPY --from=extractor /app/application/ ./
EXPOSE 8080
ENV JAVA_OPTS="-XX:+UseG1GC -XX:MaxRAMPercentage=75.0 -XX:+ExitOnOutOfMemoryError"
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} org.springframework.boot.loader.launch.JarLauncher"]
15.2 Kubernetes マニフェスト
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
template:
spec:
containers:
- name: order-service
image: myregistry/order-service:1.0.0
resources:
requests: { cpu: 500m, memory: 512Mi }
limits: { cpu: "1", memory: 1Gi }
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
livenessProbe:
httpGet: { path: /actuator/health/liveness, port: 8080 }
initialDelaySeconds: 30
readinessProbe:
httpGet: { path: /actuator/health/readiness, port: 8080 }
initialDelaySeconds: 15
lifecycle:
preStop:
exec: { command: ["sh", "-c", "sleep 10"] }
terminationGracePeriodSeconds: 60
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: order-service-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-service
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target: { type: Utilization, averageUtilization: 70 }
16. GraalVM Native Image との統合
16.1 メリットとデメリット
| 特性 | JVM モード | Native Image |
|---|---|---|
| 起動時間 | 数秒〜十数秒 | ミリ秒〜数百ミリ秒 |
| メモリ使用量 | 高い(数百MB) | 低い(数十MB) |
| ピークスループット | 高い(JIT最適化) | やや低い |
| ビルド時間 | 短い | 長い(数分〜十数分) |
| リフレクション | 完全サポート | 制限あり(要ヒント) |
| ユースケース | 長時間稼働サービス | サーバーレス、CLI |
16.2 Native Image のビルド
./mvnw -Pnative native:compile
./mvnw -Pnative spring-boot:build-image
16.3 AOT ヒントの提供
@RegisterReflectionForBinding({OrderRequest.class, OrderResponse.class})
@Configuration
public class NativeHintsConfig implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.resources().registerPattern("db/migration/*");
hints.reflection().registerType(Order.class,
MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
}
17. ベストプラクティスとアンチパターン
17.1 ベストプラクティス
プロジェクト構成
- パッケージ構成をドメインベースまたはレイヤーベースで統一する
- エンティティをAPIレスポンスとして直接返さず、DTOを使用する
- Bean Validation を活用し、Controller 層で入力検証する
@RestControllerAdviceで例外処理をグローバルに統一する
コーディング
5. フィールドインジェクションではなくコンストラクタインジェクションを使用する
6. 可能な限りイミュータブルなオブジェクト(record 等)を使用する
7. SLF4J + Logback を使用し、構造化ログを出力する
8. ハードコードせず、@ConfigurationProperties で設定を外部化する
データアクセス
9. JOIN FETCH や @EntityGraph で N+1 問題を回避する
10. spring.jpa.open-in-view=false を設定する
11. Flyway / Liquibase でスキーマ変更を管理する
運用
12. Actuator を活用してヘルスチェック、メトリクスを提供する
13. server.shutdown=graceful を設定する
14. パスワードを設定ファイルに直書きしない
17.2 アンチパターン
| アンチパターン | 問題 | 推奨 |
|---|---|---|
| エンティティをAPIレスポンスに直接使用 | 内部構造の露出 | DTO を使用 |
@Autowired フィールドインジェクション | テスト困難 | コンストラクタインジェクション |
spring.jpa.open-in-view=true | DB接続長期保持 | false に設定 |
ddl-auto=update を本番で使用 | 予期しないスキーマ変更 | validate + Flyway |
大きな @SpringBootTest ばかり | テスト実行が遅い | スライステスト活用 |
| Actuator エンドポイントの全公開 | セキュリティリスク | 必要なもののみ公開 |
17.3 パフォーマンスチェックリスト
- HikariCP の接続プールサイズは適切か
- JPA の N+1 問題は解消されているか
- open-in-view は無効になっているか
- 適切なインデックスが設定されているか
- キャッシュは適切に活用されているか
- HTTP レスポンスの圧縮は有効か
- JVM のヒープサイズは適切か
- GC アルゴリズムは適切か(G1GC / ZGC)
18. まとめ
18.1 Spring Boot の全体像
| カテゴリ | 主要技術 | 章 |
|---|---|---|
| 基盤 | DI/IoC、AOP、Bean管理 | 第2〜3章 |
| 自動構成 | Auto-Configuration、Starter | 第4〜5章 |
| 設定管理 | Properties、Profiles、外部設定 | 第6章 |
| Web 開発 | Spring MVC、WebFlux | 第7章 |
| データアクセス | JPA、JDBC、トランザクション | 第8章 |
| セキュリティ | Spring Security、JWT、OAuth2 | 第9章 |
| テスト | JUnit、MockMvc、Testcontainers | 第10章 |
| 運用監視 | Actuator、Micrometer | 第11章 |
| メッセージング | Kafka、RabbitMQ | 第12章 |
| 性能最適化 | キャッシュ、非同期、接続プール | 第13章 |
| マイクロサービス | Spring Cloud、サーキットブレーカー | 第14章 |
| コンテナ化 | Docker、Kubernetes | 第15章 |
| ネイティブ化 | GraalVM Native Image | 第16章 |
18.2 Spring Boot の進化の方向性
- Virtual Threads(Project Loom): Java 21 の Virtual Threads により、同期コードで非同期並みのスケーラビリティを実現
- GraalVM Native Image: AOT コンパイルによる起動時間の短縮とメモリ削減
- Observability: OpenTelemetry との統合強化
- Spring AI: AI/LLM との統合フレームワーク
- CRaC(Coordinated Restore at Checkpoint): チェックポイント/リストアによる起動高速化
18.3 学習リソース
| リソース | URL |
|---|---|
| Spring Boot 公式ドキュメント | https://docs.spring.io/spring-boot/docs/current/reference/html/ |
| Spring Initializr | https://start.spring.io/ |
| Spring Guides | https://spring.io/guides |
| Baeldung (チュートリアル) | https://www.baeldung.com/ |
| Spring Boot GitHub | https://github.com/spring-projects/spring-boot |
本書は Spring Boot 3.x / Java 21 をベースとして執筆されている。Spring Boot のバージョンにより、一部の機能やAPIが異なる場合がある。最新の情報は公式ドキュメントを参照のこと。