에러 메세지

#입력값
{ title: 'test posting4', content: 'nothing', categoryId: 1, hashtag: 'test' }

Executing (95522a76-98e3-47e9-9e38-97e9a7db1ed0): START TRANSACTION;
Executing (95522a76-98e3-47e9-9e38-97e9a7db1ed0): SELECT `id`, `word`, `created_at` AS `createdAt`, `updated_at` AS `updatedAt` FROM `hashtags` AS `hashtags` WHERE `hashtags`.`word` = 'test' LIMIT 1;
null
Executing (95522a76-98e3-47e9-9e38-97e9a7db1ed0): INSERT INTO `hashtags` (`id`,`word`,`created_at`,`updated_at`) VALUES (DEFAULT,?,?,?);
Executing (95522a76-98e3-47e9-9e38-97e9a7db1ed0): ROLLBACK;

# 실패한 쿼리 (Error 메세지에서 )
at async posts.create (/Users/joey/WebstormProjects/practice_DB_ORM_API/node_modules/sequelize/lib/model.js:1362:12) { sql: 'INSERT INTO `posts` (`id`,`title`,`content`,`created_at`,`updated_at`,`category_id`) VALUES (DEFAULT,?,?,?,?,?);' }

<추측>

1. 아마도 카테고리가 이미 생성되지 않은 상태에서  categoryId를 주어서 그럴까?

-> category를 먼저생성해서 확인 절차 코드를 추가하기 (이 부분에서 에러가 발생한 것이 맞다.)

 

<해결>

1. category를 먼저 생성

- 이 부분에서 저번에 했던 실수가 반복됨 : body 에서 { "category": "IT"}를 전달했는데 키를 동일하게 입력하지 않아서 오류 또 발생

const {newCategory } = req.body;

 

2. post를 게시(Post)할 때 category에 존재하는 id를 넘겨 줄것. 

 

* 추가적으로 transaction 에서 데이터와 관련된 작업을 순차적으로 진행되도록 관련된 코드앞에 await를 꼭 붙일 것

// 여기에서 await를 넣지 않음
const post = await Post.create(...)

 

>>> 내가 Post-Category 간의 관계를 설정해 두어서 새로운 Post를 생성할 때 Sequelize 에서 자동으로 주어진 categoryId 가 존재하는지 확인과정을 거쳤고, 그 결과 임의로 준 categoryId 가  Category에 존재하지 않아서 에러를 발생함. 

# 에러를 찾다가 발견한 것들

< 두 개의 table 을 연결해주는 모델을 만들때 좀더 명확하게 하는 것이 좋다.>

const PostHashtag = sequelize.define('post_hashtag', {
    id :{
        type: DataTypes.INTEGER,
        autoIncrement: true,
        primaryKey: true
    },
    postId:{
        type: DataTypes.INTEGER,
        // 명확하게 하기 위해 추가된 코드
        references:{
            model:'posts',
            key: 'id'
        }
    },
    hashtagId:{
        type: DataTypes.INTEGER,
        // 명확하게 하기 위해 추가된 코드
        references: {
            model:'hashtags',
            key:'id'
        }
    },
},{
    underscored:true
})

 

< transaction 내에서 수행한 각 쿼리의 결과를 수행하고 나면 꼭 해당 메서드를 트랜잭션에 보내서 오류가 발생하는지 파악하기>

// 예를 들어서 { transaction: t } 을 추가
const post = await Post.create({
    title,
    content,
    createdAt: new Date(),
    categoryId
    }, { transaction: t }
);
            
await post.addHashtag(existingHashtag, { transaction: t });

 

<아래의 코드 한 줄로 Post 와 Hash의 관계를 설정해주기 위해 만든 PostHashtag 에 자동으로 입력되는 것이 신기하다. >

await post.addHashtag(existingHashtag, {transaction:t});

📖 Sequelize : https://sequelize.org/docs/v6/other-topics/transactions/

 

Transactions | Sequelize

Sequelize does not use transactions by default. However, for production-ready usage of Sequelize, you should definitely configure Sequelize to use transactions.

sequelize.org

<설치>

npm i cls-hooked

 

<종류>

  • Unmanaged transactions : manually committing and rolling back the transaction by the user

 

  • Managed transaction : automatically manage committing and rolling back
