Sequelize.js是一款針對nodejs的ORM框架。
使用nodejs連接過數據庫的人肯定對數據庫不陌生了。如果是直接鏈接,需要自己建立並管理連接,還需要手動編寫sql語句。簡單的項目到是無所謂,可是一旦項目設計的東西比較複雜,表比較多的時候整個sql的編寫就非常的消耗精力。
在Java、c#等語言中已經有輕量的數據庫框架或者解決方案了。在nodejs中我推薦Sequelize。它是一個很成熟的框架,在速度和性能上也非常有優勢。而其中最關鍵的地方就在於,日常開發只需要管理對象的創建、查詢方法的調用等即可,極少需要編寫sql語句。這一個好處就是省去了複雜的sql語句維護,同時也避免了因sql而引起的不必要的bug。
Sequelize是針對node.js和io.js提供的ORM框架。具體就是突出一個支持廣泛,配置和查詢方法統一。它支持的數據庫包括:PostgreSQL、 MySQL、MariaDB、 SQLite 和 MSSQL。
本文中測試以及API展示地址:github地址
演示
Sequelize的調用突出一個簡單快捷。具體情況可以感受一下下面的代碼。如果有過開發經驗的可以略過。
Table1.findById(23);
//select a,b,c,d from table1 where id=23;
Table1.findAll({
where:{a:"test",b:76}
});
//select a,b,c,d from table1 where a="test" and "b=76;
在單表查詢的時候只需要簡單的配置即可完成查詢。是不是非常的簡單方便呢?
連接數據庫
Sequelize的連接需要傳入參數,並且可以配置開啓線程池、讀寫分庫等操作。
簡單的寫法是這樣的:new Sequelize("表名","用戶名","密碼",配置)
正常使用中很少使用到所有的參數,這裏提供一個常用的模板,只需要修改自己使用的值即可。
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost', //數據庫地址,默認本機
port:'3306',
dialect: 'mysql',
pool: { //連接池設置
max: 5, //最大連接數
min: 0, //最小連接數
idle: 10000
},
});
下面是詳細的配置參數。
const sequelize = new Sequelize('database', 'username', 'password', {
// 數據庫類型,支持: 'mysql', 'sqlite', 'postgres', 'mssql'
dialect: 'mysql',
// 自定義鏈接地址,可以是ip或者域名,默認本機:localhost
host: 'my.server.tld',
// 自定義端口,默認3306
port: 12345,
// postgres使用的參數,連接類型,默認:tcp
protocol: null,
// 是否開始日誌,默認是用console.log
// 建議開啓,方便對照生成的sql語句
logging: true,
// 默認是空
// 支持: 'mysql', 'postgres', 'mssql'
dialectOptions: {
socketPath: '/Applications/MAMP/tmp/mysql/mysql.sock',
supportBigNumbers: true,
bigNumberStrings: true
},
// sqlite的存儲位置,僅sqlite有用
// - 默認 ':memory:'
storage: 'path/to/database.sqlite',
// 是否將undefined轉化爲NULL
// - 默認: false
omitNull: true,
// pg中開啓ssl支持
// - 默認: false
native: true,
// 數據庫默認參數,全局參數
define: {
underscored: false
freezeTableName: false,
charset: 'utf8',
dialectOptions: {
collate: 'utf8_general_ci'
},
timestamps: true
},
// 是否同步
sync: { force: true },
// 連接池配置
pool: {
max: 5,
idle: 30000,
acquire: 60000,
},
isolationLevel: Transaction.ISOLATION_LEVELS.REPEATABLE_READ
})
定義模型對象
在使用之前一定要先創建模型對象。就是數據庫中表的名稱、使用到的字段、字段類型等。
這裏有一個推薦的開發方式。先在nodejs中將對象創建出來,然後調用Sequelize的同步方法,將數據庫自動創建出來。這樣就避免了既要寫代碼建表,又要手工創建數據庫中的表的操作。只需要單獨考慮代碼中的對象類型等屬性就好了。
如果數據庫中已經建好了表,並且不能刪除,這個時候就不能自動創建了,因爲創建的時候會刪除掉舊的數據。
下面是簡單的對象創建多數情況下這樣就可以了。
const users = db.define('t_user'/*自定義表名*/, {
id: {
type: Sequelize.INTEGER,
primaryKey: true, //主鍵
autoIncrement: true, //自增
comment: "自增id" //註釋:只在代碼中有效
},
//用戶名
username: {
type: Sequelize.STRING,
validate:{
isEmail: true, //類型檢測,是否是郵箱格式
}
},
//密碼
pwd: {
type: Sequelize.STRING(10),
allowNull: false,//不允許爲null
},
//狀態
status: {
type: Sequelize.INTEGER,
defaultValue: 0,//默認值是0
},
//暱稱
nickname: {
type: Sequelize.STRING
},
//token
token: {
type: Sequelize.UUID
},
create_time: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
}
}, {
//使用自定義表名
freezeTableName: true,
//去掉默認的添加時間和更新時間
timestamps: false,
indexes:[
//普通索引,默認BTREE
{
unique: true,
fields: ['pid']
},
]
});
//同步:沒有就新建,有就不變
// users.sync();
//先刪除後同步
users.sync({
force: true
});
數據類型
前段將了對象的創建,裏面用到了對象的各種類型。這裏再介紹一下類型的具體使用方式。
Sequelize.STRING //字符串,長度默認255,VARCHAR(255)
Sequelize.STRING(1234) //設定長度的字符串,VARCHAR(1234)
Sequelize.STRING.BINARY //定義類型VARCHAR BINARY
Sequelize.TEXT //長字符串,文本 TEXT
Sequelize.TEXT('tiny') //小文本字符串,TINYTEXT
Sequelize.INTEGER //int數字,int
Sequelize.BIGINT //更大的數字,BIGINT
Sequelize.BIGINT(11) //設定長度的數字,BIGINT(11)
Sequelize.FLOAT //浮點類型,FLOAT
Sequelize.FLOAT(11) //設定長度的浮點,FLOAT(11)
Sequelize.FLOAT(11, 12) //設定長度和小數位數的浮點,FLOAT(11,12)
Sequelize.REAL //REAL PostgreSQL only.
Sequelize.REAL(11) // REAL(11) PostgreSQL only.
Sequelize.REAL(11, 12) // REAL(11,12) PostgreSQL only.
Sequelize.DOUBLE // DOUBLE
Sequelize.DOUBLE(11) // DOUBLE(11)
Sequelize.DOUBLE(11, 12) // DOUBLE(11,12)
Sequelize.DECIMAL // DECIMAL
Sequelize.DECIMAL(10, 2) // DECIMAL(10,2)
Sequelize.DATE // 日期類型,DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres
Sequelize.DATE(6) // mysql 5.6.4+支持,分秒精度爲6位
Sequelize.DATEONLY // 僅日期部分
Sequelize.BOOLEAN // int類型,長度爲1,TINYINT(1)
Sequelize.ENUM('value 1', 'value 2') // 枚舉類型
Sequelize.ARRAY(Sequelize.TEXT) //PostgreSQL only.
Sequelize.ARRAY(Sequelize.ENUM) // PostgreSQL only.
Sequelize.JSON // JSON column. PostgreSQL, SQLite and MySQL only.
Sequelize.JSONB // JSONB column. PostgreSQL only.
Sequelize.BLOB // BLOB (bytea for PostgreSQL)
Sequelize.BLOB('tiny') // TINYBLOB (bytea for PostgreSQL. Other options are medium and long)
Sequelize.UUID // PostgreSQL和SQLite的數據類型是UUID, MySQL是CHAR(36)類型
Sequelize.CIDR // PostgreSQL中的CIDR類型
Sequelize.INET // PostgreSQL中的INET類型
Sequelize.MACADDR // PostgreSQL中的MACADDR類型
Sequelize.RANGE(Sequelize.INTEGER) //PostgreSQL only.
Sequelize.RANGE(Sequelize.BIGINT) // PostgreSQL only.
Sequelize.RANGE(Sequelize.DATE) //PostgreSQL only.
Sequelize.RANGE(Sequelize.DATEONLY) //PostgreSQL only.
Sequelize.RANGE(Sequelize.DECIMAL) //PostgreSQL only.
Sequelize.ARRAY(Sequelize.RANGE(Sequelize.DATE)) // PostgreSQL only.
Sequelize.GEOMETRY //PostgreSQL (with PostGIS) or MySQL only.
Sequelize.GEOMETRY('POINT') // PostgreSQL (with PostGIS) or MySQL only.
Sequelize.GEOMETRY('POINT', 4326)// PostgreSQL (with PostGIS) or MySQL only.
數據類型檢測
上面可以看到使用validate字段去驗證字段的值是否符合標準,這樣就可以在入庫之前就能知道數據是否符合規則。否則貿然將陌生的數據存入數據庫就好像將陌生人帶到家裏一樣,是否安全全靠緣分啊。
Sequelize內置支持的驗證還是非常的多的,如果這些都不滿意,還可以自己定義一個。
validate: {
is: ["^[a-z]+$",'i'], // 全匹配字母
is: /^[a-z]+$/i, // 全匹配字母,用規則表達式寫法
not: ["[a-z]",'i'], // 不能包含字母
isEmail: true, // 檢查郵件格式
isUrl: true, // 是否是合法網址
isIP: true, // 是否是合法IP地址
isIPv4: true, // 是否是合法IPv4地址
isIPv6: true, // 是否是合法IPv6地址
isAlpha: true, // 是否是字母
isAlphanumeric: true, // 是否是數字和字母
isNumeric: true, // 只允許數字
isInt: true, // 只允許整數
isFloat: true, // 是否是浮點數
isDecimal: true, // 是否是十進制書
isLowercase: true, // 是否是小寫
isUppercase: true, // 是否大寫
notNull: true, // 不允許爲null
isNull: true, // 是否是null
notEmpty: true, // 不允許爲空
equals: 'specific value', // 等於某些值
contains: 'foo', // 包含某些字符
notIn: [['foo', 'bar']], // 不在列表中
isIn: [['foo', 'bar']], // 在列表中
notContains: 'bar', // 不包含
len: [2,10], // 長度範圍
isUUID: 4, // 是否是合法 uuids
isDate: true, // 是否是有效日期
isAfter: "2011-11-05", // 是否晚於某個日期
isBefore: "2011-11-05", // 是否早於某個日期
max: 23, // 最大值
min: 23, // 最小值
isArray: true, // 是否是數組
isCreditCard: true, // 是否是有效信用卡號
// 自定義規則
isEven: function(value) {
if(parseInt(value) % 2 != 0) {
throw new Error('請輸入偶數!')
}
}
API略講
Sequelize的API基本覆蓋了常用的使用方式,其中單表查詢常用的有一下幾種。複雜的可以參考更多的API。
查詢多條 findAll(opts) 或者 all(opts)
查詢用的參數普遍通用,只有部分API的有特殊參數。這裏展示一次常用參數,下面就略過了。
let list = await model.findAll({
where:{
id:{$gt:10},//id大於10的
name:"test" //name等於test
},
order:[
"id", //根據id排序
["id","desc"]//根據id倒序
],
limit:10,//返回個數
offset:20,//起始位置,跳過數量
attributes:["attr1","attr2"], //返回的字段
});
//select attr1,attr2 from model where ......
通過id查詢 findById(id,opts)
這裏默認數據的主鍵是id,查詢的時候直接通過id查詢數據。這裏推薦在新建數據庫的時候可以添加id作爲唯一主鍵。
let model = await model.findById(12);
//select a,b,c from model where id=12;
查詢一條記錄 findOne(opts)
根據條件查詢記錄,這裏的條件一定要填寫,不然就是返回第一條數據了。
let model = await model.findOne({
where:{id:12}
});
//select a,b,c from model where id=12;
分頁查詢 findAndCount(opts) 或者 findAndCountAll
分頁查詢恐怕是另外一個常用方法了。任何一個列表都有需要分頁的時候。
這個方法會同時執行2跳語句。
let data = await model.findAndCount({
limit:10,//每頁10條
offset:0*10,//第x頁*每頁個數
where:{}
});
let list = data.rows;
let count = data.count;
//select count(*) from model where ...;
//select a,b,c from model where .... limit 0,10;
添加新數據 create(model,opts)
添加就非常的自在了。簡單的只需要傳入model對象即可。這裏要保證model對象的屬性和字段名要一致。如果不一致就會出錯。也可以傳入配置參數來增加條件等。
let model= {
name:"test",
token:"adwadfv2324"
}
await model.create(model);
//insert into model (name,token) values("test","adwadfv2324");
查詢,不存在就返回默認對象 findOrInitialize(opts)
opts.default 默認值對象
這個方法首先會查詢數據庫,如果沒有結果就會返回參數中的default對象。這個比較適合返回默認對象之類的場景。
查詢,不存在就新建一個 findOrCreate(opts)或者findCreateFind
這個方法用到的情況也比較多。通常用於自動創建不存在的數據。直接就返回了默認值。
有則更新,無則添加 upsert(model,opts) 或者 insertOrUpdate(model,opts)
根據主鍵或者唯一約束鍵匹配
常用於編輯的時候添加或者更新統一操作。
更新記錄 update(model,opts)
就是最常用的更新方法,可以傳入要更新的model對象,同時用配置參數有條件的區別要更新的對象。
刪除記錄 destroy(opts)
刪除有2種情況,一種是物理刪除。刪除就從表中不存在了。另外一種就是設置paranoid,這個是虛擬刪除,默認一個字段表示數據是否刪除,查詢的時候去掉這個條件即可查詢到刪除的數據。
恢復記錄 restore(opts)
恢復多個實例,當啓用paranoid時就可以使用這個方法將曾今刪除的數據恢復了。
其他常用API
- 指定字段查詢最大值 max(“id”,opts)
- 指定字段查詢最小值 min(“id”,opts)
- 求和 sum(“id”,opts)
- 批量添加 bulkCreate([model],opts)
- 查表結構的信息 describe()
- 遞增 increment(“id”,{by:1})
- 遞減 decrement(“id”,{by:1})
- 統計查詢個數 count(opts)
事務
Sequelize中的事務比較簡單。但是如果有多個事務的話寫出來的代碼會非常的難看。這也算是Sequelize優化的比較差的地方了。
需要記得transaction參數要一致傳遞就可以了。其他就是一個正常的Promise調用。
//調用Sequelize初始化之後的sequelize對象
return sequelize.transaction(function (t) {
//返回最終的Promise
return User.create({
firstName: 'Abraham',
lastName: 'Lincoln'
}, {transaction: t}).then(function (user) {
return user.setShooter({
firstName: 'John',
lastName: 'Boothe'
}, {transaction: t});
});
}).then(function (result) {
//主動調用commit提交結果
return t.commit();
}).catch(function (err) {
//主動回滾操作
return t.rollback();
});
多表聯查
外鍵可能算是Sequelize中的一個難點了。這裏涉及的東西稍微多一點,我們來慢慢捋一遍。
外鍵知識點
外鍵的定製作用—-三種約束模式:
- district:嚴格模式(默認), 父表不能刪除或更新一個被子表引用的記錄。
- cascade:級聯模式, 父表操作後,子表關聯的數據也跟着一起操作。也是Sequelize的默認模式。
- set null:置空模式,前提外鍵字段允許爲NLL, 父表操作後,子表對應的字段被置空。
使用外鍵的前提
在Sequelize中使用外鍵需要提前檢查一下下面的這些選項,裏面有一條出錯就會導致設置失敗。
- 表儲存引擎必須是innodb,否則創建的外鍵無約束效果。
- 外鍵的列類型必須與父表的主鍵類型完全一致。
- 外鍵的名字不能重複。
- 已經存在數據的字段被設爲外鍵時,必須保證字段中的數據與父表的主鍵數據對應起來。
使用示例—默認
默認情況下,主鍵使用的是主表的id字段,外鍵是使用的按照table+字段的方式建立的外鍵。一般情況下需要手動指定。
//主表指定關係
test1.hasMany(test2, {
foreignKey: "pid",//外鍵名稱
});
//子表指定關係
test2.belongsTo(test1, {
foreignKey: "pid",//外鍵名稱
});
默認就會在子表中添加一條外鍵記錄,指向的就是主表的id。一般情況下這樣就能夠滿足正常的使用了。比如一個主表記錄商品信息,一個子表記錄多個評論消息。
使用示例—自定義
如果主表使用的主鍵id並不能滿足正常的使用,還可以指定某一個固定的字段作爲主表中的約束關係。
tips:主表中如果不是使用id作爲主要關係,自定義的字段必須添加索引等條件,作爲依賴中的關係。
test1.hasMany(test2, {
foreignKey: "pid",//外鍵字段名
sourceKey: "pid",//主鍵字段名
});
test2.belongsTo(test1, {
foreignKey: "pid",//關聯名
targetKey:"pid"//自定義外鍵字段
});
//等待主鍵建立成功再建立子表的外鍵關係
setTimeout(() => {
test2.sync({
force: true
});
}, 2500);
使用示例—僞關係
實際使用的時候我還是傾向於這種關係。即表中關係已定的情況下僅僅指定外鍵關係。同步的時候僅僅同步表內容,不同步這個外鍵關係。
真正的建立可以使用手動建表的時候添加。或者也可以在自動建表結束後異步再執行一次外鍵關係的添加。
test1.hasMany(test2, {
foreignKey: "pid",
sourceKey: "pid",
constraints: false //不同步建立外鍵關係
});
test2.belongsTo(test1, {
foreignKey: "pid",
targetKey:"pid",
constraints: false //不同步建立外鍵關係
});
示例
實際的操作部分大家可以看github中的test.js。github地址
單表操作
Sequelize在查詢結果返回之後會返回一個它自定義的對象。這個對象是支持繼續操作的,其中具體的值存放在datavalues中。不過可以放心的是在轉化爲字符串的時候是不會帶有任何Sequelize的屬性的。
//根據條件查詢一條數據
let model = await test1.findOne({
where:{
id:5,
name:"test"
}
});
//修改其中的name字段的值
model.name="更新";
//保存,會自動update數據庫中的值
model.save();
聯查
正常的使用過程中很少會說只需要查詢一個表就能結果問題的。這裏再說一下2個表查詢的時候是怎麼使用的。
這裏的查詢默認已經做好了外鍵的的關係。不過在使用的時候不做也是可以的,就是在查詢的時候性能稍微不好而已。
//查詢主表list的數據
//一條list中的數據對應多條item中的數據
let data = await models.List.findAll({
where:{id:5},//條件,這裏jiashe只需查詢一條
include: [{
model: models.Item,
as:"items",//返回的對象修改成一個固定的名稱
}]
});
let list1=data[0];//返回的第一條數據就是要查詢的數據
let list2=list1.items;//返回子表數據,items是自定義的名稱
總結
上面的介紹已經解決了大多數情況下的查詢等操作。而且我也相信,真的遇到了瓶頸,解決方案很可能也並不是在Sequelize方面,或者說主要不是Sequelize的問題。比如大數據量的時候分表操作,就涉及到了更多的知識點。
nodejs在做後端方面還處於發展階段。希望有更多的前端能夠接觸並瞭解它。不僅僅在開發過程中對自己是一個增強,在長期的職業規劃中也是一個很好的增強自己的武器。