Preliminary preparation
database link
The image in the local docker is used here. The port mapped to the host is 9000. You can execute the following command to pull up your own docker image:
docker run --name mysql1 -e MYSQL_ONETIME_PASSWORD=123456 -p 9000:3306 -d mysql/mysql-server:latest
复制代码
After that we Sequelize
link the database by
// index.js
const { Sequelize } = require('sequelize')
const sequelize = new Sequelize('test1', "root", "123456", {
dialect: 'mysql',
host: "127.0.0.1",
protocol: "http",
port: 9000
})
try {
// 验证数据库是否链接成功
await sequelize.authenticate()
} catch (err) {}
复制代码
One-on-one
1.1 Simulation Scenario
Suppose a user (User) can only have one birth certificate (BitrhAuth) , so here we need to create two tables User
and BirthAuth
, and establish a one-to-one relationship between them
1.2 Table creation and association
1.2.1 Definition table
Note that only the structure of the table is defined here, and this table is not created in the actual database
// model.js
const { Model, INTEGER, STRING, TEXT } = require('sequelize')
// 通过Model的方式来创建表
class User extends Model {
static attributes = {
id: {
field: 'id',
autoIncrement: true,
primaryKey: true,
type: INTEGER
},
name: STRING(255)
}
// 这里的表名,就是创建到数据库中的表名
static tableName = "Buyer"
}
class File extends Model {
static attributes = {
id: {
field: 'id',
autoIncrement: true,
primaryKey: true,
type: INTEGER
},
title: STRING(255),
content: TEXT("medium"),
ownerId: INTEGER
}
static tableName = "Birth_Auth"
}
复制代码
1.2.2 Creating Tables and Association Tables
Use the init
method to create the corresponding table, and sequlize
link the model and database through parameters, and then hasOne
declare the corresponding relationship through
Until the sync
method is used, the corresponding operation is synchronized to the database at this time.
async function init() {
try {
await sequelize.authenticate()
User.init(User.attributes, {
sequelize,
tableName: User.tableName
})
File.init(File.attributes, {
sequelize,
tableName: File.tableName
})
// 一对一创建
// 后续可以通过setUser的方式来创建对应1对1的元素
// 这里的hasOne其实就是User和File的关联关系了
// 通过User的静态变量进行保存,可以在创建user的同时对File进行创建
User.File = User.hasOne(File, {
// 这个foreignKey最终是落在File表中的,就是File的配置
foreignKey: 'ownerId',
// 这个sourceKey目前代表的是一个
sourceKey: "id",
// 这个as非常重要
// 因为在产生关联关系之后,后续sequelize会增加一个setXXX的方法用于同时创建关联关系
// 这个时候直接使用的就是这个as的变量
as: "birth"
})
} catch(e) {
console.log(e)
}
// 直到使用sync才是向数据库同步表的操作~
await sequelize.sync({ force: !!isForce })
}
复制代码
1.2 Create
After completing the above relationship, let's create a data between User
andFile
// 为已有的用户添加出生证明
async function addInExist() {
// 一对一创建
const [user] = await User.findOrCreate({
where: {
name: {
[Op.like]: '%xiaodeng%'
}
},
defaults: {
name: "xiaodeng"
}
})
const file = await File.create({
title: `pi essay ${Math.random()}`,
content: `content ${Math.random()}`
})
// 这里就是通过setBirth来将user和file两者进行关联
await user.setBirth(file)
}
// 通过include model的方式可以同时创建对应一对一、一对多的关系
async function addInCreate() {
await User.create({
name: "xiaoliu",
birth: {
title: "xiaoliuzhengming",
content: 'content'
}
}, {
include: [{
model: File,
as: 'birth'
}]
})
}
复制代码
1.3 Query
When querying, the existing association relationship is queried through option
the parameters in the same as the created scene.include
async function query() {
// 一对一查询
// 一对一和一对多查询
const { rows, count } = await User.findAndCountAll({
include: [{
model: File,
as: 'birth'
}]
})
}
/*
查询出的结果如下
[
{
"id": 1,
"name": "xiaodeng",
"createdAt": "2022-04-05T05:28:08.000Z",
"updatedAt": "2022-04-05T05:28:08.000Z",
// 会同时将birth查出来
"birth": {
"id": 13,
"title": "pi essay 0.5358826142709898",
"content": "content 0.42366009995271936",
"ownerId": 1,
"createdAt": "2022-04-05T05:33:41.000Z",
"updatedAt": "2022-04-05T05:33:41.000Z"
}
}
]
*/
复制代码
Two, one-to-many
2.1 Simulation Scenario
Suppose a user (User) can buy multiple different mobile phones (Phone) , so here we need to create two tables User
and Phone
, and establish a one-to-many relationship between them
2.2 Table creation and association
2.2.1 Definition table
class Phone extends Model {
static attributes = {
id: {
field: 'id',
autoIncrement: true,
primaryKey: true,
type: INTEGER
},
name: STRING(255),
type: STRING(255)
}
static tableName = "Phone"
}
复制代码
2.2.2 Creating Tables and Association Tables
Use the init
method to create the corresponding table, and sequlize
link the model and database through parameters, and then hasMany
declare the corresponding relationship through
function init() {
// 省略1中的部分代码
// 创建Phone这张表
Phone.init(Phone.attributes, {
sequelize,
tableName: Phone.tableName
})
// 一对多的关系
// 后续可以通过Include的方式查询到对应表中的内容
User.Phones = User.hasMany(Phone, {
// sourceKey是User的field
sourceKey: "id",
// 这里的foreignKey是Phone中关联的字段
foreignKey: "ownerId",
as: "phones"
})
}
复制代码
2.3 Create
// User已有,这个时候去添加对应的手机id
async function addOne () {
const [user] = await User.findOrCreate({
where: {
id: 1
}
})
//
const phones = await Phone.bulkCreate([{
name: "iphone 13",
type: "Apple"
}, {
name: "Mate 40 Pro",
type: "HuaWei"
}])
// 这里适用setPhones,为什么是Phones?
// 因为在hasMany创建关联关系中的as就是phones
// 所以这里也是Phones
await user.setPhones(phones)
}
async function addInCreate () {
// 一对多创建
// 同时创建对应的User和其关联的Phone
await User.create({
name: "xiaoze",
phones: [{
name: "IPhone 13 pro max",
type: "Apple"
}, {
name: "Mate P30",
type: "Hua Wei"
}]
}, {
include: [{
model: Phone,
as: 'phones'
}]
})
}
复制代码
2.4 Query
查询其实和一对一一样,想要查具体的字段,直接对应的使用include
,并且关联上相关的model
即可
async function query() {
// 一对一查询
// 一对一和一对多查询
const { rows, count } = await User.findAndCountAll({
where: {
name: {
[Op.like]: '%xiaoze%'
}
},
include: [{
model: File,
as: 'birth'
},{
model: Phone,
as: 'phones'
}]
})
}
/*
[
{
"id": 11,
"name": "xiaoze",
"createdAt": "2022-04-05T07:29:49.000Z",
"updatedAt": "2022-04-05T07:29:49.000Z",
"birth": null,
"phones": [
{
"id": 21,
"name": "IPhone 13 pro max",
"type": "Apple",
"createdAt": "2022-04-05T07:29:49.000Z",
"updatedAt": "2022-04-05T07:29:49.000Z",
"ownerId": 11
},
{
"id": 22,
"name": "Mate P30",
"type": "Hua Wei",
"createdAt": "2022-04-05T07:29:49.000Z",
"updatedAt": "2022-04-05T07:29:49.000Z",
"ownerId": 11
}
]
}
]
*/
复制代码
三、多对多
3.1 模拟场景
假设一个演员 (Actor) 能出演多部电影 (Movie) ,而一本电影 (Movie),也可能有多个演员 (Actor) 所以这里我们需要创建两张表Movie
和Actor
,因为多对多的关系,需要通过中间表MovieActor
来进行维护彼此的关系,所以这次需要三张表。
3.2 表的创建以及关联关系
3.2.1 定义表
class Movie extends Model {
static attributes = {
id: {
field: 'id',
autoIncrement: true,
primaryKey: true,
type: INTEGER
},
name: STRING(255),
fee: INTEGER
}
static tableName = "Movie"
}
class Actor extends Model {
static attributes = {
id: {
field: 'id',
autoIncrement: true,
primaryKey: true,
type: INTEGER
},
sex: STRING(255),
name: STRING(255)
}
static tableName = 'Actor'
}
class MovieActor extends Model {
static attributes = {
actorId: INTEGER,
movieId: INTEGER
}
static tableName = 'MovieActor'
}
复制代码
3.2.2 创建表和关联表
使用init
方法来创建对应的表,并且通过sequlize
参数将模型和数据库进行链接,之后通过belongsToMany
来声明对应的关系
// index.js
function init() {
// ...省略上述已重复的代码
// 需要先将上述三张表进行初始化
Actor.init(Actor.attributes, {
sequelize,
tableName: Actor.tableName
})
Movie.init(Movie.attributes, {
sequelize,
tableName: Movie.tableName
})
MovieActor.init(MovieActor.attributes, {
sequelize,
tableName: MovieActor.tableName
})
// 分别向对面的表申明关系为一对多
// 这里需要注意,多对多的场景,我们需要通过中间表来处理两者的关联关系
// 所以这个through,这个中间表的字段是必填
Actor.belongsToMany(Movie, { through: MovieActor, foreignKey: 'actorId', as: 'movies' })
Movie.belongsToMany(Actor, { through: MovieActor, foreignKey: 'movieId', as: 'actors' })
}
复制代码
3.1 创建
function add () {
const actors = await Actor.bulkCreate([{
name: "wangbaoqiang",
sex: "male"
},{
name: "tongliya",
sex: "female"
}, {
name: "SunHonglei",
sex: "female"
}])
const Movies = await Movie.bulkCreate([{
name: "Qianfu",
fee: 100
}, {
name: "TangTan",
fee: 100
}, {
name: "newMoview",
fee: 200
}])
// 这里的setActors就是从as里面取的,这个就不赘述
await Movies[0].setActors(actors.slice(0, 2))
await Movies[1].setActors([actors[2]])
await Movies[2].setActors(actors)
}
复制代码
3.2 查询
这里因为是多对多的关系,所以无论是从Actors
查询Movie
还是从Movie
查Actors
都是可以的
function query() {
const { rows: movies } = await Movie.findAndCountAll({
include: [{
model: Actor,
as: 'actors'
}]
})
const { rows: actors } = await Actor.findAndCountAll({
include: [{
model: Movie,
as: 'movies'
}]
})
}
复制代码
四、TS支持
4.1 模型定义部分
在TS支持的部分只需要明确几个点:
-
如何把attributes的数据结构能够通过
.
的方式进行提示 -
对于代码中混入的那些
getActors
,setActors
等方法如何提示
其实只需要通过declare
进行声明即可~
import { Model, INTEGER, STRING, HasManySetAssociationsMixin, HasManyGetAssociationsMixin } from 'sequelize'
class Actor extends Model {
static attributes = {
id: {
field: 'id',
autoIncrement: true,
primaryKey: true,
type: INTEGER
},
sex: STRING(255),
name: STRING(255)
}
static tableName = 'Actor'
declare id: number;
declare sex: string;
declare name: string;
declare setMovies: HasManySetAssociationsMixin<Movie, number>
declare getMovies: HasManyGetAssociationsMixin<Movie>
}
class Movie extends Model {
static attributes = {
id: {
field: 'id',
autoIncrement: true,
primaryKey: true,
type: INTEGER
},
name: STRING(255),
fee: INTEGER
}
static tableName = "Movie"
declare id: number;
declare name: string;
declare fee: number;
declare getActors: HasManyGetAssociationsMixin<Actor>
declare setActors: HasManySetAssociationsMixin<Actor, number>
}
class MovieActor extends Model {
static attributes = {
actorId: INTEGER,
movieId: INTEGER
}
static tableName = 'MovieActor'
}
export {
Actor,
Movie,
MovieActor
}
复制代码
4.2 重构演员电影部分的代码
import { Sequelize } from "sequelize";
import { Movie, Actor, MovieActor } from "./models/Actor";
const sequelize = new Sequelize("seq", "root", "123456", {
dialect: "mysql",
host: "127.0.0.1",
protocol: "http",
port: 9000,
});
async function init() {
[Movie, Actor, MovieActor].forEach(async (model) => {
await model.init(model.attributes, {
sequelize,
tableName: model.tableName,
});
});
Movie.belongsToMany(Actor, {
through: MovieActor,
foreignKey: "movieId",
as: "actors",
});
Actor.belongsToMany(Movie, {
through: MovieActor,
foreignKey: "actorId",
as: "movies",
});
return sequelize.sync();
}
async function addData() {
const actors = await Actor.bulkCreate([
{
name: "wangbaoqiang",
sex: "male",
},
{
name: "tongliya",
sex: "female",
},
{
name: "SunHonglei",
sex: "female",
},
]);
const Movies = await Movie.bulkCreate([
{
name: "Qianfu",
fee: 100,
},
{
name: "TangTan",
fee: 100,
},
{
name: "newMoview",
fee: 200,
},
]);
await Movies[0].setActors(actors.slice(0, 2));
await Movies[1].setActors([actors[2]]);
await Movies[2].setActors(actors);
}
async function query() {
const { rows } = await Movie.findAndCountAll({
include: [
{
model: Actor,
as: "actors",
},
],
});
const jsonArray = rows.map((item) => item.toJSON());
console.log(JSON.stringify(jsonArray, null, 2));
}
(async () => {
await init();
await addData();
await query();
await sequelize.close();
})();
复制代码
五、几个Key的简单记忆
Here are a few Keys we use in the use of association relationships: foreignKey
, sourceKey
and targetKey
(not used this time) and hasXX(One/Many)
the BelongToXX
relationship between and, which can be summarized as follows, and I am currently applicable.
Assuming that the relationship we want is N对M
, then the corresponding creation formula can be:
N.hasXXX(M, {foreignKey: 'M的key', sourceKey: 'N的key', as: 'M的别名,用于查询时候setXX时候用'})
N.belongToXX(M, {foreignKey: 'N的key', sourceKey: 'M的key', as: 'M的别名,用于查询和赋值的时候setXX, getXX用'})
From the above rules, it belongToXX
is hasXX
basically the opposite