[BE201] 後端中階:ORM 與 Sequelize


Posted by s103071049 on 2021-08-10

什麼是 ORM?

ORM,全名是 Object Relation Mapping。ORM 會將資料庫裡 table 的物件和程式語言裡的物件建立對應關係,所以在程式中操作物件,資料庫也會跟著改動。

原本在操作資料庫的資料都是使用 sql query,但我們可以從操作物件的角度出發,以操作物件的方式操作資料庫,簡單的操作會變得很美好。

node.js 有一套 library 叫 Sequelize:Sequelize is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server. It features solid transaction support, relations, eager and lazy loading, read replication and more.

  • model name 就是 table 名稱叫 User
  • 有兩個欄位:username, birthday
  • User.create:在資料庫寫進資料
const { Sequelize, Model, DataTypes } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:'); // (連線到資料庫的指令)

class User extends Model {}
User.init({
  username: DataTypes.STRING,
  birthday: DataTypes.DATE
}, { sequelize, modelName: 'user' });

(async () => {
  await sequelize.sync();
  const jane = await User.create({ // 建立物件
    username: 'janedoe',
    birthday: new Date(1980, 6, 20)
  });
  console.log(jane.toJSON());
})();

小結

  1. orm 是將程式物件對應到資料庫,所以不用再寫 sql query,用這些指令就可以寫 CRUD

Sequelize 實際示範

參考文件

  1. $ npm install --save sequelize
  2. 用哪個 db 就安裝哪個,我們使用 mysql 所以安裝這個:$ npm install --save mysql2
  3. 定義 model 長相 官方文件,四版
  4. 設定 sequelize.sync():If you want Sequelize to automatically create the table (or modify it as needed) according to your model definition, you can use the sync method (如果希望 Sequelize 自動建立 table,就要加這一行,加這行後就會去資料庫建表)
  5. sequelize.sync() 會回傳一個 promise,透過 .then 新增欄位
  6. 執行 node xxx.js 完成後會自動於資料庫生成你定義的欄位與 createdAt、updatedAt。先建立多個 user。
  7. 找所有 user
const { Sequelize } = require('sequelize'); // 引入 sequelize
// new 一個 sequelize 的 instance,與資料庫建立連線
const sequelize = new Sequelize('database', 'username', 'password', {
  host: 'localhost',
  dialect: 'mysql'
});

orm 是把物件對應到資料庫的表,可以在文件裡的 model definition 進行相關查詢。舉例:可以設定欄位名稱、是否 unique

以下面代碼為例,我們設定名稱、型態

// 我要 define 一個叫 user 的資料結構
const User = sequelize.define('user', {
  // attributes
  firstName: {
    type: Sequelize.STRING,
    allowNull: false // 可否為空
  },
  lastName: {
    type: Sequelize.STRING
    // allowNull defaults to true
  }
}, {
  // options
});
// step5
sequelize.sync().then(() => {
  User.create({ // 新建一個 user
    firstName: 'John',
    lastName: 'Hancock'
  }).then(() => {
    console.log('created!')
  })
})

step7 找所有 user
console.log(users[0])// 輸出一個超長的東西,其中 dataValues 是他的值、其他的有些是 Sequelize 會用的東西,所以會用底線開頭

sequelize.sync().then(() => {
// Find all users
// 回傳 promise 所以後面接 .then
  User.findAll().then(users => {
    console.log("All users:", JSON.stringify(users, null, 4)); // JSON.stringify 為了格式化輸出
    console.log('-----')
    console.log(users[0]) // 輸出一個超長的東西

    // 進行存取
    console.log(users[0].id, users[0].firstName) // 有了 1 John
  });
})

如果想指定條件,可以與 where 搭配使用。若指定不存在的資料,會出錯。

更多詳細用法,可以參考官方文件 query 的部份

sequelize.sync().then(() => {
  User.findAll({
    where: {
      firstName: 'Peter'
    }
  }).then(users => {
    console.log(users[0].id, users[0].firstName) // 找到了 2 Peter
  });
})

資料庫的 id 就是 primary key
參考官方文件 Model usage 的範例,只要找一個可以用 findByPkfindOne

確定好有抓對 user,對這個 user 可以直接做操作,他其實是 sequelize 回傳的特定資料格式,所以他有些自己的 method。

sequelize.sync().then(() => {
  User.findOne({
    where: {
      firstName: 'Peter'
    }
  }).then(user => {
    console.log(user.id)
  })
})

更新資料

