언더독 레볼루션 학습 커리큘럼을 따라갑니다.
이번 커리큘럼의 목표는 node.js 와 mysql을 연동하여 mysql을 node.js에서 컨트롤 할 수 있는 방법에 대해 알아보는 것이 목표이다. 즉 이번 ch7/7.6/learn-sequelize 폴더의 있는 내용을 이해하고 실행시키는 것이 이번 커리큘럼의 목적이다.
앞선 목적을 이루기 위해 MYSQL의 대한 기본적인 개념을 이해하고 이를 시퀄라이즈로 어떻게 응용하는지 수업의 순서를 맞추고 있다.
맨 처음 docker를 이용하여 실습을 하기 위한 mysql 컨테이너를 만들었다. 이유는 로컬에 직접 데이터베이스를 설치하면 다른 프로젝트와의 데이터베이스를 관리하기가 더 까다롭고 포트관리가 터미널 상에서 이루어져야하기 때문에 컨테이너를 활용하여 환경을 구축하였다.
docker-compose를 활용하여 컨테이너를 구축하였고 이와 관련된 코드는 이렇다.
코드version: '3' # 파일 규격 번호
services:
local-db:
platform: linux/x86_64 # 추가된 라인 M1, M2 에서만 추가
image: library/mysql:8.0.23
container_name: local-db
restart: always
command: # 명령어 실행 한글 깨짐 방지
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
ports:
- 3306:3306 # 호스트 : 컨테이너
environment:
MYSQL_ROOT_PASSWORD: root # 초기비밀번호
TZ: Asia/Seoul
volumes:
- ./db/mysql/data:/var/lib/mysql # 디렉토리 마운트 설정를 입력하세요
이후 DBeaver를 활용하여 mysql 워크벤치 대신 사용하기로 했다. mac에서는 제일 유명하고 귀엽기 때문에 선택했다.
현재 이 화면이 시퀄라이즈를 활용하여 실습 내용이다. 이 실습을 진행하고 실행했다면 절반은 이해했다고 볼 수 있을거라 생각한다.
실행 순서는 다음과 같다.
$ npm i express morgan nunjucks sequelize sequelize-cli mysql2
$ npm i -D nodemon
{
"development": {
"username": "root",
"password": "root",
"database": "nodejs",
"host": "127.0.0.1",
"dialect": "mysql"
},
"test": {
"username": "root",
"password": "root",
"database": "test",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": "root",
"database": "production",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
development만 설정하면 됨. 나머지 두개는 테스트환경에서만 신경쓰면 됨.
만들어 둔 docker-compose에 작성한 mysql 설정에 맞춰서 username과 password를 한다음 database를 생성하여 이름을 맞추어 설정하면 됨.
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const nunjucks = require('nunjucks');
const { sequelize } = require('./models'); // Sequelize와 데이터베이스 모델을 불러옵니다.
const indexRouter = require('./routes'); // 루트 라우터를 불러옵니다.
const usersRouter = require('./routes/users'); // 사용자 라우터를 불러옵니다.
const commentsRouter = require('./routes/comments'); // 댓글 라우터를 불러옵니다.
const app = express(); // Express 애플리케이션을 생성합니다.
app.set('port', process.env.PORT || 3008); // 포트 설정. 기본값은 3008입니다.
app.set('view engine', 'html'); // 뷰 엔진으로 Nunjucks를 설정합니다.
nunjucks.configure('views', {
express: app,
watch: true, // Nunjucks 파일의 변경을 실시간으로 감지하여 자동으로 업데이트합니다.
});
sequelize.sync({ force: false }) // Sequelize를 사용하여 데이터베이스와 모델을 동기화합니다.
.then(() => {
console.log('데이터베이스 연결 성공');
})
.catch((err) => {
console.error(err);
});
app.use(morgan('dev')); // HTTP 요청 로그를 기록하는 미들웨어를 사용합니다.
app.use(express.static(path.join(__dirname, 'public'))); // 정적 파일을 제공하기 위한 미들웨어를 설정합니다.
app.use(express.json()); // JSON 데이터를 파싱하기 위한 미들웨어를 사용합니다.
app.use(express.urlencoded({ extended: false })); // URL-encoded 데이터를 파싱하기 위한 미들웨어를 사용합니다.
app.use('/', indexRouter); // 루트 경로에 대한 라우터를 등록합니다.
app.use('/users', usersRouter); // '/users' 경로에 대한 사용자 라우터를 등록합니다.
app.use('/comments', commentsRouter); // '/comments' 경로에 대한 댓글 라우터를 등록합니다.
app.use((req, res, next) => {
const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
error.status = 404;
next(error); // 404 오류를 발생시키고 다음 미들웨어로 전달합니다.
});
app.use((err, req, res, next) => {
res.locals.message = err.message; // 에러 메시지를 렌더링할 수 있도록 로컬 변수에 설정합니다.
res.locals.error = process.env.NODE_ENV !== 'production' ? err : {}; // 개발 환경에서만 에러 상세 정보를 렌더링합니다.
res.status(err.status || 500); // 에러 상태 코드를 설정합니다. 기본값은 500 (서버 오류)입니다.
res.render('error'); // 에러 페이지를 렌더링합니다.
});
app.listen(app.get('port'), () => {
console.log(app.get('port'), '번 포트에서 대기 중'); // 서버가 지정된 포트에서 대기 중입니다.
});
app.js를 위와 같은 형태로 작성하고 route를 설정합니다. route는 추후에 좀더 살펴보아도 되고 여기서 중요한 시퀄라이즈를 통한 데이터베이스 연결이 핵심입니다.
models, view는 해당 예제와 함께 같이 사용하면 되고 sequelize.js를 자세히 살펴봐야한다고 생각하여 주석을 상세히 작성하고 이를 통해 models를 이해하면 될거 같습니다.
// 사용자 이름 눌렀을 때 댓글 로딩
document.querySelectorAll('#user-list tr').forEach((el) => {
el.addEventListener('click', function () {
const id = el.querySelector('td').textContent;
getComment(id); // 사용자의 ID를 사용하여 댓글을 가져오는 함수 호출
});
});
// 사용자 로딩
async function getUser() {
try {
const res = await axios.get('/users'); // 서버로부터 사용자 목록을 가져옵니다.
const users = res.data; // 가져온 사용자 목록을 저장합니다.
console.log(users);
const tbody = document.querySelector('#user-list tbody'); // 사용자 목록을 표시할 테이블의 tbody 요소를 찾습니다.
tbody.innerHTML = ''; // tbody를 비웁니다.
users.map(function (user) {
const row = document.createElement('tr'); // 사용자 정보를 표시할 새로운 행을 생성합니다.
row.addEventListener('click', () => {
getComment(user.id); // 사용자 이름을 클릭하면 해당 사용자의 댓글을 가져오는 함수를 호출합니다.
});
// 로우 셀 추가
let td = document.createElement('td');
td.textContent = user.id; // 사용자 ID를 표시합니다.
row.appendChild(td);
td = document.createElement('td');
td.textContent = user.name; // 사용자 이름을 표시합니다.
row.appendChild(td);
td = document.createElement('td');
td.textContent = user.age; // 사용자 나이를 표시합니다.
row.appendChild(td);
td = document.createElement('td');
td.textContent = user.married ? '기혼' : '미혼'; // 사용자의 결혼 상태를 표시합니다.
row.appendChild(td);
tbody.appendChild(row); // 행을 tbody에 추가합니다.
});
} catch (err) {
console.error(err); // 오류 발생 시 에러를 콘솔에 출력합니다.
}
}
// 댓글 로딩
async function getComment(id) {
try {
const res = await axios.get(`/users/${id}/comments`); // 특정 사용자의 댓글을 가져옵니다.
const comments = res.data; // 가져온 댓글 목록을 저장합니다.
const tbody = document.querySelector('#comment-list tbody'); // 댓글 목록을 표시할 테이블의 tbody 요소를 찾습니다.
tbody.innerHTML = ''; // tbody를 비웁니다.
comments.map(function (comment) {
// 로우 셀 추가
const row = document.createElement('tr'); // 댓글 정보를 표시할 새로운 행을 생성합니다.
let td = document.createElement('td');
td.textContent = comment.id; // 댓글 ID를 표시합니다.
row.appendChild(td);
td = document.createElement('td');
td.textContent = comment.User.name; // 댓글 작성자의 이름을 표시합니다.
row.appendChild(td);
td = document.createElement('td');
td.textContent = comment.comment; // 댓글 내용을 표시합니다.
row.appendChild(td);
const edit = document.createElement('button');
edit.textContent = '수정';
edit.addEventListener('click', async () => {
// 수정 버튼을 클릭하면 댓글을 수정하는 함수를 호출합니다.
const newComment = prompt('바꿀 내용을 입력하세요');
if (!newComment) {
return alert('내용을 반드시 입력하셔야 합니다');
}
try {
await axios.patch(`/comments/${comment.id}`, { comment: newComment }); // 서버에 댓글 수정 요청을 보냅니다.
getComment(id); // 수정 후 댓글을 다시 불러옵니다.
} catch (err) {
console.error(err);
}
});
const remove = document.createElement('button');
remove.textContent = '삭제';
remove.addEventListener('click', async () => {
// 삭제 버튼을 클릭하면 댓글을 삭제하는 함수를 호출합니다.
try {
await axios.delete(`/comments/${comment.id}`); // 서버에 댓글 삭제 요청을 보냅니다.
getComment(id); // 삭제 후 댓글을 다시 불러옵니다.
} catch (err) {
console.error(err);
}
});
// 버튼 추가
td = document.createElement('td');
td.appendChild(edit); // 수정 버튼을 추가합니다.
row.appendChild(td);
td = document.createElement('td');
td.appendChild(remove); // 삭제 버튼을 추가합니다.
row.appendChild(td);
tbody.appendChild(row); // 행을 tbody에 추가합니다.
});
} catch (err) {
console.error(err); // 오류 발생 시 에러를 콘솔에 출력합니다.
}
}
// 사용자 등록 시
document.getElementById('user-form').addEventListener('submit', async (e) => {
e.preventDefault(); // 기본 폼 제출 동작을 막습니다.
const name = e.target.username.value;
const age = e.target.age.value;
const married = e.target.married.checked;
if (!name) {
return alert('이름을 입력하세요');
}
if (!age) {
return alert('나이를 입력하세요');
}
try {
await axios.post('/users', { name, age, married }); // 새 사용자를 서버에 등록합니다.
getUser(); // 등록 후 사용자 목록을 다시 불러옵니다.
} catch (err) {
console.error(err); // 오류 발생 시 에러를 콘솔에 출력합니다.
}
e.target.username.value = '';
e.target.age.value = '';
e.target.married.checked = false;
});
// 댓글 등록 시
document.getElementById('comment-form').addEventListener('submit', async (e) => {
e.preventDefault(); // 기본 폼 제출 동작을 막습니다.
const id = e.target.userid.value;
const comment = e.target.comment.value;
if (!id) {
return alert('아이디를 입력하세요');
}
if (!comment) {
return alert('댓글을 입력하세요');
}
try {
await axios.post('/comments', { id, comment }); // 새 댓글을 서버에 등록합니다.
getComment(id); // 등록 후 댓글 목록을 다시 불러옵니다.
} catch (err) {
console.error(err); // 오류 발생 시 에러를 콘솔에 출력합니다.
}
e.target.userid.value = '';
e.target.comment.value = '';
});
아래에 정리한 내용을 토대로 위의 수업을 따라갈 수 있었습니다.
(Create, Read, Update, Delete) 작업을 수행하는 명령어를 간단히 정리해보겠습니다.
CREATE (데이터 생성)
데이터베이스 생성:
CREATE DATABASE database_name;
테이블 생성:
CREATE TABLE table_name (
column1 datatype,
column2 datatype,
...
);
데이터 추가 (레코드 삽입):
INSERT INTO table_name (column1, column2, ...)
VALUES (value1, value2, ...);
READ (데이터 조회)
모든 데이터 조회:
SELECT * FROM table_name;
특정 조건을 만족하는 데이터 조회:
SELECT * FROM table_name WHERE condition;
UPDATE (데이터 업데이트)
UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE condition;
DELETE (데이터 삭제)
DELETE FROM table_name WHERE condition;
여기서 database_name
, table_name
, column1
, value1
, condition
등은 실제 데이터베이스와 테이블 이름, 열 이름, 값을 대신하여 사용해야 합니다. 데이터베이스, 테이블, 레코드를 생성, 조회, 업데이트 및 삭제할 때에는 각 명령에 맞게 SQL 문을 작성하면 됩니다.
더 복잡한 데이터베이스 조작이 필요한 경우 JOIN, 서브쿼리, 그룹화 등의 고급 SQL 기능을 사용할 수 있습니다.
하고 사용하기 위한 예제와 명령어를 각각 다루겠습니다.
1:1 관계에서는 하나의 레코드가 다른 테이블의 하나의 레코드와 관련이 있습니다. 예를 들어, "Users" 테이블과 "UserProfiles" 테이블 간의 1:1 관계를 생각해봅시다. 각 사용자는 하나의 프로필만 가질 수 있습니다.
CREATE TABLE Users (
user_id INT PRIMARY KEY,
username VARCHAR(50)
);
CREATE TABLE UserProfiles (
profile_id INT PRIMARY KEY,
user_id INT,
full_name VARCHAR(100),
bio TEXT,
FOREIGN KEY (user_id) REFERENCES Users(user_id)
);
INSERT INTO Users (user_id, username) VALUES (1, 'JohnDoe');
INSERT INTO UserProfiles (profile_id, user_id, full_name, bio) VALUES (101, 1, 'John Doe', 'A software developer.');
UserProfiles
테이블의 user_id
열은 Users
테이블의 user_id
열을 참조하고 있습니다. 이것은 1:1 관계를 설정합니다.
1:n 관계에서 하나의 레코드가 다른 테이블의 여러 레코드와 관련이 있습니다. 예를 들어, "Departments" 테이블과 "Employees" 테이블 간의 1:n 관계를 생각해봅시다. 하나의 부서에 여러 명의 직원이 속할 수 있습니다.
CREATE TABLE Departments (
department_id INT PRIMARY KEY,
department_name VARCHAR(50)
);
CREATE TABLE Employees (
employee_id INT PRIMARY KEY,
department_id INT,
employee_name VARCHAR(100),
FOREIGN KEY (department_id) REFERENCES Departments(department_id)
);
INSERT INTO Departments (department_id, department_name) VALUES (1, 'HR');
INSERT INTO Employees (employee_id, department_id, employee_name) VALUES (101, 1, 'Alice');
INSERT INTO Employees (employee_id, department_id, employee_name) VALUES (102, 1, 'Bob');
Employees
테이블의 department_id
열은 Departments
테이블의 department_id
열을 참조하고 있습니다. 이것은 1:n 관계를 설정합니다.
n:n 관계에서 여러 레코드가 다른 테이블의 여러 레코드와 관련이 있습니다. 이런 관계를 관리하기 위해 중간 테이블이 필요합니다. 예를 들어, "Students" 테이블과 "Courses" 테이블 간의 n:n 관계를 생각해봅시다. 여러 학생이 여러 과목을 수강할 수 있습니다.
CREATE TABLE Students (
student_id INT PRIMARY KEY,
student_name VARCHAR(100)
);
CREATE TABLE Courses (
course_id INT PRIMARY KEY,
course_name VARCHAR(100)
);
CREATE TABLE StudentCourses (
student_id INT,
course_id INT,
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES Students(student_id),
FOREIGN KEY (course_id) REFERENCES Courses(course_id)
);
INSERT INTO Students (student_id, student_name) VALUES (1, 'Alice');
INSERT INTO Students (student_id, student_name) VALUES (2, 'Bob');
INSERT INTO Courses (course_id, course_name) VALUES (101, 'Math');
INSERT INTO Courses (course_id, course_name) VALUES (102, 'History');
INSERT INTO StudentCourses (student_id, course_id) VALUES (1, 101);
INSERT INTO StudentCourses (student_id, course_id) VALUES (1, 102);
INSERT INTO StudentCourses (student_id, course_id) VALUES (2, 101);
StudentCourses
테이블을 사용하여 학생과 강좌 간의 n:n 관계를 설정합니다. 이 테이블의 student_id
및 course_id
열은 각각 Students
및 Courses
테이블을 참조합니다.
이것으로 MySQL에서 1:1, 1:n 및 n:n 관계를 설정하고 사용하는 방법에 대한 간단한 예제와 명령어를 제공했습니다. 관계형 데이터베이스를 사용하여 복잡한 데이터 모델을 만들고 관리할 수 있습니다.
Node.js에서 사용되는 ORM(Object-Relational Mapping) 라이브러리로, MySQL과 같은 관계형 데이터베이스를 다루기 위한 도구입니다. 시퀄라이즈를 사용하여 1:1, 1:n 및 n:n 관계를 설정하는 방법을 아래에서 상세히 설명하겠습니다. 이 예제에서는 시퀄라이즈 모델을 사용하여 MySQL 데이터베이스를 조작합니다.
1:1 관계 설정 (One-to-One Relationship):
// models/User.js
const { DataTypes } = require('sequelize');
const sequelize = require('../db'); // sequelize 연결 설정
const User = sequelize.define('User', {
username: DataTypes.STRING,
});
module.exports = User;
// models/UserProfile.js
const { DataTypes } = require('sequelize');
const sequelize = require('../db');
const UserProfile = sequelize.define('UserProfile', {
full_name: DataTypes.STRING,
bio: DataTypes.TEXT,
});
User.hasOne(UserProfile); // 1:1 관계 설정
UserProfile.belongsTo(User);
// 사용자 생성 및 프로필 연결
const User = require('./models/User');
const UserProfile = require('./models/UserProfile');
(async () => {
const user = await User.create({ username: 'JohnDoe' });
const userProfile = await UserProfile.create({
full_name: 'John Doe',
bio: 'A software developer.',
});
await user.setUserProfile(userProfile);
})();
1:n 관계 설정 (One-to-Many Relationship):
// models/Department.js
const { DataTypes } = require('sequelize');
const sequelize = require('../db');
const Department = sequelize.define('Department', {
department_name: DataTypes.STRING,
});
module.exports = Department;
// models/Employee.js
const { DataTypes } = require('sequelize');
const sequelize = require('../db');
const Employee = sequelize.define('Employee', {
employee_name: DataTypes.STRING,
});
Department.hasMany(Employee); // 1:n 관계 설정
Employee.belongsTo(Department);
// 부서 생성 및 직원 연결
const Department = require('./models/Department');
const Employee = require('./models/Employee');
(async () => {
const hrDepartment = await Department.create({ department_name: 'HR' });
await Employee.create({ employee_name: 'Alice', DepartmentId: hrDepartment.id });
await Employee.create({ employee_name: 'Bob', DepartmentId: hrDepartment.id });
})();
n:n 관계 설정 (Many-to-Many Relationship):
// models/Student.js
const { DataTypes } = require('sequelize');
const sequelize = require('../db');
const Student = sequelize.define('Student', {
student_name: DataTypes.STRING,
});
module.exports = Student;
// models/Course.js
const { DataTypes } = require('sequelize');
const sequelize = require('../db');
const Course = sequelize.define('Course', {
course_name: DataTypes.STRING,
});
module.exports = Course;
// models/StudentCourse.js (중간 테이블)
const StudentCourse = sequelize.define('StudentCourse', {});
Student.belongsToMany(Course, { through: StudentCourse });
Course.belongsToMany(Student, { through: StudentCourse });
// 학생 및 과목 생성 및 연결
const Student = require('./models/Student');
const Course = require('./models/Course');
(async () => {
const alice = await Student.create({ student_name: 'Alice' });
const bob = await Student.create({ student_name: 'Bob' });
const math = await Course.create({ course_name: 'Math' });
const history = await Course.create({ course_name: 'History' });
await alice.addCourse(math);
await alice.addCourse(history);
await bob.addCourse(math);
})();
위의 예제는 시퀄라이즈를 사용하여 MySQL 데이터베이스에서 1:1, 1:n 및 n:n 관계를 설정하고 데이터를 다루는 방법을 보여줍니다. 관계형 데이터베이스 모델을 설정하고 조작하는 데 유용한 도구입니다.
1:1 관계 설정 및 CRUD:
// models/User.js
const { DataTypes } = require('sequelize');
const sequelize = require('../db');
const User = sequelize.define('User', {
username: DataTypes.STRING, // 사용자명을 저장하는 컬럼
});
// models/UserProfile.js
const UserProfile = sequelize.define('UserProfile', {
full_name: DataTypes.STRING, // 전체 이름을 저장하는 컬럼
bio: DataTypes.TEXT, // 사용자 정보를 저장하는 컬럼
});
User.hasOne(UserProfile); // User 모델과 UserProfile 모델 간의 1:1 관계 설정
UserProfile.belongsTo(User);
// 사용자 및 사용자 프로필 생성
const User = require('./models/User');
const UserProfile = require('./models/UserProfile');
(async () => {
const user = await User.create({ username: 'JohnDoe' });
const userProfile = await UserProfile.create({
full_name: 'John Doe',
bio: '소프트웨어 개발자입니다.',
});
// 사용자와 프로필 연결
await user.setUserProfile(userProfile);
// 사용자의 프로필 읽기
const userWithProfile = await User.findOne({
where: { username: 'JohnDoe' },
include: UserProfile,
});
console.log(userWithProfile.UserProfile.full_name);
// 사용자 프로필 업데이트
await user.setUserProfile({ full_name: '갱신된 이름', bio: '갱신된 소개' });
// 사용자 삭제
await user.destroy();
})();
1:n 관계 설정 및 CRUD:
// models/Department.js
const { DataTypes } = require('sequelize');
const sequelize = require('../db');
const Department = sequelize.define('Department', {
department_name: DataTypes.STRING, // 부서 이름을 저장하는 컬럼
});
// models/Employee.js
const Employee = sequelize.define('Employee', {
employee_name: DataTypes.STRING, // 직원 이름을 저장하는 컬럼
});
Department.hasMany(Employee); // Department 모델과 Employee 모델 간의 1:n 관계 설정
Employee.belongsTo(Department);
// 부서 및 직원 생성
const Department = require('./models/Department');
const Employee = require('./models/Employee');
(async () => {
const hrDepartment = await Department.create({ department_name: '인사부' });
await Employee.create({ employee_name: 'Alice', DepartmentId: hrDepartment.id });
await Employee.create({ employee_name: 'Bob', DepartmentId: hrDepartment.id });
// 인사부의 직원 읽기
const hrEmployees = await Employee.findAll({ where: { DepartmentId: hrDepartment.id } });
console.log(hrEmployees);
// 직원 업데이트
const alice = await Employee.findOne({ where: { employee_name: 'Alice' });
await alice.update({ employee_name: 'Alicia' });
// 직원 삭제
await alice.destroy();
})();
n:n 관계 설정 및 CRUD:
// models/Student.js
const { DataTypes } = require('sequelize');
const sequelize = require('../db');
const Student = sequelize.define('Student', {
student_name: DataTypes.STRING, // 학생 이름을 저장하는 컬럼
});
// models/Course.js
const Course = sequelize.define('Course', {
course_name: DataTypes.STRING, // 강좌 이름을 저장하는 컬럼
});
// models/StudentCourse.js (중간 테이블)
const StudentCourse = sequelize.define('StudentCourse', {});
Student.belongsToMany(Course, { through: StudentCourse }); // Student 모델과 Course 모델 간의 n:n 관계 설정
Course.belongsToMany(Student, { through: StudentCourse });
// 학생 및 강좌 생성
const Student = require('./models/Student');
const Course = require('./models/Course');
(async () => {
const alice = await Student.create({ student_name: 'Alice' });
const bob = await Student.create({ student_name: 'Bob' });
const math = await Course.create({ course_name: '수학' });
const history = await Course.create({ course_name: '역사' });
// 학생을 강좌에 등록
await alice.addCourse(math);
await alice.addCourse(history);
await bob.addCourse(math);
// Alice가 수강한 강좌 읽기
const aliceCourses = await alice.getCourses();
console.log(aliceCourses);
// 강좌 업데이트
await math.update({ course_name: '고급 수학' });
// Bob을 강좌에서 제외
await bob.removeCourse(math);
// 학생 삭제
await bob.destroy();
})();
위의 코드는 각 관계 유형에 대한 시퀄라이즈 예제와 CRUD 작업을 담고 있습니다. 데이터베이스 테이블을 생성하고 데이터를 조작하며 관계를 설정하는 방법을 보여줍니다.