harness/plugins/zioinfo/skills/zio-harness/references/spring-boot.md
DESKTOP-TKLFCPR\ython 6caf43e1ed feat!: v2.0.0 — 4개 플러그인 zioinfo 단일 통합 + 최신 플러그인 기술 적용
- harness·zio-harness·proposal-builder·zioinfo → plugins/zioinfo (git mv 히스토리 보존)
- 스킬 4·커맨드 3(/zioinfo:pmo·proposal·wiki)·에이전트 15·graphify 훅·knowledge 통합
- 신규: /zioinfo:wiki (graphify LLM wiki — graphify-out/wiki/ 커뮤니티별 아티클)
- 신규: ZIO WISE 테마 (themes/zioinfo.json, experimental)
- manifest 최신화: $schema·displayName(ZIO INFOTECH Suite)·experimental.themes
- marketplace.json 단일 엔트리, 루트 plugin.json 제거
- CLAUDE.md·PROJECT_MAP·docs/plugins.md·README 3종·CHANGELOG·설치가이드 pptx 재구성

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 20:16:36 +09:00

6.8 KiB

Spring Boot 개발 가이드

스택 가정

  • Spring Boot 3.x + Java 17+ / Kotlin
  • Spring Data JPA + Hibernate
  • Spring Security (JWT)
  • Flyway (DB 마이그레이션)
  • Gradle 빌드
  • 데이터베이스: PostgreSQL (기본), MySQL 지원

레이어 아키텍처

Controller (HTTP 진입점)
    ↓ DTO (Request)
Service (비즈니스 로직)
    ↓ Domain/Entity
Repository (데이터 접근)
    ↓
Database

폴더 구조

src/main/java/{base.package}/
├── controller/     # @RestController — HTTP 요청 처리만
├── service/        # @Service — 비즈니스 로직
├── repository/     # @Repository — JPA 인터페이스
├── domain/         # @Entity — JPA 엔티티
├── dto/
│   ├── request/    # {Action}{Resource}Request
│   └── response/   # {Resource}Response
├── config/         # SecurityConfig, CorsConfig, etc.
├── exception/      # 커스텀 예외 + GlobalExceptionHandler
└── util/           # 유틸리티 클래스

컨트롤러 패턴

@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @GetMapping
    public ResponseEntity<List<UserResponse>> getUsers() {
        return ResponseEntity.ok(userService.findAll());
    }

    @GetMapping("/{id}")
    public ResponseEntity<UserResponse> getUser(@PathVariable Long id) {
        return ResponseEntity.ok(userService.findById(id));
    }

    @PostMapping
    public ResponseEntity<UserResponse> createUser(@Valid @RequestBody CreateUserRequest request) {
        return ResponseEntity.status(HttpStatus.CREATED)
            .body(userService.create(request));
    }

    @PutMapping("/{id}")
    public ResponseEntity<UserResponse> updateUser(
            @PathVariable Long id,
            @Valid @RequestBody UpdateUserRequest request) {
        return ResponseEntity.ok(userService.update(id, request));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.delete(id);
        return ResponseEntity.noContent().build();
    }
}

서비스 패턴

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserService {

    private final UserRepository userRepository;

    public List<UserResponse> findAll() {
        return userRepository.findAll().stream()
            .map(UserResponse::from)
            .toList();
    }

    public UserResponse findById(Long id) {
        User user = userRepository.findById(id)
            .orElseThrow(() -> new EntityNotFoundException("User not found: " + id));
        return UserResponse.from(user);
    }

    @Transactional
    public UserResponse create(CreateUserRequest request) {
        User user = User.builder()
            .email(request.getEmail())
            .name(request.getName())
            .build();
        return UserResponse.from(userRepository.save(user));
    }
}

엔티티 패턴

@Entity
@Table(name = "users")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
@AllArgsConstructor
public class User extends BaseEntity {  // createdAt, updatedAt 포함

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String email;

    @Column(nullable = false)
    private String name;
}

// BaseEntity.java
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
    @CreatedDate
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;
}

DTO 패턴

// Request
public record CreateUserRequest(
    @NotBlank @Email String email,
    @NotBlank @Size(min=2, max=50) String name
) {}

// Response — 정적 팩토리 메서드 포함
public record UserResponse(Long id, String email, String name) {
    public static UserResponse from(User user) {
        return new UserResponse(user.getId(), user.getEmail(), user.getName());
    }
}

예외 처리

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(EntityNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(EntityNotFoundException e) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(new ErrorResponse("NOT_FOUND", e.getMessage()));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getFieldErrors().stream()
            .map(f -> f.getField() + ": " + f.getDefaultMessage())
            .collect(Collectors.joining(", "));
        return ResponseEntity.badRequest()
            .body(new ErrorResponse("VALIDATION_ERROR", message));
    }
}

public record ErrorResponse(String code, String message) {}

JWT 보안 설정

# application.yml
spring:
  datasource:
    url: ${DB_URL:jdbc:postgresql://localhost:5432/myapp}
    username: ${DB_USERNAME:postgres}
    password: ${DB_PASSWORD:password}
  jpa:
    hibernate:
      ddl-auto: validate  # Flyway 사용 시 validate
    open-in-view: false

jwt:
  secret: ${JWT_SECRET}
  expiration: 86400000  # 24시간 (ms)

DB 마이그레이션 (Flyway)

src/main/resources/db/migration/
├── V1__create_users_table.sql
├── V2__add_user_role.sql
└── V3__create_posts_table.sql

네이밍: V{version}__{description}.sql (V 대문자, 밑줄 2개)


테스트 패턴

// 서비스 단위 테스트
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @InjectMocks UserService userService;
    @Mock UserRepository userRepository;

    @Test
    void 사용자_생성_성공() {
        // given
        given(userRepository.save(any())).willReturn(testUser());
        // when
        UserResponse response = userService.create(new CreateUserRequest("a@b.com", "Alice"));
        // then
        assertThat(response.email()).isEqualTo("a@b.com");
    }
}

// 통합 테스트
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
    @Autowired MockMvc mockMvc;

    @Test
    void GET_users_returns_list() throws Exception {
        mockMvc.perform(get("/api/v1/users").contentType(APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$").isArray());
    }
}

컨벤션

항목 규칙
엔티티 단수 명사 (User, Post)
컨트롤러 {Resource}Controller
서비스 {Resource}Service
리포지토리 {Resource}Repository
Request DTO {Action}{Resource}Request
Response DTO {Resource}Response
API 경로 /api/v1/{resource} (복수, kebab-case)