본문 바로가기

블록체인_9기/💚 Node.js

34강_230519_(ORM, Sequelize, ajax, Sequelize모델, 모델간의 관계)

728x90

 

 

 


ORM

  • Object Relational Mapping
  • 객체지향 패러다임을 활용하여 관계형 데이터베이스(RDB)의 데이터를 조작하게 하는 기술이다.
  • 직접 sql문을 작성하지 않아도 프로그래밍 언어를 이용해서 DB에 접속할 수 있다.

 

 

 


Sequelize

  • ORM의 일종으로, 자바스크립트 객체와 데이터베이스의 릴레이션을 매핑해주는 도구이다.
  • 프로그래밍 언어를 사용하여 DB에 접근할 수 있으므로 SQL 문법을 몰라도 된다는 장점을 가진다.

 

 

 


sequelize로 게시판 생성

#. 폴더의 경로는 다음과 같다.

 

1. app.js_웹 서버를 열기 위한 기본 세팅 설정

  • 사용할 모듈을 저장 및 불러온다.
// npm i express ejs dotenv mysql2 sequelize

const express = require("express");
const path = require("path");
  • 서버객체를 생성 하고 서버를 대기시킨다.
const app = express();

app.listen(6767,()=>{
    console.log("서버열림");
})
  • view 엔진의 경로를 설정하고 ejs를 사용한다.
app.set("views", path.join(__dirname,"page"));
app.set("view engine", "ejs");
  • body객체를 사용한다.
app.use(express.urlencoded({extended:false}));

2. app.js_sequelize 구성 연결 매핑 설정

  • ' /models/index.js '에서 db의 객체를 sequelize 모델로 매핑하여 등록한다.
    • db의 객체로는 sequelize 모델로 정의한 객체들이 담겨있다.
  • Sequelize.sync: 데이터베이스와 동기화 시켜주는 메소드이다.
// focus - true: 초기화
// focus - false: 초기화 안함
Sequelize.sync({focus : true}).then(()=>{
    // 연결성공
}).catch((err)=>{
    // 연결실패
    console.log(err);
})

3. .env_sequelize 객체 생성 시 들어갈 데이터의 value값 설정한다.

  • ' .env '에서 USERNAME을 사용하면 로컬 환경에서의 username을 가져오기 때문에 해당 이름은 사용할 수 없다.
USERNAMES = root
PASSWORD = 00000
DATABASE = test9
HOST = 127.0.0.1

4. config.js_sequelize 객체 생성 시 들어갈 데이터를 설정한다.

const dot = require('dotenv').config();

const config = {
    dev: {
        username: process.env.USERNAMES,
        password: process.env.PASSWORD,
        database: process.env.DATABASE,
        host: process.env.HOST,
        // dialect: 사용하는 데이터 베이스
        dialect: "mysql"
    }
}

module.exports = config;

4. posts.js_sequelize의 Post의 모델을 설정한다.

  • Model클래스를 확장하면 정적 메소드 ' init( ) '을 사용할 수 있다.
    • static init( )
      • 테이블에 대한 설정을 한다.
      • super.init( )을 통해 관련 인스턴스를 생성할 수 있다.
        • super init( ){ { 컬럼의 내용 }, {테이블의 내용 } }
          • 컬럼의 내용: 테이블의 컬럼이름과 속성 값을 전달한다.
          • 테이블의 내용: 테이블 생성에 필요한 기타 값을 전달한다.
    • static associate()
      • 다른 모델과의 관계 (예. 1 : 1 / 1 : N)를 설정한다.
        • 1 : N : 예) 하나의 유저가 여러 개의 글을 쓰는 경우
        • 관계에 관한 메소드 (아래에 자세히 설명예정이다.)
          • hasMany / belongsTo ( 1 : N )
          • hasOne / belongsTo ( 1 : 1 ) 
          • belongsToMany ( N : M)
  • ⭐sequelize는 알아서 id 컬럼을 기본키로 연결하므로 별도로 설정해 줄 필요가 없다.

