Skip to content

Java / Spring 컨벤션

1. URL

1-1. 공통 규칙

  • 소문자를 사용합니다.
  • 가급적 명사를 사용합니다.
  • 언더바(_) 대신 하이픈(-) 사용

1-2. Restful API

  • URL은 정보의 자원을 표현합니다.
  • 자원에 대한 행위는 가능하면 HTTP Method(GET, POST, PUT, DELETE)로 표현합니다.
  • 그 외의 액션에 대해서는 _를 붙여 사용합니다.
액션설명
/_countcount 조회
_excelexcel 다운로드
_check검증. 확인.

URL 규칙 /api로 시작:

java
@RequestMapping("/api")
public class StaffResource {
    
  @GetMapping("/user")
  public ResponseEntity<User> getUser() {
    ...
  }
  
}

1-3. Controller

URL 규칙 /page로 시작합니다:

java
@RequestMapping("/page/counsel")
public class CounselController {
  @GetMapping({ "", "/" })
  ...
}

CRUD 예시 (REST API)

행위는 HTTP 메서드로 표현합니다. URL 에 add / modify / remove 를 넣지 않습니다.

java
@GetMapping("/board")               // 목록 조회
@GetMapping("/board/{id}")          // 상세 조회
@PostMapping("/board")              // 등록
@PutMapping("/board/{id}")          // 수정
@DeleteMapping("/board/{id}")       // 삭제

HTTP 메서드로 표현되지 않는 부가 동작만 _ 접두사를 붙입니다.

java
@GetMapping("/board/_count")          // 건수 조회
@GetMapping("/board/_empty")          // 빈 객체(등록 폼 초기값)
@GetMapping("/board/_download_task")  // 엑셀 다운로드 예약

엑셀 다운로드는 produces 로 구분합니다

목록과 같은 URL 을 쓰고 응답 타입만 다르게 합니다 — 검색 조건이 완전히 동일하기 때문입니다.

java
@GetMapping(value = "/board", produces = MediaType.APPLICATION_JSON_VALUE)      // 목록
@GetMapping(value = "/board", produces = {"application/vnd.ms-excel"})          // 엑셀

팝업 URL 예시 (/pop/)

/counsel/pop/{팝업명}

1-4. SPA Router

URL은 디렉토리 구조에 따른다:

/product/goods
/product/delivery

2. Method

2-1. Service Method

service method에 도메인명을 포함합니다:

java
boardService.saveBoard(); 
boardService.savePost();
boardService.saveComment();

2-2. Repository Method

repository method에는 도메인명을 생략합니다:

java
boardRepository.save();
postRepository.save();
commentRepository.save();

3. 레이어 경계

각 레이어가 무엇을 소유하는지가 이 프로젝트에서 가장 중요한 규칙입니다.

레이어하는 일하지 않는 일
Resource요청 수신, 권한 체크, 응답 조립업무 로직, SQL
Service업무 로직, 트랜잭션, 어떤 조건·정렬을 쓸지 결정SQL 조립
RepositorySQL 작성, 서버 강제조건(불변식) 소유업무 판단

서비스는 SQL 을 조립하지 않습니다

java
// ❌ 서비스에서 술어(predicate) 조립 — repository 의 일입니다
QueryBuilder.of(q, schema, dsl -> dsl.and(col, isEqualTo(v)), t);

// ✅ 조건값·정렬·페이지를 골라 넘기는 것은 서비스의 본래 일
repository.listBoardProvider(q, Pagination.sortModel.get());

컬럼을 "가리키는 것" 은 정당합니다

SQL 을 조립하는 것과 컬럼을 참조하는 것은 다릅니다. 정렬 키를 지정할 때는 문자열이 아니라 Dsl 로 참조하세요 — 컬럼이 바뀌면 컴파일 에러로 잡힙니다.

java
BoardDsl b = BoardDsl.defaultAlias();
new SortModel().setSortBy(b.desc(b.createdDt));    // ✅
new SortBy("createdDt", SortDirection.DESC);       // ❌ 리네임 시 조용히 폴백

서버 강제조건은 Repository 가 소유합니다

"이 엔드포인트가 무엇인가" 를 정의하는 조건(공유 테이블의 type, soft-delete deleted=false, 테넌시)은 검색 조건이 아니라 불변식입니다.

java
// AdminRepository — private 이라 서비스가 우회할 수 없습니다
private QueryBuilder adminQuery(NvQuery q, AccountDsl u) {
    return searchQuery(q, dsl -> dsl.and(u.type, isEqualTo(AccountType.ADMIN)), u);
}

판별 기준: 안전하려면 모든 호출부에서 조건을 넣어줘야 한다면, 그건 불변식입니다. 호출부가 하나라도 빠뜨리면 데이터가 새기 때문입니다.

자세한 내용 → 목록과 페이징

4. 문자열 SQL 금지

java
"... WHERE ${whereClause} ORDER BY ${sortBy}"    // ❌ 프로젝트 전체 금지

${} 문자열 치환은 값이 SQL 에 그대로 박혀 인젝션 경로가 됩니다. 테스트(DollarTokenRatchetTest)가 mapper XML 에 이런 토큰이 들어오는 것을 자동으로 막습니다.

동적 조건은 provider 의 applyWhere, 정렬은 SortSpecification[] 으로 작성합니다 → MyBatis Dynamic SQL