try {

  const result = await sequelize.transaction(async (t) => {

    const user = await User.create({
      firstName: 'Abraham',
      lastName: 'Lincoln'
    }, { transaction: t });

    await user.setShooter({
      firstName: 'John',
      lastName: 'Boothe'
    }, { transaction: t });

    return user;

  });

  // If the execution reaches this line, the transaction has been committed successfully
  // `result` is whatever was returned from the transaction callback (the `user`, in this case)

} catch (error) {

  // If the execution reaches this line, an error occurred.
  // The transaction has already been rolled back automatically by Sequelize!

}

If all queries are successful (in the sense of not throwing any error), but you still want to rollback the transaction, you should throw an error yourself

await sequelize.transaction(async t => {
  const user = await User.create({
    firstName: 'Abraham',
    lastName: 'Lincoln'
  }, { transaction: t });

  // Woops, the query was successful but we still want to roll back!
  // We throw an error manually, so that Sequelize handles everything automatically.
  throw new Error();
});

 

  • Automatically pass transactions to all queries
const cls = require('cls-hooked');
const namespace = cls.createNamespace('my-very-own-namespace');

//To enable CLS, you must tell sequelize which namespace to use by using a static method of the sequelize constructor
const Sequelize = require('sequelize');

//All instances will share the same namespace, we cannot enable it only for some instances.
Sequelize.useCLS(namespace); 

new Sequelize(....);

 

 

간단한

>> Automatically pass transactions to all queries 이용

< 요약>

  1. npm i cls-hooked
  2. const cls = require('cls-hooked');
    const namespace = cls.createNamespace('my-very-own-namespace');
    const Sequelize = require('sequelize');
    Sequelize.useCLS(namespace); 

 

 

 

📖 wikipedia : https://en.wikipedia.org/wiki/Database_transaction

 

 

트랜잭션이란?

- 데이터베이스 시스템에서 실행되는 하나의 논리적인 작업 혹은 데이터 조작 단위를 나타낸다.

- 데이터베이스에 대한 일련의 작업을 묶어서 처리하는 방법을 제공하여 데이터 일관성무결성을 보장

 

특징

1. 원자성 ( Atomicity)
일련의 작업이 모두 성공적으로 완료되어야한다. 하나라도 실패할 시 처음 상태로 rollback(되돌림)된다.

 

2. 일관성 (Consistency)

트랜잭션이 실행 전과 후의 데이터베이스가 일관된 상태여야 한다. 이는 트랜잭션 실행후 데이터베이스가  데이터의 무결성(데이터베이스 내의 데이터가 일관된 규칙과 데이터 형식을 따르고, 고유키 또는 제약조건을 준수하는 것)을 유지, 비즈니스 논리와 일치, 관계 무결성(데이터베이스 내의 데이터간의 관계)를 유지 해야 한다는 것을 의미한다. 

 

3.지속성 (Durability)

트랜잭션이 성공적으로 완료된 후에는 그 결과가 영구적으로 저장되어야 한다. 시스템 장애 또는 전원 공급 장애와 같은 문제가 발생하더라도 트랜잭션의 결과는 유지되어야 한다.

 

필요한 이유?

데이터 조작을 안전하게 다루기 위함이다.

예) A가 은행에서 돈을 인출하고 B 에게 송금을 하려고 할때, A가 인출한 돈을 B에게 송금을 했는데 B의 계좌에 도착을 하지 않는 경우.
이때, 이 일련의 작업이 모두 성공적으로 완료 되지 않으므로 초기 상태로 롤백을 하여 송금 절차를 안전하게 수행 할 수 있도록 한다.

📖 Sequelize :  https://sequelize.org/docs/v6/core-concepts/assocs/

 

foriegn key 는 자동으로 가 아니라 1 이나 N에 해당하는 테이블이 있는 열에
1의 primary key(P.K) 에 해당하는 값을 넣어 연결시켜주는 다리 역할

 

❗️1 : 1  (One-To-One relationship)

The .hasOne and .bleongTo associstions 가 함께 사용된다.
특징> 두 테이블중 아무곳이나 다른 테이블의 P.K 값을 넣어주어도 상관없다.

const Profile = require('./profile');
const User = require('./user');

User.hasOne(Profile);
Profile.belongsTo(User);

