Java / Spring 컨벤션
1. URL
1-1. 공통 규칙
- 소문자를 사용합니다.
- 가급적 명사를 사용합니다.
- 언더바(
_) 대신 하이픈(-) 사용
1-2. Restful API
- URL은 정보의 자원을 표현합니다.
- 자원에 대한 행위는 가능하면 HTTP Method(GET, POST, PUT, DELETE)로 표현합니다.
- 그 외의 액션에 대해서는
_를 붙여 사용합니다.
| 액션 | 설명 |
|---|---|
/_count | count 조회 |
_excel | excel 다운로드 |
_check | 검증. 확인. |
URL 규칙 /api로 시작:
@RequestMapping("/api")
public class StaffResource {
@GetMapping("/user")
public ResponseEntity<User> getUser() {
...
}
}1-3. Controller
URL 규칙 /page로 시작합니다:
@RequestMapping("/page/counsel")
public class CounselController {
@GetMapping({ "", "/" })
...
}CRUD 예시 (REST API)
행위는 HTTP 메서드로 표현합니다. URL 에 add / modify / remove 를 넣지 않습니다.
@GetMapping("/board") // 목록 조회
@GetMapping("/board/{id}") // 상세 조회
@PostMapping("/board") // 등록
@PutMapping("/board/{id}") // 수정
@DeleteMapping("/board/{id}") // 삭제HTTP 메서드로 표현되지 않는 부가 동작만 _ 접두사를 붙입니다.
@GetMapping("/board/_count") // 건수 조회
@GetMapping("/board/_empty") // 빈 객체(등록 폼 초기값)
@GetMapping("/board/_download_task") // 엑셀 다운로드 예약엑셀 다운로드는 produces 로 구분합니다
목록과 같은 URL 을 쓰고 응답 타입만 다르게 합니다 — 검색 조건이 완전히 동일하기 때문입니다.
@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/delivery2. Method
2-1. Service Method
service method에 도메인명을 포함합니다:
boardService.saveBoard();
boardService.savePost();
boardService.saveComment();2-2. Repository Method
repository method에는 도메인명을 생략합니다:
boardRepository.save();
postRepository.save();
commentRepository.save();3. 레이어 경계
각 레이어가 무엇을 소유하는지가 이 프로젝트에서 가장 중요한 규칙입니다.
| 레이어 | 하는 일 | 하지 않는 일 |
|---|---|---|
| Resource | 요청 수신, 권한 체크, 응답 조립 | 업무 로직, SQL |
| Service | 업무 로직, 트랜잭션, 어떤 조건·정렬을 쓸지 결정 | SQL 조립 |
| Repository | SQL 작성, 서버 강제조건(불변식) 소유 | 업무 판단 |
서비스는 SQL 을 조립하지 않습니다
// ❌ 서비스에서 술어(predicate) 조립 — repository 의 일입니다
QueryBuilder.of(q, schema, dsl -> dsl.and(col, isEqualTo(v)), t);
// ✅ 조건값·정렬·페이지를 골라 넘기는 것은 서비스의 본래 일
repository.listBoardProvider(q, Pagination.sortModel.get());컬럼을 "가리키는 것" 은 정당합니다
SQL 을 조립하는 것과 컬럼을 참조하는 것은 다릅니다. 정렬 키를 지정할 때는 문자열이 아니라 Dsl 로 참조하세요 — 컬럼이 바뀌면 컴파일 에러로 잡힙니다.
BoardDsl b = BoardDsl.defaultAlias();
new SortModel().setSortBy(b.desc(b.createdDt)); // ✅
new SortBy("createdDt", SortDirection.DESC); // ❌ 리네임 시 조용히 폴백서버 강제조건은 Repository 가 소유합니다
"이 엔드포인트가 무엇인가" 를 정의하는 조건(공유 테이블의 type, soft-delete deleted=false, 테넌시)은 검색 조건이 아니라 불변식입니다.
// AdminRepository — private 이라 서비스가 우회할 수 없습니다
private QueryBuilder adminQuery(NvQuery q, AccountDsl u) {
return searchQuery(q, dsl -> dsl.and(u.type, isEqualTo(AccountType.ADMIN)), u);
}판별 기준: 안전하려면 모든 호출부에서 조건을 넣어줘야 한다면, 그건 불변식입니다. 호출부가 하나라도 빠뜨리면 데이터가 새기 때문입니다.
자세한 내용 → 목록과 페이징
4. 문자열 SQL 금지
"... WHERE ${whereClause} ORDER BY ${sortBy}" // ❌ 프로젝트 전체 금지${} 문자열 치환은 값이 SQL 에 그대로 박혀 인젝션 경로가 됩니다. 테스트(DollarTokenRatchetTest)가 mapper XML 에 이런 토큰이 들어오는 것을 자동으로 막습니다.
동적 조건은 provider 의 applyWhere, 정렬은 SortSpecification[] 으로 작성합니다 → MyBatis Dynamic SQL