Repository 인터페이스만 만들었는데 REST API가 자동으로 생성된다면, Controller를 직접 작성하는 것과 비교해서 어떤 장단점이 있을까요?

Spring Data REST란

Spring Data REST는 Spring Data Repository를 분석하여 HATEOAS 기반의 RESTful API를 자동으로 생성하는 프로젝트입니다. Controller를 작성하지 않아도 CRUD 엔드포인트가 만들어집니다.

PLAINTEXT
JpaRepository<User, Long> → 자동 생성:
  GET    /users          — 전체 조회 (페이징)
  GET    /users/{id}     — 단건 조회
  POST   /users          — 생성
  PUT    /users/{id}     — 전체 수정
  PATCH  /users/{id}     — 부분 수정
  DELETE /users/{id}     — 삭제

기본 설정

JAVA
// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
YAML
# application.yml
spring:
  data:
    rest:
      base-path: /api           # 기본 경로 설정
      default-page-size: 20     # 기본 페이지 크기
      max-page-size: 100        # 최대 페이지 크기
      return-body-on-create: true
      return-body-on-update: true

엔티티와 Repository 정의

JAVA
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Article {

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

    private String title;

    @Lob
    private String content;

    private String author;

상태값은 Enum으로 관리하고, @ManyToOne으로 카테고리 연관관계를 설정합니다.

JAVA
    @Enumerated(EnumType.STRING)
    private ArticleStatus status;

    @CreatedDate
    private LocalDateTime createdAt;

    @ManyToOne(fetch = FetchType.LAZY)
    private Category category;
}

@RepositoryRestResource로 컬렉션 이름과 URL 경로를 지정하면 CRUD 엔드포인트가 자동 생성됩니다.

JAVA
@RepositoryRestResource(
    collectionResourceRel = "articles",  // JSON에서의 컬렉션 이름
    path = "articles"                     // URL 경로
)
public interface ArticleRepository
        extends JpaRepository<Article, Long> {

    // 자동으로 /api/articles/search/findByAuthor 엔드포인트 생성
    List<Article> findByAuthor(@Param("author") String author);

    // 페이징 지원
    Page<Article> findByStatus(
        @Param("status") ArticleStatus status, Pageable pageable);

    // 이 메서드는 API로 노출하지 않음
    @RestResource(exported = false)
    void deleteByStatus(ArticleStatus status);
}

HATEOAS 응답 형태

API 응답에는 하이퍼미디어 링크가 포함됩니다. 클라이언트가 다음에 어떤 행동을 할 수 있는지 응답 자체가 알려주는 방식입니다.

JSON
// GET /api/articles/1
{
  "title": "Spring Data REST 가이드",
  "content": "...",
  "author": "김개발",
  "status": "PUBLISHED",
  "createdAt": "2026-03-19T10:00:00",
  "_links": {
    "self": {
      "href": "http://localhost:8080/api/articles/1"
    },
    "article": {
      "href": "http://localhost:8080/api/articles/1"
    },
    "category": {
      "href": "http://localhost:8080/api/articles/1/category"
    }
  }
}
JSON
// GET /api/articles (컬렉션)
{
  "_embedded": {
    "articles": [ ... ]
  },
  "_links": {
    "self": { "href": "/api/articles" },
    "search": { "href": "/api/articles/search" }
  },
  "page": {
    "size": 20,
    "totalElements": 42,
    "totalPages": 3,
    "number": 0
  }
}

프로젝션 — 필드 선택적 노출

엔티티의 모든 필드를 노출하는 것이 아니라, 용도에 맞게 필드를 선택해서 반환할 수 있습니다.

JAVA
// 목록용 프로젝션 — 요약 정보만
@Projection(
    name = "summary",
    types = { Article.class }
)
public interface ArticleSummary {
    String getTitle();
    String getAuthor();
    LocalDateTime getCreatedAt();

    // SpEL로 가공된 데이터 반환
    @Value("#{target.title + ' by ' + target.author}")
    String getDisplayName();
}
PLAINTEXT
GET /api/articles?projection=summary

Excerpt Projection — 기본 프로젝션 설정

컬렉션 조회 시 항상 특정 프로젝션이 적용되도록 설정할 수 있습니다.

JAVA
@RepositoryRestResource(
    excerptProjection = ArticleSummary.class  // 목록 조회 시 기본 적용
)
public interface ArticleRepository
        extends JpaRepository<Article, Long> {
}

이벤트 핸들링

엔티티가 생성, 수정, 삭제될 때 비즈니스 로직을 추가할 수 있습니다.

JAVA
@Component
@RepositoryEventHandler
public class ArticleEventHandler {

    // 생성 전
    @HandleBeforeCreate
    public void handleBeforeCreate(Article article) {
        // 유효성 검증이나 기본값 설정
        if (article.getStatus() == null) {
            article.setStatus(ArticleStatus.DRAFT);
        }
    }

    // 생성 후
    @HandleAfterCreate
    public void handleAfterCreate(Article article) {
        log.info("새 글 생성: {}", article.getTitle());
        // 알림 발송, 검색 인덱스 업데이트 등
    }

저장(수정) 전과 삭제 전 이벤트에서 권한 검증이나 비즈니스 규칙을 추가할 수 있습니다.

JAVA
    // 저장(수정) 전
    @HandleBeforeSave
    public void handleBeforeSave(Article article) {
        // 수정 시간 업데이트, 권한 검증 등
    }

    // 삭제 전
    @HandleBeforeDelete
    public void handleBeforeDelete(Article article) {
        if (article.getStatus() == ArticleStatus.PUBLISHED) {
            throw new IllegalStateException("발행된 글은 삭제할 수 없습니다");
        }
    }
}

보안 연동