시퀄라이즈 옵션

  • super.init의 두 번째 인수인 테이블에 대한 옵션
  • sequelize
    • static init 메서드의 매개변수와 연결되는 옵션으로,db.sequelize 객체를 넣어야 한다.
      나중에 models/index.js에서 연결한다.
  • timestamps
    • 이 속성 값이 true면 시퀄라이즈는 createdAt updatedAt 컬럼을 추가하며, 각각 로우가 생성될 때와 수정될 때의 시간이 자동으로 입력된다.
  • underscored
    • 시퀄라이즈는 기본적으로 테이블명과 컬럼명을 스네이크 표기법 (snake  case) 으로 만든다. 이를 카멜 표기법 (camel case) 으로 바꾸는 옵션이다
      • 예) updated_atupdatedAt 으로
  • modelName
    • 모델 이름을 설정할 수 있다. 노드 프로젝트에서 사용한다.
  • tableName
    • 실제 데이터베이스의 테이블 이름.
    • 기본적으로 모델 이름의 소문자 및 복수형으로 만든다.
      • 예를 들어 모델 이름이 User 라면 테이블 이름은 users 이다.
  • paranoid
    • true로 설정하면 deletedAt이라는 컬럼이 생긴다.
    • 로우를 삭제할 때 완전히 지우지 않고, deletedAt에 지운 시각이 기록된다.
    • 로우를 조회하는 명령을 내렸을 경우 deletedAt의 값이 null인 로우를 조회한다.
    • true로 사용하는 이유? 후에 로우를 복원하기 위해서다. 
      • 로우를 복원해야 할 상황이 생길 것 같다면 미리 true로 설정해두자.
  • charset / collate
    • 각각 utf8 과 utf8_general_ci 로 설정해야 한글이 입력된다.
      이모티콘까지 입력할 수 있게 하고 싶다면 utf8mb4 와 utf8mb4_general_ci 를 입력한다.

 

모델간의 관계

  • 1:N 관계 (hasMany, belongsTo)
    • hasMany
      • 시퀄라이즈에서 1:N관계를 정의해준다.
      • 다른 테이블의 칼럼의 내 테이블의 칼럼을 참조한다.
      • 첫 번째 메소드: 연결을 할 테이블을 기입한다.
      • 두 번째 메소드 : 참조를 정의해주는 객체를 정의한다.
        • foreignkey
          • 다른 테이블에서 내 칼럼을 참조할 새로운 칼럼의 이름을 기입한다.
          • 따로 지정하지 않으면 " 모델명 + 기본키 "가 이름인 칼럼이 자동으로 생성된다.
            • 모델명: user
            • 기본키: id
            • foreignkey 이름은 userid이다.
        • sourceKey
          • foreignkey로 참조할 내 테이블의 칼럼을 적어준다. 
      • belongsTo
        • 내 테이블의 칼럼은 다른 테이블의 칼럼에 속해있다.
    • belongsTo
      • foreignkey
        • 내 테이블이 참조 받을 다른 칼럼의 이름을 기입한다.
        • 따로 지정하지 않으면 " 모델명 + 기본키 "가 이름인 칼럼이 자동으로 생성된다.
          • 모델명: user
          • 기본키: id
          • foreignkey 이름은 userid이다.
      • targetKey
        • foreignkey로 참조한 다른 테이블의 칼럼을 적어준다. 
  • 1:1 관계 (hasOne, belongsTo)
    • hasOne
      • foreignkey, sourceKey, targetKey의 내용은 위와 같다.
    • belongsTo
      • 위에 기입한 내용과 같다.
  • N:M 관계 (belongsToMany)
    • 어느 한 테이블이 다른 테이블에 종속하는 것이 아닌, 서로의 데이터를 공유한다.
    • N:M 관계의 특성 상 새로운 모델이 다음과 같이 생성되며 ' through ' 속성에 이름을 적으면 된다.
// N:M 관계 정의 예시 코드 -------------------------------------------------------------
// 출처 : https://inpa.tistory.com/

// Post
db.Post.belongsToMany(db.Hashtag, { through: 'PostHashtag' });

// Hashtag
db.Hashtag.belongsToMany(db.Post, { through: 'PostHashtag' });

출처: https://inpa.tistory.com/entry/ORM-%F0%9F%93%9A-%EC%8B%9C%ED%80%84%EB%9D%BC%EC%9D%B4%EC%A6%88-%EB%AA%A8%EB%8D%B8-%EC%A0%95%EC%9D%98%ED%95%98%EA%B8%B0

 

//posts.js -------------------------------------------------------------------------

const Sequelize = require("sequelize");

