게시판 프로젝트를 선택한 이유는 기획 시간을 줄이기 위해서다.
디자인에도 시간을 많이 들이고 싶지 않아서 UI는 개발자 커뮤니티 OKKY를 참고했다. 디자인과 구조가 심플했기 때문이다. 게시판의 주제는 방송대 컴퓨터과학과 오픈 카톡에서 아이디어를 얻었다.
기능 구현은 회원, 게시글, 댓글 도메인을 우선적으로 구현하기로 했다.
휴면 계정 시스템도 넣을까 했었는데, 2023년 9월부터 개인정보보호법의 개정으로 많은 사이트에서 없애고 있는 추세라고 하고, 탈퇴 계정 처리와 차이가 크지 않아 생략했다. 개인정보보호법 개정으로 인한 변화에 대해서는 요즘 IT의 “계정이 휴면 해제될 예정입니다” 메일 오는 이유에서 잘 정리해주었다.
DBMS는 많이 쓰이고 익숙한 MySQL을 선택했다. 개발 과정에서는 인메모리 모드를 지원하는 H2를 사용했는데, MySQL 모드로 설정하면 대부분의 MySQL 문법을 사용할 수 있다.
ID 컬럼의 이름을 id
로 하는 것이 좋은지 tablename_id
로 하는게 좋은지는 의견이 분분하다.
나는 SQL에서는 tablename_id
형태인 것이 JOIN할 때 어느 테이블의 ID인지 헷갈리지 않아 좋은 것 같다고 생각했고, Java에서는 객체를 통해 member.getId
와 같은 형태로 사용되기 때문에 필드 이름으로는 간단하게 id
로 사용하는 것이 좋아보였다.
따라서 DB 컬럼명으로는 tablename_id
, 클래스 필드명으로는 id
로 사용했고 MyBatis의 resultMap
에서 매핑해주었다.
ID 컬럼 타입으로는 INT
혹은 BIGINT
를 사용할 수 있을 것이다. MySQL 문서를 보면 차이는 다음과 같다.
INT
: 4 바이트, 표현 가능한 최대값 2147483647 (약 20억)BIGINT
: 8 바이트, 표현 가능한 최대값 2^63-1INT
도 20억이 넘는 값을 표현할 수 있지만, ID는 변경이 어렵기 때문에 대체로 BIGINT
를 사용하는 것 같다.
unsigned
타입을 사용하지 않는 이유 : [Java] Java에도 Unsigned 타입이 있을까?회원 고유번호(user_no
), 로그인 아이디(login_name
)으로 이루어진 심플한 테이블이다. 회원 관련 테이블의 코어라고 할 수 있다.
이 구조는 회원 가입 및 로그인을 위한 테이블 설계 블로그 글을 참고했다.
아이디는 로그인 외에는 거의 사용하지 않는데, 사용 맥락이 다른 프로필 정보와 묶어다니는 것은 좋지 않다. 아이디를 공개적으로 노출하는 것은 보안상으로도 좋지 않을 것이고, 추후 소셜 로그인 연동 등 인증 정보가 복잡해졌을 때를 대비해서도 별도 테이블로 분리하는 것이 좋다고 생각했다.
애플리케이션에서 자주 사용되는 회원의 프로필 정보를 담은 테이블이다.
학년, 지역, 권한 컬럼의 값을 제한하기 위해서 참조 테이블을 사용할 수 있다. 그런데 각각에 대해 참조 테이블을 만들다보니 ERD가 너무 복잡해져서 핵심 테이블 구조를 파악하기 어려웠다.
제한된 값이라면 참조 테이블 대신 Java의 Enum을 사용하는 것은 어떨까 싶었다.
학년, 지역, 권한 컬럼에 대응하는 Grade
, Region
, Authority
Enum 클래스를 만들었다. MySQL에서 ENUM
컬럼 타입을 제공하지만, 호환성 문제로 사용하지 않았다. 개발 과정에서 사용한 H2에서 MySQL 모드로 설정했음에도 호환이 되지 않았기 때문에, DB에서는 기본 타입(TINYINY
, VARCHAR
)을 사용하고 CHECK
제약조건과 함께 사용했다.
편입여부 컬럼은 Y
, N
두 개의 값만 존재하기 때문에 Java 필드에서는 boolean
값을 사용했고 DB 컬럼에서는 TINYINT
를 사용했다. MySQL에는 boolean 타입이 없고 1 바이트를 가지는 TINYINT
가 가장 작은 정수 타입 컬럼이다(참고).
비밀번호와 비밀번호 변경일을 저장하는 테이블이다.
비밀번호는 원본을 저장하지 않고 단방향 암호화하여 저장한다.
업로드한 프로필 이미지 파일의 원본 이름을 저장하기 위한 테이블이다. 이미지를 다운로드 할 때 원본 이름을 제공하기 위해 만들었다.
회원이 탈퇴하면 withdrawal_user
테이블에 탈퇴한 회원의 user_no
만 저장한다. member_user
데이터가 삭제되고, 연관된 테이블의 데이터도 DELETE CASCADE
된다.
단, 회원이 탈퇴해도 게시물과 댓글은 남도록 했다. member_user
테이블은 회원 관련 테이블과만 물리적으로 연관관계가 있으며, 게시글과 댓글 테이블과는 논리적으로만 연결되어 있다.
회원탈퇴시 사용자에게 탈퇴사유를 입력받고 테이블에 기록한다. 탈퇴일(withdrawal_date
)도 함께 남긴다.
집계가 용이하도록 값은 코드화했고, Java에서 Enum 클래스로 관리했다.
status_code
는 자진 탈퇴인지, 강제 탈퇴인지, 장기 미사용으로 인한 탈퇴인지 구분하는 코드이다. reason_code
는 상세한 탈퇴 사유에 대한 코드이다. 사용자가 자진 탈퇴한 경우 졸업/휴학/편입 등으로 이용 사유가 없어져 탈퇴하는지, 다른 서비스를 이용해서 사용하는 지 등 상세한 사유를 선택하도록 했다. 만약 탈퇴 사유가 '기타'인 경우 reason_text
컬럼에 직접 사유를 남길 수 있다.
게시물 ID, 작성자, 제목, 내용, 생성일, 수정일, 조회수 컬럼을 가지며, 게시글의 카테고리를 구분하기 위한 토픽 컬럼(topic
)이 있다.
Bangcom 게시판은 네 개의 게시판과 그 하위에 토픽이 있는 구조이다.
모든 게시판이 같은 형태이고 레코드가 아주 많지 않기 때문에 하나의 DB 테이블로 통합하기로 했다. 토픽을 구분하기 위한 topic
컬럼을 만들어 주었다. 게시판 구분 컬럼은 중복되기 때문에 토픽 컬럼으로만 게시판을 구분하도록 했다.
그런데 객체지향 애플리케이션에서는 토픽이 게시판을 결정짓는 형태보다는, 게시판 객체가 토픽 객체를 가지는 형태가 편하다. 따라서 게시판을 의미하는 TopicGroup
클래스가 Topic
클래스를 가지도록 했다. 또한 게시판과 토픽은 한정된 값이기 때문에 TopicGroup
, Topic
은 모두 Enum 클래스로 만들었다.
@Getter
@AllArgsConstructor
public enum TopicGroup {
NOTICE("공지사항", new Topic[]{Topic.NOTICE}, "notice"),
INFO("정보", new Topic[]{Topic.MENTOR, Topic.USER}, "info"),
COMMUNITY("커뮤니티", new Topic[]{Topic.CAMPUS, Topic.LIFE, Topic.MARKET}, "community"),
QUESTIONS("Q&A", new Topic[]{Topic.CAREER, Topic.STUDY, Topic.QNA_ETC}, "questions");
private String description;
private Topic[] topics;
private String uri; // 게시판 URI
// 생략
}
자세한 내용은 다른 포스팅에서 정리하겠다.
최대 4000자 까지 허용할 내용 컬럼(content
)은 TEXT
타입으로 설정해주었다. 크기가 크고 자주 사용되지 않는 컬럼은 VARCHAR
보다 TEXT
가 좋다고 한다. 두 타입의 비교는 다음 블로그를 참고했다.
게시글의 작성일, 수정일은 날짜와 시간 정보가 필요하다. MySQL의 DATETIME
, TIMESTAMP
모두 YYYY-MM-DD hh:mm:ss[.fraction]
형식으로 날짜를 저장할 수 있다. ([.fraction]
은 6자리 마이크로초 형식으로, 필요한 경우 추가할 수 있다.)
둘의 주요 차이는 다음과 같다.
TIMESTAMP
는 DB에 값을 저장할 때 UTC로 자동 변환하고, 값을 꺼내줄 때도 현재 타임존으로 자동 변환해준다. (타임존 인식은 커넥션마다 다르다.)DATETIME
의 표현 범위가 더 넓다. DATETIME
은 1000-01-01 00:00:00.000000
~ 9999-12-31 23:59:59.499999'
이고, TIMESTAMP
는 1970-01-01 00:00:01.000000
~ 2038-01-19 03:14:07.499999
이다.글로벌 서비스를 제공한다면 TIMESTAMP
를 사용하는 것이 맞겠다. 그러나 국내 서비스라면 유동적인 타임존 때문에 혼란이 생길 수 있어 DATETIME
을 사용하는 것을 추천하는 사람들이 많았다. Bangcom은 국내 서비스 콘셉트 이기 때문에 DATETIME
을 사용하기로 했다.
DB에서 사용할 타임존을 선택하는 방법은 다음을 참고했다. 나는 애플리케이션 설정인 @PostConstruct
를 사용했는데, 글로벌 서비스를 위해 TIMESTAMP
를 사용할 경우 DB 커넥션마다 다르게 타임존을 설정해야 할 수 있는 URL 파라미터를 사용하는 것이 좋다.
댓글 테이블은 댓글 ID, 게시글 ID, 작성자, 내용, 작성일, 수정일 컬럼을 가지며, 대댓글 구현을 위한 부모 댓글 ID, 그룹, 깊이, 정렬 순서에 대한 컬럼을 추가로 가진다.
게시글에 소속되어있기 때문에 게시글이 삭제되면 ON DELETE CASCADE
되도록 설정했다.
대댓글은 셀프조인을 통해 구현했다. 자세한 내용은 다른 포스팅에서 정리하겠다.