Spring Security와 연동하여 API 접근을 제어할 수 있습니다.

메서드 레벨 보안

JAVA
@RepositoryRestResource
public interface ArticleRepository
        extends JpaRepository<Article, Long> {

    @Override
    @PreAuthorize("hasRole('ADMIN')")
    void deleteById(Long id);

    @Override
    @PreAuthorize("hasRole('EDITOR')")
    <S extends Article> S save(S entity);

    // 누구나 조회 가능 (기본값)
    @Override
    Optional<Article> findById(Long id);
}

SecurityConfiguration에서 경로 보안

JAVA
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http)
            throws Exception {
        return http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers(HttpMethod.GET, "/api/**").permitAll()
                .requestMatchers(HttpMethod.POST, "/api/**")
                    .hasRole("EDITOR")
                .requestMatchers(HttpMethod.DELETE, "/api/**")
                    .hasRole("ADMIN")
            )
            .build();
    }
}

커스터마이징

Validator 연동

JAVA
@Configuration
public class RestValidationConfig
        implements RepositoryRestConfigurer {

    @Autowired
    private Validator validator;

    @Override
    public void configureValidatingRepositoryEventListener(
            ValidatingRepositoryEventListener listener) {
        listener.addValidator("beforeCreate", validator);
        listener.addValidator("beforeSave", validator);
    }
}

특정 필드 노출/숨김 제어

JAVA
@Configuration
public class RestConfig implements RepositoryRestConfigurer {

    @Override
    public void configureRepositoryRestConfiguration(
            RepositoryRestConfiguration config, CorsRegistry cors) {
        // ID 필드를 응답에 노출
        config.exposeIdsFor(Article.class, Category.class);

        // CORS 설정
        cors.addMapping("/api/**")
            .allowedOrigins("http://localhost:3000")
            .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE");
    }
}

커스텀 Controller와의 공존

Spring Data REST가 생성하는 API로 부족할 때, 특정 엔드포인트만 직접 Controller로 작성할 수 있습니다.

JAVA
@RepositoryRestController  // 주의: @RestController가 아님
@RequestMapping("/api/articles")
@RequiredArgsConstructor
public class ArticleController {

    private final ArticleRepository articleRepository;
    private final ArticleService articleService;

    // 커스텀 엔드포인트 — 기존 자동 생성 엔드포인트를 오버라이드
    @PostMapping("/{id}/publish")
    public ResponseEntity<?> publish(@PathVariable Long id) {
        Article article = articleService.publish(id);
        EntityModel<Article> model = EntityModel.of(article);
        return ResponseEntity.ok(model);
    }
}

@RepositoryRestController를 사용하면 Spring Data REST의 base-path 설정이 적용됩니다.

장단점 정리

장점

  • ** 빠른 개발 **: Repository만으로 완전한 CRUD API 생성
  • **HATEOAS 준수 **: 표준적인 하이퍼미디어 응답
  • ** 페이징/정렬 자동 지원 **: 쿼리 파라미터로 바로 사용
  • ** 검색 엔드포인트 자동 생성 **: Repository 메서드가 곧 API

단점

  • ** 복잡한 비즈니스 로직 **: 이벤트 핸들러로는 한계가 있음
  • ** 응답 형태 제어 어려움 **: HATEOAS 구조가 고정적
  • ** 학습 곡선 **: 커스터마이징할수록 일반 Controller보다 복잡해질 수 있음
  • ** 프론트엔드 호환 **: 일부 프론트엔드 프레임워크는 HATEOAS 응답 파싱이 불편

언제 사용할까

상황Spring Data REST일반 Controller
관리 도구(Admin) API적합과한 편
데이터 중심 CRUD적합가능
복잡한 비즈니스 로직부적합적합
프론트엔드 맞춤 API부적합적합

주의할 점

1. 엔티티가 그대로 노출되어 민감한 필드가 외부에 공개될 수 있다

Spring Data REST는 기본적으로 엔티티의 모든 필드를 응답에 포함합니다. 비밀번호, 내부 상태 값 같은 민감한 정보가 의도치 않게 노출될 수 있습니다. @JsonIgnore로 필드를 숨기거나, Projection을 적극 활용하여 공개할 필드를 명시적으로 제어해야 합니다.

2. @RepositoryRestController@RestController를 혼동하면 base-path가 적용되지 않는다

커스텀 엔드포인트를 추가할 때 @RestController를 사용하면 spring.data.rest.base-path 설정이 적용되지 않아 URL 경로가 일관되지 않습니다. Spring Data REST의 경로 체계와 통합하려면 반드시 @RepositoryRestController를 사용해야 합니다.

3. 복잡한 비즈니스 로직을 이벤트 핸들러에 넣으면 유지보수가 어려워진다

@HandleBeforeCreate, @HandleBeforeSave 같은 이벤트 핸들러에 검증, 연관 데이터 갱신, 외부 API 호출 등을 넣으면 로직이 분산되어 디버깅과 테스트가 어려워집니다. 단순 CRUD를 넘어서는 비즈니스 로직이 필요하다면 별도 Controller를 작성하는 것이 더 나은 선택입니다.

정리

  • Spring Data REST 는 Repository에서 HATEOAS 기반 REST API를 자동으로 생성합니다.
  • 프로젝션 으로 응답 필드를 제어하고, 이벤트 핸들러 로 비즈니스 로직을 추가합니다.
  • 보안 연동 은 메서드 레벨 @PreAuthorize나 SecurityFilterChain으로 처리합니다.
  • 데이터 중심의 CRUD API에 적합하며, 복잡한 비즈니스 로직에는 일반 Controller를 병행하세요.
  • @RepositoryRestController로 기존 자동 생성 엔드포인트를 오버라이드할 수 있습니다.
댓글 로딩 중...