sequelize.sync().then(() => {
  User.findOne({
    where: {
      firstName: 'Peter'
    }
  }).then(user => {
    user.update({
      lastName: 'hahaha'
    }).then(() => {
      console.log('done')
    })
  })
})

刪除資料

sequelize.sync().then(() => {
  User.findOne({
    where: {
      firstName: 'Peter'
    }
  }).then(user => {
    user.destroy().then(() => {
      console.log('done')
    })
  })
})

小結

  1. sequelize 透過內建的 method,進行 CRUD
  2. 可以參考官方文件 model usage 獲得更多用法和資料,東西都在文件上
  3. sequelize 學習目標:對物件進行操作

資料庫關聯

使用 orm 後要如何在 model 建立關聯?原本使用 sql 指令,靠 userid 透過 join 結合幾個 table

以留言板作為範例

現在我有兩個 model,透過文件 association 的章節,User.hasOne(Project);,表示這個 User 有個 Project

const User = sequelize.define('user', {
  firstName: {
    type: Sequelize.STRING,
    allowNull: false
  },
  lastName: {
    type: Sequelize.STRING
  }
});

const Comment = sequelize.define('comment', {
  content: {
    type: Sequelize.STRING,
  }
});
  1. User.hasMany(Comment) 我建立這兩個 model 的關聯,表示一個使用者可以有很多個評論。加上這行,sequelize 會自動在 Comment model 裡加上 userId
  2. 如果需要新增一個評論:model usage 查詢用法。因為有設定規則,所以點了 userId 會自動連到關聯的 table
  3. 建立多筆資料,找 user 出來
const User = sequelize.define('user', {
  firstName: {
    type: Sequelize.STRING,
    allowNull: false
  },
  lastName: {
    type: Sequelize.STRING
  }
});

const Comment = sequelize.define('comment', {
  content: {
    type: Sequelize.STRING,
  }
});

User.hasMany(Comment) // 一個使用者可以有很多個評論

sequelize.sync().then(() => {
  Comment.create({
    userId: 1,
    content: 'hello'
  }).then(() => {
    console.log('done')
  })
  User.findOne({
    where: {
      firstName: 'Ban'
    }
  }).then(user => {
  })
})

會印出 userId 是 3 使用者發出的評論。所以可以在找使用者時,加上 include,把它底下發過所有評論都撈出來。因為我有加 User.hasMany(Comment)

User.hasMany(Comment)
sequelize.sync().then(() => {
  User.findOne({
    where: {
      firstName: 'Ban'
    },
    include: Comment // 傳 array [] 因為可以引入不同的 model
  }).then(user => {
    console.log(user)
    console.log(user.comments)
    console.log('=====')
    // 變成 json 輸出會將一些東西忽略掉,所以這樣的做法可以讓印出的東西沒有很亂
    console.log(JSON.stringify(user.comments, null, 4))
  })
})

找 comment 時也可以這麼做
將評論印出來

const User = sequelize.define('user', {
  firstName: {
    type: Sequelize.STRING,
    allowNull: false
  },
  lastName: {
    type: Sequelize.STRING
  }
});

const Comment = sequelize.define('comment', {
  content: {
    type: Sequelize.STRING,
  }
});

User.hasMany(Comment) // 一個使用者可以有很多個評論

sequelize.sync().then(() => {
  Comment.findOne({
    where: {
      content: 'hello'
    },
  }).then(comment => {
    console.log(JSON.stringify(comment, null, 4))
  })
})

也可以將評論相對應的 user 給撈出來,藉由加上 include: User,但出錯了 user is not associated to comment!

因為 User.hasMany(Comment) 是單向的,user 有很多評論,但我們並沒有說評論與 user 的關係是甚麼,所以要加上這行 Comment.belongsTo(User),說明這個評論是屬於 user。

要兩個都使用才能建立雙向關係

sequelize.sync().then(() => {
  Comment.findOne({
    where: {
      content: 'hello'
    },
    include: User
  }).then(comment => {
    console.log(JSON.stringify(comment, null, 4))
    console.log(JSON.stringify(comment.user.firstName, null, 4))
  })
})

小結

  1. 透過 Comment.belongsTo(User)User.hasMany(Comment),建立雙向關係,就和用 join 達到的效果是類似的,幫我們進行資料庫表格的關聯。
  2. 更複雜的操作,可以在用到時再進行文件查詢。

#ORM #Sequelize #資料庫關聯







Related Posts

Git 的各種狀況劇

Git 的各種狀況劇

CS50 Tries

CS50 Tries

Hello World

Hello World


Comments