이전 포스트에서 -mrc 옵션을 이용해 모델과 마이그레이션, 컨트롤러를 명령어 한 줄로 뚝딱 만들어보았다.
오늘은 그렇게 생성된 마이그레이션 파일의 뼈대를 직접 수정하여 우리가 원하는 컬럼들을 추가하고, 실제 DB에 반영하는 과정을 다루어 보겠다.
이전 포스트
php artisan make:model -mrc 옵션 (Laravel 모델, 마이그레이션, 컨트롤러 역할 정리)
posts 테이블에 추가할 컬럼들
create_posts_table.php
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('message', 300);
$table->timestamps();
});
}
생성한 create_posts_table.php 파일을 이렇게 수정한다.
id와 timestamps는 원래 있었고, 그 사이에 2줄이 추가됐다.
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
constrained() 메서드는 외래 키 제약 조건을 정의하는 마이그레이션에서 사용된다.
이 메서드를 사용하면 외래 키 제약 조건을 정의할 때 편리하게 사용할 수 있다.
위의 코드는 user_id라는 외래 키 컬럼을 생성하고, 이 컬럼이 users 테이블의 id 컬럼을 참조한다는 것을 정의한다.
즉, user_id 컬럼은 users 테이블의 id 컬럼을 참조하는 외래 키이며, 참조 무결성을 위해 자동으로 외래 키 제약 조건이 생성된다.
※ 주의: constrained() 메서드는 데이터베이스 수준에서 외래 키 제약 조건을 설정하는 것일 뿐이므로, 라라벨 PHP 코드에서 데이터 관계를 편리하게 조회하려면 모델(Model) 파일 내부에 hasMany나 belongsTo 같은 관계 정의 코드를 반드시 별도로 명시해 주어야 한다.
constrained() 메서드를 호출할 때 인수를 전달하지 않으면 (괄호 안에 아무것도 쓰지 않으면),
Laravel은 기본적으로 외래 키 이름에서 _id를 뗀 단어를 복수형으로 변환하여 참조할 테이블을 자동으로 유추한다. (예: user_id ➔ users 테이블)
따라서 테이블 이름이 규칙에 맞다면 명시적으로 테이블 이름을 지정하는 것은 선택 사항이다.
만약 constrained('users')와 같이 명시적으로 지정하면 코드를 더 명확하게 만들어 가독성을 높이는 데 도움이 될 수 있다. (하지만 여기서는 생략했다.)
거기에 더해 cascadeOnDelete() 메서드도 추가했다.
cascadeOnDelete를 사용하지 않으면 부모인 user 레코드를 삭제할 때, 자식 레코드(예: 해당 유저가 쓴 글)가 존재한다면 데이터베이스 무결성이 깨지므로 SQLSTATE[23000] 에러가 발생한다.
SQLSTATE[23000] 에러는 데이터베이스에서 무결성 제약 조건을 위반했을 때 발생하는 일반적인 SQL 예외 코드다.
cascadeOnDelete()를 사용하여 외래 키 제약 조건을 설정하면, 해당 외래 키가 참조하는 행(부모)이 삭제될 때 연관된 행(자식)도 함께 삭제된다.
이는 데이터베이스에서 "CASCADE" 제약 조건과 동등한 효과를 가진다.
예를 들어, users 테이블에서 어떤 회원이 탈퇴하면(계정이 삭제되면), 해당 회원의 user_id를 참조하는 posts 테이블의 글들도 함께 삭제된다.
무슨 말이냐면, A라는 사용자가 10개의 글을 남겼는데, A라는 사용자의 계정이 삭제되면 그 계정으로 남겼던 10개의 글도 자동으로 삭제가 된다는 말이다.
따라서 이 메서드를 사용하면 연관된 데이터를 자동으로 청소할 수 있어서 데이터 참조 무결성을 유지하기가 훨씬 쉬워진다.
$table->string('message', 300);
메시지의 내용을 저장하는 문자열 컬럼이다.
여기서는 최대 300자까지 저장할 수 있게 설정해 놨다.
$table->string(...)은 데이터베이스에서 가변 길이 문자열을 저장하는 VARCHAR 타입을 생성하는 코드다.
글의 제목, 이메일 주소, 댓글과 같은 짧은 메시지, 비밀번호 같은 일반적인 글자(문자열)를 저장할 때 사용한다.
이 칸에 저장할 수 있는 문자의 최대 길이를 300자로 제한하도록 설정했는데, 이렇게 항상 글자 수 제한을 설정해 두는 것이 좋다.
제한을 두는 이유에는 다음과 같은 것들이 있다.
- 악의적인 해커가 매크로 프로그램을 돌려서 댓글 한 칸에 수십억 개의 글자(몇 GB 용량의 텍스트)를 강제로 밀어 넣는 공격을 할 수 있다. 그러면 순식간에 하드디스크 용량이 가득 차서 서버와 데이터베이스가 뻗어버리게 된다.
string('message', 300)처럼 락을 걸어두면 이런 무식한 공격을 데이터베이스 입구에서 컷할 수 있다. - 데이터베이스는 데이터를 정렬하거나 계산할 때 메모리(RAM)를 사용한다. 글자 수 제한이 있을 경우에는 데이터베이스가 "이 칸은 아무리 커봤자 300자구나"라고 미리 계산하고 사이즈를 예측하여 메모리를 딱 필요한 만큼만 영리하게 나누어 쓴다.
- 휴대폰 번호나 우편번호처럼 글자 수 제한이 명확한 컬럼에 제한을 두면, 엉뚱하게 수백 자짜리 쓰레기 데이터가 실수로 흘러 들어와 데이터베이스가 지저분해지는 것을 원천 봉쇄할 수 있다.
- 웹사이트를 만들 때 댓글 공간을 만들어 두었는데, 어떤 사용자가 제한이 없다고 해서 5,000자짜리 댓글을 적어버리면 웹사이트 레이아웃이 엉망진창이 된다. 데이터베이스에서 글자 수를 딱 잡아주면 화면을 설계할 때 디자인 가이드라인을 지키기가 훨씬 수월해진다.
나중에 긴 장문의 글을 저장하는 게시판이나 블로그를 만들 때는 string 대신 text라는 메서드를 사용해야 한다.
make app #컨테이너 안으로 들어간다.
php artisan migrate
VSCode의 터미널에서 이 명령어들을 실행한다.
그러면 데이터베이스에 posts 테이블이 생성된다.
캡처 이미지에서는 컨테이너 안으로 들어갈 때, make app이 아닌 make bash 명령어를 입력했는데, 어느 쪽이든 상관없다.
php artisan migrate 명령어는 프로젝트 폴더(src/) 안에 파일을 새로 만드는 명령어가 아니기 때문이다.
VSCode를 DB 툴처럼! SQLTools 익스텐션 설치 및 도커 연결 가이드
캡처 이미지 속의 VSCode 익스텐션은 이 포스트에서 소개했었다.
users 테이블의 마이그레이션 파일도 살펴보자
users 테이블을 생성한 마이그레이션 파일에서도 몇 개의 코드들을 살펴보겠다.
->unique();
해당 컬럼에 똑같은 값이 중복해서 들어오는 것을 데이터베이스 레벨에서 원천 봉쇄한다.
회원의 아이디나 이메일 주소, 주민등록번호, 폰 번호 등의 컬럼에 사용한다.
->nullable();
기본적으로 라라벨의 모든 컬럼은 값을 무조건 채워야 하는 '필수 입력' 상태다.
하지만 이 메서드를 붙이면 값을 넣지 않고 비워두어도(NULL) 데이터베이스가 에러를 내지 않고 통과시켜 준다.
회원가입 시 필수 입력 사항이 아닌 '프로필 한 줄 소개' 등이나 글을 작성할 때 첨부할 이미지 등의 컬럼에 사용한다.
->index();
데이터베이스가 해당 컬럼을 기준으로 데이터를 빛의 속도로 찾아낼 수 있도록 뒤편에 '색인(Index) 주소록'을 생성하라는 명령이다.
WHERE 조건문이나 검색창에서 매일같이 단골로 검색 조건에 들어가는 컬럼(예: 글 카테고리, 작성일자, 상태 값 등)에 사용한다.
그 외 마이그레이션 관련 명령어
위에서 사용한 php artisan migrate 이외의 명령어들도 몇 개 소개하고 글을 마치겠다.
마이그레이션 파일의 내부에는 up 메서드와 down 메서드가 있다.
up은 실행(테이블 작성)이고, down은 php artisan migrate를 실행하기 전의 상태로 되돌린다.
즉, 만들었던 테이블을 삭제하는 것이다.
php artisan migrate:rollback
이 명령어를 실행하면 down 메서드가 실행되면서 원래대로 돌아온다.
연속해서 사용하면 방금 전에 실행한 것뿐만 아니라 그전에 php artisan migrate를 실행했던 것도 취소시킬 수 있다.
php artisan migrate:fresh
이 명령어는 현재 데이터베이스에 있는 모든 테이블을 묻지도 따지지도 않고 통째로 완전히 삭제해 버린 뒤, database/migrations/ 폴더에 있는 설계도들을 처음부터 다시 새롭게 실행(migrate)한다.
그러니 개발 중에만 사용해야 할 명령어다.
실제로 배포해서 운영 중인 웹사이트에서 실행해서는 안된다.
마이그레이션 파일 내부 코드가 어떻게 짜여 있든 상관없이 데이터베이스 전체를 깨끗하게 비워버리기 때문에, 외래 키 제약 조건 등으로 인해 꼬여있던 에러들이 무조건 100% 한 방에 해결된다.
개발용 가짜 데이터를 넣는 시딩 명령어인 --seed 옵션을 붙여서 php artisan migrate:fresh --seed 형태로 자주 쓴다.
라라벨의 마이그레이션은 단순히 데이터베이스 테이블을 만드는 것을 넘어, 팀원들과 데이터베이스 구조를 코드로 공유하고 안전하게 형상 관리를 할 수 있도록 돕는 아주 강력한 도구다.
오늘 알아본 각종 속성(unique, nullable, index)과 rollback, fresh 명령어들의 주의사항을 잘 숙지해 두시면, 개발 도중 데이터 구조가 바뀌더라도 유연하고 안전하게 대처할 수 있을 것이다.
데이터베이스 설계도가 깔끔하게 준비되었으니, 다음 포스트에서는 이 테이블에 데이터를 채우고 가공하는 컨트롤러Controller의 실제 로직 작성법을 다뤄보겠다.
다음 포스트
0 Comments
댓글 쓰기
🔸 댓글은 블로그 운영자의 승인 후에 블로그에 표시됩니다.
🔸 비로그인 방문자 분께서는 '익명'보다 이름/URL로 댓글을 남겨주시면 감사하겠습니다. (URL은 생략 가능합니다.)
🔸 구글 로그인 방문자는 '알림 사용'에 체크를 하시면, 남겨주신 댓글에 대한 답글 알림을 메일로 받아볼 수 있습니다. 📩