class Post extends Sequelize.Model {
    static init(sequelize){
        // 첫 번째 매개변수: 컬럼의 내용 / 두 번째 매개변수: 테이블의 내용
        return super.init({
            msg: {
                type : Sequelize.STRING(100),
                allowNull : false
            }
        },{
            sequelize,
            timestamps: true,
            modelNameL : "Post",
            tableName: "posts",
            charset : "utf8",
            collate : "utf8_general_ci"
        })
    }
    static associate(db){
        // 1 : N 관계
        // belongsTO 메소드를 사용해서 user에 id를  foreignkey로 연결한다.
        // 유저의 id가 따라갈 키. 참조키는 user_id
        db.Post.belongsTo(db.User, { foreignkey : "user_id", targetKey : "id"});
    }
}

module.exports = Post;

4. users.js_sequelize의 User의 모델을 설정한다.

const Sequelize = require("sequelize");

// User클래스에 시퀄라이즈 안의 Model클래스를 상속 시켜준다.
class User extends Sequelize.Model {
    static init(sequelize){
        return super.init({
            // name 컬럼
            name : {
                // VARCHAR === STRING
                type: Sequelize.STRING(20),
                // allowNull: null을 허용할지 여부를 결정
                allowNull : false,
                // unique: 고유키로 사용할 것인지. 중복되는 않는 값
                unique : true,
                // primaryKey: 고유키로 설정할 것인지 유무
                // primaryKey : true
            },
            age : {
                // INT === INTEGER
                type : Sequelize.INTEGER,
                allowNull : false
            },
            msg: {
                // TEXT === TEXT
                type : Sequelize.TEXT
            }
        },{
            sequelize,
            timestamps : true,
            underscored: false,
            modelName: "user",
            tableName: "users",
            paranoid : false,
            // 인코딩 방식이다. 필수로 작성해줘야하는 것!
            charset : "utf8",
            // 인코딩 방식이다. 필수로 작성해줘야하는 것!
            collate : "utf8_general_ci"
        });
    }
    static associate(db){
        // 1 : N 관계
        db.User.hasMany(db.Post, { foreignkey : "user_id", sourceKey : "id"});
    };
};

module.exports = User;

5. index.js_sequelize의 객체를 생성하고, db 객체를 내보내준다.

const Sequelize = require("sequelize");
const config = require("./config");
const User = require("./users");
const Post = require("./posts")

// 시퀄라이즈 객체 생성
const _sequelize = new Sequelize(
    config.dev.database,
    config.dev.username,
    config.dev.password,
    config.dev
);

// 내보내 줄 빈 객체
const db = {};
db.Sequelize = _sequelize;
db.User = User;
db.Post = Post;

// 테이블을 초기화 하는 구문
User.init(_sequelize);
Post.init(_sequelize);
User.associate(db);
Post.associate(db);

module.exports = db;

5. create.ejs_유저를 등록할 수 있는 페이지를 생성한다.

<body>
    <form action="/create" method="post">
        <input type="text" name="name" placeholder="이름"> <br>
        <input type="text" name="age" placeholder="나이"> <br>
        <input type="text" name="msg" placeholder="메세지"> <br>
        <button>유저 등록</button>
    </form>
</body>

6. view.ejs_특정 유저가 등록한 게시글들을 확인할 수 있는 페이지를 생성한다.

<body>
    <div>작성자: <%= data.name %></div>
    <ul>
        <% data.Posts.forEach((el) => { %>
            <li>
                <p><%= el.id %></p>
                <p><%= el.msg %></p>
            </li>
        <% }); %>
    </ul>
</body>

7. main.ejs_등록한 유저 목록을 확인할 수 있고, 유저별로 게시글을 작성할 수 있는 페이지를 생성한다.

  • 유저마다 부여된 ' 글 등록 '버튼을 반복문 forEach문으로 돌려 해당 버튼의 index값을 받아, input의 value 값과 유저이름에 적용된 ' id=name ' 값을 불러온다.
    • 불러온 값을 ajax 메소드를 사용하여 data의 객체로 app.js에 전달해준다. 

 

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="http://code.jquery.com/jquery-latest.min.js"></script>
</head>

<body>
    <h1>유저 페이지</h1>
    <ul>
        <% data.forEach((el)=> { %>
            <li>
                <span class="name"><%= el.name %></span>님 (<%= el.age %>세)
                <div>코멘트 : <%= el.msg %></div>
                <input type="text" class="item-input"> <button class="item-btn">글 등록</button>
            </li>
        
        <% }); %>
    </ul>
