sequelize 應用hook 實現對分表的訪問


https://github.com/cclient/sequelize-sharding
https://www.npmjs.com/package/sequelize-sharding

實際有效的代碼就幾行,主要時間都花在這篇博客和readme.md上

————————————————

公司里有node項目用sequelize插件操作mysql

mysql部分表涉及到分表方案,而sequelizel 默認不支持分表

mysql服務端運維層面,java客戶端應用層面,對分庫分表的支持比較成熟,而nodejs客戶端就比較欠缺了

sequelize默認不支持,只能選擇手動擴展

思路和java方面的一致,都很簡單,就是在dao操作前后作一個切面,定制個改要操作的庫/表,不只是分庫分表,讀寫分離,異構表優化,多租戶資源隔離,都是按這種思路作的,sequelize也一致

簡單梳理了一下sequelize的執行結構

用戶操作直接面對的是Model

Model定義類似

export class User {
    constructor(id: number,name: string,created_on: Date,updated_on: Date) {
        this.id = id
        this.name = name
        this.created_on = created_on
        this.updated_on = updated_on
    }
    id: number
    name: string
    created_on: Date
    updated_on: Date
}


const USER_ORM_MAP={    
    id: { type: Sequelize.INTEGER, autoIncrement: true,primaryKey: true },
    name : Sequelize.STRING,
    created_on : Sequelize.DATE,
    updated_on : Sequelize.DATE
}

const DUser = sequelize.define("user", USER_ORM_MAP,{ freezeTableName: true,createdAt:false,updatedAt:false,deletedAt:false});

 

所有的dao操作都過Model類的實例DUser完成

export async function  getUsersByIds(ids:Array<string>) :Promise<Array<User>>
{
    if(site_ids.length==0){
        return [];
    }
    let hasUsers= await DUser.findAll({where:{id:ids}})
    return hasUsers as Array<User>;
}

 

Model源碼
https://github.com/sequelize/sequelize/blob/b37985d550e8aeb2bc518f3bd5dcb09c95a142d8/lib/model.js
內有兩個核心對象

static get QueryInterface() {
    return this.sequelize.getQueryInterface();
}

static get QueryGenerator() {
    return this.QueryInterface.QueryGenerator;
}

 

Model對sql的操作通過QueryGenerator實現

https://github.com/sequelize/sequelize/blob/b37985d550e8aeb2bc518f3bd5dcb09c95a142d8/test/unit/sql/insert.test.js

const Support   = require('../support'),
  DataTypes = require('../../../lib/data-types'),
  expectsql = Support.expectsql,
  current   = Support.sequelize,
  sql       = current.dialect.QueryGenerator;

expectsql(sql.insertQuery(User.tableName, {user_name: 'triggertest'}, User.rawAttributes, options),
        {
          query: {
            mssql: 'declare @tmp table ([id] INTEGER,[user_name] NVARCHAR(255));INSERT INTO [users] ([user_name]) OUTPUT INSERTED.[id],INSERTED.[user_name] into @tmp VALUES ($1);select * from @tmp;',
            postgres: 'INSERT INTO "users" ("user_name") VALUES ($1) RETURNING *;',
            default: 'INSERT INTO `users` (`user_name`) VALUES ($1);'
          },
          bind: ['triggertest']
        });

 

源碼不用再看,到這里就夠了

sql = current.dialect.QueryGenerator;
sql.insertQuery(User.tableName, {user_name: 'triggertest'}, User.rawAttributes, options)

 

再看Model的源碼 tableName的部分相關信息

* @param {string}                  [options.tableName] Defaults to pluralized model name, unless freezeTableName is true, in which case it uses model name verbatim

if (!this.options.tableName) {
      this.tableName = this.options.freezeTableName ? this.name : Utils.underscoredIf(Utils.pluralize(this.name), this.underscored);
    } else {
      this.tableName = this.options.tableName;
    }


static getTableName() { // testhint options:none
    return this.QueryGenerator.addSchema(this);
}

 

默認不公開tableName字段,也不提供setTableName,tableName完全在定義時構造

理論上更改User.tableName,即可完成換表操作

測試

if (!module.parent) {
getUsersByIds([1,2,3,4]).then(u1=>{
        DUser.tableName="user2";
}).then(()=>{
        getUsersByIds([1,2,3,4]).then(bbb=>{
        })
})
}

 

打印日志

sequelize deprecated String based operators are now deprecated. Please use Symbol based operators for better security, read more at http://docs.sequelizejs.com/manual/tutorial/querying.html#operators node_modules/sequelize/lib/sequelize.js:237:13

Executing (default): SELECT `id`, `name`, `created_on`, `updated_on` FROM `user` AS `user` WHERE `user`.`id` IN ('1', '2');

Executing (default): SELECT `id`, `name`, `created_on`, `updated_on` FROM `user2` AS `user` WHERE `user`.`id` IN ('1', '2');

驗證成功

也幸好tableName並未設為不可修改,不然改sequelize源碼還得多花點工夫

剩下就是功能開發了

在User操作之前

let customTableName=getCustomTableName(user);
User.tableName=customTableName;
User.insert(user);

這樣作代碼侵入性較高,少點改改還可以,大規模應用則太不優雅

優雅的方案
1 定制修改 sequelize 代碼,使支持分表,對sequelize源碼不是非常熟悉,滿足某列動態生成的的需求,最多1天搞定,如果想優雅的提到源碼里,要花的工夫就多了
2 定制切面 shimmer,這是nodejs aop編程的工具,目的是aop,實現方式和java不同,java是運行時通過反射動態構造類,nodejs是在包裹原始包的的對象,像是裝飾者模式
3 思路和2類似,其實sequelize提了原生的切面,用原生切面即可

sequelize提供幾項Hooks
https://github.com/sequelize/sequelize/blob/master/docs/hooks.md
原本是為數據操作提供入口

User.addHook('beforeValidate', (user, options) => {
  user.mood = 'happy';
});

 


主要是操作對象是user實體,hooks算是sequelize默認提供的部分切面

我們可以看看在部分hooks上能否實現對User.tableName的更改

查看所有 hooks https://github.com/sequelize/sequelize/blob/master/lib/hooks.js

先在beforeFind上作測試

if (!module.parent) {
    User.addHook('beforeFind', (user, options) => {
        User.tableName="user2";
        console.log("beforeFind","set custom tableName",User.tableName)
      })

getUsersByIds([1,2,3,4]).then(u1=>{
}).then(()=>{
        getUsersByIds([1,2,3,4]).then(bbb=>{
        })
})
}

 

看日志 果然生效

最終選型,先用方案3快速實現,方案3完成后,有精力用方案1實現,方案2因為方案3的存在,沒什么應用的價值

----

源碼寫的不是很細致,只是滿足現階段最初級的需求,更細化和其他拓展需求,可以用同樣的思路實現

去掉異常檢測的代碼,可以去掉所有依賴

實現用ts,看sequelize原生類結構清晰,測試則是js,js原生代碼很久不寫了,湊合吧

最后其實分庫分表分區的應用,有點過氣的感覺(應用還是很廣,但因為各方面都很成熟完備,沒有再討論優化的空間,顯得很冷清),有空可以單寫一篇出來,技術性的文章太多了,主要想扯點非技術性的東西,給過氣的東西一個總結


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM