MySQL 외래키 방향과 검증 3층: 가리키는 쪽이 남의 번호를 든다

MySQL 외래키는 어느 테이블에 넣나. 가리키는 쪽이 상대 번호를 든다. FOREIGN KEY 두 줄 세트 문법과, private getter/setter가 실제로 값을 막지 않는 이유.

조회
목차

게시판 테이블을 설계하고 자바 쪽 도메인 객체를 처음 만들었다. DB 설계와 자바 클래스, 둘 다 처음이었다.

외래키를 반대 테이블에 넣었다. 세 번이나

boardcategory를 가리키는 관계인데 categorycategory_id를 넣었다. 자기가 자기 번호를 또 드는 꼴이었다.

데이터로 그려보니 바로 보였다.

category                  board
 id | name                 id | category_id | title
 1  | JAVA                 1  |      1      | 서블릿이란
 2  | Database             2  |      1      | JDBC
                           3  |      2      | MySQL 설치

가리키는 쪽이 상대의 번호를 적어둔다. category에는 category_id가 없다. 자기 번호는 이미 id니까.

외우는 규칙: 그 테이블에 _id로 끝나는 컬럼이 있으면 그 컬럼마다 외래키 선언이 하나씩 필요하다. 없으면 외래키 선언도 없다.

그리고 외래키는 두 줄 세트다.

category_id BIGINT NOT NULL,                       -- 컬럼
FOREIGN KEY (category_id) REFERENCES category(id)  -- 설명

설명만 쓰고 컬럼을 안 만들어서 계속 에러가 났다.

VARCHAR(3-5)처럼 범위를 쓰려고 했다

명세가 "3자 이상 5자 미만"이라 그대로 적었다. VARCHAR최대 길이 하나만 받는다. 그리고 "5 미만"이니 최대는 4다.

최솟값은 DB가 안 막아준다. 코드에서 해야 한다.

프론트 검증이 무슨 의미인지 의심했다

자바 쪽 setter가 아무 검사 없이 값을 통과시키는 걸 보고 "그럼 프론트에서 검증하는 게 무슨 의미냐"고 물었다. 이 의심이 맞았다.

프론트 검증은 방어가 아니다. 사용자에게 빨리 알려주는 친절함이다. 프론트 코드는 사용자 손에 있어서 신뢰할 수 없다 — curl로 직접 보내면 통째로 건너뛴다.

층이 셋이고 목적이 다르다.

프론트    빠른 안내       (건너뛸 수 있음)
서버      실제 방어       ← 진짜는 여기
DB        마지막 그물     (NOT NULL, 길이 제한)

private이 왜 필요한지 납득이 안 갔다

getter와 setter를 다 열어둘 거면 private이 무슨 의미냐고 물었다. setter가 아무 검사도 안 하는데 직접 접근을 막는 게 소용이 있냐고도.

지금은 소용없다는 게 맞는 답이었다.

setter가 그냥 대입만 하니까 private을 붙여도 실질 차이가 0이다. 두 길이든 한 길이든 같은 값이 들어간다.

private이 하는 일은 값을 막는 게 아니라 **"나중에 검사를 넣을 자리를 한 곳으로 보장하는 것"**뿐이다. 검사가 없으면 보장할 게 없어서 아무 일도 안 한다.

실제로 값을 막는 건 서버 진입점 검증과 DB 제약이고, private은 거기 안 낀다.

이 비판은 나만 하는 게 아니어서 자바에 record가 생겼다. record는 getter를 자동으로 만들고 setter는 없다. "만든 뒤엔 안 바뀐다"를 기본으로 삼은 거다.

getter/setter 노가다에서 사고가 났다

필드 8개 × 2 = 16개 메서드를 손으로 썼다. 그 와중에 이런 게 나왔다.

public void setUpdated_at(LocalDateTime created_at) {
    this.created_at = created_at;   // updated_at 에 넣어야 하는데
}

복사해서 붙이고 안 바꾼 거다.

컴파일 에러가 안 난다. 수정일시를 넣으면 등록일시가 바뀐다. 글을 수정할 때마다 등록일시가 덮어써진다. 값이 그럴듯해서 화면만 봐선 못 알아챈다.

에디터 색깔을 안 믿고 DB에 물어봤다

comment가 파란 글씨로 나와서 예약어인 줄 알았는데, MySQL에 직접 물어보니 아니었다.

SELECT WORD, RESERVED FROM INFORMATION_SCHEMA.KEYWORDS WHERE WORD = 'COMMENT';

comments는 MySQL에 아예 없는 단어인데도 에디터는 색을 칠했다. 하이라이터가 자기 나름의 목록을 쓰는 거지 MySQL 기준이 아니다.

남길 것

  • 외래키는 가리키는 쪽이 든다. 컬럼 선언과 외래키 선언은 두 줄 세트.
  • DB 제약은 최댓값만 막는다. 최솟값과 형식은 코드 몫.
  • 프론트 검증은 방어가 아니라 안내다. 서버가 다시 검사해야 한다.
  • private은 값을 막지 않는다. 검사를 넣을 자리를 보장할 뿐이고, 검사가 없으면 아무 일도 안 한다. 관습으로 쓰는 걸 인정하고 쓰는 게 정직하다.
  • 에디터 색은 참고, DB가 판정. 도구의 표시를 사실로 믿으면 안 된다.
  • 손으로 반복하는 코드에서 사고가 난다. 복사·붙여넣기 후 안 바꾼 한 줄이 컴파일도 통과하고 값도 그럴듯해서 제일 늦게 발견된다.

더 봐야 할 것

  • 자동 증가 기본키에 삭제로 구멍이 생기는 게 정말 문제가 안 되는지
  • record를 이 프로젝트에 써보면 어떤지
  • 목록에서 안 채우는 필드가 null로 남는 게 나중에 사고를 내는지

사이트 내 검색

제목·태그·설명·본문으로 발행 글을 찾습니다