</body>
<script>
    // ajax: 요청과 응답에 대한 처리를 할 수 있게 도와주는 메소드
    window.onload = ()=>{
        let btns = document.querySelectorAll('.item-btn');
        btns.forEach((el, index)=>{
            el.onclick = ()=>{
                let value = document.querySelectorAll('.item-input')[index].value;
                let name = document.querySelectorAll('.name')[index].innerText;
                console.log("value", value);
                console.log("name", name);
                //요청해서 값을 넣자 form tag 사용 X
                // ajax 메소드 사용
                $.ajax({
                    //요청할 url
                    url : "/create_post",
                    // 요청 방식
                    type : "post",
                    // 전달할 데이터
                    data: { name, value }
                })
            }
        })
    }
</script>
</html>

8. app.js_ejs 파일별 router와 post를 정의해준다.

  • create.ejs => ' / '경로  |  ' /create ' 경로에서 받은 input의 값으로 User 컬럼에 데이터를 추가한다.
app.get('/',(req,res)=>{
    res.render("create")
})


app.post('/create',(req,res)=>{
    const { name, age, msg } = req.body;
    // create메소드: insert 문을 실행시켜주는 메서드
    // 매개변수로 컬럼의 내용을 객체로 만들어서 전달
    User.create({
        // name 컬럼의 값
        name : name,
        // age 컬럼의 값
        age : age,
        // msg 컬럼의 값
        msg : msg
    })
    res.send("값 추가 완료")
})
  • main.ejs => ' /main '경로. 'findAll' 메소드를 사용하여 모든 유저의 정보를 불러온다.
app.get('/main',(req,res)=>{
    // 유저 전체 조회
    // findAll메소드: 매개변수로 검색 조건을 객체로 추가할 수 있다.
    // 지금은 전체조회를 할 것이기 때문에 조건을 넣지 않는다.

    // SELECT * FROM User 한거랑 같구나
    User.findAll({})
    .then((e)=>{
        // 성공 시
        res.render("main", { data : e });
    })
    .catch((e)=>{
        // 실패 시
        res.send("유저 조회 실패")
    })
})
  • main.ejs => ' /create_post '경로(글 등록 버튼을 누르면 요청되는 url이다).  |  ' /create_post ' 경로에서 받은 값을 받아 User 테이블 영역에서 해당 유저가 존재하는지를 검색하고 있다면 input의 value 값으로 받은 내용을 Post 값으로 추가한다.
app.post('/create_post', (req,res)=>{
    const { name, value } = req.body;
    console.log("name, value",name, value);
    // findOne: 한 개의 값을 조회하는 메소드
    User.findOne({
        // 검색 조건 추가
        where : {name: name}
    }).then((e)=>{
        Post.create({
            msg : value,
            userId : e.id
        })
    })
    res.send();
})
  • view.ejs => ' /view/:name ' 경로. 'findOne'메소드를 사용해서 params의 값을 불러와 조건으로 넣어 유저정보를 검색한 후 검색된 유저의 정보의 id로 참조된 Post 테이블의 값을 불러온다.
    • 불러와진 Post 값이 then 함수의 매개변수로 담겨, 우리가 원하는 정보를 사용하기 위해, 반환 받은 ' e.dataValues.post '의 dataValues를 map함수로 돌아 'e.dataValues.Posts '로 대입한다.
    • 이 과정을 거치면 하단의 사진처럼 값이 불러와진다.
    • 이렇게 반환받은 값을 view 페이지에 넘겨준다.

가장 상위 배열의 값은 유저의 정보이며, 그 하위로는 Posts의 정보이다.

app.get('/view/:name',(req,res)=>{
    // 유저를 조회하고 가지고 있는 글을 볼거임!
    User.findOne({
        where : { 
            // 해당 이름의 유저를 조회하면서
            name : req.params.name 
        },
        // raw 속성을 주면 관계형으로 불러온 값을 다 풀어서 볼 수가 있는데
        // raw: true,
        // 해당 유저의 id로 참조된 user_id가 있는 post 테이블의 값을 같이 조회한다.
        include : [
            // 조회할 모듈 = Post모델
            {model : Post}
        ]
    }).then((e)=>{
        e.dataValues.Posts = e.dataValues.Posts.map((i)=> {return i.dataValues});
        const Posts = e.dataValues;
        res.render("view", {data : Posts});
    })
})

 

 

 

 

 

 

728x90