//----
const saveUSer = await User.create({
	...
});

const saveProfile = await Profile.create({
    gender: "male",
    photo: "me.jpg",
    userId: saveUser.id // User table의 primary key(P.K) 입력하여 관계를 형성
});


// or

const saveProfile = await Profile.create({
    gender: "male",
    photo: "me.jpg",
});

const saveUser = await User.create({
	name: "name",
    userId: saveProfile.id
});

 

❗️ 1 : N  (One-To-Many relationship)

The .hasMany and .belongsTo associations 가 함께 사용된다.

특징> N 에 해당하는 테이블에 1에 있는 PK 값을 입력하여 두 테이블을 연결시킨다.

const User = require('./user');
const Post = require('./post');

User.hasMany(Post);
Post.belongTo(User);

const saveUSer = await User.create({
	...
});

const savePost = await Post.create({
    title: "demo",
    content: "nothing",
    userId: saveUser.id // User table의 primary key(P.K) 입력하여 관계를 형성
});

 

❗️N : M ( Many-To-Many relationship)

The .hasMany and .belongsToMany associations 가 함께 사용된다.

특징>  두 테이블을 연결시켜줄 새로운 테이블을 형성해 주고, 새로운 테이블에 두테이블의 P.K 값을 입력하여 연결시킨다.

const Post = require("./post");
const Hashtag = require("./hashtag");

const PostHashtag = sequelize.define('post_hashtag', {
  post_id: {
    type: DataTypes.INTEGER,
    references: {
      model: Post,
      key: 'id'
    }
  },
  hashtag_id: {
    type: DataTypes.INTEGER,
    references: {
      model: Hashtag, 
      key: 'id'
    }
  }
});

// PostHashtag 이용해서 두테이블을 연결해준다.
Post.hasMany(Hashtag, { through: 'PostHashtag'});
Hashtag.belongToMany(Post, { through: 'PostHashtag'});

 

'DataBase > Database 지식 기록' 카테고리의 다른 글

Sequelize을 이용한 Transaction  (0) 2023.10.05
트랜잭션(Transaction)  (0) 2023.10.04
[Feedback] DB modeling  (0) 2023.09.30
데이터베이스 정규화  (0) 2023.09.13
데이터베이스 이상 현상 (Anomaly)  (0) 2023.09.13

 

 

테이블을 생성시 몇 대 몇인지 확인하기

  • 1 : m -> foreign key 가 M 의 테이블에 붙는다.
  • 1 : 1 -> foreign key 가 둘중 어디에든 붙일 수 있다.
  • N : M -> 두개의 테이블간에 연결고리를 해줄 하나의 테이블을 생성한다.

중복을 일으키는 경우

  • 직관적이지 않은 네이밍-> "중복" 의 느낌을 불러온다.
    • 주문내역 -> 주문한 상품
    • 결제내역 -> 결제내역
    • 가격 -> 상품가격
    • 가격 -> 주문한 당시의 상품가격

중복여부의 결정기준

만약 "가격" 이란 항목이 "상품정보" 와 "주문한 상품" 두곳에 있다면 이는 중복일까?

  • 중복이 아니다!!
  • 상품 정보의 가격이 바뀐다고 이미 주문한 상품의 가격이 바뀔 이유가 없기 때문 이다.
  • 중복이라는 오해를 피하기 위해
  • 주문한 상품 - 주문한 당시의 상품가격 으로 네이밍하는 것을 추천

주문한 총가격 확인

이는 주문한 상품의 가격정보와 갯수 정보가 있는 테이블을 불러와서 계산이 가능하므로 따로 목록을 만들어서 저장을 하지 않는다.

즉, 다른 테이블을 이용해서 정보를 생성할 수 있는 경우는 따로 정보를 저장하지 않는다 -> 데이터의 일과성을 유지하기 위함

왜 관리자와 유저를 분리하는가?

이것은 주어진 항목에 따라 다르다.

  • 유저를 등록하기 위한 정보 = 관리자를 등록하기 위한 정보
    • 분리할 필요가 없다.
  • 유저를 등록하기 위한 정보 ≠ 관리자를 등록하기 위한 정보
    • 불필요한 정보[(ex) NULL ]가 추가되어야 하므로 분리해야한다. 

+ Recent posts