├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── need-help-issue.md ├── CHANGELOG.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── Readme.md ├── _config.yml ├── advanced-association-concepts ├── advanced-many-to-many.md ├── association-scopes.md ├── creating-with-associations.md ├── eager-loading.md └── polymorphic-associations.md ├── core-concepts ├── assocs.md ├── getters-setters-virtuals.md ├── getting-started.md ├── model-basics.md ├── model-instances.md ├── model-querying-basics.md ├── model-querying-finders.md ├── paranoid.md ├── raw-queries.md └── validations-and-constraints.md └── other-topics ├── connection-pool.md ├── constraints-and-circularities.md ├── dialect-specific-things.md ├── extending-data-types.md ├── hooks.md ├── indexes.md ├── legacy.md ├── migrations.md ├── naming-strategies.md ├── optimistic-locking.md ├── other-data-types.md ├── query-interface.md ├── read-replication.md ├── resources.md ├── scopes.md ├── sub-queries.md ├── transactions.md ├── typescript.md ├── upgrade-to-v6.md └── upgrade-to-v7.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # demopark 4 | patreon: demopark 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ["https://raw.githubusercontent.com/demopark/electron-api-demos-Zh_CN/master/assets/img/td.png",] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help improve(翻译问题反馈) 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **sequelize 文档版本** 11 | (对应的文档版本) 12 | 13 | **sequelize 目录章节** 14 | (对应的目录章节版本) 15 | 16 | **反馈描述** 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/need-help-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Need help issue 3 | about: Question for use (问题求助) 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | **当前使用的 Node.js 版本 (node -v)** 11 | (可用 node -v 命令 查看) 12 | 13 | **当前使用的 sequelize 版本** 14 | (package.json 所指定的版本) 15 | 16 | **问题描述** 17 | (描述出现的或寻求解决的问题) 18 | 19 | **代码片段** 20 | ``` 21 | // 相关代码或报错信息 22 | ``` 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 更新记录 2 | 3 | ## 2023-06-19 4 | * master 分支同步到 7.0.0-alpha.23 5 | 6 | ## 2022-02-10 7 | * master 分支同步翻译到 v7 文档 7.1.0 8 | * v6 文档启用单独分支 9 | 10 | ## 2022-02-9 11 | * master 分支同步翻译到 v6 文档 6.16.0 12 | 13 | ## 2021-10-8 14 | * master 分支同步翻译到 v6 文档 6.6.5 15 | 16 | ## 2021-4-12 17 | * master 分支同步翻译到 v6 文档 6.6.2 18 | 19 | ## 2020-11-24 20 | * master 分支同步翻译到 v6 文档 6.3.5 21 | 22 | ## 2020-07-29 23 | * master 分支同步翻译到 v6 文档 6.3.3 24 | 25 | ## 2020-03-13 26 | * master 同步提交 v6-beta 文档 27 | 28 | ## 2020-02-25 29 | * v5分支同步翻译到v5文档 5.21.5 30 | 31 | ## 2019-10-23 32 | * master分支同步翻译到v5文档 5.21.1 33 | 34 | ## 2019-9-5 35 | * master分支同步翻译到v5文档 5.18.1 36 | 37 | ## 2019-5-28 38 | * master分支同步翻译到v5文档 5.8.6 39 | 40 | ## 2019-5-27 41 | * v4文档启用单独分支 42 | 43 | ## 2019-2-14 44 | * 同步官方最新文档版本 4.42.0 45 | 46 | ## 2018-6-22 47 | * 同步官方最新文档版本 4.38.0 48 | 49 | ## 2018-5-24 50 | * 同步官方最新文档版本 4.37.8 51 | * 增加了 v5 的升级说明和部分介绍 52 | 53 | ## 2018-2-23 54 | * 修复部分内部链接 55 | * 同步官方最新文档版本 4.33.4 56 | 57 | ## 2018-1-22 58 | * 同步官方最新文档版本 4.31.2 59 | 60 | ## 2017-12-22 61 | * 同步官方最新文档版本 4.28.6 62 | 63 | ## 2017-11-23 64 | * 同步官方最新文档版本 4.22.12 65 | 66 | ## 2017-10-19 67 | * 修改部分方法名使用原文 68 | 69 | ## 2017-10-16 70 | * 文档翻译完成 71 | 72 | 73 | ## 2017-10-12 74 | 75 | ### 更新 76 | * 同步官方最新文档版本 4.13.8 77 | * 翻译 getting-started.md 78 | * 翻译 models-definition.md 79 | * 翻译 models-usage.md 80 | * 翻译 querying.md 81 | 82 | ## 2017-07-14 83 | 84 | ### 新增 85 | * 增加来自 v4 的原文文档 86 | * 增加 README 文档 87 | * 增加 CHANGELOG 文档 -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 1. 文档问题 2 | * sequelize 文档版本 3 | * sequelize 目录章节 4 | 2. 技术问题 5 | * 当前使用的 Node.js 版本 (node -v) 6 | * 当前使用的 sequelize 版本 (package.json) 7 | * 相关代码或报错信息 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Sequelize Docs 中文版 2 | 3 | ![](http://docs.sequelizejs.com/manual/asset/logo-small.png) 4 | 5 | [![npm version](https://badgen.net/npm/v/@sequelize/core)](https://www.npmjs.com/package/@sequelize/core) 6 | [![npm downloads](https://badgen.net/npm/dm/@sequelize/core)](https://www.npmjs.com/package/@sequelize/core) 7 | [![contributors](https://img.shields.io/github/contributors/sequelize/sequelize)](https://github.com/sequelize/sequelize/graphs/contributors) 8 | [![Open Collective](https://img.shields.io/opencollective/backers/sequelize)](https://opencollective.com/sequelize#section-contributors) 9 | [![sponsor](https://img.shields.io/opencollective/all/sequelize?label=sponsors)](https://opencollective.com/sequelize) 10 | [![Merged PRs](https://badgen.net/github/merged-prs/sequelize/sequelize)](https://github.com/sequelize/sequelize) 11 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 12 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 13 | 14 | > 此项目同步自 [sequelize](https://github.com/sequelize) / [sequelize](https://github.com/sequelize/sequelize) 项目. 15 | > 16 | > 更新日志请参阅: [CHANGELOG](CHANGELOG.md) 17 | 18 | Sequelize 是一个易用且基于 promise 的 [Node.js](https://nodejs.org/en/about/) [ORM 工具](https://en.wikipedia.org/wiki/Object-relational_mapping) 适用于 [Postgres](https://en.wikipedia.org/wiki/PostgreSQL), [MySQL](https://en.wikipedia.org/wiki/MySQL), [MariaDB](https://en.wikipedia.org/wiki/MariaDB), [SQLite](https://en.wikipedia.org/wiki/SQLite), [DB2](https://en.wikipedia.org/wiki/IBM_Db2_Family), [Microsoft SQL Server](https://en.wikipedia.org/wiki/Microsoft_SQL_Server), [Snowflake](https://www.snowflake.com/), [Oracle DB](https://www.oracle.com/database/) 和 [Db2 for IBM i](https://www.ibm.com/support/pages/db2-ibm-i). 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能. 19 | 20 | Sequelize 遵从 [语义版本控制](http://semver.org) 和 [官方 Node.js LTS 版本](https://nodejs.org/en/about/releases/). Sequelize v7 版本正式支持 Node.js `^14.17,0`, `^16.0.0`. 其他版本或可正常工作. 21 | 22 | 你目前正在查看 Sequelize 的**教程和指南**.你可能还对[API 参考](https://sequelize.org/api/v7/) (英文)感兴趣. 23 | 24 | 25 | ![赞赏支持](https://raw.githubusercontent.com/demopark/electron-api-demos-Zh_CN/master/assets/img/td.png) 26 | 27 | 28 | ## 文档版本 29 | 30 | - [v7 中文文档](https://github.com/demopark/sequelize-docs-Zh-CN/tree/master)(开发版本) 31 | 32 | - [v6 中文文档](https://github.com/demopark/sequelize-docs-Zh-CN/tree/v6)(保持更新) 33 | 34 | - [v5 中文文档](https://github.com/demopark/sequelize-docs-Zh-CN/tree/v5)(停止更新) 35 | 36 | - [v4 中文文档](https://github.com/demopark/sequelize-docs-Zh-CN/tree/v4)(停止更新) 37 | 38 | ## 主要版本变更日志 39 | 40 | 在此处可以找到主要版本的升级信息: 41 | 42 | - [从 v5 升级到 v6](other-topics/upgrade-to-v6.md) 43 | - [从 v6 升级到 v7](other-topics/upgrade-to-v7.md) 44 | 45 | ## 文档(v7-alpha) 46 | 47 | **注意** 由于当前alpha阶段api调整, 文档中的API参考指向尚未确定. 可前往 [V7 API 参考](https://sequelize.org/api/v7/)自行查询. 48 | 49 | ### 核心概念 50 | 51 | - [Getting Started - 入门](core-concepts/getting-started.md) 52 | - [Model Basics - 模型基础](core-concepts/model-basics.md) 53 | - [Model Instances - 模型实例](core-concepts/model-instances.md) 54 | - [Model Querying - Basics - 模型查询(基础)](core-concepts/model-querying-basics.md) 55 | - [Model Querying - Finders - 模型查询(查找器)](core-concepts/model-querying-finders.md) 56 | - [Getters, Setters & Virtuals - 获取器, 设置器 & 虚拟字段](core-concepts/getters-setters-virtuals.md) 57 | - [Validations & Constraints - 验证 & 约束](core-concepts/validations-and-constraints.md) 58 | - [Raw Queries - 原始查询](core-concepts/raw-queries.md) 59 | - [Associations - 关联](core-concepts/assocs.md) 60 | - [Paranoid - 偏执表](core-concepts/paranoid.md) 61 | 62 | ### 高级关联概念 63 | 64 | - [Eager Loading - 预先加载](advanced-association-concepts/eager-loading.md) 65 | - [Creating with Associations - 创建关联](advanced-association-concepts/creating-with-associations.md) 66 | - [Advanced M:N Associations - 高级 M:N 关联](advanced-association-concepts/advanced-many-to-many.md) 67 | - [Association Scopes - 关联作用域](advanced-association-concepts/association-scopes.md) 68 | - [Polymorphic Associations - 多态关联](advanced-association-concepts/polymorphic-associations.md) 69 | 70 | ### 其它主题 71 | 72 | - [Dialect-Specific Things - 方言特定事项](other-topics/dialect-specific-things.md) 73 | - [Transactions - 事务](other-topics/transactions.md) 74 | - [Hooks - 钩子](other-topics/hooks.md) 75 | - [Query Interface - 查询接口](other-topics/query-interface.md) 76 | - [Naming Strategies - 命名策略](other-topics/naming-strategies.md) 77 | - [Scopes - 作用域](other-topics/scopes.md) 78 | - [Sub Queries - 子查询](other-topics/sub-queries.md) 79 | - [Other Data Types - 其他数据类型](other-topics/other-data-types.md) 80 | - [Constraints & Circularities - 约束 & 循环](other-topics/constraints-and-circularities.md) 81 | - [Extending Data Types - 扩展数据类型](other-topics/extending-data-types.md) 82 | - [Indexes - 索引](other-topics/indexes.md) 83 | - [Optimistic Locking - 乐观锁定](other-topics/optimistic-locking.md) 84 | - [Read Replication - 读取复制](other-topics/read-replication.md) 85 | - [Connection Pool - 连接池](other-topics/connection-pool.md) 86 | - [Working with Legacy Tables - 使用遗留表](other-topics/legacy.md) 87 | - [Migrations - 迁移](other-topics/migrations.md) 88 | - [TypeScript](other-topics/typescript.md) 89 | - [Resources - 资源](other-topics/resources.md) 90 | 91 | ## 安装 92 | 93 | ```sh 94 | # 使用 npm 95 | npm install sequelize # 这将安装最新版本的 Sequelize 96 | # 使用 yarn 97 | yarn add sequelize 98 | ``` 99 | 100 | ```sh 101 | # 用于支持数据库方言的库: 102 | # 使用 npm 103 | npm i pg pg-hstore # PostgreSQL 104 | npm i mysql2 # MySQL 105 | npm i mariadb # MariaDB 106 | npm i sqlite3 # SQLite 107 | npm i tedious # Microsoft SQL Server 108 | npm i ibm_db # DB2 109 | npm i odbc # IBM i 110 | 111 | # 使用 yarn 112 | yarn add pg pg-hstore # PostgreSQL 113 | yarn add mysql2 # MySQL 114 | yarn add mariadb # MariaDB 115 | yarn add sqlite3 # SQLite 116 | yarn add tedious # Microsoft SQL Server 117 | yarn add ibm_db # DB2 118 | yarn add odbc # IBM i 119 | ``` 120 | 121 | ## 简单示例 122 | 123 | #### TypeScript 124 | 125 | ```javascript 126 | import { Sequelize, Model, DataTypes, InferAttributes, InferCreationAttributes } from 'sequelize'; 127 | 128 | const sequelize = new Sequelize('sqlite::memory:'); 129 | 130 | class User extends Model, InferCreationAttributes> { 131 | declare username: string | null; 132 | declare birthday: Date | null; 133 | } 134 | 135 | User.init({ 136 | username: DataTypes.STRING, 137 | birthday: DataTypes.DATE 138 | }, { sequelize, modelName: 'user' }); 139 | 140 | (async () => { 141 | await sequelize.sync(); 142 | const jane = await User.create({ 143 | username: 'janedoe', 144 | birthday: new Date(1980, 6, 20), 145 | }); 146 | console.log(jane.toJSON()); 147 | })(); 148 | ``` 149 | 150 | #### JavaScript (CJS) 151 | 152 | ```javascript 153 | const { Sequelize, Model, DataTypes } = require('sequelize'); 154 | const sequelize = new Sequelize('sqlite::memory:'); 155 | 156 | class User extends Model {} 157 | User.init({ 158 | username: DataTypes.STRING, 159 | birthday: DataTypes.DATE 160 | }, { sequelize, modelName: 'user' }); 161 | 162 | (async () => { 163 | await sequelize.sync(); 164 | const jane = await User.create({ 165 | username: 'janedoe', 166 | birthday: new Date(1980, 6, 20) 167 | }); 168 | console.log(jane.toJSON()); 169 | })(); 170 | ``` 171 | 172 | 请通过 [Getting started - 入门](core-concepts/getting-started.md) 来学习更多相关内容. 如果你想要学习 Sequelize API 请通过 [API 参考](https://sequelize.org/api/v7/) (英文). 173 | 174 | ## 下载量趋势 175 | 176 | [近五年Sequelize下载量趋势](https://npm-compare.com/img/npm-trend/FIVE_YEARS/sequelize.png) 177 | 178 | 179 | NPM Usage Trend of sequelize 180 | 181 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /advanced-association-concepts/association-scopes.md: -------------------------------------------------------------------------------- 1 | # Association Scopes - 关联作用域 2 | 3 | 本节涉及关联作用域,它们与[模型作用域](../other-topics/scopes.md)类似但不相同. 4 | 5 | 关联作用域既可以放在关联的模型(关联的目标)上,也可以放在多对多关系的联结表中. 6 | 7 | ## 概念 8 | 9 | 与[模型作用域](../other-topics/scopes.md)如何自动应用于模型静态调用(例如 `Model.scope('foo').findAll()`)类似,关联作用域也是一个规则(更确切地说, 一组默认属性和参数),这些属性会自动应用于模型中的实例调用. 这里,*实例调用*是指从实例(而不是从 Model 本身)调用的方法调用. Mixins 是实例方法的主要示例(`instance.getSomething`, `instance.setSomething`, `instance.addSomething` 和 `instance.createSomething`). 10 | 11 | 关联作用域的行为就像模型作用域一样,在某种意义上,两者都导致将诸如 `where` 子句之类的内容自动应用到查找器调用; 区别在于,关联作用域不是自动应用于静态finder调用(模型范围就是这种情况),而是自动应用于实例finder调用(例如mixins). 12 | 13 | ## 示例 14 | 15 | 下面显示了模型 `Foo` 和 `Bar` 之间的一对多关联的关联范围的基本示例. 16 | 17 | * 设置: 18 | 19 | ```js 20 | const Foo = sequelize.define('foo', { name: DataTypes.STRING }); 21 | const Bar = sequelize.define('bar', { status: DataTypes.STRING }); 22 | Foo.hasMany(Bar, { 23 | scope: { 24 | status: 'open' 25 | }, 26 | as: 'openBars' 27 | }); 28 | await sequelize.sync(); 29 | const myFoo = await Foo.create({ name: "My Foo" }); 30 | ``` 31 | 32 | * 设置完成后,调用 `myFoo.getOpenBars()` 会生成以下SQL: 33 | 34 | ```sql 35 | SELECT 36 | `id`, `status`, `createdAt`, `updatedAt`, `fooId` 37 | FROM `bars` AS `bar` 38 | WHERE `bar`.`status` = 'open' AND `bar`.`fooId` = 1; 39 | ``` 40 | 41 | 这样,我们可以看到,调用 `.getOpenBars()` mixin 之后,关联作用域 `{ status: 'open' }` 被自动应用到生成的 SQL 的 `WHERE` 子句中. 42 | 43 | ## 在标准作用域内实现相同的行为 44 | 45 | 我们可以使用标准作用域实现相同的行为: 46 | 47 | ```js 48 | // Foo.hasMany(Bar, { 49 | // scope: { 50 | // status: 'open' 51 | // }, 52 | // as: 'openBars' 53 | // }); 54 | 55 | Bar.addScope('open', { 56 | where: { 57 | status: 'open' 58 | } 59 | }); 60 | Foo.hasMany(Bar); 61 | Foo.hasMany(Bar.scope('open'), { as: 'openBars' }); 62 | ``` 63 | 64 | 使用上面的代码,`myFoo.getOpenBars()` 产生与上面所示相同的 SQL. -------------------------------------------------------------------------------- /advanced-association-concepts/creating-with-associations.md: -------------------------------------------------------------------------------- 1 | # Creating with Associations - 创建关联 2 | 3 | 只要所有元素都是新元素,就可以一步创建带有嵌套关联的实例. 4 | 5 | 相反,无法执行涉及嵌套对象的更新和删除. 为此,你将必须明确执行每个单独的操作. 6 | 7 | ## BelongsTo / HasMany / HasOne 关联 8 | 9 | 考虑以下模型: 10 | 11 | ```js 12 | class Product extends Model {} 13 | Product.init({ 14 | title: DataTypes.STRING 15 | }, { sequelize, modelName: 'product' }); 16 | class User extends Model {} 17 | User.init({ 18 | firstName: DataTypes.STRING, 19 | lastName: DataTypes.STRING 20 | }, { sequelize, modelName: 'user' }); 21 | class Address extends Model {} 22 | Address.init({ 23 | type: DataTypes.STRING, 24 | line1: DataTypes.STRING, 25 | line2: DataTypes.STRING, 26 | city: DataTypes.STRING, 27 | state: DataTypes.STRING, 28 | zip: DataTypes.STRING, 29 | }, { sequelize, modelName: 'address' }); 30 | 31 | // 我们保存关联设置调用的返回值,以便以后使用 32 | Product.User = Product.belongsTo(User); 33 | User.Addresses = User.hasMany(Address); 34 | // 也适用于 `hasOne` 35 | ``` 36 | 37 | 一个新的 `Product`,`User` 和一个或多个 `Address` 可以按以下步骤一步创建: 38 | 39 | ```js 40 | return Product.create({ 41 | title: 'Chair', 42 | user: { 43 | firstName: 'Mick', 44 | lastName: 'Broadstone', 45 | addresses: [{ 46 | type: 'home', 47 | line1: '100 Main St.', 48 | city: 'Austin', 49 | state: 'TX', 50 | zip: '78704' 51 | }] 52 | } 53 | }, { 54 | include: [{ 55 | association: Product.User, 56 | include: [ User.Addresses ] 57 | }] 58 | }); 59 | ``` 60 | 61 | 观察 `Product.create` 调用中 `include` 参数的用法. 这对于 Sequelize 理解与关联一起创建的内容很有必要. 62 | 63 | 注意:这里,我们的用户模型称为`user`,小写的`u`-这意味着对象中的属性也应为`user`. 如果给`sequelize.define`的名称是`User`,则对象中的 key 也应该是`User`. 对于 `addresses` 也是如此,除了它是 `hasMany` 关联的复数形式. 64 | 65 | ## 一个别名 BelongsTo 关联 66 | 67 | 可以扩展前面的示例以支持关联别名. 68 | 69 | ```js 70 | const Creator = Product.belongsTo(User, { as: 'creator' }); 71 | 72 | return Product.create({ 73 | title: 'Chair', 74 | creator: { 75 | firstName: 'Matt', 76 | lastName: 'Hansen' 77 | } 78 | }, { 79 | include: [ Creator ] 80 | }); 81 | ``` 82 | 83 | ## HasMany / BelongsToMany 关联 84 | 85 | 让我们介绍将产品与许多标签关联的功能. 设置模型如下所示: 86 | 87 | ```js 88 | class Tag extends Model {} 89 | Tag.init({ 90 | name: DataTypes.STRING 91 | }, { sequelize, modelName: 'tag' }); 92 | 93 | Product.hasMany(Tag); 94 | // 也适用于 `belongsToMany`. 95 | ``` 96 | 97 | 现在,我们可以通过以下方式创建具有多个标签的产品: 98 | 99 | ```js 100 | Product.create({ 101 | id: 1, 102 | title: 'Chair', 103 | tags: [ 104 | { name: 'Alpha'}, 105 | { name: 'Beta'} 106 | ] 107 | }, { 108 | include: [ Tag ] 109 | }) 110 | ``` 111 | 112 | 并且,我们可以修改此示例以支持别名: 113 | 114 | ```js 115 | const Categories = Product.hasMany(Tag, { as: 'categories' }); 116 | 117 | Product.create({ 118 | id: 1, 119 | title: 'Chair', 120 | categories: [ 121 | { id: 1, name: 'Alpha' }, 122 | { id: 2, name: 'Beta' } 123 | ] 124 | }, { 125 | include: [{ 126 | association: Categories, 127 | as: 'categories' 128 | }] 129 | }) 130 | ``` -------------------------------------------------------------------------------- /advanced-association-concepts/eager-loading.md: -------------------------------------------------------------------------------- 1 | # Eager Loading - 预先加载 2 | 3 | 如[关联指南](../core-concepts/assocs.md)中简要提到的,预先加载是一次查询多个模型(一个"主"模型和一个或多个关联模型)的数据的行为. 在 SQL 级别上,这是具有一个或多个 [join](https://en.wikipedia.org/wiki/Join_\(SQL\)) 的查询. 4 | 5 | 完成此操作后,Sequelize 将在返回的对象中将适当关联的模型添加到适当命名的自动创建的字段中. 6 | 7 | 在 Sequelize 中,主要通过在模型查找器查询中使用 `include` 参数(例如,`findOne`, `findAll` 等)来完成预先加载. 8 | 9 | ## 基本示例 10 | 11 | 让我们假设以下设置: 12 | 13 | ```js 14 | const User = sequelize.define('user', { name: DataTypes.STRING }, { timestamps: false }); 15 | const Task = sequelize.define('task', { name: DataTypes.STRING }, { timestamps: false }); 16 | const Tool = sequelize.define('tool', { 17 | name: DataTypes.STRING, 18 | size: DataTypes.STRING 19 | }, { timestamps: false }); 20 | User.hasMany(Task); 21 | Task.belongsTo(User); 22 | User.hasMany(Tool, { as: 'Instruments' }); 23 | ``` 24 | 25 | ### 获取单个关联元素 26 | 27 | 首先,让我们用其关联的用户加载所有任务: 28 | 29 | ```js 30 | const tasks = await Task.findAll({ include: User }); 31 | console.log(JSON.stringify(tasks, null, 2)); 32 | ``` 33 | 34 | 输出: 35 | 36 | ```json 37 | [{ 38 | "name": "A Task", 39 | "id": 1, 40 | "userId": 1, 41 | "user": { 42 | "name": "John Doe", 43 | "id": 1 44 | } 45 | }] 46 | ``` 47 | 48 | 这里,`tasks[0].user instanceof User` 是 `true`. 这表明,当 Sequelize 提取关联的模型时,它们将作为模型实例添加到输出对象. 49 | 50 | 上面,在获取的任务中,关联的模型被添加到名为 `user` 的新字段中. Sequelize 会根据关联模型的名称自动选择此字段的名称,在适用的情况下(即关联为 `hasMany` 或 `belongsToMany`)使用该字段的复数形式. 换句话说,由于`Task.belongsTo(User)`导致一项任务与一个用户相关联,因此逻辑选择是单数形式(Sequelize 自动遵循该形式). 51 | 52 | ### 获取所有关联的元素 53 | 54 | 现在,我们将执行相反的操作,而不是加载与给定任务关联的用户,我们将找到与给定用户关联的所有任务. 55 | 56 | 方法调用本质上是相同的. 唯一的区别是,现在在查询结果中创建的额外字段使用复数形式(在这种情况下为 `tasks`),其值是任务实例的数组(而不是上面的单个实例). 57 | 58 | ```js 59 | const users = await User.findAll({ include: Task }); 60 | console.log(JSON.stringify(users, null, 2)); 61 | ``` 62 | 63 | 输出: 64 | 65 | ```json 66 | [{ 67 | "name": "John Doe", 68 | "id": 1, 69 | "tasks": [{ 70 | "name": "A Task", 71 | "id": 1, 72 | "userId": 1 73 | }] 74 | }] 75 | ``` 76 | 77 | 注意,由于关联是一对多的,因此访问器(结果实例中的`tasks`属性)是复数的. 78 | 79 | ### 获取别名关联 80 | 81 | 如果关联是别名的(使用`as`参数),则在包含模型时必须指定此别名. 与其直接将模型传递给 `include` 参数,不如为对象提供两个选项:`model` 和 `as`. 82 | 83 | 注意上面的用户的 `Tool` 是如何被别名为 `Instruments` 的. 为了实现这一点,你必须指定要加载的模型以及别名: 84 | 85 | ```js 86 | const users = await User.findAll({ 87 | include: { model: Tool, as: 'Instruments' } 88 | }); 89 | console.log(JSON.stringify(users, null, 2)); 90 | ``` 91 | 92 | Output: 93 | 94 | ```json 95 | [{ 96 | "name": "John Doe", 97 | "id": 1, 98 | "Instruments": [{ 99 | "name": "Scissor", 100 | "id": 1, 101 | "userId": 1 102 | }] 103 | }] 104 | ``` 105 | 106 | 你也可以包括指定的关联别名相匹配的字符串: 107 | 108 | ```js 109 | User.findAll({ include: 'Instruments' }); // 也可以正常使用 110 | User.findAll({ include: { association: 'Instruments' } }); // 也可以正常使用 111 | ``` 112 | 113 | ### 需要预先加载 114 | 115 | 预先加载时,我们可以强制查询仅返回具有关联模型的记录,从而有效地将查询从默认的 `OUTER JOIN` 转为 `INNER JOIN`. 这是通过 `required: true` 参数完成的,如下所示: 116 | 117 | ```js 118 | User.findAll({ 119 | include: { 120 | model: Task, 121 | required: true 122 | } 123 | }); 124 | ``` 125 | 126 | 此参数也适用于嵌套包含. 127 | 128 | ### 在模型级别的预先加载过滤 129 | 130 | 预先加载时,我们还可以使用 `where` 参数过滤关联的模型,如以下示例所示: 131 | 132 | ```js 133 | User.findAll({ 134 | include: { 135 | model: Tool, 136 | as: 'Instruments', 137 | where: { 138 | size: { 139 | [Op.ne]: 'small' 140 | } 141 | } 142 | } 143 | }); 144 | ``` 145 | 146 | 生成 SQL: 147 | 148 | ```sql 149 | SELECT 150 | `user`.`id`, 151 | `user`.`name`, 152 | `Instruments`.`id` AS `Instruments.id`, 153 | `Instruments`.`name` AS `Instruments.name`, 154 | `Instruments`.`size` AS `Instruments.size`, 155 | `Instruments`.`userId` AS `Instruments.userId` 156 | FROM `users` AS `user` 157 | INNER JOIN `tools` AS `Instruments` ON 158 | `user`.`id` = `Instruments`.`userId` AND 159 | `Instruments`.`size` != 'small'; 160 | ``` 161 | 162 | 请注意,上面生成的 SQL 查询将仅获取具有至少一个符合条件(在这种情况下为 `small`)的工具的用户. 出现这种情况是因为,当在 `include` 内使用 `where` 参数时,Sequelize 会自动将 `required` 参数设置为 `true`. 这意味着,将执行 `INNER JOIN` 而不是 `OUTER JOIN`,仅返回具有至少一个匹配子代的父代模型. 163 | 164 | 还要注意,使用的 `where` 参数已转换为 `INNER JOIN` 的 `ON` 子句的条件. 为了获得 *顶层* 的 `WHERE` 子句,而不是 `ON` 子句,必须做一些不同的事情.接下来将展示. 165 | 166 | #### 参考其他列 167 | 168 | 如果你想在包含模型中应用 `WHERE` 子句来引用关联模型中的值,则可以简单地使用 `Sequelize.col` 函数,如以下示例所示: 169 | 170 | ```js 171 | // 查找所有具有至少一项任务的项目,其中 task.state === project.state 172 | Project.findAll({ 173 | include: { 174 | model: Task, 175 | where: { 176 | state: Sequelize.col('project.state') 177 | } 178 | } 179 | }) 180 | ``` 181 | 182 | ### 顶层的复杂 where 子句 183 | 184 | 为了获得涉及嵌套列的顶级 `WHERE` 子句,Sequelize 提供了一种引用嵌套列的方法:`'$nested.column$'` 语法. 185 | 186 | 例如,它可以用于将 `where` 条件从包含的模型从 `ON` 条件移动到顶层的 `WHERE` 子句. 187 | 188 | ```js 189 | User.findAll({ 190 | where: { 191 | '$Instruments.size$': { [Op.ne]: 'small' } 192 | }, 193 | include: [{ 194 | model: Tool, 195 | as: 'Instruments' 196 | }] 197 | }); 198 | ``` 199 | 200 | 生成 SQL: 201 | 202 | ```sql 203 | SELECT 204 | `user`.`id`, 205 | `user`.`name`, 206 | `Instruments`.`id` AS `Instruments.id`, 207 | `Instruments`.`name` AS `Instruments.name`, 208 | `Instruments`.`size` AS `Instruments.size`, 209 | `Instruments`.`userId` AS `Instruments.userId` 210 | FROM `users` AS `user` 211 | LEFT OUTER JOIN `tools` AS `Instruments` ON 212 | `user`.`id` = `Instruments`.`userId` 213 | WHERE `Instruments`.`size` != 'small'; 214 | ``` 215 | 216 | `$nested.column$` 语法也适用于嵌套了多个级别的列,例如 `$some.super.deeply.nested.column$`. 因此,你可以使用它对深层嵌套的列进行复杂的过滤. 217 | 218 | 为了更好地理解内部的 `where` 参数(在 `include` 内部使用)和使用与不使用 `required` 参数与使用 `$nested.column$` 语法的顶级 `where` 之间的所有区别. ,下面我们为你提供四个示例: 219 | 220 | ```js 221 | // Inner where, 默认使用 `required: true` 222 | await User.findAll({ 223 | include: { 224 | model: Tool, 225 | as: 'Instruments', 226 | where: { 227 | size: { [Op.ne]: 'small' } 228 | } 229 | } 230 | }); 231 | 232 | // Inner where, `required: false` 233 | await User.findAll({ 234 | include: { 235 | model: Tool, 236 | as: 'Instruments', 237 | where: { 238 | size: { [Op.ne]: 'small' } 239 | }, 240 | required: false 241 | } 242 | }); 243 | 244 | // 顶级 where, 默认使用 `required: false` 245 | await User.findAll({ 246 | where: { 247 | '$Instruments.size$': { [Op.ne]: 'small' } 248 | }, 249 | include: { 250 | model: Tool, 251 | as: 'Instruments' 252 | } 253 | }); 254 | 255 | // 顶级 where, `required: true` 256 | await User.findAll({ 257 | where: { 258 | '$Instruments.size$': { [Op.ne]: 'small' } 259 | }, 260 | include: { 261 | model: Tool, 262 | as: 'Instruments', 263 | required: true 264 | } 265 | }); 266 | ``` 267 | 268 | 生成 SQL: 269 | 270 | ```sql 271 | -- Inner where, 默认使用 `required: true` 272 | SELECT [...] FROM `users` AS `user` 273 | INNER JOIN `tools` AS `Instruments` ON 274 | `user`.`id` = `Instruments`.`userId` 275 | AND `Instruments`.`size` != 'small'; 276 | 277 | -- Inner where, `required: false` 278 | SELECT [...] FROM `users` AS `user` 279 | LEFT OUTER JOIN `tools` AS `Instruments` ON 280 | `user`.`id` = `Instruments`.`userId` 281 | AND `Instruments`.`size` != 'small'; 282 | 283 | -- 顶级 where, 默认使用 `required: false` 284 | SELECT [...] FROM `users` AS `user` 285 | LEFT OUTER JOIN `tools` AS `Instruments` ON 286 | `user`.`id` = `Instruments`.`userId` 287 | WHERE `Instruments`.`size` != 'small'; 288 | 289 | -- 顶级 where, `required: true` 290 | SELECT [...] FROM `users` AS `user` 291 | INNER JOIN `tools` AS `Instruments` ON 292 | `user`.`id` = `Instruments`.`userId` 293 | WHERE `Instruments`.`size` != 'small'; 294 | ``` 295 | 296 | ### 使用 `RIGHT OUTER JOIN` 获取 (仅限 MySQL, MariaDB, PostgreSQL 和 MSSQL) 297 | 298 | 默认情况下,关联是使用 `LEFT OUTER JOIN` 加载的 - 也就是说,它仅包含来自父表的记录. 如果你使用的方言支持,你可以通过传递 `right` 选项来将此行为更改为 `RIGHT OUTER JOIN`. 299 | 300 | 当前, SQLite 不支持 [right joins](https://www.sqlite.org/omitted.html). 301 | 302 | *注意:* 仅当 `required` 为 false 时才遵循 `right`. 303 | 304 | ```js 305 | User.findAll({ 306 | include: [{ 307 | model: Task // 将创建一个 left join 308 | }] 309 | }); 310 | User.findAll({ 311 | include: [{ 312 | model: Task, 313 | right: true // 将创建一个 right join 314 | }] 315 | }); 316 | User.findAll({ 317 | include: [{ 318 | model: Task, 319 | required: true, 320 | right: true // 没有效果, 将创建一个 inner join 321 | }] 322 | }); 323 | User.findAll({ 324 | include: [{ 325 | model: Task, 326 | where: { name: { [Op.ne]: 'empty trash' } }, 327 | right: true // 没有效果, 将创建一个 inner join 328 | }] 329 | }); 330 | User.findAll({ 331 | include: [{ 332 | model: Tool, 333 | where: { name: { [Op.ne]: 'empty trash' } }, 334 | required: false // 将创建一个 left join 335 | }] 336 | }); 337 | User.findAll({ 338 | include: [{ 339 | model: Tool, 340 | where: { name: { [Op.ne]: 'empty trash' } }, 341 | required: false 342 | right: true // 将创建一个 right join 343 | }] 344 | }); 345 | ``` 346 | 347 | ## 多次预先加载 348 | 349 | `include` 参数可以接收一个数组,以便一次获取多个关联的模型: 350 | 351 | ```js 352 | Foo.findAll({ 353 | include: [ 354 | { 355 | model: Bar, 356 | required: true 357 | }, 358 | { 359 | model: Baz, 360 | where: /* ... */ 361 | }, 362 | Qux // { model: Qux } 的简写语法在这里也适用 363 | ] 364 | }) 365 | ``` 366 | 367 | ## 多对多关系的预先加载 368 | 369 | 当你对具有 "多对多" 关系的模型执行预先加载时,默认情况下,Sequelize 也将获取联结表数据. 例如: 370 | 371 | ```js 372 | const Foo = sequelize.define('Foo', { name: DataTypes.TEXT }); 373 | const Bar = sequelize.define('Bar', { name: DataTypes.TEXT }); 374 | Foo.belongsToMany(Bar, { through: 'Foo_Bar' }); 375 | Bar.belongsToMany(Foo, { through: 'Foo_Bar' }); 376 | 377 | await sequelize.sync(); 378 | const foo = await Foo.create({ name: 'foo' }); 379 | const bar = await Bar.create({ name: 'bar' }); 380 | await foo.addBar(bar); 381 | const fetchedFoo = await Foo.findOne({ include: Bar }); 382 | console.log(JSON.stringify(fetchedFoo, null, 2)); 383 | ``` 384 | 385 | 输出: 386 | 387 | ```json 388 | { 389 | "id": 1, 390 | "name": "foo", 391 | "Bars": [ 392 | { 393 | "id": 1, 394 | "name": "bar", 395 | "Foo_Bar": { 396 | "FooId": 1, 397 | "BarId": 1 398 | } 399 | } 400 | ] 401 | } 402 | ``` 403 | 404 | 请注意,每个预先加载到 `Bars` 属性中的 bar 实例都有一个名为 `Foo_Bar` 的额外属性,它是联结模型的相关 Sequelize 实例. 默认情况下,Sequelize 从联结表中获取所有属性,以构建此额外属性. 405 | 406 | 然而,你可以指定要获取的属性. 这是通过在包含的 `through` 参数中应用 `attributes` 参数来完成的. 例如: 407 | 408 | ```js 409 | Foo.findAll({ 410 | include: [{ 411 | model: Bar, 412 | through: { 413 | attributes: [/* 在此处列出所需的属性 */] 414 | } 415 | }] 416 | }); 417 | ``` 418 | 419 | 如果你不需要联结表中的任何内容,则可以在 `include` 选项的 `through` 内显式地为 `attributes` 参数提供一个空数组,在这种情况下,将不会获取任何内容,甚至不会创建额外的属性: 420 | 421 | ```js 422 | Foo.findOne({ 423 | include: { 424 | model: Bar, 425 | through: { 426 | attributes: [] 427 | } 428 | } 429 | }); 430 | ``` 431 | 432 | 输出: 433 | 434 | ```json 435 | { 436 | "id": 1, 437 | "name": "foo", 438 | "Bars": [ 439 | { 440 | "id": 1, 441 | "name": "bar" 442 | } 443 | ] 444 | } 445 | ``` 446 | 447 | 每当包含 "多对多" 关系中的模型时,也可以在联结表上应用过滤器. 这是通过在 `include` 的 `through` 参数中应用 `where` 参数来完成的. 例如: 448 | 449 | ```js 450 | User.findAll({ 451 | include: [{ 452 | model: Project, 453 | through: { 454 | where: { 455 | // 这里,`completed` 是联结表上的一列 456 | completed: true 457 | } 458 | } 459 | }] 460 | }); 461 | ``` 462 | 463 | 生成 SQL (使用 SQLite): 464 | 465 | ```sql 466 | SELECT 467 | `User`.`id`, 468 | `User`.`name`, 469 | `Projects`.`id` AS `Projects.id`, 470 | `Projects`.`name` AS `Projects.name`, 471 | `Projects->User_Project`.`completed` AS `Projects.User_Project.completed`, 472 | `Projects->User_Project`.`UserId` AS `Projects.User_Project.UserId`, 473 | `Projects->User_Project`.`ProjectId` AS `Projects.User_Project.ProjectId` 474 | FROM `Users` AS `User` 475 | LEFT OUTER JOIN `User_Projects` AS `Projects->User_Project` ON 476 | `User`.`id` = `Projects->User_Project`.`UserId` 477 | LEFT OUTER JOIN `Projects` AS `Projects` ON 478 | `Projects`.`id` = `Projects->User_Project`.`ProjectId` AND 479 | `Projects->User_Project`.`completed` = 1; 480 | ``` 481 | 482 | ## 包括一切 483 | 484 | 要包括所有关联的模型,可以使用 `all` 和 `nested` 参数: 485 | 486 | ```js 487 | // 提取与用户关联的所有模型 488 | User.findAll({ include: { all: true }}); 489 | 490 | // 递归获取与用户及其嵌套关联关联的所有模型 491 | User.findAll({ include: { all: true, nested: true }}); 492 | ``` 493 | 494 | ## 包括软删除的记录 495 | 496 | 如果你想加载软删除的记录,可以通过将 `include.paranoid` 设置为 `false` 来实现: 497 | 498 | ```js 499 | User.findAll({ 500 | include: [{ 501 | model: Tool, 502 | as: 'Instruments', 503 | where: { size: { [Op.ne]: 'small' } }, 504 | paranoid: false 505 | }] 506 | }); 507 | ``` 508 | 509 | ## 排序预先加载的关联 510 | 511 | 当你想将 `ORDER` 子句应用于预先加载的模型时,必须对扩展数组使用顶层 `order` 参数,从要排序的嵌套模型开始. 512 | 513 | 通过示例可以更好地理解这一点. 514 | 515 | ```js 516 | Company.findAll({ 517 | include: Division, 518 | order: [ 519 | // 我们从要排序的模型开始排序数组 520 | [Division, 'name', 'ASC'] 521 | ] 522 | }); 523 | Company.findAll({ 524 | include: Division, 525 | order: [ 526 | [Division, 'name', 'DESC'] 527 | ] 528 | }); 529 | Company.findAll({ 530 | // 如果包含使用别名... 531 | include: { model: Division, as: 'Div' }, 532 | order: [ 533 | // ...我们在排序数组的开头使用来自 `include` 的相同语法 534 | [{ model: Division, as: 'Div' }, 'name', 'DESC'] 535 | ] 536 | }); 537 | 538 | Company.findAll({ 539 | // 如果我们包含嵌套在多个级别中... 540 | include: { 541 | model: Division, 542 | include: Department 543 | }, 544 | order: [ 545 | // ... 我们在排序数组的开头复制需要的 include 链 546 | [Division, Department, 'name', 'DESC'] 547 | ] 548 | }); 549 | ``` 550 | 551 | 对于多对多关系,你还可以按联结表中的属性进行排序.例如,假设我们在 `Division` 和 `Department` 之间存在多对多关系,联结模型为 `DepartmentDivision`, 你可以这样: 552 | 553 | ```js 554 | Company.findAll({ 555 | include: { 556 | model: Division, 557 | include: Department 558 | }, 559 | order: [ 560 | [Division, DepartmentDivision, 'name', 'ASC'] 561 | ] 562 | }); 563 | ``` 564 | 565 | 在以上所有示例中,你已经注意到在顶层使用了 `order` 参数. 但是在`separate: true` 时,`order` 也可以在 `include` 参数中使用. 在这种情况下,用法如下: 566 | 567 | ```js 568 | // 这仅能用于 `separate: true` (反过来仅适用于 HasMany 关系). 569 | User.findAll({ 570 | include: { 571 | model: Post, 572 | separate: true, 573 | order: [ 574 | ['createdAt', 'DESC'] 575 | ] 576 | } 577 | }); 578 | ``` 579 | 580 | ### 涉及子查询的复杂排序 581 | 582 | 查看[子查询指南](../other-topics/sub-queries.md)上的示例,了解如何使用子查询来协助更复杂的排序. 583 | 584 | ## 嵌套的预先加载 585 | 586 | 你可以使用嵌套的预先加载来加载相关模型的所有相关模型: 587 | 588 | ```js 589 | const users = await User.findAll({ 590 | include: { 591 | model: Tool, 592 | as: 'Instruments', 593 | include: { 594 | model: Teacher, 595 | include: [ /* ... */ ] 596 | } 597 | } 598 | }); 599 | console.log(JSON.stringify(users, null, 2)); 600 | ``` 601 | 602 | 输出: 603 | 604 | ```json 605 | [{ 606 | "name": "John Doe", 607 | "id": 1, 608 | "Instruments": [{ // 1:M 和 N:M 关联 609 | "name": "Scissor", 610 | "id": 1, 611 | "userId": 1, 612 | "Teacher": { // 1:1 关联 613 | "name": "Jimi Hendrix" 614 | } 615 | }] 616 | }] 617 | ``` 618 | 619 | 这将产生一个外部连接. 但是,相关模型上的 `where` 子句将创建内部联接,并且仅返回具有匹配子模型的实例. 要返回所有父实例,你应该添加 `required: false`. 620 | 621 | ```js 622 | User.findAll({ 623 | include: [{ 624 | model: Tool, 625 | as: 'Instruments', 626 | include: [{ 627 | model: Teacher, 628 | where: { 629 | school: "Woodstock Music School" 630 | }, 631 | required: false 632 | }] 633 | }] 634 | }); 635 | ``` 636 | 637 | 上面的查询将返回所有用户及其所有乐器,但仅返回与 `Woodstock Music School` 相关的那些老师. 638 | 639 | ## 使用带有 include 的 `findAndCountAll` 640 | 641 | `findAndCountAll` 实用功能支持 include. 仅将标记为 `required` 的 include 项视为 `count`. 例如,如果你要查找并统计所有拥有个人资料的用户: 642 | 643 | ```js 644 | User.findAndCountAll({ 645 | include: [ 646 | { model: Profile, required: true } 647 | ], 648 | limit: 3 649 | }); 650 | ``` 651 | 652 | 因为 `Profile` 的 include 已设置为 `required`,它将导致内部联接,并且仅统计具有个人资料的用户. 如果我们从包含中删除 `required`,则包含和不包含配置文件的用户都将被计数. 在 include 中添加一个 `where` 子句会自动使它成为 required: 653 | 654 | ```js 655 | User.findAndCountAll({ 656 | include: [ 657 | { model: Profile, where: { active: true } } 658 | ], 659 | limit: 3 660 | }); 661 | ``` 662 | 663 | 上面的查询仅会统计拥有有效个人资料的用户,因为当你在 `include` 中添加 `where` 子句时,`required` 会隐式设置为 `true`. -------------------------------------------------------------------------------- /advanced-association-concepts/polymorphic-associations.md: -------------------------------------------------------------------------------- 1 | # Polymorphic Associations - 多态关联 2 | 3 | _**注意:** 如本指南所述,在 Sequelize 中使用多态关联时应谨慎行事. 不要只是从此处复制粘贴代码,否则你可能会容易出错并在代码中引入错误. 请确保你了解发生了什么._ 4 | 5 | ## 概念 6 | 7 | 一个 **多态关联** 由使用同一外键发生的两个(或多个)关联组成. 8 | 9 | 例如,考虑模型 `Image`, `Video` 和 `Comment`. 前两个代表用户可能发布的内容. 我们希望允许将评论放在两者中. 这样,我们立即想到建立以下关联: 10 | 11 | * `Image` 和 `Comment` 之间的一对多关联: 12 | 13 | ```js 14 | Image.hasMany(Comment); 15 | Comment.belongsTo(Image); 16 | ``` 17 | 18 | * `Video` 和 `Comment` 之间的一对多关联: 19 | 20 | ```js 21 | Video.hasMany(Comment); 22 | Comment.belongsTo(Video); 23 | ``` 24 | 25 | 但是,以上操作将导致 Sequelize 在 `Comment` 表上创建两个外键: `ImageId` 和 `VideoId`. 这是不理想的,因为这种结构使评论看起来可以同时附加到一个图像和一个视频上,这是不正确的. 取而代之的是,我们真正想要的是一个多态关联,其中一个 `Comment` 指向一个 **可评论**,它是表示 `Image` 或 `Video` 之一的抽象多态实体. 26 | 27 | 在继续配置此类关联之前,让我们看看如何使用它: 28 | 29 | ```js 30 | const image = await Image.create({ url: "https://placekitten.com/408/287" }); 31 | const comment = await image.createComment({ content: "Awesome!" }); 32 | 33 | console.log(comment.commentableId === image.id); // true 34 | 35 | // 我们还可以检索与评论关联的可评论类型. 36 | // 下面显示了相关的可注释实例的模型名称. 37 | console.log(comment.commentableType); // "Image" 38 | 39 | // 我们可以使用多态方法来检索相关的可评论内容, 40 | // 而不必关心它是图像还是视频. 41 | const associatedCommentable = await comment.getCommentable(); 42 | 43 | // 在此示例中,`associatedCommentable` 与 `image` 是同一件事: 44 | const isDeepEqual = require('deep-equal'); 45 | console.log(isDeepEqual(image, commentable)); // true 46 | ``` 47 | 48 | ## 配置一对多多态关联 49 | 50 | 要为上述示例(这是一对多多态关联的示例)设置多态关联,我们需要执行以下步骤: 51 | 52 | * 在 `Comment` 模型中定义一个名为 `commentableType` 的字符串字段; 53 | * 在 `Image`/`Video` 和 `Comment` 之间定义 `hasMany` 和 `belongsTo` 关联: 54 | * 禁用约束(即使用 `{ constraints: false }`),因为同一个外键引用了多个表; 55 | * 指定适当的 [关联作用域](../advanced-association-concepts/association-scopes.md); 56 | * 为了适当地支持延迟加载,请在 `Comment` 模型上定义一个名为 `getCommentable` 的新实例方法,该方法在后台调用正确的 mixin 来获取适当的注释对象; 57 | * 为了正确支持预先加载,请在 `Comment` 模型上定义一个 `afterFind` hook,该 hook 将在每个实例中自动填充 `commentable` 字段; 58 | * 为了防止预先加载的 bug/错误,你还可以在相同的 `afterFind` hook 中从 Comment 实例中删除具体字段 `image` 和 `video`,仅保留抽象的 `commentable` 字段可用. 59 | 60 | 这是一个示例: 61 | 62 | ```js 63 | // Helper 方法 64 | const uppercaseFirst = str => `${str[0].toUpperCase()}${str.substr(1)}`; 65 | 66 | class Image extends Model {} 67 | Image.init({ 68 | title: DataTypes.STRING, 69 | url: DataTypes.STRING 70 | }, { sequelize, modelName: 'image' }); 71 | 72 | class Video extends Model {} 73 | Video.init({ 74 | title: DataTypes.STRING, 75 | text: DataTypes.STRING 76 | }, { sequelize, modelName: 'video' }); 77 | 78 | class Comment extends Model { 79 | getCommentable(options) { 80 | if (!this.commentableType) return Promise.resolve(null); 81 | const mixinMethodName = `get${uppercaseFirst(this.commentableType)}`; 82 | return this[mixinMethodName](options); 83 | } 84 | } 85 | Comment.init({ 86 | title: DataTypes.STRING, 87 | commentableId: DataTypes.INTEGER, 88 | commentableType: DataTypes.STRING 89 | }, { sequelize, modelName: 'comment' }); 90 | 91 | Image.hasMany(Comment, { 92 | foreignKey: 'commentableId', 93 | constraints: false, 94 | scope: { 95 | commentableType: 'image' 96 | } 97 | }); 98 | Comment.belongsTo(Image, { foreignKey: 'commentableId', constraints: false }); 99 | 100 | Video.hasMany(Comment, { 101 | foreignKey: 'commentableId', 102 | constraints: false, 103 | scope: { 104 | commentableType: 'video' 105 | } 106 | }); 107 | Comment.belongsTo(Video, { foreignKey: 'commentableId', constraints: false }); 108 | 109 | Comment.hooks.addListener("afterFind", findResult => { 110 | if (!Array.isArray(findResult)) findResult = [findResult]; 111 | for (const instance of findResult) { 112 | if (instance.commentableType === "image" && instance.image !== undefined) { 113 | instance.commentable = instance.image; 114 | } else if (instance.commentableType === "video" && instance.video !== undefined) { 115 | instance.commentable = instance.video; 116 | } 117 | // 防止错误: 118 | delete instance.image; 119 | delete instance.dataValues.image; 120 | delete instance.video; 121 | delete instance.dataValues.video; 122 | } 123 | }); 124 | ``` 125 | 126 | 由于 `commentableId` 列引用了多个表(本例中为两个表),因此我们无法向其添加 `REFERENCES` 约束. 这就是为什么使用 `constraints: false` 参数的原因. 127 | 128 | 注意,在上面的代码中: 129 | 130 | * *Image -> Comment* 关联定义了一个关联作用域: `{ commentableType: 'image' }` 131 | * *Video -> Comment* 关联定义了一个关联作用域: `{ commentableType: 'video' }` 132 | 133 | 使用关联函数时,这些作用域会自动应用(如[关联作用域](../advanced-association-concepts/association-scopes.md)指南中所述). 以下是一些示例及其生成的 SQL 语句: 134 | 135 | * `image.getComments()`: 136 | 137 | ```sql 138 | SELECT "id", "title", "commentableType", "commentableId", "createdAt", "updatedAt" 139 | FROM "comments" AS "comment" 140 | WHERE "comment"."commentableType" = 'image' AND "comment"."commentableId" = 1; 141 | ``` 142 | 143 | 在这里我们可以看到 `` `comment`.`commentableType` = 'image'`` 已自动添加到生成的 SQL 的 `WHERE` 子句中. 这正是我们想要的行为. 144 | 145 | * `image.createComment({ title: 'Awesome!' })`: 146 | 147 | ```sql 148 | INSERT INTO "comments" ( 149 | "id", "title", "commentableType", "commentableId", "createdAt", "updatedAt" 150 | ) VALUES ( 151 | DEFAULT, 'Awesome!', 'image', 1, 152 | '2018-04-17 05:36:40.454 +00:00', '2018-04-17 05:36:40.454 +00:00' 153 | ) RETURNING *; 154 | ``` 155 | 156 | * `image.addComment(comment)`: 157 | 158 | ```sql 159 | UPDATE "comments" 160 | SET "commentableId"=1, "commentableType"='image', "updatedAt"='2018-04-17 05:38:43.948 +00:00' 161 | WHERE "id" IN (1) 162 | ``` 163 | 164 | ### 多态延迟加载 165 | 166 | `Comment` 上的 `getCommentable` 实例方法为延迟加载相关的 commentable 提供了一种抽象 - 无论注释属于 Image 还是 Video,都可以工作. 167 | 168 | 通过简单地将 `commentableType` 字符串转换为对正确的 mixin( `getImage` 或 `getVideo`)的调用即可工作. 169 | 170 | 注意上面的 `getCommentable` 实现: 171 | 172 | * 不存在关联时返回 `null`; 173 | * 允许你将参数对象传递给 `getCommentable(options)`,就像其他任何标准 Sequelize 方法一样. 对于示例,这对于指定 where 条件或 include 条件很有用. 174 | 175 | ### 多态预先加载 176 | 177 | 现在,我们希望对一个(或多个)注释执行关联的可评论对象的多态预先加载. 我们想要实现类似以下的东西: 178 | 179 | ```js 180 | const comment = await Comment.findOne({ 181 | include: [ /* ... */ ] 182 | }); 183 | console.log(comment.commentable); // 这是我们的目标 184 | ``` 185 | 186 | 解决的办法是告诉 Sequelize 同时包含图像和视频,以便上面定义的 `afterFind` hook可以完成工作,并自动向实例对象添加 `commentable` 字段,以提供所需的抽象. 187 | 188 | 示例: 189 | 190 | ```js 191 | const comments = await Comment.findAll({ 192 | include: [Image, Video] 193 | }); 194 | for (const comment of comments) { 195 | const message = `Found comment #${comment.id} with ${comment.commentableType} commentable:`; 196 | console.log(message, comment.commentable.toJSON()); 197 | } 198 | ``` 199 | 200 | 输出: 201 | 202 | ```text 203 | Found comment #1 with image commentable: { id: 1, 204 | title: 'Meow', 205 | url: 'https://placekitten.com/408/287', 206 | createdAt: 2019-12-26T15:04:53.047Z, 207 | updatedAt: 2019-12-26T15:04:53.047Z } 208 | ``` 209 | 210 | ### 注意 - 可能无效的 预先/延迟 加载! 211 | 212 | 注释 `Foo`,其 `commentableId` 为 2,而 `commentableType` 为 `image`. 然后 `Image A` 和 `Video X` 的 ID 都恰好等于 2.从概念上讲,很明显,`Video X` 与 `Foo` 没有关联,因为即使其 ID 为 2,`Foo` 的 `commentableType` 是 `image`,而不是 `video`. 然而,这种区分仅在 Sequelize 的 `getCommentable` 和我们在上面创建的 hook 执行的抽象级别上进行. 213 | 214 | 这意味着如果在上述情况下调用 `Comment.findAll({ include: Video })`,`Video X` 将被预先加载到 `Foo` 中. 幸运的是,我们的 `afterFind` hook将自动删除它,以帮助防止错误. 你了解发生了什么是非常重要的. 215 | 216 | 防止此类错误的最好方法是 **不惜一切代价直接使用具体的访问器和mixin** (例如 `.image`, `.getVideo()`, `.setImage()` 等) ,总是喜欢我们创建的抽象,例如 `.getCommentable()` and `.commentable`. 如果由于某种原因确实需要访问预先加载的 `.image` 和 `.video` 请确保将其包装在类型检查中,例如 `comment.commentableType === 'image'`. 217 | 218 | ## 配置多对多多态关联 219 | 220 | 在上面的示例中,我们将模型 `Image` 和 `Video` 抽象称为 *commentables*,其中一个 *commentable* 具有很多注释. 但是,一个给定的注释将属于一个 *commentable* - 这就是为什么整个情况都是一对多多态关联的原因. 221 | 222 | 现在,考虑多对多多态关联,而不是考虑注释,我们将考虑标签. 为了方便起见,我们现在将它们称为 *taggables*,而不是将它们称为 *commentables*. 一个 *taggable* 可以具有多个标签,同时一个标签可以放置在多个 *taggables* 中. 223 | 224 | 为此设置如下: 225 | 226 | * 明确定义联结模型,将两个外键指定为 `tagId` 和 `taggableId`(这样,它是 `Tag` 与 *taggable* 抽象概念之间多对多关系的联结模型); 227 | * 在联结模型中定义一个名为 `taggableType` 的字符串字段; 228 | * 定义两个模型之间的 `belongsToMany` 关联和 `标签`: 229 | * 禁用约束 (即, 使用 `{ constraints: false }`), 因为同一个外键引用了多个表; 230 | * 指定适当的 [关联作用域](../advanced-association-concepts/association-scopes.md); 231 | * 在 `Tag` 模型上定义一个名为 `getTaggables` 的新实例方法,该方法在后台调用正确的 mixin 来获取适当的 taggables. 232 | 233 | 实践: 234 | 235 | ```js 236 | class Tag extends Model { 237 | getTaggables(options) { 238 | const images = await this.getImages(options); 239 | const videos = await this.getVideos(options); 240 | // 在单个 taggables 数组中合并 images 和 videos 241 | return images.concat(videos); 242 | } 243 | } 244 | Tag.init({ 245 | name: DataTypes.STRING 246 | }, { sequelize, modelName: 'tag' }); 247 | 248 | // 在这里,我们明确定义联结模型 249 | class Tag_Taggable extends Model {} 250 | Tag_Taggable.init({ 251 | tagId: { 252 | type: DataTypes.INTEGER, 253 | unique: 'tt_unique_constraint' 254 | }, 255 | taggableId: { 256 | type: DataTypes.INTEGER, 257 | unique: 'tt_unique_constraint', 258 | references: null 259 | }, 260 | taggableType: { 261 | type: DataTypes.STRING, 262 | unique: 'tt_unique_constraint' 263 | } 264 | }, { sequelize, modelName: 'tag_taggable' }); 265 | 266 | Image.belongsToMany(Tag, { 267 | through: { 268 | model: Tag_Taggable, 269 | unique: false, 270 | scope: { 271 | taggableType: 'image' 272 | } 273 | }, 274 | foreignKey: 'taggableId', 275 | constraints: false 276 | }); 277 | Tag.belongsToMany(Image, { 278 | through: { 279 | model: Tag_Taggable, 280 | unique: false 281 | }, 282 | foreignKey: 'tagId', 283 | constraints: false 284 | }); 285 | 286 | Video.belongsToMany(Tag, { 287 | through: { 288 | model: Tag_Taggable, 289 | unique: false, 290 | scope: { 291 | taggableType: 'video' 292 | } 293 | }, 294 | foreignKey: 'taggableId', 295 | constraints: false 296 | }); 297 | Tag.belongsToMany(Video, { 298 | through: { 299 | model: Tag_Taggable, 300 | unique: false 301 | }, 302 | foreignKey: 'tagId', 303 | constraints: false 304 | }); 305 | ``` 306 | 307 | `constraints: false` 参数禁用引用约束,因为 `taggableId` 列引用了多个表,因此我们无法向其添加 `REFERENCES` 约束. 308 | 309 | 注意下面: 310 | 311 | * 对 *Image -> Tag* 关联定义了一个关联范围: `{ taggableType: 'image' }` 312 | * 对 *Video -> Tag* 关联定义了一个关联范围: `{ taggableType: 'video' }` 313 | 314 | 使用关联函数时,将自动应用这些作用域. 以下是一些示例及其生成的 SQL 语句: 315 | 316 | * `image.getTags()`: 317 | 318 | ```sql 319 | SELECT 320 | `tag`.`id`, 321 | `tag`.`name`, 322 | `tag`.`createdAt`, 323 | `tag`.`updatedAt`, 324 | `tag_taggable`.`tagId` AS `tag_taggable.tagId`, 325 | `tag_taggable`.`taggableId` AS `tag_taggable.taggableId`, 326 | `tag_taggable`.`taggableType` AS `tag_taggable.taggableType`, 327 | `tag_taggable`.`createdAt` AS `tag_taggable.createdAt`, 328 | `tag_taggable`.`updatedAt` AS `tag_taggable.updatedAt` 329 | FROM `tags` AS `tag` 330 | INNER JOIN `tag_taggables` AS `tag_taggable` ON 331 | `tag`.`id` = `tag_taggable`.`tagId` AND 332 | `tag_taggable`.`taggableId` = 1 AND 333 | `tag_taggable`.`taggableType` = 'image'; 334 | ``` 335 | 336 | 在这里我们可以看到 `` `tag_taggable`.`taggableType` = 'image'`` 已被自动添加到生成的 SQL 的 WHERE 子句中. 这正是我们想要的行为. 337 | 338 | * `tag.getTaggables()`: 339 | 340 | ```sql 341 | SELECT 342 | `image`.`id`, 343 | `image`.`url`, 344 | `image`.`createdAt`, 345 | `image`.`updatedAt`, 346 | `tag_taggable`.`tagId` AS `tag_taggable.tagId`, 347 | `tag_taggable`.`taggableId` AS `tag_taggable.taggableId`, 348 | `tag_taggable`.`taggableType` AS `tag_taggable.taggableType`, 349 | `tag_taggable`.`createdAt` AS `tag_taggable.createdAt`, 350 | `tag_taggable`.`updatedAt` AS `tag_taggable.updatedAt` 351 | FROM `images` AS `image` 352 | INNER JOIN `tag_taggables` AS `tag_taggable` ON 353 | `image`.`id` = `tag_taggable`.`taggableId` AND 354 | `tag_taggable`.`tagId` = 1; 355 | 356 | SELECT 357 | `video`.`id`, 358 | `video`.`url`, 359 | `video`.`createdAt`, 360 | `video`.`updatedAt`, 361 | `tag_taggable`.`tagId` AS `tag_taggable.tagId`, 362 | `tag_taggable`.`taggableId` AS `tag_taggable.taggableId`, 363 | `tag_taggable`.`taggableType` AS `tag_taggable.taggableType`, 364 | `tag_taggable`.`createdAt` AS `tag_taggable.createdAt`, 365 | `tag_taggable`.`updatedAt` AS `tag_taggable.updatedAt` 366 | FROM `videos` AS `video` 367 | INNER JOIN `tag_taggables` AS `tag_taggable` ON 368 | `video`.`id` = `tag_taggable`.`taggableId` AND 369 | `tag_taggable`.`tagId` = 1; 370 | ``` 371 | 372 | 请注意,上述 `getTaggables()` 的实现允许你将选项对象传递给 `getCommentable(options)`,就像其他任何标准 Sequelize 方法一样. 例如,这对于指定条件或包含条件很有用. 373 | 374 | ### 在目标模型上应用作用域 375 | 376 | 在上面的示例中,`scope` 参数(例如 `scope: { taggableType: 'image' }`)应用于 *联结* 模型,而不是 *目标* 模型,因为它是在 `through` 下使用的参数. 377 | 378 | 我们还可以在目标模型上应用关联作用域. 我们甚至可以同时进行. 379 | 380 | 为了说明这一点,请考虑上述示例在标签和可标记之间的扩展,其中每个标签都有一个状态. 这样,为了获取图像的所有待处理标签,我们可以在 `Image` 和 `Tag` 之间建立另一个 `belognsToMany` 关系,这一次在联结模型上应用作用域,在目标模型上应用另一个作用域: 381 | 382 | ```js 383 | Image.belongsToMany(Tag, { 384 | through: { 385 | model: Tag_Taggable, 386 | unique: false, 387 | scope: { 388 | taggableType: 'image' 389 | } 390 | }, 391 | scope: { 392 | status: 'pending' 393 | }, 394 | as: 'pendingTags', 395 | foreignKey: 'taggableId', 396 | constraints: false 397 | }); 398 | ``` 399 | 400 | 这样,当调用 `image.getPendingTags()` 时,将生成以下 SQL 查询: 401 | 402 | ```sql 403 | SELECT 404 | `tag`.`id`, 405 | `tag`.`name`, 406 | `tag`.`status`, 407 | `tag`.`createdAt`, 408 | `tag`.`updatedAt`, 409 | `tag_taggable`.`tagId` AS `tag_taggable.tagId`, 410 | `tag_taggable`.`taggableId` AS `tag_taggable.taggableId`, 411 | `tag_taggable`.`taggableType` AS `tag_taggable.taggableType`, 412 | `tag_taggable`.`createdAt` AS `tag_taggable.createdAt`, 413 | `tag_taggable`.`updatedAt` AS `tag_taggable.updatedAt` 414 | FROM `tags` AS `tag` 415 | INNER JOIN `tag_taggables` AS `tag_taggable` ON 416 | `tag`.`id` = `tag_taggable`.`tagId` AND 417 | `tag_taggable`.`taggableId` = 1 AND 418 | `tag_taggable`.`taggableType` = 'image' 419 | WHERE ( 420 | `tag`.`status` = 'pending' 421 | ); 422 | ``` 423 | 424 | 我们可以看到两个作用域都是自动应用的: 425 | 426 | * `` `tag_taggable`.`taggableType` = 'image'`` 被自动添加到 `INNER JOIN`; 427 | * `` `tag`.`status` = 'pending'`` 被自动添加到外部 where 子句. -------------------------------------------------------------------------------- /core-concepts/getters-setters-virtuals.md: -------------------------------------------------------------------------------- 1 | # Getters, Setters & Virtuals - 获取器, 设置器 & 虚拟字段 2 | 3 | Sequelize 允许你为模型的属性定义自定义获取器和设置器. 4 | 5 | Sequelize 还允许你指定所谓的 *虚拟属性*,它们是 Sequelize 模型上的属性,这些属性在基础 SQL 表中实际上并不存在,而是由 Sequelize 自动填充. 它们对于创建自定义属性非常有用, 这也可以简化你的代码. 6 | 7 | ## 获取器 8 | 9 | 获取器是为模型定义中的一列定义的 `get()` 函数: 10 | 11 | ```js 12 | const User = sequelize.define('user', { 13 | // 假设我们想要以大写形式查看每个用户名, 14 | // 即使它们在数据库本身中不一定是大写的 15 | username: { 16 | type: DataTypes.STRING, 17 | get() { 18 | const rawValue = this.getDataValue('username'); 19 | return rawValue ? rawValue.toUpperCase() : null; 20 | } 21 | } 22 | }); 23 | ``` 24 | 25 | 就像标准 JavaScript 获取器一样,在读取字段值时会自动调用此获取器: 26 | 27 | ```js 28 | const user = User.build({ username: 'SuperUser123' }); 29 | console.log(user.username); // 'SUPERUSER123' 30 | console.log(user.getDataValue('username')); // 'SuperUser123' 31 | ``` 32 | 33 | 注意,尽管上面记录为 `SUPERUSER123`,但是真正存储在数据库中的值仍然是 `SuperUser123`. 我们使用了 `this.getDataValue('username')` 来获得该值,并将其转换为大写. 34 | 35 | 如果我们尝试在获取器中使用 `this.username`,我们将陷入无限循环! 这就是为什么 Sequelize 提供 `getDataValue` 方法的原因. 36 | 37 | ## 设置器 38 | 39 | 设置器是为模型定义中的一列定义的 `set()` 函数. 它接收要设置的值: 40 | 41 | ```js 42 | const User = sequelize.define('user', { 43 | username: DataTypes.STRING, 44 | password: { 45 | type: DataTypes.STRING, 46 | set(value) { 47 | // 在数据库中以明文形式存储密码是很糟糕的. 48 | // 使用适当的哈希函数来加密哈希值更好. 49 | this.setDataValue('password', hash(value)); 50 | } 51 | } 52 | }); 53 | ``` 54 | 55 | ```js 56 | const user = User.build({ username: 'someone', password: 'NotSo§tr0ngP4$SW0RD!' }); 57 | console.log(user.password); // '7cfc84b8ea898bb72462e78b4643cfccd77e9f05678ec2ce78754147ba947acc' 58 | console.log(user.getDataValue('password')); // '7cfc84b8ea898bb72462e78b4643cfccd77e9f05678ec2ce78754147ba947acc' 59 | ``` 60 | 61 | Sequelize 在将数据发送到数据库之前自动调用了设置器. 数据库得到的唯一数据是已经散列过的值. 62 | 63 | 如果我们想将模型实例中的另一个字段包含在计算中,那也是可以的,而且非常容易! 64 | 65 | ```js 66 | const User = sequelize.define('user', { 67 | username: DataTypes.STRING, 68 | password: { 69 | type: DataTypes.STRING, 70 | set(value) { 71 | // 在数据库中以明文形式存储密码是很糟糕的. 72 | // 使用适当的哈希函数来加密哈希值更好. 73 | // 使用用户名作为盐更好. 74 | this.setDataValue('password', hash(this.username + value)); 75 | } 76 | } 77 | }); 78 | ``` 79 | 80 | **注意:** 上面涉及密码处理的示例尽管比单纯以明文形式存储密码要好得多,但远非完美的安全性. 正确处理密码很困难,这里的所有内容只是为了举例说明 Sequelize 功能. 我们建议让网络安全专家阅读 [OWASP](https://www.owasp.org/) 文档或者访问 [InfoSec StackExchange](https://security.stackexchange.com/). 81 | 82 | ## 组合获取器和设置器 83 | 84 | 获取器和设置器都可以在同一字段中定义. 85 | 86 | 举个例子,假设我们正在建一个 `Post` 模型,其 `content` 是无限长度的文本. 假设要提高内存使用率,我们要存储内容的压缩版本. 87 | 88 | *注意:在这种情况下,现代数据库应会自动进行一些压缩. 这只是为了举例.* 89 | 90 | ```js 91 | const { gzipSync, gunzipSync } = require('zlib'); 92 | 93 | const Post = sequelize.define('post', { 94 | content: { 95 | type: DataTypes.TEXT, 96 | get() { 97 | const storedValue = this.getDataValue('content'); 98 | const gzippedBuffer = Buffer.from(storedValue, 'base64'); 99 | const unzippedBuffer = gunzipSync(gzippedBuffer); 100 | return unzippedBuffer.toString(); 101 | }, 102 | set(value) { 103 | const gzippedBuffer = gzipSync(value); 104 | this.setDataValue('content', gzippedBuffer.toString('base64')); 105 | } 106 | } 107 | }); 108 | ``` 109 | 110 | 通过上述设置,每当我们尝试与 `Post` 模型的 `content` 字段进行交互时,Sequelize 都会自动处理自定义的获取器和设置器. 例如: 111 | 112 | ```js 113 | const post = await Post.create({ content: 'Hello everyone!' }); 114 | 115 | console.log(post.content); // 'Hello everyone!' 116 | // 一切都在幕后进行,所以我们甚至都可以忘记内容实际上是 117 | // 作为 gzip 压缩的 base64 字符串存储的! 118 | 119 | // 但是,如果我们真的很好奇,我们可以获取 'raw' 数据... 120 | console.log(post.getDataValue('content')); 121 | // Output: 'H4sIAAAAAAAACvNIzcnJV0gtSy2qzM9LVQQAUuk9jQ8AAAA=' 122 | ``` 123 | 124 | ## 虚拟字段 125 | 126 | 虚拟字段是 Sequelize 在后台填充的字段,但实际上它们不存在于数据库中. 127 | 128 | 例如,假设我们有一个 User 的 `firstName` 和 `lastName` 属性.\ 129 | 130 | *同样,这[仅是为了示例](https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/).* 131 | 132 | 如果有一种简单的方法能直接获取 *全名* 那会非常好! 我们可以将 `getters` 的概念与 Sequelize 针对这种情况提供的特殊数据类型结合使用:`DataTypes.VIRTUAL`: 133 | 134 | ```js 135 | const { DataTypes } = require('@sequelize/core'); 136 | 137 | const User = sequelize.define('user', { 138 | firstName: DataTypes.TEXT, 139 | lastName: DataTypes.TEXT, 140 | fullName: { 141 | type: DataTypes.VIRTUAL, 142 | get() { 143 | return `${this.firstName} ${this.lastName}`; 144 | }, 145 | set(value) { 146 | throw new Error('不要尝试设置 `fullName` 的值!'); 147 | } 148 | } 149 | }); 150 | ``` 151 | 152 | `VIRTUAL` 字段不会导致数据表也存在此列. 换句话说,上面的模型虽然没有 `fullName` 列. 但是它似乎存在着! 153 | 154 | ```js 155 | const user = await User.create({ firstName: 'John', lastName: 'Doe' }); 156 | console.log(user.fullName); // 'John Doe' 157 | ``` 158 | -------------------------------------------------------------------------------- /core-concepts/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started - 入门 2 | 3 | 在本教程中,你将进行学习 Sequelize 的简单设置. 4 | 5 | ## 安装 6 | 7 | Sequelize 的使用可以通过 [npm](https://www.npmjs.com/package/@sequelize/core) (或 [yarn](https://yarnpkg.com/package/@sequelize/core)). 8 | 9 | 以下命令将安装 Sequelize v7. 如果你正在寻找 Sequelize v6(发布为 `sequelize` 而不是 `@sequelize/core`), 10 | [访问 v6 文档](https://github.com/demopark/sequelize-docs-Zh-CN/tree/v6) 11 | 12 | ```bash 13 | # 这将安装 v6 最新版本的 Sequelize 14 | npm i sequelize 15 | 16 | # 这将安装 v7 最新 alpha 版本的 Sequelize 17 | npm i @sequelize/core 18 | ``` 19 | 20 | ```bash 21 | # 这将安装 v6 最新版本的 Sequelize 22 | yarn add sequelize 23 | 24 | # 这将安装 v7 最新 alpha 版本的 Sequelize 25 | yarn add @sequelize/core 26 | ``` 27 | 28 | 接下来, 你还必须手动为所选数据库安装驱动程序: 29 | 30 | ```bash 31 | # 使用 npm 32 | npm i pg pg-hstore # PostgreSQL 33 | npm i mysql2 # MySQL 34 | npm i mariadb # MariaDB 35 | npm i sqlite3 # SQLite 36 | npm i tedious # Microsoft SQL Server 37 | npm i ibm_db # DB2 38 | npm i odbc # IBM i 39 | 40 | # 使用 yarn 41 | yarn add pg pg-hstore # PostgreSQL 42 | yarn add mysql2 # MySQL 43 | yarn add mariadb # MariaDB 44 | yarn add sqlite3 # SQLite 45 | yarn add tedious # Microsoft SQL Server 46 | yarn add ibm_db # DB2 47 | yarn add odbc # IBM i 48 | ``` 49 | 50 | ## 连接到数据库 51 | 52 | 要连接到数据库,必须创建一个 Sequelize 实例. 这可以通过将连接参数分别传递到 Sequelize 构造函数或通过传递一个连接 URI 来完成: 53 | 54 | ```javascript 55 | const { Sequelize } = require('@sequelize/core'); 56 | 57 | // 方法 1: 传递一个连接 URI 58 | const sequelize = new Sequelize('sqlite::memory:') // Sqlite 示例 59 | const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname') // Postgres 示例 60 | 61 | // 方法 2: 分别传递参数 (sqlite) 62 | const sequelize = new Sequelize({ 63 | dialect: 'sqlite', 64 | storage: 'path/to/database.sqlite' 65 | }); 66 | 67 | // 方法 3: 分别传递参数 (其它数据库) 68 | const sequelize = new Sequelize('database', 'username', 'password', { 69 | host: 'localhost', 70 | // 选择一种支持的数据库: 71 | // 'mysql', 'mariadb', 'postgres', 'mssql', 'sqlite', 'snowflake', 'db2' or 'ibmi' 72 | dialect: 'postgres' 73 | }); 74 | ``` 75 | 76 | Sequelize 构造函数接受很多参数. 它们记录在 [API 参考](https://sequelize.org/api/v7/classes/Sequelize.html#constructor)中. 77 | 78 | ### 测试连接 79 | 80 | 你可以使用 `.authenticate()` 函数测试连接是否正常: 81 | 82 | ```js 83 | try { 84 | await sequelize.authenticate(); 85 | console.log('Connection has been established successfully.'); 86 | } catch (error) { 87 | console.error('Unable to connect to the database:', error); 88 | } 89 | ``` 90 | 91 | ### 关闭连接 92 | 93 | 默认情况下,Sequelize 将保持连接打开状态,并对所有查询使用相同的连接. 如果你需要关闭连接,请调用 `sequelize.close()`(这是异步的并返回一个 Promise). 94 | 95 | **注意:** 一旦 `sequelize.close()` 被调用, 就不可能打开新的连接. 你将需要创建一个新的 Sequelize 实例以再次访问你的数据库. 96 | 97 | ## 术语约定 98 | 99 | 请注意,在上面的示例中,`Sequelize` 是指库本身,而 `sequelize` 是指 Sequelize 的实例,它表示与一个数据库的连接. 这是官方推荐的约定,在整个文档中都将遵循. 100 | 101 | ## 阅读文档的提示 102 | 103 | 我们鼓励你在阅读 Sequelize 文档时在本地运行代码示例. 这将帮助你更快地学习. 最简单的方法是使用 SQLite 方言: 104 | 105 | ```js 106 | const { Sequelize, Op, Model, DataTypes } = require('@sequelize/core'); 107 | const sequelize = new Sequelize("sqlite::memory:"); 108 | 109 | // 这是代码! 它是可用的! 110 | ``` 111 | 112 | 要尝试使用在本地难以设置的其他方言,可以使用 [Sequelize SSCCE](https://github.com/papb/sequelize-sscce) GitHub 存储库,该库可让你在所有受支持的方言上运行代码, 直接从 GitHub 免费获得,无需任何设置! 113 | 114 | ## 新数据库与现有数据库 115 | 116 | 如果你是从头开始一个项目,且你的数据库是空的,那么一开始就可以使用 Sequelize,以便自动创建数据库中的每个表. 117 | 118 | 除此之外,如果你想使用 Sequelize 连接到已经充满了表和数据的数据库,那也可以正常工作! 在两种情况下,Sequelize 都能满足你的要求. 119 | 120 | ## 记录日志 121 | 122 | 默认情况下,Sequelize 将记录控制台执行的每个SQL查询. 可以使用 `options.logging` 参数来自定义每次 Sequelize 记录某些内容时将执行的函数. 默认值为 `console.log`,使用该值时仅显示日志函数调用的第一个参数. 例如,对于查询日志记录,第一个参数是原始查询,第二个参数(默认情况下是隐藏的)是 Sequelize 对象. 123 | 124 | `options.logging` 的常用值: 125 | 126 | ```js 127 | const sequelize = new Sequelize('sqlite::memory:', { 128 | // 选择一种日志记录参数 129 | logging: console.log, // 默认值,显示日志函数调用的第一个参数 130 | logging: (...msg) => console.log(msg), // 显示所有日志函数调用参数 131 | logging: false, // 禁用日志记录 132 | logging: msg => logger.debug(msg), // 使用自定义记录器(例如Winston 或 Bunyan),显示第一个参数 133 | logging: logger.debug.bind(logger) // 使用自定义记录器的另一种方法,显示所有消息 134 | }); 135 | ``` 136 | 137 | ## Promises 和 async/await 138 | 139 | Sequelize 提供的大多数方法都是异步的,因此返回 Promises. 它们都是 [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), 因此你可以直接使用Promise API(例如,使用 `then`, `catch`, `finally`). 140 | 141 | 当然,使用 `async` 和 `await` 也可以正常工作. -------------------------------------------------------------------------------- /core-concepts/model-basics.md: -------------------------------------------------------------------------------- 1 | # Model Basics - 模型基础 2 | 3 | 在本教程中,你将学习 Sequelize 中的模型以及如何使用它们. 4 | 5 | ## 概念 6 | 7 | 模型是 Sequelize 的本质. 模型是代表数据库中表的抽象. 在 Sequelize 中,它是一个 [Model](/api/v7/classes/Model.html) 的扩展类. 8 | 9 | 该模型告诉 Sequelize 有关它代表的实体的几件事,例如数据库中表的名称以及它具有的列(及其数据类型). 10 | 11 | Sequelize 中的模型有一个名称. 此名称不必与它在数据库中表示的表的名称相同. 通常,模型具有单数名称(例如,`User`),而表具有复数名称(例如, `Users`),当然这是完全可配置的. 12 | 13 | ## 模型定义 14 | 15 | 在 Sequelize 中可以用两种等效的方式定义模型: 16 | 17 | * 调用 [`sequelize.define(modelName, attributes, options)`](/api/v7/classes/Sequelize.html#define) 18 | * 扩展 [Model](/api/v7/classes/Model.html) 并调用 [`init(attributes, options)`](/api/v7/classes/Model.html#init) 19 | 20 | 定义模型后,可通过其模型名称在 `sequelize.models` 中使用该模型. 21 | 22 | 为了学习一个示例,我们将考虑创建一个代表用户的模型,该模型具有一个 `firstName` 和一个 `lastName`. 我们希望将模型称为 `User`,并将其表示的表在数据库中称为 `Users`. 23 | 24 | 定义该模型的两种方法如下所示. 定义后,我们可以使用 `sequelize.models.User` 访问模型. 25 | 26 | ### 使用 [`sequelize.define`](/api/v7/classes/Sequelize.html#define): 27 | 28 | ```js 29 | const { Sequelize, DataTypes } = require('@sequelize/core'); 30 | const sequelize = new Sequelize('sqlite::memory:'); 31 | 32 | const User = sequelize.define('User', { 33 | // 在这里定义模型属性 34 | firstName: { 35 | type: DataTypes.STRING, 36 | allowNull: false 37 | }, 38 | lastName: { 39 | type: DataTypes.STRING 40 | // allowNull 默认为 true 41 | } 42 | }, { 43 | // 这是其他模型参数 44 | }); 45 | 46 | // `sequelize.define` 会返回模型 47 | console.log(User === sequelize.models.User); // true 48 | ``` 49 | 50 | ### 扩展 [Model](/api/v7/classes/Model.html) 51 | 52 | ```js 53 | const { Sequelize, DataTypes, Model } = require('@sequelize/core'); 54 | const sequelize = new Sequelize('sqlite::memory:'); 55 | 56 | class User extends Model {} 57 | 58 | User.init({ 59 | // 在这里定义模型属性 60 | firstName: { 61 | type: DataTypes.STRING, 62 | allowNull: false 63 | }, 64 | lastName: { 65 | type: DataTypes.STRING 66 | // allowNull 默认为 true 67 | } 68 | }, { 69 | // 这是其他模型参数 70 | sequelize, // 我们需要传递连接实例 71 | modelName: 'User' // 我们需要选择模型名称 72 | }); 73 | 74 | // 定义的模型是类本身 75 | console.log(User === sequelize.models.User); // true 76 | ``` 77 | 78 | 在内部,`sequelize.define` 调用 `Model.init`,因此两种方法本质上是等效的. 79 | 80 | 81 | #### 公共类字段的注意事项 82 | 83 | 添加与模型属性之一同名的[公共类字段](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields)会出现问题. Sequelize 为通过 `Model.init` 定义的每个属性添加一个 getter 和一个 setter. 添加公共类字段将隐藏那些 getter 和 setter, 从而阻止对模型的实际数据的访问. 84 | 85 | ```typescript 86 | // 无效的 87 | class User extends Model { 88 | id; // 此字段将影响 sequelize 的 getter 和 setter. 它应该被删除. 89 | otherPublicField; // 这个字段不会影响任何东西. 没问题. 90 | } 91 | 92 | User.init({ 93 | id: { 94 | type: DataTypes.INTEGER, 95 | autoIncrement: true, 96 | primaryKey: true 97 | } 98 | }, { sequelize }); 99 | 100 | const user = new User({ id: 1 }); 101 | user.id; // undefined 102 | ``` 103 | 104 | ```typescript 105 | // 有效的 106 | class User extends Model { 107 | otherPublicField; 108 | } 109 | 110 | User.init({ 111 | id: { 112 | type: DataTypes.INTEGER, 113 | autoIncrement: true, 114 | primaryKey: true 115 | } 116 | }, { sequelize }); 117 | 118 | const user = new User({ id: 1 }); 119 | user.id; // 1 120 | ``` 121 | 122 | 在 TypeScript 中, 你可以使用 `declare` 关键字添加键入信息, 而无需添加实际的公共类字段: 123 | 124 | ```typescript 125 | // 有效 126 | class User extends Model { 127 | declare id: number; // 你可以使用 `declare` 关键字添加键入信息, 而无需添加实际的公共类字段. 128 | } 129 | 130 | User.init({ 131 | id: { 132 | type: DataTypes.INTEGER, 133 | autoIncrement: true, 134 | primaryKey: true 135 | } 136 | }, { sequelize }); 137 | 138 | const user = new User({ id: 1 }); 139 | user.id; // 1 140 | ``` 141 | 142 | ## 表名推断 143 | 144 | 请注意,在以上两种方法中,都从未明确定义表名(`Users`). 但是,给出了模型名称(`User`). 145 | 146 | 默认情况下,当未提供表名时,Sequelize 会自动将模型名复数并将其用作表名. 这种复数是通过称为 [inflection](https://www.npmjs.com/package/inflection) 的库在后台完成的,因此可以正确计算不规则的复数(例如 `person -> people`). 147 | 148 | 当然,此行为很容易配置. 149 | 150 | ### 强制表名称等于模型名称 151 | 152 | 你可以使用 `freezeTableName: true` 参数停止 Sequelize 执行自动复数化. 这样,Sequelize 将推断表名称等于模型名称,而无需进行任何修改: 153 | 154 | ```js 155 | sequelize.define('User', { 156 | // ... (属性) 157 | }, { 158 | freezeTableName: true 159 | }); 160 | ``` 161 | 162 | 上面的示例将创建一个名为 `User` 的模型,该模型指向一个也名为 `User` 的表. 163 | 164 | 也可以为 sequelize 实例全局定义此行为: 165 | 166 | ```js 167 | const sequelize = new Sequelize('sqlite::memory:', { 168 | define: { 169 | freezeTableName: true 170 | } 171 | }); 172 | ``` 173 | 174 | 这样,所有表将使用与模型名称相同的名称. 175 | 176 | ### 直接提供表名 177 | 178 | 你也可以直接告诉 Sequelize 表名称: 179 | 180 | ```js 181 | sequelize.define('User', { 182 | // ... (属性) 183 | }, { 184 | tableName: 'Employees' 185 | }); 186 | ``` 187 | 188 | ## 模型同步 189 | 190 | 定义模型时,你要告诉 Sequelize 有关数据库中表的一些信息. 但是,如果该表实际上不存在于数据库中怎么办? 如果存在,但具有不同的列,较少的列或任何其他差异,该怎么办? 191 | 192 | 这就是模型同步的来源.可以通过调用一个异步函数(返回一个Promise)[`model.sync(options)`](/api/v7/classes/Model.html#sync). 通过此调用,Sequelize 将自动对数据库执行 SQL 查询. 请注意,这仅更改数据库中的表,而不更改 JavaScript 端的模型. 193 | 194 | * `User.sync()` - 如果表不存在,则创建该表(如果已经存在,则不执行任何操作) 195 | * `User.sync({ force: true })` - 将创建表,如果表已经存在,则将其首先删除 196 | * `User.sync({ alter: true })` - 这将检查数据库中表的当前状态(它具有哪些列,它们的数据类型等),然后在表中进行必要的更改以使其与模型匹配. 197 | 198 | 示例: 199 | 200 | ```js 201 | await User.sync({ force: true }); 202 | console.log("用户模型表刚刚(重新)创建!"); 203 | ``` 204 | 205 | ### 一次同步所有模型 206 | 207 | 你可以使用 [`sequelize.sync()`](/api/v7/classes/Sequelize.html#sync) 自动同步所有模型. 示例: 208 | 209 | ```js 210 | await sequelize.sync({ force: true }); 211 | console.log("所有模型均已成功同步."); 212 | ``` 213 | 214 | ### 删除表 215 | 216 | 删除与模型相关的表: 217 | 218 | ```js 219 | await User.drop(); 220 | console.log("用户表已删除!"); 221 | ``` 222 | 223 | 删除所有表: 224 | 225 | ```js 226 | await sequelize.drop(); 227 | console.log("所有表已删除!"); 228 | ``` 229 | 230 | ### 数据库安全检查 231 | 232 | 如上所示,`sync`和`drop`操作是破坏性的. Sequelize 使用 `match` 参数作为附加的安全检查,该检查将接受 RegExp: 233 | 234 | ```js 235 | // 仅当数据库名称以 '_test' 结尾时,它才会运行.sync() 236 | sequelize.sync({ force: true, match: /_test$/ }); 237 | ``` 238 | 239 | ### 生产环境同步 240 | 241 | 如上所示,`sync({ force: true })` 和 `sync({ alter: true })` 可能是破坏性操作. 因此,不建议将它们用于生产级软件中. 相反,应该在 [Sequelize CLI](https://github.com/sequelize/cli) 的帮助下使用高级概念 [Migrations](../other-topics/migrations.md)(迁移) 进行同步. 242 | 243 | ## 时间戳 244 | 245 | 默认情况下,Sequelize 使用数据类型 `DataTypes.DATE` 自动向每个模型添加 `createdAt` 和 `updatedAt` 字段. 这些字段会自动进行管理 - 每当你使用Sequelize 创建或更新内容时,这些字段都会被自动设置. `createdAt` 字段将包含代表创建时刻的时间戳,而 `updatedAt` 字段将包含最新更新的时间戳. 246 | 247 | **注意:** 这是在 Sequelize 级别完成的(即未使用 *SQL触发器* 完成). 这意味着直接 SQL 查询(例如,通过任何其他方式在不使用 Sequelize 的情况下执行的查询)将不会导致这些字段自动更新. 248 | 249 | 对于带有 `timestamps: false` 参数的模型,可以禁用此行为: 250 | 251 | ```js 252 | sequelize.define('User', { 253 | // ... (属性) 254 | }, { 255 | timestamps: false 256 | }); 257 | ``` 258 | 259 | 也可以只启用 `createdAt`/`updatedAt` 之一,并为这些列提供自定义名称: 260 | 261 | ```js 262 | class Foo extends Model {} 263 | Foo.init({ /* 属性 */ }, { 264 | sequelize, 265 | 266 | // 不要忘记启用时间戳! 267 | timestamps: true, 268 | 269 | // 不想要 createdAt 270 | createdAt: false, 271 | 272 | // 想要 updatedAt 但是希望名称叫做 updateTimestamp 273 | updatedAt: 'updateTimestamp' 274 | }); 275 | ``` 276 | 277 | ## 防止创建默认的 PK 属性 278 | 279 | 默认情况下, 当没有手动定义主键时, Sequelize 会自动将主键属性 `id` 添加到每个模型. 为防止这种情况, 你可以在定义模型时将 `noPrimaryKey` 选项设置为 true. 280 | 281 | ```js 282 | const User = sequelize.define('User', { 283 | name: DataTypes.STRING, 284 | }, { 285 | noPrimaryKey: true, 286 | }); 287 | ``` 288 | 289 | 如果你想阻止为每个模型添加默认主键: 290 | 291 | ```js 292 | const sequelize = new Sequelize({ 293 | define: { 294 | noPrimaryKey: true, 295 | }, 296 | }); 297 | 298 | const User = sequelize.define('User', { 299 | name: { 300 | type: DataTypes.STRING 301 | } 302 | }); 303 | ``` 304 | 305 | ## 列声明简写语法 306 | 307 | 如果关于列的唯一指定内容是其数据类型,则可以缩短语法: 308 | 309 | ```js 310 | // 例如: 311 | sequelize.define('User', { 312 | name: { 313 | type: DataTypes.STRING 314 | } 315 | }); 316 | 317 | // 可以简写为: 318 | sequelize.define('User', { name: DataTypes.STRING }); 319 | ``` 320 | 321 | ## 默认值 322 | 323 | 默认情况下,Sequelize 假定列的默认值为 `NULL`. 可以通过将特定的 `defaultValue` 传递给列定义来更改此行为: 324 | 325 | ```js 326 | sequelize.define('User', { 327 | name: { 328 | type: DataTypes.STRING, 329 | defaultValue: "John Doe" 330 | } 331 | }); 332 | ``` 333 | 334 | 可以使用 `fn` 将原生 SQL 函数用作默认值: 335 | 336 | ```js 337 | sequelize.define('Foo', { 338 | myUuid: { 339 | type: DataTypes.UUID, 340 | defaultValue: fn('uuid_generate_v4'), 341 | } 342 | }); 343 | ``` 344 | 345 | Sequelize 提供了一系列你可以使用的内置默认值: 346 | 347 | - [`DataTypes.NOW`](../other-topics/other-data-types.md) 348 | - [`DataTypes.UUIDV1`, `DataTypes.UUIDV4`](../other-topics/other-data-types.md) 349 | 350 | ## 数据类型 351 | 352 | 你在模型中定义的每一列都必须具有数据类型. Sequelize 提供[很多内置数据类型](https://github.com/sequelize/sequelize/blob/main/src/data-types.js). 要访问内置数据类型,必须导入 `DataTypes`: 353 | 354 | ```js 355 | const { DataTypes } = require('@sequelize/core'); // 导入内置数据类型 356 | ``` 357 | 358 | ### 字符串 359 | 360 | ```js 361 | DataTypes.STRING // VARCHAR(255) 362 | DataTypes.STRING(1234) // VARCHAR(1234) 363 | DataTypes.STRING.BINARY // VARCHAR BINARY 364 | DataTypes.TEXT // TEXT 365 | DataTypes.TEXT('tiny') // TINYTEXT 366 | DataTypes.CITEXT // CITEXT 仅 PostgreSQL 和 SQLite. 367 | DataTypes.TSVECTOR // TSVECTOR 仅 PostgreSQL. 368 | ``` 369 | 370 | ### 布尔 371 | 372 | ```js 373 | DataTypes.BOOLEAN // TINYINT(1) 374 | ``` 375 | 376 | ### 数字 377 | 378 | ```js 379 | DataTypes.INTEGER // INTEGER 380 | DataTypes.BIGINT // BIGINT 381 | DataTypes.BIGINT(11) // BIGINT(11) 382 | 383 | DataTypes.FLOAT // FLOAT 384 | DataTypes.FLOAT(11) // FLOAT(11) 385 | DataTypes.FLOAT(11, 10) // FLOAT(11,10) 386 | 387 | DataTypes.REAL // REAL 仅 PostgreSQL. 388 | DataTypes.REAL(11) // REAL(11) 仅 PostgreSQL. 389 | DataTypes.REAL(11, 12) // REAL(11,12) 仅 PostgreSQL. 390 | 391 | DataTypes.DOUBLE // DOUBLE 392 | DataTypes.DOUBLE(11) // DOUBLE(11) 393 | DataTypes.DOUBLE(11, 10) // DOUBLE(11,10) 394 | 395 | DataTypes.DECIMAL // DECIMAL 396 | DataTypes.DECIMAL(10, 2) // DECIMAL(10,2) 397 | ``` 398 | 399 | #### 无符号和零填充整数 - 仅限于MySQL/MariaDB 400 | 401 | 在 MySQL 和 MariaDB 中,可以将数据类型`INTEGER`, `BIGINT`, `FLOAT` 和 `DOUBLE` 设置为无符号或零填充(或两者),如下所示: 402 | 403 | ```js 404 | DataTypes.INTEGER.UNSIGNED 405 | DataTypes.INTEGER.ZEROFILL 406 | DataTypes.INTEGER.UNSIGNED.ZEROFILL 407 | // 你还可以指定大小,即INTEGER(10)而不是简单的INTEGER 408 | // 同样适用于 BIGINT, FLOAT 和 DOUBLE 409 | ``` 410 | 411 | ### 日期 412 | 413 | ```js 414 | DataTypes.DATE // DATETIME 适用于 mysql / sqlite, 带时区的TIMESTAMP 适用于 postgres 415 | DataTypes.DATE(6) // DATETIME(6) 适用于 mysql 5.6.4+. 支持6位精度的小数秒 416 | DataTypes.DATEONLY // 不带时间的 DATE 417 | ``` 418 | 419 | ### UUID 420 | 421 | 对于 UUID,使用 `DataTypes.UUID`. 对于 PostgreSQL 和 SQLite,它会是 `UUID` 数据类型;对于 MySQL,它则变成`CHAR(36)`. Sequelize 可以自动为这些字段生成 UUID,只需使用 `DataTypes.UUIDV1` 或 `DataTypes.UUIDV4` 作为默认值即可: 422 | 423 | ```js 424 | { 425 | type: DataTypes.UUID, 426 | defaultValue: DataTypes.UUIDV4 // 或 DataTypes.UUIDV1 427 | } 428 | ``` 429 | 430 | ### 其它 431 | 432 | 还有其他数据类型,请参见[其它数据类型](../other-topics/other-data-types.md). 433 | 434 | ## 列参数 435 | 436 | 在定义列时,除了指定列的 `type` 以及上面提到的 `allowNull` 和 `defaultValue` 参数外,还有很多可用的参数. 下面是一些示例. 437 | 438 | ```js 439 | const { Model, DataTypes, Deferrable } = require('@sequelize/core'); 440 | 441 | class Foo extends Model {} 442 | Foo.init({ 443 | // 实例化将自动将 flag 设置为 true (如果未设置) 444 | flag: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true }, 445 | 446 | // 日期的默认值 => 当前时间 447 | myDate: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, 448 | 449 | // 将 allowNull 设置为 false 将为该列添加 NOT NULL, 450 | // 这意味着如果该列为 null,则在执行查询时将从数据库引发错误. 451 | // 如果要在查询数据库之前检查值是否不为 null,请查看下面的验证部分. 452 | title: { type: DataTypes.STRING, allowNull: false }, 453 | 454 | // 创建两个具有相同值的对象将引发错误. 455 | // unique 属性可以是布尔值或字符串. 456 | // 如果为多个列提供相同的字符串,则它们将形成一个复合唯一键. 457 | uniqueOne: { type: DataTypes.STRING, unique: 'compositeIndex' }, 458 | uniqueTwo: { type: DataTypes.INTEGER, unique: 'compositeIndex' }, 459 | 460 | // unique 属性是创建唯一约束的简写. 461 | someUnique: { type: DataTypes.STRING, unique: true }, 462 | 463 | // 继续阅读有关主键的更多信息 464 | identifier: { type: DataTypes.STRING, primaryKey: true }, 465 | 466 | // autoIncrement 可用于创建 auto_incrementing 整数列 467 | incrementMe: { type: DataTypes.INTEGER, autoIncrement: true }, 468 | 469 | // 你可以通过 'field' 属性指定自定义列名称: 470 | fieldWithUnderscores: { type: DataTypes.STRING, field: 'field_with_underscores' }, 471 | 472 | // 可以创建外键: 473 | bar_id: { 474 | type: DataTypes.INTEGER, 475 | 476 | references: { 477 | // 这是对另一个模型的参考 478 | model: Bar, 479 | 480 | // 这是引用模型的列名 481 | key: 'id', 482 | 483 | // 使用 PostgreSQL,可以通过 Deferrable 类型声明何时检查外键约束. 484 | deferrable: Deferrable.INITIALLY_IMMEDIATE 485 | // 参数: 486 | // - `Deferrable.INITIALLY_IMMEDIATE` - 立即检查外键约束 487 | // - `Deferrable.INITIALLY_DEFERRED` - 将所有外键约束检查推迟到事务结束 488 | // - `Deferrable.NOT` - 完全不推迟检查(默认) - 这将不允许你动态更改事务中的规则 489 | } 490 | }, 491 | 492 | // 注释只能添加到 MySQL,MariaDB,PostgreSQL 和 MSSQL 的列中 493 | commentMe: { 494 | type: DataTypes.INTEGER, 495 | comment: '这是带有注释的列' 496 | } 497 | }, { 498 | sequelize, 499 | modelName: 'foo', 500 | 501 | // 在上面的属性中使用 `unique: true` 与在模型的参数中创建索引完全相同: 502 | indexes: [{ unique: true, fields: ['someUnique'] }] 503 | }); 504 | ``` 505 | 506 | ## 利用模型作为类 507 | 508 | Sequelize 模型是 [ES6 类](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). 你可以非常轻松地添加自定义实例或类级别的方法. 509 | 510 | ```js 511 | class User extends Model { 512 | static classLevelMethod() { 513 | return 'foo'; 514 | } 515 | instanceLevelMethod() { 516 | return 'bar'; 517 | } 518 | getFullname() { 519 | return [this.firstname, this.lastname].join(' '); 520 | } 521 | } 522 | User.init({ 523 | firstname: DataTypes.TEXT, 524 | lastname: DataTypes.TEXT 525 | }, { sequelize }); 526 | 527 | console.log(User.classLevelMethod()); // 'foo' 528 | const user = User.build({ firstname: 'Jane', lastname: 'Doe' }); 529 | console.log(user.instanceLevelMethod()); // 'bar' 530 | console.log(user.getFullname()); // 'Jane Doe' 531 | ``` -------------------------------------------------------------------------------- /core-concepts/model-instances.md: -------------------------------------------------------------------------------- 1 | # Model Instances - 模型实例 2 | 3 | 如你所知,模型是 [ES6 类](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). 类的实例表示该模型中的一个对象(该对象映射到数据库中表的一行). 这样,模型实例就是 [DAOs](https://en.wikipedia.org/wiki/Data_access_object) 4 | 5 | 对于本指南,将假定以下设置: 6 | 7 | ```js 8 | const { Sequelize, Model, DataTypes } = require('@sequelize/core'); 9 | const sequelize = new Sequelize("sqlite::memory:"); 10 | 11 | const User = sequelize.define("user", { 12 | name: DataTypes.TEXT, 13 | favoriteColor: { 14 | type: DataTypes.TEXT, 15 | defaultValue: 'green' 16 | }, 17 | age: DataTypes.INTEGER, 18 | cash: DataTypes.INTEGER 19 | }); 20 | 21 | (async () => { 22 | await sequelize.sync({ force: true }); 23 | // 这里是代码 24 | })(); 25 | ``` 26 | 27 | ## 创建实例 28 | 29 | 尽管模型是一个类,但是你不应直接使用 `new` 运算符来创建实例. 相反,应该使用 [`build`](/api/v7/classes/Model.html#build) 方法: 30 | 31 | ```js 32 | const jane = User.build({ name: "Jane" }); 33 | console.log(jane instanceof User); // true 34 | console.log(jane.name); // "Jane" 35 | ``` 36 | 37 | 但是,以上代码根本无法与数据库通信(请注意,它甚至不是异步的)! 这是因为 [`build`](/api/v7/classes/Model.html#build) 方法仅创建一个对象,该对象 *表示* *可以* 映射到数据库的数据. 为了将这个实例真正保存(即持久保存)在数据库中,应使用 [`save`](/api/v7/classes/Model.html#save) 方法: 38 | 39 | ```js 40 | await jane.save(); 41 | console.log('Jane 已保存到数据库!'); 42 | ``` 43 | 44 | 请注意,从上面代码段中的 `await` 用法来看,`save` 是一种异步方法. 实际上,几乎每个 Sequelize 方法都是异步的. `build` 是极少数例外之一. 45 | 46 | ### 非常有用的捷径: `create` 方法 47 | 48 | Sequelize提供了 [`create`](/api/v7/classes/Model.html#create) 方法,该方法将上述的 `build` 方法和 `save` 方法合并为一个方法: 49 | 50 | ```js 51 | const jane = await User.create({ name: "Jane" }); 52 | // Jane 现在存在于数据库中! 53 | console.log(jane instanceof User); // true 54 | console.log(jane.name); // "Jane" 55 | ``` 56 | 57 | ## 注意: 实例记录 58 | 59 | 尝试将模型实例直接记录到 `console.log` 会产生很多问题,因为 Sequelize 实例具有很多附加条件. 相反,你可以使用 `.toJSON()` 方法(顺便说一句,它会自动保证实例被 `JSON.stringify` 编辑好). 60 | 61 | ```js 62 | const jane = await User.create({ name: "Jane" }); 63 | // console.log(jane); // 不要这样! 64 | console.log(jane.toJSON()); // 这样最好! 65 | console.log(JSON.stringify(jane, null, 4)); // 这样也不错! 66 | ``` 67 | 68 | ## 默认值 69 | 70 | 内置实例将自动获得默认值: 71 | 72 | ```js 73 | const jane = User.build({ name: "Jane" }); 74 | console.log(jane.favoriteColor); // "green" 75 | ``` 76 | 77 | ## 更新实例 78 | 79 | 如果你更改实例的某个字段的值,则再次调用 `save` 将相应地对其进行更新: 80 | 81 | ```js 82 | const jane = await User.create({ name: "Jane" }); 83 | console.log(jane.name); // "Jane" 84 | jane.name = "Ada"; 85 | // 数据库中的名称仍然是 "Jane" 86 | await jane.save(); 87 | // 现在该名称已在数据库中更新为 "Ada"! 88 | ``` 89 | 90 | 你可以使用 [`set`](/api/v7/classes/Model.html#set) 方法一次更新多个字段: 91 | 92 | ```js 93 | const jane = await User.create({ name: "Jane" }); 94 | 95 | jane.set({ 96 | name: "Ada", 97 | favoriteColor: "blue" 98 | }); 99 | // 如上, 数据库中还是 "Jane" 和 "green" 100 | await jane.save(); 101 | // 数据库现在将 "Ada" 和 "blue" 作为 name 和 favoriteColor 102 | ``` 103 | 104 | 请注意, 此处的 `save()` 也将保留在此实例上所做的任何其他更改, 而不仅仅是之前的 `set` 调用中的更改.如果要更新一组特定的字段, 可以使用[`update`](/api/v7/classes/Model.html#update): 105 | 106 | ```js 107 | const jane = await User.create({ name: "Jane" }); 108 | jane.favoriteColor = "blue" 109 | await jane.update({ name: "Ada" }) 110 | // 数据库现在将 "Ada" 作为 name, 但仍然有默认的 "green" 作为 favoriteColor 111 | await jane.save() 112 | // 数据库现在将 "Ada" 作为 name, 但仍然有默认的 "blue" 作为 favoriteColor 113 | ``` 114 | 115 | 116 | ## 删除实例 117 | 118 | 你可以通过调用 [`destroy`](/api/v7/classes/Model.html#destroy) 来删除实例: 119 | 120 | ```js 121 | const jane = await User.create({ name: "Jane" }); 122 | console.log(jane.name); // "Jane" 123 | await jane.destroy(); 124 | // 现在该条目已从数据库中删除 125 | ``` 126 | 127 | ## 重载实例 128 | 129 | 你可以通过调用 [`reload`](/api/v7/classes/Model.html#reload) 从数据库中重新加载实例: 130 | 131 | ```js 132 | const jane = await User.create({ name: "Jane" }); 133 | console.log(jane.name); // "Jane" 134 | jane.name = "Ada"; 135 | // 数据库中的名称依然是 "Jane" 136 | await jane.reload(); 137 | console.log(jane.name); // "Jane" 138 | ``` 139 | 140 | reload 调用生成一个 `SELECT` 查询,以从数据库中获取最新数据. 141 | 142 | ## 仅保存部分字段 143 | 144 | 通过传递一个列名数组,可以定义在调用 `save` 时应该保存哪些属性. 145 | 146 | 当你基于先前定义的对象设置属性时,例如,当你通过 Web 应用程序的形式获取对象的值时,这很有用. 此外,这在 `update` 实现中内部使用. 它是这样的: 147 | 148 | ```js 149 | const jane = await User.create({ name: "Jane" }); 150 | console.log(jane.name); // "Jane" 151 | console.log(jane.favoriteColor); // "green" 152 | jane.name = "Jane II"; 153 | jane.favoriteColor = "blue"; 154 | await jane.save({ fields: ['name'] }); 155 | console.log(jane.name); // "Jane II" 156 | console.log(jane.favoriteColor); // "blue" 157 | // 上面显示为 "blue",因为本地对象将其设置为 "blue", 158 | // 但是在数据库中它仍然是 "green": 159 | await jane.reload(); 160 | console.log(jane.name); // "Jane II" 161 | console.log(jane.favoriteColor); // "green" 162 | ``` 163 | 164 | ## Change-awareness of save 165 | 166 | The `save` method is optimized internally to only update fields that really changed. This means that if you don't change anything and call `save`, Sequelize will know that the save is superfluous and do nothing, i.e., no query will be generated (it will still return a Promise, but it will resolve immediately). 167 | 168 | Also, if only a few attributes have changed when you call `save`, only those fields will be sent in the `UPDATE` query, to improve performance. 169 | 170 | ## 递增和递减整数值 171 | 172 | 为了递增/递减实例的值而不会遇到并发问题,Sequelize提供了 [`increment`](/api/v7/classes/Model.html#increment) 和 [`decrement`](/api/v7/classes/Model.html#decrement) 实例方法. 173 | 174 | ```js 175 | const jane = await User.create({ name: "Jane", age: 100 }); 176 | const incrementResult = await jane.increment('age', { by: 2 }); 177 | // 注意: 如只增加 1, 你可以省略 'by' 参数, 只需执行 `user.increment('age')` 178 | 179 | // 在 PostgreSQL 中, 除非设置了 `{returning:false}` 参数(不然它将是 undefined), 180 | // 否则 `incrementResult` 将是更新后的 user. 181 | 182 | // 在其它数据库方言中, `incrementResult` 将会是 undefined. 如果你需要更新的实例, 你需要调用 `user.reload()`. 183 | ``` 184 | 185 | 你也可以一次递增多个字段: 186 | 187 | ```js 188 | const jane = await User.create({ name: "Jane", age: 100, cash: 5000 }); 189 | await jane.increment({ 190 | 'age': 2, 191 | 'cash': 100 192 | }); 193 | 194 | // 如果值增加相同的数量,则也可以使用以下其他语法: 195 | await jane.increment(['age', 'cash'], { by: 2 }); 196 | ``` 197 | 198 | 递减的工作原理完全相同. -------------------------------------------------------------------------------- /core-concepts/model-querying-finders.md: -------------------------------------------------------------------------------- 1 | # Model Querying - Finders - 模型查询(查找器) 2 | 3 | Finder 方法是生成 `SELECT` 查询的方法. 4 | 5 | 默认情况下,所有 finder 方法的结果都是模型类的实例(与普通的 JavaScript 对象相反). 这意味着在数据库返回结果之后,Sequelize 会自动将所有内容包装在适当的实例对象中. 在少数情况下,当结果太多时,这种包装可能会效率低下. 要禁用此包装并收到简单的响应,请将 `{ raw: true }` 作为参数传递给 finder 方法. 6 | 7 | ## `findAll` 8 | 9 | 在上一教程中已经知道 `findAll` 方法. 它生成一个标准的 `SELECT` 查询,该查询将从表中检索所有条目(除非受到 `where` 子句的限制). 10 | 11 | ## `findByPk` 12 | 13 | `findByPk` 方法使用提供的主键从表中仅获得一个条目. 14 | 15 | ```js 16 | const project = await Project.findByPk(123); 17 | if (project === null) { 18 | console.log('Not found!'); 19 | } else { 20 | console.log(project instanceof Project); // true 21 | // 它的主键是 123 22 | } 23 | ``` 24 | 25 | ## `findOne` 26 | 27 | `findOne` 方法获得它找到的第一个条目(它可以满足提供的可选查询参数). 28 | 29 | ```js 30 | const project = await Project.findOne({ where: { title: 'My Title' } }); 31 | if (project === null) { 32 | console.log('Not found!'); 33 | } else { 34 | console.log(project instanceof Project); // true 35 | console.log(project.title); // 'My Title' 36 | } 37 | ``` 38 | 39 | ## `findOrCreate` 40 | 41 | 除非找到一个满足查询参数的结果,否则方法 `findOrCreate` 将在表中创建一个条目. 在这两种情况下,它将返回一个实例(找到的实例或创建的实例)和一个布尔值,指示该实例是已创建还是已经存在. 42 | 43 | 使用 `where` 参数来查找条目,而使用 `defaults` 参数来定义必须创建的内容. 如果 `defaults` 不包含每一列的值,则 Sequelize 将采用 `where` 的值(如果存在). 44 | 45 | 假设我们有一个空的数据库,该数据库具有一个 `User` 模型,该模型具有一个 `username` 和一个 `job`. 46 | 47 | ```js 48 | const [user, created] = await User.findOrCreate({ 49 | where: { username: 'sdepold' }, 50 | defaults: { 51 | job: 'Technical Lead JavaScript' 52 | } 53 | }); 54 | console.log(user.username); // 'sdepold' 55 | console.log(user.job); // 这可能是也可能不是 'Technical Lead JavaScript' 56 | console.log(created); // 指示此实例是否刚刚创建的布尔值 57 | if (created) { 58 | console.log(user.job); // 这里肯定是 'Technical Lead JavaScript' 59 | } 60 | ``` 61 | 62 | ## `findAndCountAll` 63 | 64 | `findAndCountAll` 方法是结合了 `findAll` 和 `count` 的便捷方法. 在处理与分页有关的查询时非常有用,在分页中,你想检索带有 `limit` 和 `offset` 的数据,但又需要知道与查询匹配的记录总数. 65 | 66 | 当没有提供 `group` 时, `findAndCountAll` 方法返回一个具有两个属性的对象: 67 | 68 | * `count` - 一个整数 - 与查询匹配的记录总数 69 | * `rows` - 一个数组对象 - 获得的记录 70 | 71 | 当提供了 `group` 时, `findAndCountAll` 方法返回一个具有两个属性的对象: 72 | 73 | * `count` - 一个数组对象 - 包含每组中的合计和预设属性 74 | * `rows` - 一个数组对象 - 获得的记录 75 | 76 | ```js 77 | const { count, rows } = await Project.findAndCountAll({ 78 | where: { 79 | title: { 80 | [Op.like]: 'foo%' 81 | } 82 | }, 83 | offset: 10, 84 | limit: 2 85 | }); 86 | console.log(count); 87 | console.log(rows); 88 | ``` -------------------------------------------------------------------------------- /core-concepts/paranoid.md: -------------------------------------------------------------------------------- 1 | # Paranoid - 偏执表 2 | 3 | Sequelize 支持 *paranoid* 表的概念. 一个 *paranoid* 表是一个被告知删除记录时不会真正删除它的表.反而一个名为 `deletedAt` 的特殊列会将其值设置为该删除请求的时间戳. 4 | 5 | 这意味着偏执表会执行记录的 *软删除*,而不是 *硬删除*. 6 | 7 | ## 将模型定义为 paranoid 8 | 9 | 要定义 paranoid 模型,必须将 `paranoid: true` 参数传递给模型定义. Paranoid 需要时间戳才能起作用(即,如果你传递 `timestamps: false` 了,paranoid 将不起作用). 10 | 11 | 你还可以将默认的列名(默认是 `deletedAt`)更改为其他名称. 12 | 13 | ```js 14 | class Post extends Model {} 15 | Post.init({ /* 这是属性 */ }, { 16 | sequelize, 17 | paranoid: true, 18 | 19 | // 如果要为 deletedAt 列指定自定义名称 20 | deletedAt: 'destroyTime' 21 | }); 22 | ``` 23 | 24 | ## 删除 25 | 26 | 当你调用 `destroy` 方法时,将发生软删除: 27 | 28 | ```js 29 | await Post.destroy({ 30 | where: { 31 | id: 1 32 | } 33 | }); 34 | // UPDATE "posts" SET "deletedAt"=[timestamp] WHERE "deletedAt" IS NULL AND "id" = 1 35 | ``` 36 | 37 | 如果你确实想要硬删除,并且模型是 paranoid,则可以使用 `force: true` 参数强制执行: 38 | 39 | ```js 40 | await Post.destroy({ 41 | where: { 42 | id: 1 43 | }, 44 | force: true 45 | }); 46 | // DELETE FROM "posts" WHERE "id" = 1 47 | ``` 48 | 49 | 上面的示例以静态的 `destroy` 方法为例(`Post.destroy`),所有实例方法的工作方式相同: 50 | 51 | ```js 52 | const post = await Post.create({ title: 'test' }); 53 | console.log(post instanceof Post); // true 54 | await post.destroy(); // 只设置 `deletedAt` 标志 55 | await post.destroy({ force: true }); // 真的会删除记录 56 | ``` 57 | 58 | ## 恢复 59 | 60 | 要恢复软删除的记录,可以使用 `restore` 方法,该方法在静态版本和实例版本中都提供: 61 | 62 | ```js 63 | // 展示实例 `restore` 方法的示例 64 | // 我们创建一个帖子,对其进行软删除,然后将其还原 65 | const post = await Post.create({ title: 'test' }); 66 | console.log(post instanceof Post); // true 67 | await post.destroy(); 68 | console.log('soft-deleted!'); 69 | await post.restore(); 70 | console.log('restored!'); 71 | 72 | // 展示静态 `restore` 方法的示例. 73 | // 恢复每个 likes 大于 100 的软删除的帖子 74 | await Post.restore({ 75 | where: { 76 | likes: { 77 | [Op.gt]: 100 78 | } 79 | } 80 | }); 81 | ``` 82 | 83 | ## 其他查询行为 84 | 85 | Sequelize 执行的每个查询将自动忽略软删除的记录(当然,原始查询除外). 86 | 87 | 这意味着,例如,`findAll` 方法将看不到软删除的记录,仅获取未删除的记录. 88 | 89 | 即使你单纯的调用提供了软删除记录主键的`findByPk`,结果也将是 `null`,就好像该记录不存在一样. 90 | 91 | 如果你真的想让查询看到被软删除的记录,可以将 `paranoid: false` 参数传递给查询方法. 例如: 92 | 93 | ```js 94 | await Post.findByPk(123); // 如果 ID 123 的记录被软删除,则将返回 `null` 95 | await Post.findByPk(123, { paranoid: false }); // 这将检索记录 96 | 97 | await Post.findAll({ 98 | where: { foo: 'bar' } 99 | }); // 这将不会检索软删除的记录 100 | 101 | await Post.findAll({ 102 | where: { foo: 'bar' }, 103 | paranoid: false 104 | }); // 这还将检索软删除的记录 105 | ``` -------------------------------------------------------------------------------- /core-concepts/raw-queries.md: -------------------------------------------------------------------------------- 1 | # Raw Queries - 原始查询 2 | 3 | 由于常常使用简单的方式来执行原始/已经准备好的SQL查询,因此可以使用 [`sequelize.query`](/api/v7/classes/Sequelize.html#query) 方法. 4 | 5 | 默认情况下,函数将返回两个参数 - 一个结果数组,以及一个包含元数据(例如受影响的行数等)的对象. 请注意,由于这是一个原始查询,所以元数据都是具体的方言. 某些方言返回元数据 "within" 结果对象(作为数组上的属性). 但是,将永远返回两个参数,但对于MSSQL和MySQL,它将是对同一对象的两个引用. 6 | 7 | ```js 8 | const [results, metadata] = await sequelize.query("UPDATE users SET y = 42 WHERE x = 12"); 9 | // 结果将是一个空数组,元数据将包含受影响的行数. 10 | ``` 11 | 12 | 在不需要访问元数据的情况下,你可以传递一个查询类型来告诉后续如何格式化结果. 例如,对于一个简单的选择查询你可以做: 13 | 14 | ```js 15 | const { QueryTypes } = require('@sequelize/core'); 16 | const users = await sequelize.query("SELECT * FROM `users`", { type: QueryTypes.SELECT }); 17 | // 我们不需要在这里分解结果 - 结果会直接返回 18 | 19 | ``` 20 | 21 | 还有其他几种查询类型可用. [详细了解来源](https://github.com/sequelize/sequelize/blob/main/packages/core/src/query-types.ts). 22 | 23 | 第二种选择是模型. 如果传递模型,返回的数据将是该模型的实例. 24 | 25 | ```js 26 | // Callee 是模型定义. 这样你就可以轻松地将查询映射到预定义的模型 27 | const projects = await sequelize.query('SELECT * FROM projects', { 28 | model: Projects, 29 | mapToModel: true // 如果你有任何映射字段,则在此处传递 true 30 | }); 31 | // 现在,`projects` 的每个元素都是 Project 的一个实例 32 | ``` 33 | 34 | 查看 [Query API 参考](/api/v7/classes/Sequelize.html#query)中的更多参数. 以下是一些例子: 35 | 36 | ```js 37 | const { QueryTypes } = require('@sequelize/core'); 38 | await sequelize.query('SELECT 1', { 39 | // 用于记录查询的函数(或false) 40 | // 将调用发送到服务器的每个SQL查询. 41 | logging: console.log, 42 | 43 | // 如果plain为true,则sequelize将仅返回结果集的第一条记录. 44 | // 如果是false,它将返回所有记录. 45 | plain: false, 46 | 47 | // 如果你没有查询的模型定义,请将此项设置为true. 48 | raw: false, 49 | 50 | // 你正在执行的查询类型. 查询类型会影响结果在传回之前的格式. 51 | type: QueryTypes.SELECT 52 | }); 53 | 54 | // 注意第二个参数为null! 55 | // 即使我们在这里声明了一个被调用对象, 56 | // raw: true 也会取代并返回一个原始对象. 57 | 58 | console.log(await sequelize.query('SELECT * FROM projects', { raw: true })); 59 | ``` 60 | 61 | ## "Dotted" 属性 和 `nest` 参数 62 | 63 | 如果表的属性名称包含点,则可以通过设置 `nest: true` 参数将生成的对象变为嵌套对象. 这可以通过 [dottie.js](https://github.com/mickhansen/dottie.js/) 在后台实现. 见下文: 64 | 65 | * 不使用 `nest: true`: 66 | 67 | ```js 68 | const { QueryTypes } = require('@sequelize/core'); 69 | const records = await sequelize.query('select 1 as `foo.bar.baz`', { 70 | type: QueryTypes.SELECT 71 | }); 72 | console.log(JSON.stringify(records[0], null, 2)); 73 | ``` 74 | 75 | ```json 76 | { 77 | "foo.bar.baz": 1 78 | } 79 | ``` 80 | 81 | * 使用 `nest: true`: 82 | 83 | ```js 84 | const { QueryTypes } = require('@sequelize/core'); 85 | const records = await sequelize.query('select 1 as `foo.bar.baz`', { 86 | nest: true, 87 | type: QueryTypes.SELECT 88 | }); 89 | console.log(JSON.stringify(records[0], null, 2)); 90 | ``` 91 | 92 | ```json 93 | { 94 | "foo": { 95 | "bar": { 96 | "baz": 1 97 | } 98 | } 99 | } 100 | ``` 101 | 102 | ## 替换 103 | 104 | 替换是在查询中传递变量的一种方式. 它们是 [绑定参数](#绑定参数) 的替代方法. 105 | 106 | 替换参数和绑定参数之间的区别在于, 替换参数在查询发送到数据库之前被转义并由 Sequelize 插入到查询中, 107 | 而绑定参数与 SQL 查询文本分开发送到数据库, 并由数据库本身进行 "转义". 108 | 109 | 替换可以用两种不同的方式编写: 110 | 111 | - 使用数字标识符 (由一个 `?` 表示). `replacements` 参数必须是一个数组. 这些值将按照它们在数组和查询中出现的顺序被替换. 112 | - 或者使用字母标识符 (例如 `:firstName`, `:status`, 等…). 这些标识符遵循通用标识符规则 (仅限字母数字和下划线, 不能以数字开头). `replacements` 参数必须是包含每个参数(没有 `:` 前缀)的普通对象. 113 | 114 | `replacements` 参数必须包含所有绑定值, 否则 Sequelize 将抛出错误. 115 | 116 | 示例: 117 | 118 | ```js 119 | const { QueryTypes } = require('@sequelize/core'); 120 | 121 | await sequelize.query( 122 | 'SELECT * FROM projects WHERE status = ?', 123 | { 124 | replacements: ['active'], 125 | type: QueryTypes.SELECT 126 | } 127 | ); 128 | 129 | await sequelize.query( 130 | 'SELECT * FROM projects WHERE status = :status', 131 | { 132 | replacements: { status: 'active' }, 133 | type: QueryTypes.SELECT, 134 | }, 135 | ); 136 | ``` 137 | 138 | 当使用 `LIKE` 等运算符时, 请记住替换中的特殊字符确实保留其特殊含义. 139 | 140 | 例如 以下查询匹配名称以 "ben" 开头的用户: 141 | 142 | ```js 143 | const { QueryTypes } = require('@sequelize/core'); 144 | 145 | await sequelize.query( 146 | 'SELECT * FROM users WHERE name LIKE :searchName', 147 | { 148 | replacements: { searchName: 'ben%' }, 149 | type: QueryTypes.SELECT 150 | } 151 | ); 152 | ``` 153 | 154 | 数组替换将自动处理,以下查询将搜索状态与值数组匹配的项目. 155 | 156 | ```js 157 | const { QueryTypes } = require('sequelize'); 158 | 159 | await sequelize.query( 160 | 'SELECT * FROM projects WHERE status IN(:status)', 161 | { 162 | replacements: { status: ['active', 'inactive'] }, 163 | type: QueryTypes.SELECT 164 | } 165 | ); 166 | ``` 167 | 168 | 要使用通配符运算符 `%`,请将其附加到你的替换中. 以下查询与名称以 'ben' 开头的用户相匹配. 169 | 170 | ```js 171 | const { QueryTypes } = require('sequelize'); 172 | 173 | await sequelize.query( 174 | 'SELECT * FROM users WHERE name LIKE :search_name', 175 | { 176 | replacements: { search_name: 'ben%' }, 177 | type: QueryTypes.SELECT 178 | } 179 | ); 180 | ``` 181 | 182 | **警告** 183 | 184 | Sequelize 目前不支持[指定替换的数据类型](https://github.com/sequelize/sequelize/issues/14410) 的方法, 并且会在序列化之前尝试猜测其类型. 185 | 186 | 例如, 数组不会被序列化为 SQL 的 "ARRAY" 类型. 取而代之的是以下查询: 187 | 188 | ```js 189 | const { QueryTypes } = require('@sequelize/core'); 190 | 191 | await sequelize.query( 192 | 'SELECT * FROM projects WHERE status IN (:status)', 193 | { 194 | replacements: { status: ['active', 'inactive'] }, 195 | type: QueryTypes.SELECT 196 | } 197 | ); 198 | ``` 199 | 200 | 将导产生此 SQL: 201 | 202 | ```sql 203 | SELECT * FROM projects WHERE status IN ('active', 'inactive') 204 | ``` 205 | 206 | 在实现此类功能之前, 你可以使用 [绑定参数](#绑定参数) 并将其强制转换. 207 | 208 | ## 绑定参数 209 | 210 | 绑定参数是一种在查询中传递变量的方法. 它们是 [替换](#替换) 的替代品. 211 | 212 | 替换参数和绑定参数之间的区别在于, 替换参数在查询发送到数据库之前被转义并由 Sequelize 插入到查询中, 213 | 而绑定参数与 SQL 查询文本分开发送到数据库, 并由数据库本身 "转义". 214 | 215 | 一个查询可以同时具有绑定参数和替换参数. 216 | 217 | 每个数据库对绑定参数使用不同的语法, 但 Sequelize 提供了自己统一的层. 218 | 219 | 与你使用哪个数据库无关紧要, 在 Sequelize 中, 绑定参数是按照类似 postgres 的语法编写的. 你也可以: 220 | 221 | - 使用数字标识符 (例如 `$1`, `$2`, 等...). 请注意, 这些标识符从 1 开始, 而不是 0. `bind` 参数必须是一个数组, 其中包含查询中使用的每个标识符的值(`$1` 绑定到数组中的第一个元素(`bind[0]`), 以此类推...). 222 | - 使用字母标识符 (例如 `$firstName`, `$status`, 等...). 这些标识符遵循通用标识符规则 (仅限字母数字和下划线, 不能以数字开头). `bind` 参数必须是一个普通对象, 其中包含每个绑定参数(没有 `$` 前缀). 223 | 224 | `bind` 餐食必须包含所有绑定值, 否则 Sequelize 将抛出错误. 225 | 226 | **注意** 227 | 228 | 绑定参数只能用于数据值. 绑定参数不能用于动态更改表名, 列名或查询的其他非数据值部分. 229 | 230 | 你的数据库可能对绑定参数有进一步的限制. 231 | 232 | 示例: 233 | 234 | ```js 235 | const { QueryTypes } = require('@sequelize/core'); 236 | 237 | await sequelize.query( 238 | 'SELECT * FROM projects WHERE status = $1', 239 | { 240 | bind: ['active'], 241 | type: QueryTypes.SELECT, 242 | }, 243 | ); 244 | 245 | await sequelize.query( 246 | 'SELECT * FROM projects WHERE status = $status', 247 | { 248 | bind: { status: 'active' }, 249 | type: QueryTypes.SELECT, 250 | }, 251 | ); 252 | ``` 253 | 254 | Sequelize 目前不支持[指定绑定参数的数据类型](https://github.com/sequelize/sequelize/issues/14410). 255 | 在实现此类功能之前, 如果需要更改绑定参数的数据类型, 则可以强制转换它们: 256 | 257 | ```js 258 | const { QueryTypes } = require('@sequelize/core'); 259 | 260 | await sequelize.query( 261 | 'SELECT * FROM projects WHERE id = CAST($1 AS int)', 262 | { 263 | bind: [5], 264 | type: QueryTypes.SELECT, 265 | }, 266 | ); 267 | ``` 268 | 269 | **注意** 270 | 271 | 某些方言 (例如 PostgreSQL 和 IBM Db2) 支持更简洁的强制转换语法, 你可以根据需要使用该语法: 272 | 273 | ```typescript 274 | await sequelize.query('SELECT * FROM projects WHERE id = $1::int'); 275 | ``` 276 | -------------------------------------------------------------------------------- /core-concepts/validations-and-constraints.md: -------------------------------------------------------------------------------- 1 | # Validations & Constraints - 验证 & 约束 2 | 3 | 在本教程中,你将学习如何在 Sequelize 中设置模型的验证和约束. 4 | 5 | 对于本教程,将假定以下设置: 6 | 7 | ```js 8 | const { Sequelize, Op, Model, DataTypes } = require('@sequelize/core'); 9 | const sequelize = new Sequelize("sqlite::memory:"); 10 | 11 | const User = sequelize.define("user", { 12 | username: { 13 | type: DataTypes.TEXT, 14 | allowNull: false, 15 | unique: true 16 | }, 17 | hashedPassword: { 18 | type: DataTypes.STRING(64), 19 | validate: { 20 | is: /^[0-9a-f]{64}$/i 21 | } 22 | } 23 | }); 24 | 25 | (async () => { 26 | await sequelize.sync({ force: true }); 27 | // 这是代码 28 | })(); 29 | ``` 30 | 31 | ## 验证和约束的区别 32 | 33 | 验证是在纯 JavaScript 中在 Sequelize 级别执行的检查. 如果你提供自定义验证器功能,它们可能会非常复杂,也可能是 Sequelize 提供的内置验证器之一. 如果验证失败,则根本不会将 SQL 查询发送到数据库. 34 | 35 | 另一方面,约束是在 SQL 级别定义的规则. 约束的最基本示例是唯一约束. 如果约束检查失败,则数据库将引发错误,并且 Sequelize 会将错误转发给 JavaScript(在此示例中,抛出 `SequelizeUniqueConstraintError`). 请注意,在这种情况下,与验证不同,它执行了 SQL 查询. 36 | 37 | ## 唯一约束 38 | 39 | 下面的代码示例在 `username` 字段上定义了唯一约束: 40 | 41 | ```js 42 | /* ... */ { 43 | username: { 44 | type: DataTypes.TEXT, 45 | allowNull: false, 46 | unique: true 47 | }, 48 | } /* ... */ 49 | ``` 50 | 51 | 同步此模型后(例如,通过调用`sequelize.sync`),在表中将 `username` 字段创建为 `` `username` TEXT UNIQUE``,如果尝试插入已存在的用户名将抛出 `SequelizeUniqueConstraintError`. 52 | 53 | ## 允许/禁止 null 值 54 | 55 | 默认情况下,`null` 是模型每一列的允许值. 可以通过为列设置 `allowNull: false` 参数来禁用它,就像在我们的代码示例的 `username` 字段中所做的一样: 56 | 57 | ```js 58 | /* ... */ { 59 | username: { 60 | type: DataTypes.TEXT, 61 | allowNull: false, 62 | unique: true 63 | }, 64 | } /* ... */ 65 | ``` 66 | 67 | 如果没有 `allowNull: false`, 那么调用 `User.create({})` 将会生效. 68 | 69 | ### 关于 `allowNull` 实现的说明 70 | 71 | 按照本教程开头所述,`allowNull` 检查是 Sequelize 中唯一由 *验证* 和 *约束* 混合而成的检查. 这是因为: 72 | 73 | * 如果试图将 `null` 设置到不允许为 null 的字段,则将抛出`ValidationError` ,而且 *不会执行任何 SQL 查询*. 74 | * 另外,在 `sequelize.sync` 之后,具有 `allowNull: false` 的列将使用 `NOT NULL` SQL 约束进行定义. 这样,尝试将值设置为 `null` 的直接 SQL 查询也将失败. 75 | 76 | ## 验证器 77 | 78 | 使用模型验证器,可以为模型的每个属性指定 格式/内容/继承 验证. 验证会自动在 `create`, `update` 和 `save` 时运行. 你还可以调用 `validate()` 来手动验证实例. 79 | 80 | ### 按属性验证 81 | 82 | 你可以定义你的自定义验证器,也可以使用由 [validator.js (10.11.0)](https://github.com/chriso/validator.js) 实现的多个内置验证器,如下所示. 83 | 84 | ```js 85 | sequelize.define('foo', { 86 | bar: { 87 | type: DataTypes.STRING, 88 | validate: { 89 | is: /^[a-z]+$/i, // 匹配这个 RegExp 90 | is: ["^[a-z]+$",'i'], // 与上面相同,但是以字符串构造 RegExp 91 | not: /^[a-z]+$/i, // 不匹配 RegExp 92 | not: ["^[a-z]+$",'i'], // 与上面相同,但是以字符串构造 RegExp 93 | isEmail: true, // 检查 email 格式 (foo@bar.com) 94 | isUrl: true, // 检查 url 格式 (https://foo.com) 95 | isIP: true, // 检查 IPv4 (129.89.23.1) 或 IPv6 格式 96 | isIPv4: true, // 检查 IPv4 格式 (129.89.23.1) 97 | isIPv6: true, // 检查 IPv6 格式 98 | isAlpha: true, // 只允许字母 99 | isAlphanumeric: true, // 将仅允许使用字母数字,因此 '_abc' 将失败 100 | isNumeric: true, // 只允许数字 101 | isInt: true, // 检查有效的整数 102 | isFloat: true, // 检查有效的浮点数 103 | isDecimal: true, // 检查任何数字 104 | isLowercase: true, // 检查小写 105 | isUppercase: true, // 检查大写 106 | notNull: true, // 不允许为空 107 | isNull: true, // 只允许为空 108 | notEmpty: true, // 不允许空字符串 109 | equals: 'specific value', // 仅允许 'specific value' 110 | contains: 'foo', // 强制特定子字符串 111 | notIn: [['foo', 'bar']], // 检查值不是这些之一 112 | isIn: [['foo', 'bar']], // 检查值是其中之一 113 | notContains: 'bar', // 不允许特定的子字符串 114 | len: [2,10], // 仅允许长度在2到10之间的值 115 | isUUID: 4, // 只允许 uuid 116 | isDate: true, // 只允许日期字符串 117 | isAfter: "2011-11-05", // 仅允许特定日期之后的日期字符串 118 | isBefore: "2011-11-05", // 仅允许特定日期之前的日期字符串 119 | max: 23, // 仅允许值 <= 23 120 | min: 23, // 仅允许值 >= 23 121 | isCreditCard: true, // 检查有效的信用卡号 122 | 123 | // 自定义验证器的示例: 124 | isEven(value) { 125 | if (parseInt(value) % 2 !== 0) { 126 | throw new Error('Only even values are allowed!'); 127 | } 128 | } 129 | isGreaterThanOtherField(value) { 130 | if (parseInt(value) <= parseInt(this.otherField)) { 131 | throw new Error('Bar must be greater than otherField.'); 132 | } 133 | } 134 | } 135 | } 136 | }); 137 | ``` 138 | 139 | 请注意,在需要将多个参数传递给内置验证函数的情况下,要传递的参数必须位于数组中. 但是,如果要传递单个数组参数,例如,`isIn` 可接受的字符串数组,则将其解释为多个字符串参数,而不是一个数组参数. 要解决此问题,请传递一个单长度的参数数组,例如上面所示的 `[['foo', 'bar']]` . 140 | 141 | 要使用自定义错误消息而不是 [validator.js](https://github.com/chriso/validator.js) 提供的错误消息,请使用对象而不是纯值或参数数组,例如验证器 不需要参数就可以给自定义消息 142 | 143 | ```js 144 | isInt: { 145 | msg: "必须是价格的整数" 146 | } 147 | ``` 148 | 149 | 或者如果还需要传递参数,则添加一个 `args` 属性: 150 | 151 | ```js 152 | isIn: { 153 | args: [['en', 'zh']], 154 | msg: "必须为英文或中文" 155 | } 156 | ``` 157 | 158 | 使用自定义验证器功能时,错误消息将是抛出的 `Error` 对象所持有的任何消息. 159 | 160 | 有关内置验证方法的更多详细信息,请参见[validator.js 项目](https://github.com/chriso/validator.js). 161 | 162 | **提示:** 你还可以为日志记录部分定义自定义功能. 只需传递一个函数. 第一个参数是记录的字符串. 163 | 164 | ### `allowNull` 与其他验证器的交互 165 | 166 | 如果将模型的特定字段设置为不允许为 null(使用 `allowNull: false`),并且该值已设置为 `null`,则将跳过所有验证器,并抛出 `ValidationError`. 167 | 168 | 另一方面,如果将其设置为允许 null(使用 `allowNull: true`),并且该值已设置为 `null`,则仅会跳过内置验证器,而自定义验证器仍将运行. 169 | 170 | 举例来说,这意味着你可以拥有一个字符串字段,该字段用于验证其长度在5到10个字符之间,但也允许使用 `null` (因为当该值为 `null` 时,长度验证器将被自动跳过): 171 | 172 | ```js 173 | class User extends Model {} 174 | User.init({ 175 | username: { 176 | type: DataTypes.STRING, 177 | allowNull: true, 178 | validate: { 179 | len: [5, 10] 180 | } 181 | } 182 | }, { sequelize }); 183 | ``` 184 | 185 | 你也可以使用自定义验证器有条件地允许 `null` 值,因为不会跳过它: 186 | 187 | ```js 188 | class User extends Model {} 189 | User.init({ 190 | age: DataTypes.INTEGER, 191 | name: { 192 | type: DataTypes.STRING, 193 | allowNull: true, 194 | validate: { 195 | customValidator(value) { 196 | if (value === null && this.age !== 10) { 197 | throw new Error("除非年龄为10,否则名称不能为 null"); 198 | } 199 | }) 200 | } 201 | } 202 | }, { sequelize }); 203 | ``` 204 | 205 | 你可以通过设置 `notNull` 验证器来自定义 `allowNull` 错误消息: 206 | 207 | ```js 208 | class User extends Model {} 209 | User.init({ 210 | name: { 211 | type: DataTypes.STRING, 212 | allowNull: false, 213 | validate: { 214 | notNull: { 215 | msg: '请输入你的名字' 216 | } 217 | } 218 | } 219 | }, { sequelize }); 220 | ``` 221 | 222 | ### 模型范围内的验证 223 | 224 | 还可以定义验证,来在特定于字段的验证器之后检查模型. 例如,使用此方法,可以确保既未设置 `latitude` 和 `longitude`,又未同时设置两者. 如果设置了一个但未设置另一个,则失败. 225 | 226 | 使用模型对象的上下文调用模型验证器方法,如果它们抛出错误,则认为失败,否则将通过. 这与自定义字段特定的验证器相同. 227 | 228 | 所收集的任何错误消息都将与字段验证错误一起放入验证结果对象中,其关键字以 `validate` 选项对象中验证方法失败的键命名. 即便在任何时候每种模型验证方法都只有一个错误消息,但它会在数组中显示为单个字符串错误,以最大程度地提高与字段错误的一致性. 229 | 230 | 一个例子: 231 | 232 | ```js 233 | class Place extends Model {} 234 | Place.init({ 235 | name: DataTypes.STRING, 236 | address: DataTypes.STRING, 237 | latitude: { 238 | type: DataTypes.INTEGER, 239 | validate: { 240 | min: -90, 241 | max: 90 242 | } 243 | }, 244 | longitude: { 245 | type: DataTypes.INTEGER, 246 | validate: { 247 | min: -180, 248 | max: 180 249 | } 250 | }, 251 | }, { 252 | sequelize, 253 | validate: { 254 | bothCoordsOrNone() { 255 | if ((this.latitude === null) !== (this.longitude === null)) { 256 | throw new Error('Either both latitude and longitude, or neither!'); 257 | } 258 | } 259 | } 260 | }) 261 | ``` 262 | 263 | 在这种简单的情况下,如果只给定了纬度或经度,而不是同时给出两者, 则不能验证对象. 如果我们尝试构建一个超出范围的纬度且没有经度的对象,则`somePlace.validate()` 可能会返回: 264 | 265 | ```js 266 | { 267 | 'latitude': ['Invalid number: latitude'], 268 | 'bothCoordsOrNone': ['Either both latitude and longitude, or neither!'] 269 | } 270 | ``` 271 | 272 | 也可以使用在单个属性上定义的自定义验证程序(例如 `latitude` 属性,通过检查 `(value === null) !== (this.longitude === null)` )来完成此类验证, 但模型范围内的验证方法更为简洁. 273 | -------------------------------------------------------------------------------- /other-topics/connection-pool.md: -------------------------------------------------------------------------------- 1 | # Connection Pool - 连接池 2 | 3 | 如果要从单个进程连接到数据库,则应仅创建一个 Sequelize 实例. Sequelize 将在初始化时建立连接池. 可以通过构造函数的 `options` 参数(使用 `options.pool`)来配置此连接池,如以下示例所示: 4 | 5 | ```js 6 | const sequelize = new Sequelize(/* ... */, { 7 | // ... 8 | pool: { 9 | max: 5, 10 | min: 0, 11 | acquire: 30000, 12 | idle: 10000 13 | } 14 | }); 15 | ``` 16 | 17 | 在 [Sequelize 构造函数的 API 参考](/api/v7/classes/Sequelize.html#constructor)中了解更多信息. 如果要从多个进程连接到数据库,则必须为每个进程创建一个实例,但是每个实例的最大连接池大小应达到最大总大小. 例如,如果你希望最大连接池大小为90,并且有三个进程,则每个进程的 Sequelize 实例的最大连接池大小应为30. 18 | -------------------------------------------------------------------------------- /other-topics/constraints-and-circularities.md: -------------------------------------------------------------------------------- 1 | # Constraints & Circularities - 约束 & 循环 2 | 3 | 在表之间添加约束意味着使用 `sequelize.sync` 时必须在数据库中以一定顺序创建表. 如果 `Task` 具有对 `User` 的引用,则必须先创建 `User` 表,然后才能创建 `Task` 表. 有时这可能会导致循环引用,而 Sequelize 无法找到同步的顺序. 想象一下文档和版本的情况. 一个文档可以有多个版本,为方便起见,文档引用了其当前版本. 4 | 5 | ```js 6 | const { Sequelize, Model, DataTypes } = require('@sequelize/core'); 7 | 8 | class Document extends Model {} 9 | Document.init({ 10 | author: DataTypes.STRING 11 | }, { sequelize, modelName: 'document' }); 12 | 13 | class Version extends Model {} 14 | Version.init({ 15 | timestamp: DataTypes.DATE 16 | }, { sequelize, modelName: 'version' }); 17 | 18 | Document.hasMany(Version); // 这会将 documentId 属性添加到 version 中 19 | Document.belongsTo(Version, { 20 | as: 'Current', 21 | foreignKey: 'currentVersionId' 22 | }); // 这会将 currentVersionId 属性添加到 document 中 23 | ``` 24 | 25 | 但是,不幸的是,上面的代码将导致以下错误: 26 | 27 | ```text 28 | Cyclic dependency found. documents is dependent of itself. Dependency chain: documents -> versions => documents 29 | ``` 30 | 31 | 为了减少这种情况,我们可以将 `constraints: false` 传递给关联之一: 32 | 33 | ```js 34 | Document.hasMany(Version); 35 | Document.belongsTo(Version, { 36 | as: 'Current', 37 | foreignKey: 'currentVersionId', 38 | constraints: false 39 | }); 40 | ``` 41 | 42 | 这将使我们能够正确同步表: 43 | 44 | ```sql 45 | CREATE TABLE IF NOT EXISTS "documents" ( 46 | "id" SERIAL, 47 | "author" VARCHAR(255), 48 | "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, 49 | "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, 50 | "currentVersionId" INTEGER, 51 | PRIMARY KEY ("id") 52 | ); 53 | 54 | CREATE TABLE IF NOT EXISTS "versions" ( 55 | "id" SERIAL, 56 | "timestamp" TIMESTAMP WITH TIME ZONE, 57 | "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, 58 | "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, 59 | "documentId" INTEGER REFERENCES "documents" ("id") ON DELETE 60 | SET 61 | NULL ON UPDATE CASCADE, 62 | PRIMARY KEY ("id") 63 | ); 64 | ``` 65 | 66 | ## 不受限制地强制执行外键引用 67 | 68 | 有时,你可能希望引用另一个表,而不添加任何约束或关联. 在这种情况下,你可以将引用属性手动添加到架构定义中,并标记它们之间的关系. 69 | 70 | ```js 71 | class Trainer extends Model {} 72 | Trainer.init({ 73 | firstName: DataTypes.STRING, 74 | lastName: DataTypes.STRING 75 | }, { sequelize, modelName: 'trainer' }); 76 | 77 | // 在我们调用 Trainer.hasMany(series) 之后, 78 | // Series 将会有一个 trainerId = Trainer.id 外参考键 79 | class Series extends Model {} 80 | Series.init({ 81 | title: DataTypes.STRING, 82 | subTitle: DataTypes.STRING, 83 | description: DataTypes.TEXT, 84 | // 设置与 `Trainer` 的外键关系(hasMany) 85 | trainerId: { 86 | type: DataTypes.INTEGER, 87 | references: { 88 | model: Trainer, 89 | key: 'id' 90 | } 91 | } 92 | }, { sequelize, modelName: 'series' }); 93 | 94 | // 在我们调用 Series.hasOne(Video) 之后, 95 | // Video 将具有 seriesId = Series.id 外参考键 96 | class Video extends Model {} 97 | Video.init({ 98 | title: DataTypes.STRING, 99 | sequence: DataTypes.INTEGER, 100 | description: DataTypes.TEXT, 101 | // 设置与 `Series` 的关系(hasOne) 102 | seriesId: { 103 | type: DataTypes.INTEGER, 104 | references: { 105 | model: Series, // 可以是代表表名的字符串,也可以是 Sequelize 模型 106 | key: 'id' 107 | } 108 | } 109 | }, { sequelize, modelName: 'video' }); 110 | 111 | Series.hasOne(Video); 112 | Trainer.hasMany(Series); 113 | ``` -------------------------------------------------------------------------------- /other-topics/dialect-specific-things.md: -------------------------------------------------------------------------------- 1 | # Dialect-Specific Things - 方言特定事项 2 | 3 | ## 底层连接器库 4 | 5 | ### PostgreSQL 6 | 7 | Sequelize for PostgreSQL 使用的底层连接器库是 [pg](https://www.npmjs.com/package/pg) 包.查看 [Releases](https://sequelize.org/releases/#postgresql-support-table) 查看支持哪些版本的 PostgreSQL 和 pg. 8 | 9 | 你可以使用 Sequelize 构造函数中的 `dialectOptions` 为其提供自定义参数: 10 | 11 | ```js 12 | const sequelize = new Sequelize('database', 'username', 'password', { 13 | dialect: 'postgres', 14 | dialectOptions: { 15 | // 你的 pg 参数 16 | } 17 | }); 18 | ``` 19 | 20 | 以下字段可以传递给 Postgres `dialectOptions`: 21 | 22 | - `application_name`: pg_stat_activity 中的应用程序名称. 参阅 [Postgres 文档](https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-APPLICATION-NAME) 获取更多详细信息. 23 | - `ssl`: SSL 参数. 参阅 [`pg` 文档](https://node-postgres.com/features/ssl) 获取更多详细信息. 24 | - `client_encoding`: // 设置 'auto' 根据客户端 LC_CTYPE 环境变量确定语言环境. 参阅 [Postgres 文档](https://www.postgresql.org/docs/current/multibyte.html) 获取更多详细信息. 25 | - `keepAlive`: 启用 TCP KeepAlive 的布尔值. 参阅 [`pg` 更新记录](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md#v600) 获取更多详细信息. 26 | - `statement_timeout`: 在设定的时间后超时查询(以毫秒为单位). 添加于 pg v7.3. 参阅 [Postgres 文档](https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-STATEMENT-TIMEOUT) 获取更多详细信息. 27 | - `idle_in_transaction_session_timeout`: 终止超过指定持续时间(以毫秒为单位)的空闲事务会话. 参阅 [Postgres 文档](https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-IDLE-IN-TRANSACTION-SESSION-TIMEOUT) 获取更多详细信息. 28 | 29 | 要通过 Unix 域套接字进行连接,请在 `host` 参数中指定套接字目录的路径. 套接字路径必须以 `/` 开头. 30 | 31 | ```js 32 | const sequelize = new Sequelize('database', 'username', 'password', { 33 | dialect: 'postgres', 34 | host: '/path/to/socket_directory' 35 | }); 36 | ``` 37 | 38 | sequelize 中默认的 `client_min_messages` 配置是 `WARNING`. 39 | 40 | ### Amazon Redshift 41 | 42 | **注意** 43 | 44 | 虽然 Redshift 基于 PostgreSQL, 但它不支持与 PostgreSQL 相同的功能集. 45 | 我们的 PostgreSQL 实施未针对 Redshift 进行集成测试, 并且支持有限. 46 | 47 | 大多数配置与上面的 PostgreSQL 相同. 48 | 49 | Redshift 不支持 `client_min_messages`, 你必须要设置 'ignore' 来跳过配置: 50 | 51 | ```js 52 | const sequelize = new Sequelize('database', 'username', 'password', { 53 | dialect: 'postgres', 54 | dialectOptions: { 55 | // 你的 pg 参数 56 | // ... 57 | clientMinMessages: 'ignore' // 不区分大小写 58 | } 59 | }); 60 | ``` 61 | 62 | ### MariaDB 63 | 64 | Sequelize 对于 MariaDB 使用的基础连接器库是 [mariadb](https://www.npmjs.com/package/mariadb) 软件包.请参阅 [Releases](https://sequelize.org/releases/#mariadb-support-table) 查看支持哪些版本的 MariaDB 和 mariadb (npm). 65 | 66 | 你可以使用 Sequelize 构造函数中的 `dialectOptions` 为其提供自定义参数: 67 | 68 | ```js 69 | const sequelize = new Sequelize('database', 'username', 'password', { 70 | dialect: 'mariadb', 71 | dialectOptions: { 72 | // 你的 mariadb 参数 73 | // connectTimeout: 1000 74 | } 75 | }); 76 | ``` 77 | 78 | `dialectOptions` 直接传递给 MariaDB 连接构造函数. 完整的选项列表可以在 [MariaDB 文档](https://mariadb.com/kb/en/nodejs-connection-options/) 中找到. 79 | 80 | ### MySQL 81 | 82 | Sequelize 对于 MySQL 使用的基础连接器库是 [mysql2](https://www.npmjs.com/package/mysql2) 软件包.查看 [Releases](https://sequelize.org/releases/#mysql-support-table) 查看支持哪些版本的 MySQL 和 mysql2. 83 | 84 | 你可以使用 Sequelize 构造函数中的 `dialectOptions` 为其提供自定义参数: 85 | 86 | ```js 87 | const sequelize = new Sequelize('database', 'username', 'password', { 88 | dialect: 'mysql', 89 | dialectOptions: { 90 | // 你的 mysql2 参数 91 | } 92 | }) 93 | ``` 94 | 95 | `dialectOptions` 直接传递给 MySQL 连接构造函数. 完整的选项列表可以在 [MySQL 文档](https://www.npmjs.com/package/mysql#connection-options) 中找到. 96 | 97 | ### Microsoft SQL Server (mssql) 98 | 99 | Sequelize for MSSQL 使用的底层连接器库是 [tedious](https://www.npmjs.com/package/tedious) 包. 请参阅 [Releases](https://sequelize.org/releases/#microsoft-sql-server-mssql-support-table) 查看支持哪些版本的 SQL Server & tedious. 100 | 101 | 你可以使用 Sequelize 构造函数中的 `dialectOptions.options` 为其提供自定义参数: 102 | 103 | ```js 104 | const sequelize = new Sequelize('database', 'username', 'password', { 105 | dialect: 'mssql', 106 | dialectOptions: { 107 | // 观察 MSSQL 这个嵌套的 `options` 字段 108 | options: { 109 | // 你的 tedious 参数 110 | useUTC: false, 111 | dateFirst: 1 112 | } 113 | } 114 | }); 115 | ``` 116 | 117 | 完整的选项列表可以在 [tedious 文档](https://tediousjs.github.io/tedious/api-connection.html#function_newConnection) 中找到. 118 | 119 | #### MSSQL 域账户 120 | 121 | 为了连接域帐户,请使用以下格式. 122 | 123 | ```js 124 | const sequelize = new Sequelize('database', null, null, { 125 | dialect: 'mssql', 126 | dialectOptions: { 127 | authentication: { 128 | type: 'ntlm', 129 | options: { 130 | domain: 'yourDomain', 131 | userName: 'username', 132 | password: 'password' 133 | } 134 | }, 135 | options: { 136 | instanceName: 'SQLEXPRESS' 137 | } 138 | } 139 | }) 140 | ``` 141 | 142 | ### SQLite 143 | 144 | Sequelize 对于 SQLite 使用的基础连接器库是 [sqlite3](https://www.npmjs.com/package/sqlite3) 程序包.请参阅 [Releases](https://sequelize.org/releases/#sqlite-support-table) 查看支持哪些版本的 sqlite3. 145 | 146 | 你可以在 Sequelize 构造函数中使用 `storage` 参数指定存储文件(对于内存中的SQLite实例,请使用 `:memory:`). 147 | 148 | 你可以使用 Sequelize 构造函数中的 `dialectOptions` 为其提供自定义参数: 149 | 150 | ```js 151 | import { Sequelize } from 'sequelize'; 152 | import SQLite from 'sqlite3'; 153 | 154 | const sequelize = new Sequelize('database', 'username', 'password', { 155 | dialect: 'sqlite', 156 | storage: 'path/to/database.sqlite', // 或 ':memory:' 157 | dialectOptions: { 158 | // 你的 sqlite3 参数 159 | // 对于实例, 这是配置数据库打开模式的方法: 160 | mode: SQLite.OPEN_READWRITE | SQLite.OPEN_CREATE | SQLite.OPEN_FULLMUTEX, 161 | } 162 | }); 163 | ``` 164 | 165 | 以下字段可以传递给 SQLite `dialectOptions`: 166 | 167 | - `mode`: 设置 SQLite 连接的打开模式. 潜在值由 `sqlite3` 包提供, 并且包括 `SQLite.OPEN_READONLY`, `SQLite.OPEN_READWRITE`, 或 `SQLite.OPEN_CREATE`. 请参阅 [sqlite3 的 API 参考](https://github.com/TryGhost/node-sqlite3/wiki/API) 和 [SQLite C 接口文档](https://www.sqlite.org/c3ref/open.html) 获取更多细节. 168 | 169 | ### Snowflake 170 | 171 | **注意** 172 | 173 | 虽然这种方言包含在 Sequelize 中, 但是对 Snowflake 的支持是有限的, 因为它不是由核心团队处理的. 174 | 175 | Sequelize 用于 Snowflake 的底层连接器库是 [snowflake-sdk](https://www.npmjs.com/package/snowflake-sdk) 包.请参阅 [Releases](https://sequelize.org/releases/#snowflake-support-table) 查看支持哪些版本的 Snowflake 和 snowflake-sdk. 176 | 177 | 为了与帐户连接, 请使用以下格式: 178 | 179 | ```js 180 | const sequelize = new Sequelize('database', null, null, { 181 | dialect: 'snowflake', 182 | dialectOptions: { 183 | // 把你的snowflake帐户放在这里, 184 | account: 'myAccount', // my-app.us-east-1 185 | 186 | // 下面的选项是可选的 187 | role: 'myRole', 188 | warehouse: 'myWarehouse', 189 | schema: 'mySchema' 190 | }, 191 | // 和其他方言一样 192 | username: 'myUserName', 193 | password: 'myPassword', 194 | database: 'myDatabaseName' 195 | }); 196 | ``` 197 | 198 | **注意** 没有提供测试沙箱, 因此 snowflake 集成测试不是 pipeline 的一部分. 核心团队也很难进行分类和调试. 这种方言现在需要由 snowflake 用户/社区维护. 199 | 200 | 用于运行集成测试: 201 | 202 | ```bash 203 | # using npm 204 | SEQ_ACCOUNT=myAccount SEQ_USER=myUser SEQ_PW=myPassword SEQ_ROLE=myRole SEQ_DB=myDatabaseName SEQ_SCHEMA=mySchema SEQ_WH=myWareHouse npm run test-integration-snowflake 205 | # using yarn 206 | SEQ_ACCOUNT=myAccount SEQ_USER=myUser SEQ_PW=myPassword SEQ_ROLE=myRole SEQ_DB=myDatabaseName SEQ_SCHEMA=mySchema SEQ_WH=myWareHouse yarn test-integration-snowflake 207 | ``` 208 | 209 | ### Db2 210 | 211 | **注意** 212 | 213 | 虽然这种方言包含在 Sequelize 中, 但对 Db2 的支持是有限的, 因为它不是由核心团队处理的. 214 | 215 | Sequelize for Db2 使用的底层连接器库是 [ibm_db](https://www.npmjs.com/package/ibm_db) npm 包. 216 | 请参阅 [Releases](https://sequelize.org/releases/#db2-support-table) 以查看支持哪些版本的 DB2 和 ibm_db. 217 | 218 | ### Db2 for IBM i 219 | 220 | **注意** 221 | 222 | 虽然这种方言包含在 Sequelize 中, 但对 *Db2 for IBM i* 的支持是有限的, 因为它不是由核心团队处理的. 223 | 224 | Sequelize 为 *Db2 for IBM i* 使用的底层连接器库是 [odbc](https://www.npmjs.com/package/odbc) npm 包. 225 | 请参阅 [Releases](https://sequelize.org/releases/#db2-for-ibm-i-support-table) 查看支持哪些版本的 IBMi 和 odbc. 226 | 227 | 要了解有关将 ODBC 与 IBM i 结合使用的更多信息, 请参阅 [IBM i 和 ODBC 文档](https://ibmi-oss-docs.readthedocs.io/en/latest/odbc/README.html). 228 | 229 | 将参数传递给构造函数时, `database` 的概念被映射到 ODBC 的 `DSN`. 你可以使用 `dialectOptions.odbcConnectionString` 为 Sequelize 提供额外的连接字符串参数. 然后, 此连接字符串会附加在参数中找到 `database`, `username`, 和 `password` 的值: 230 | 231 | ```js 232 | const sequelize = new Sequelize('MY_DSN', 'username', 'password', { 233 | dialect: 'ibmi', 234 | dialectOptions: { 235 | odbcConnectionString: 'CMT=1;NAM=0;...' 236 | }, 237 | }); 238 | ``` 239 | 240 | 上述配置生成的最终连接字符串类似于`CMT=1;NAMING=0;...;DSN=MY_DSN;UID=username;PWD=password;`. 此外, `host` 参数将映射 `SYSTEM=` 连接字符串键. 241 | 242 | ## 数据类型: TIMESTAMP WITHOUT TIME ZONE - 仅限 PostgreSQL 243 | 244 | 如果你使用的是 PostgreSQL `TIMESTAMP WITH TIME ZONE`,并且需要将其解析为其他时区,请使用 pg 库自己的解析器: 245 | 246 | ```js 247 | require('pg').types.setTypeParser(1114, stringValue => { 248 | return new Date(stringValue + '+0000'); 249 | // 例如 UTC 偏移量. 使用任何你想要的偏移量. 250 | }); 251 | ``` 252 | 253 | ## 数据类型: ARRAY(ENUM) - 仅限 PostgreSQL 254 | 255 | Array(Enum)类型需要特殊处理. 每当 Sequelize 与数据库对话时,它都必须使用 ENUM 名称转换数组值. 256 | 257 | 因此,此枚举名称必须遵循这种格式 `enum__`. 如果你使用 `sync`,则将自动生成正确的名称. 258 | 259 | ## 表提示 - 仅限 MSSQL 260 | 261 | `tableHint` 属性可用于定义表提示. 提示必须是来自 `TableHints` 的值,并且仅在绝对必要时才使用. 当前每个查询仅支持单个表提示. 262 | 263 | 表提示通过指定某些参数来覆盖 MSSQL 查询优化器的默认行为. 它们仅影响该子句中引用的表或视图. 264 | 265 | ```js 266 | const { TableHints } = require('@sequelize/core'); 267 | Project.findAll({ 268 | // 添加表提示 NOLOCK 269 | tableHint: TableHints.NOLOCK 270 | // 这将生成 SQL 'WITH (NOLOCK)' 271 | }); 272 | ``` 273 | 274 | ## 索引提示 - 仅限 MySQL/MariaDB 275 | 276 | `indexHints` 参数可以用来定义索引提示. 提示类型必须是 `IndexHints` 中的值,并且这些值应引用现有索引. 277 | 278 | 索引提示[将覆盖 MySQL 查询优化器的默认行为](https://dev.mysql.com/doc/refman/5.7/en/index-hints.html). 279 | 280 | ```js 281 | const { IndexHints } = require('@sequelize/core'); 282 | Project.findAll({ 283 | indexHints: [ 284 | { type: IndexHints.USE, values: ['index_project_on_name'] } 285 | ], 286 | where: { 287 | id: { 288 | [Op.gt]: 623 289 | }, 290 | name: { 291 | [Op.like]: 'Foo %' 292 | } 293 | } 294 | }); 295 | ``` 296 | 297 | 上面的代码将生成一个如下所示的 MySQL 查询: 298 | 299 | ```sql 300 | SELECT * FROM Project USE INDEX (index_project_on_name) WHERE name LIKE 'FOO %' AND id > 623; 301 | ``` 302 | 303 | `Sequelize.IndexHints` 包含 `USE`, `FORCE`, 和 `IGNORE`. 304 | 305 | 参考 [Issue #9421](https://github.com/sequelize/sequelize/issues/9421) 有关原始 API 提案. 306 | 307 | ## 引擎 - 仅限 MySQL/MariaDB 308 | 309 | 模型的默认引擎是 InnoDB. 310 | 311 | 你可以使用 `engine` 参数更改模型的引擎(例如,更改为 MyISAM): 312 | 313 | ```js 314 | const Person = sequelize.define('person', { /* 属性 */ }, { 315 | engine: 'MYISAM' 316 | }); 317 | ``` 318 | 319 | 像模型定义的每个参数一样,也可以使用 Sequelize 构造函数的 `define` 参数来全局更改此设置: 320 | 321 | ```js 322 | const sequelize = new Sequelize(db, user, pw, { 323 | define: { engine: 'MYISAM' } 324 | }); 325 | ``` 326 | 327 | ## 表注释 - 仅限 MySQL/MariaDB/PostgreSQL 328 | 329 | 你可以在定义模型时为表指定注释: 330 | 331 | ```js 332 | class Person extends Model {} 333 | Person.init({ /* 属性 */ }, { 334 | comment: "I'm a table comment!", 335 | sequelize 336 | }); 337 | ``` 338 | 339 | 调用 `sync()` 时将设置注释. -------------------------------------------------------------------------------- /other-topics/extending-data-types.md: -------------------------------------------------------------------------------- 1 | # Extending Data Types - 扩展数据类型 2 | 3 | 你尝试实现的类型很可能已经包含在[数据类型](../core-concepts/model-basics.md)中. 如果不包括新的数据类型, 本手册将展示如何自己编写或扩展现有数据类型. 4 | 5 | ## 创建一个新的数据类型 6 | 7 | DataType 是一个扩展于 `DataTypes.ABSTRACT` 并实现其 `toSql` 方法的类: 8 | 9 | ```typescript 10 | import { Sequelize, DataTypes } from '@sequelize/core'; 11 | 12 | // 所有数据类型都必须继承自 DataTypes.ABSTRACT. 13 | export class MyDateType extends DataTypes.ABSTRACT { 14 | // toSql 必须返回将在 CREATE TABLE 语句中使用的 SQL. 15 | toSql() { 16 | return 'TIMESTAMP'; 17 | } 18 | } 19 | ``` 20 | 21 | 然后, 你可以在模型中使用新的数据类型: 22 | 23 | ```typescript 24 | import { MyDateType } from './custom-types.js'; 25 | 26 | const sequelize = new Sequelize('sqlite::memory:'); 27 | 28 | const User = sequelize.define('User', { 29 | birthday: { 30 | // 新数据类型 31 | type: MyDateType, 32 | }, 33 | }, { timestamps: false, noPrimaryKey: true, underscored: true }); 34 | 35 | await User.sync(); 36 | ``` 37 | 38 | 以上将产生以下SQL: 39 | 40 | ```sql 41 | CREATE TABLE IF NOT EXISTS "users" ( 42 | "birthday" TIMESTAMP 43 | ); 44 | ``` 45 | 46 | ### 验证用户输入 47 | 48 | 现在, 我们的数据类型非常简单. 它不进行任何规范化, 而是按原样将值传递给数据库. 它具有与我们将 [属性的类型设置为字符串](../other-topics/other-data-types.md) 相同的基本行为. 49 | 50 | 你可以实现一系列方法来更改数据类型的行为: 51 | 52 | - `validate(value): void` - 在模型实例上设置值时调用此方法. 如果它返回 `false`, 该值将被拒绝. 53 | - `sanitize(value): unknown` - 在模型实例上设置值时调用此方法. 它在验证之前被调用. 你可以使用它来规范化一个值, 例如将字符串转换为 Date 对象. 54 | - `areValuesEqual(a, b): boolean` - 在比较数据类型的两个值时, 在确定需要保存模型的哪些属性时调用此方法. 55 | 如果它返回 `false`, 新值将被保存. 默认情况下, 它使用 lodash 的 isEqual 方法. 56 | 57 | ```typescript 58 | import { Sequelize, DataTypes, ValidationErrorItem } from '@sequelize/core'; 59 | 60 | export class MyDateType extends DataTypes.ABSTRACT { 61 | toSql() { 62 | return 'TIMESTAMP'; 63 | } 64 | 65 | sanitize(value: unknown): unknown { 66 | if (value instanceof Date) { 67 | return value; 68 | } 69 | 70 | if (typeof value === 'string') { 71 | return new Date(value); 72 | } 73 | 74 | throw new ValidationErrorItem('Invalid date'); 75 | } 76 | 77 | validate(value: unknown): void { 78 | if (!(value instanceof Date)) { 79 | ValidationErrorItem.throwDataTypeValidationError('Value must be a Date object'); 80 | } 81 | 82 | if (Number.isNaN(value.getTime())) { 83 | ValidationErrorItem.throwDataTypeValidationError('Value is an Invalid Date'); 84 | } 85 | } 86 | 87 | sanitize(value: unknown): unknown { 88 | if (typeof value === 'string') { 89 | return new Date(value); 90 | } 91 | } 92 | } 93 | ``` 94 | 95 | ### 序列化 & 反序列化 96 | 97 | 我们有 4 个方法可以用来定义数据类型在与数据库交互时如何序列化和反序列化值: 98 | 99 | - `parseDatabaseValue(value): unknown`: 转换从数据库中检索的值. 100 | - `toBindableValue(value): unknown`: 使用 [绑定参数](../core-concepts/raw-queries.md) 时将值转换为连接器库接受的值. 101 | - `escape(value): string`: 转义用于内联原始 SQL 的值, 例如在使用 [替换](../core-concepts/raw-queries.md) 时. 默认情况下, 如果 `toBindableValue` 返回一个字符串, 此方法将将该字符串转义为 SQL 字符串. 102 | 103 | ```typescript 104 | import { DataTypes, StringifyOptions } from '@sequelize/core'; 105 | 106 | export class MyDateType extends DataTypes.ABSTRACT { 107 | // [...] 截断的例子 108 | 109 | parseDatabaseValue(value: unknown): Date { 110 | assert(typeof value === 'string', 'Expected to receive a string from the database'); 111 | 112 | return new Date(value); 113 | } 114 | 115 | toBindableValue(value: Date): unknown { 116 | return value.toISOString(); 117 | } 118 | 119 | escape(value: Date, options: StringifyOptions): string { 120 | return options.dialect.escapeString(value.toISOString()); 121 | } 122 | } 123 | ``` 124 | 125 | ## 修改现有数据类型 126 | 127 | 你可以继承现有数据类型的实现来自定义其行为. 比如, 让你的类扩展你希望修改的数据类型, 而不是由 `DataTypes.ABSTRACT` 替代. 128 | 129 | 请注意以下示例是如何继承自 `DataTypes.STRING` 而不是 `DataTypes.ABSTRACT` 的: 130 | 131 | ```typescript 132 | import { Sequelize, DataTypes } from '@sequelize/core'; 133 | 134 | export class MyStringType extends DataTypes.STRING { 135 | toSql() { 136 | return 'TEXT'; 137 | } 138 | } 139 | ``` 140 | 141 | 就像自定义数据类型一样, 使用你的数据类型类而不是你正在扩展的类型: 142 | 143 | ```typescript 144 | import { MyStringType } from './custom-types.js'; 145 | 146 | const sequelize = new Sequelize('sqlite::memory:'); 147 | 148 | const User = sequelize.define('User', { 149 | firstName: { 150 | // 新数据类型 151 | type: MyStringType, 152 | }, 153 | }, { timestamps: false, noPrimaryKey: true, underscored: true }); 154 | 155 | await User.sync(); 156 | ``` 157 | 158 | ## 限制 159 | 160 | 某些方言支持通过使用 [`CREATE TYPE`](https://www.postgresql.org/docs/current/sql-createtype.html) 语句创建自定义 SQL 数据类型. postgres 中的枚举就是这种情况. 161 | 162 | 使用 `DataTypes.ENUM` 时, Sequelize 会自动在数据库中创建枚举类型. 这对于自定义类型是不可能的. 163 | 如果你需要创建自定义类型, 则需要先在数据库中手动创建它, 然后才能在你的某个模型中使用它. 164 | -------------------------------------------------------------------------------- /other-topics/hooks.md: -------------------------------------------------------------------------------- 1 | # Hooks - 钩子 2 | 3 | Hook 是你可以监听的事件, 当调用相应的方法时会触发这些事件. 4 | 5 | 它们对于向应用程序的核心添加自定义功能很有用. 6 | 例如, 如果你想在保存模型之前始终在模型上设置一个值, 你可以监听模型的 `beforeUpdate` hook. 7 | 8 | ## 静态 Sequelize Hook 9 | 10 | `Sequelize` 类支持以下 hook: 11 | 12 | | Hook name | Async | Run when | 13 | |----------------------------|-------|---------------------------------| 14 | | `beforeInit`, `afterInit` | ❌ | 创建一个 sequelize 实例 | 15 | 16 | 示例: 17 | 18 | ```typescript 19 | import { Sequelize } from '@sequelize/core'; 20 | 21 | Sequelize.hooks.addListener('beforeInit', () => { 22 | console.log('A new sequelize instance is being created'); 23 | }); 24 | ``` 25 | 26 | ## 实例 Sequelize Hook 27 | 28 | `Sequelize` 实例支持以下 hook: 29 | 30 | | Hook name | Async | Run when | 31 | |---------------------------------------|-------|------------------------------------------------------------| 32 | | `beforeDefine`, `afterDefine` | ❌ | 正在注册一个新的 `Model` 类 | 33 | | `beforeQuery`, `afterQuery` | ✅ | 正在运行 SQL 查询 | 34 | | `beforeBulkSync`, `afterBulkSync` | ✅ | `sequelize.sync` 被调用 | 35 | | `beforeConnect`, `afterConnect` | ✅ | 每当创建与数据库的新连接时 | 36 | | `beforeDisconnect`, `afterDisconnect` | ✅ | 每当关闭与数据库的连接时 | 37 | 38 | **注意** 39 | 40 | 所有 [模型 hook] 也会在 sequelize 实例上触发: 41 | 42 | ```typescript 43 | import { Sequelize } from '@sequelize/core'; 44 | 45 | const sequelize = new Sequelize(/* options */); 46 | 47 | // 每当在任何模型上调用 findAll 时都会调用它 48 | sequelize.hooks.addListener('beforeFind', () => { 49 | console.log('findAll 已被模型调用'); 50 | }); 51 | ``` 52 | 53 | ### 注册 Sequelize Hook 54 | 55 | 实例 Sequelize hook 可以通过两种方式注册: 56 | 57 | 1. 使用 `Sequelize` 实例的 `hooks` 属性: 58 | 59 | ```typescript 60 | import { Sequelize } from '@sequelize/core'; 61 | 62 | const sequelize = new Sequelize(/* options */); 63 | 64 | // highlight-next-line 65 | sequelize.hooks.addListener('beforeDefine', () => { 66 | console.log('正在初始化一个新模型'); 67 | }); 68 | ``` 69 | 70 | 2. 通过 `Sequelize` 参数: 71 | 72 | ```typescript 73 | import { Sequelize } from '@sequelize/core'; 74 | 75 | const sequelize = new Sequelize({ 76 | // highlight-next-line 77 | hooks: { 78 | beforeDefine: () => { 79 | console.log('正在初始化一个新模型'); 80 | }, 81 | }, 82 | }); 83 | ``` 84 | 85 | ## 模型 Sequelize Hook 86 | 87 | `Model` 类支持以下 hook: 88 | 89 | | Hook name | Async | Run when | 90 | |----------------------------------------------------------------------------------------|-------|---------------------------------------------------------------------------------| 91 | | `beforeAssociate`, `afterAssociate` | ❌ | 每当在模型上声明关联时 | 92 | | `beforeSync`, `afterSync` | ✅ | 当调用 `sequelize.sync` 或 `Model.sync` 时 | 93 | | `beforeValidate`, `afterValidate`, `validationFailed` | ✅ | 当验证模型的属性时(发生在大多数模型方法中) | 94 | | `beforeFind`, `beforeFindAfterExpandIncludeAll`, `beforeFindAfterOptions`, `afterFind` | ✅ | 当调用 `Model.findAll` 时[^find-all] | 95 | | `beforeCount` | ✅ | 当调用 `Model.count` 时 | 96 | | `beforeUpsert`, `afterUpsert` | ✅ | 当调用 `Model.upsert` 时 | 97 | | `beforeBulkCreate`, `afterBulkCreate` | ✅ | 当调用 `Model.bulkCreate` 时 | 98 | | `beforeBulkDestroy`, `afterBulkDestroy` | ✅ | 当调用 `Model.destroy` 时 | 99 | | `beforeDestroy`, `afterDestroy` | ✅ | 当调用 `Model#destroy` 时 | 100 | | `beforeBulkRestore`, `afterBulkRestore` | ✅ | 当调用 `Model.restore` 时 | 101 | | `beforeRestore`, `afterRestore` | ✅ | 当调用 `Model#restore` 时 | 102 | | `beforeBulkUpdate`, `afterBulkUpdate` | ✅ | 当调用 `Model.update` 时 | 103 | | `beforeUpdate`, `afterUpdate` | ✅ | 当调用 `Model#update` 或 `Model#save` 时(并且模型不是新的) | 104 | | `beforeCreate`, `afterCreate` | ✅ | 当调用 `Model#save` 时(并且模型是新的) | 105 | | `beforeSave`, `afterSave` | ✅ | 当调用 `Model#update` 或 `Model#save` 时 | 106 | 107 | ### 注册模型 hook 108 | 109 | 实例 Sequelize hook 可以通过三种方式注册: 110 | 111 | 1. 使用 `Sequelize` 实例的 `hooks` 属性: 112 | 113 | ```typescript 114 | import { Sequelize, DataTypes } from '@sequelize/core'; 115 | 116 | const sequelize = new Sequelize(/* options */); 117 | 118 | const MyModel = sequelize.define('MyModel', { 119 | name: DataTypes.STRING, 120 | }); 121 | 122 | // highlight-next-line 123 | MyModel.hooks.addListener('beforeFind', () => { 124 | console.log('findAll has been called on MyModel'); 125 | }); 126 | ``` 127 | 2. 通过 `Model.init` 或 `sequelize.define` 的参数: 128 | 129 | ```typescript 130 | import { DataTypes } from '@sequelize/core'; 131 | 132 | const MyModel = sequelize.define('MyModel', { 133 | name: DataTypes.STRING, 134 | }, { 135 | // highlight-next-line 136 | hooks: { 137 | beforeFind: () => { 138 | console.log('findAll has been called on MyModel'); 139 | }, 140 | }, 141 | }); 142 | ``` 143 | 3. 使用装饰器 144 | 145 | ```typescript 146 | import { Sequelize, Model, Hook } from '@sequelize/core'; 147 | import { BeforeFind } from '@sequelize/core/decorators-legacy'; 148 | 149 | export class MyModel extends Model { 150 | // highlight-next-line 151 | @BeforeFind 152 | static logFindAll() { 153 | console.log('findAll has been called on MyModel'); 154 | } 155 | } 156 | ``` 157 | 158 | ## 同步 vs 异步 Hook 159 | 160 | 在上表中, 你可以看到一些挂钩被标记为 `Async`. 161 | 这意味着你的侦听器可以返回一个将在继续执行 hook 之前等待的 `Promise`. 162 | 163 | 在 hook 中进行异步操作时要小心. hook 是连续运行的, 在你的 hook 被解析之前不会运行下一个 hook. 164 | 如果你有很多 hook, 这可能会导致性能问题. 165 | 166 | 尝试从同步 hook 返回一个 `Promise` 会引发错误. 167 | 168 | ## 移除 Hook 169 | 170 | 你可以使用 `hooks.removeListener` 并提供与 `hooks.addListener` 相同的回调或你的侦听器名称来移除 hook: 171 | 172 | ```js 173 | class Book extends Model {} 174 | Book.init({ 175 | title: DataTypes.STRING 176 | }, { sequelize }); 177 | 178 | const myListener = (book, options) => { 179 | // ... 180 | }; 181 | 182 | Book.hooks.addListener('afterCreate', 'yourHookIdentifier', myListener); 183 | 184 | // 这两种都将删除 hook: 185 | // success-next-line 186 | Book.hooks.removeListener('afterCreate', myListener); 187 | // success-next-line 188 | Book.hooks.removeListener('afterCreate', 'yourHookIdentifier'); 189 | ``` 190 | 191 | **注意** 192 | 193 | 装饰器添加的 hook 不能使用回调实例删除, 但如果它们被命名仍然可以删除: 194 | 195 | ```typescript 196 | import { Sequelize, Model, Hook } from '@sequelize/core'; 197 | import { BeforeFind } from '@sequelize/core/decorators-legacy'; 198 | 199 | export class MyModel extends Model { 200 | @BeforeFind({ name: 'yourHookIdentifier' }) 201 | static logFindAll() { 202 | console.log('findAll has been called on MyModel'); 203 | } 204 | } 205 | 206 | // 这行不通 207 | // error-next-line 208 | MyModel.hooks.removeListener('beforeFind', MyModel.logFindAll); 209 | 210 | // 但是这种可以 211 | // success-next-line 212 | MyModel.hooks.removeListener('beforeFind', 'yourHookIdentifier'); 213 | ``` 214 | 215 | 但是, 我们不建议删除通过装饰器添加的 hook, 因为这可能会使你的代码更难理解. 216 | 217 | 218 | ## 关联方法 Hook 219 | 220 | [关联](../core-concepts/assocs.md) 在你的模型上添加的方法确实提供了特定于它们的 hook, 但它们是建立在顶部的 221 | 常规模型方法, 这将触发 hook. 222 | 223 | 例如, 使用 `add` / `set` 混合方法将触发 `beforeUpdate` 和 `afterUpdate` hook. 224 | 225 | ## `individualHooks` 226 | 227 | **注意** 228 | 229 | 不鼓励使用此参数有两个原因: 230 | 231 | - 与 "普通" hook 不同, 无法修改提供给 `individualHooks` 发出的事件的数据. 只能修改 "批量" 事件的日期. 232 | - 这个参数很慢, 因为它要获取需要销毁的实例. 233 | 234 | 小心使用. 如果你需要对数据库更改做出应对, 请考虑改用数据库的触发器和通知系统. 235 | 236 | 当使用 `Model.destroy` 或 `Model.update` 等静态模型方法时, 只会调用它们对应的 "批量" hook(例如 `beforeBulkDestroy`), 而不是实例 hook (例如 `beforeDestroy`). 237 | 238 | 如果要触发实例 hook, 请使用方法的 `individualHooks` 参数为将受到影响的每一行运行实例 hook. 239 | 240 | 以下示例将为将要删除的每一行触发 `beforeDestroy` 和 `afterDestroy` hook: 241 | 242 | ```js 243 | User.destroy({ 244 | where: { 245 | id: [1, 2, 3], 246 | }, 247 | individualHooks: true, 248 | }); 249 | ``` 250 | 251 | ## 例外情况 252 | 253 | 只有 __Model 方法__ 触发hook. 这意味着在许多情况下, Sequelize 将与数据库交互而不触发 hook. 254 | 255 | 这些包括但不限于: 256 | 257 | - 由于 `ON DELETE CASCADE` 约束而被数据库删除的实例, [除非 `hooks` 参数为 true]. 258 | - 由于 `SET NULL` 或 `SET DEFAULT` 约束, 数据库正在更新实例. 259 | - [Raw 查询](../core-concepts/raw-queries.md). 260 | - 所有 QueryInterface 方法. 261 | 262 | 如果你需要对这些事件做出应对, 请考虑改用数据库的本机和通知系统. 263 | 264 | ## 级联删除的 Hook 265 | 266 | 如 [例外情况] 中所示, 由于 `ON DELETE CASCADE` 约束, 当数据库删除实例时, Sequelize 不会触发挂钩. 267 | 268 | 但是, 如果你在定义关联时将 `hooks` 参数设置为 `true`, Sequelize 将为已删除的实例触发 `beforeDestroy` 和 `afterDestroy` hook. 269 | 270 | **注意** 271 | 272 | 由于以下原因, 不鼓励使用此参数: 273 | 274 | - 这个参数需要很多额外的查询. `destroy` 方法通常执行单个查询. 如果启用此参数, 将执行一个额外的 `SELECT` 查询, 以及一个额外的 `DELETE` 查询, 用于选择返回的每一行. 275 | - 如果你不在事务中运行此查询, 并且发生错误, 你最终可能会删除一部分行, 而一些行没有被删除. 276 | - 此参数仅在使用 `destroy` 的 *实例* 版本时有效. 静态版本不会触发 hook, 即使有 `individualHooks`. 277 | - 此参数在 `paranoid` 模式下不起作用. 278 | - 如果你只在拥有外键的模型上定义关联, 则此参数将不起作用. 你还需要定义反向关联. 279 | 280 | 此参数被视为遗留选项. 如果你需要收到数据库更改通知, 我们强烈建议你使用数据库的触发器和通知系统. 281 | 282 | 以下是如何使用此参数的示例: 283 | 284 | ```ts 285 | import { Model } from '@sequelize/core'; 286 | import { HasMany, BeforeDestroy } from '@sequelize/core/decorators-legacy'; 287 | 288 | class User extends Model { 289 | // 这个"hook"参数将导致"beforeDestroy"和"afterDestroy" 290 | // highlight-next-line 291 | @HasMany(() => Post, { hooks: true }) 292 | declare posts: Post[]; 293 | } 294 | 295 | class Post extends Model { 296 | @BeforeDestroy 297 | static logDestroy() { 298 | console.log('帖子已被销毁'); 299 | } 300 | } 301 | 302 | const sequelize = new Sequelize({ 303 | /* 参数 */ 304 | models: [User, Post], 305 | }); 306 | 307 | await sequelize.sync({ force: true }); 308 | 309 | const user = await User.create(); 310 | const post = await Post.create({ userId: user.id }); 311 | 312 | // 这会输出 "帖子已被销毁" 313 | await user.destroy(); 314 | ``` 315 | 316 | ## Hook 和 事务 317 | 318 | Sequelize 中的许多模型操作都支持在方法的参数中指定事务. 如果在原始调用中 *指定* 了事务, 它将出现在传递给 hook 函数的参数中. 319 | 320 | 例如, 考虑以下代码片段: 321 | 322 | ```js 323 | User.hooks.addListener('afterCreate', async (user, options) => { 324 | // 我们可以使用 `options.transaction` 325 | // 使用与触发此 hook 的调用相同的事务来执行其他调用 326 | await User.update({ mood: 'sad' }, { 327 | where: { 328 | id: user.id 329 | }, 330 | // highlight-next-line 331 | transaction: options.transaction 332 | }); 333 | }); 334 | 335 | await sequelize.transaction(async transaction => { 336 | await User.create({ 337 | username: 'someguy', 338 | mood: 'happy' 339 | }, { 340 | transaction, 341 | }); 342 | }); 343 | ``` 344 | 345 | 如果我们在前面的代码中调用 User.update 时没有包含事务参数, 则不会发生任何变化, 因为我们新创建的用户在待处理事务之前不存在于数据库中. 346 | 347 | [^find-all]: **findAll**: 请注意, 一些方法, 例如 `Model.findOne`、`Model.findAndCountAll` 和关联获取器也会在内部调用 `Model.findAll`. 这也会导致为这些方法调用 `beforeFind` hook. -------------------------------------------------------------------------------- /other-topics/indexes.md: -------------------------------------------------------------------------------- 1 | # Indexes - 索引 2 | 3 | Sequelize 支持在模型定义上添加索引,该索引将在 [`sequelize.sync()`](/api/v7/classes/Sequelize.html#sync) 上创建 4 | 5 | ```js 6 | const User = sequelize.define('User', { /* 属性 */ }, { 7 | indexes: [ 8 | // 在 email 上创建唯一索引 9 | { 10 | unique: true, 11 | fields: ['email'] 12 | }, 13 | 14 | // 使用 jsonb_path_ops 运算符在 data 上创建 gin 索引 15 | { 16 | fields: ['data'], 17 | using: 'gin', 18 | operator: 'jsonb_path_ops' 19 | }, 20 | 21 | // 默认情况下,索引名称将为 [table]_[fields] 22 | // 创建多列部分索引 23 | { 24 | name: 'public_by_author', 25 | fields: ['author', 'status'], 26 | where: { 27 | status: 'public' 28 | } 29 | }, 30 | 31 | // 具有 order 字段的 BTREE 索引 32 | { 33 | name: 'title_index', 34 | using: 'BTREE', 35 | fields: [ 36 | 'author', 37 | { 38 | name: 'title', 39 | collate: 'en_US', 40 | order: 'DESC', 41 | length: 5 42 | } 43 | ] 44 | } 45 | ] 46 | }); 47 | ``` -------------------------------------------------------------------------------- /other-topics/legacy.md: -------------------------------------------------------------------------------- 1 | # Working with Legacy Tables - 使用遗留表 2 | 3 | 虽然 Sequelize 自认为可以开箱即用, 但是如果你要处理遗留表并向前验证应用程序,仅需要通过定义(否则生成)表和字段名称即可. 4 | 5 | ## 表 6 | 7 | ```js 8 | class User extends Model {} 9 | User.init({ 10 | // ... 11 | }, { 12 | modelName: 'user', 13 | tableName: 'users', 14 | sequelize, 15 | }); 16 | ``` 17 | 18 | ## 字段 19 | 20 | ```js 21 | class MyModel extends Model {} 22 | MyModel.init({ 23 | userId: { 24 | type: DataTypes.INTEGER, 25 | field: 'user_id' 26 | } 27 | }, { sequelize }); 28 | ``` 29 | 30 | ## 主键 31 | 32 | 默认情况下,Sequelize 会假设你的表具有 `id` 主键属性. 33 | 34 | 定义自己的主键: 35 | 36 | ```js 37 | class Collection extends Model {} 38 | Collection.init({ 39 | uid: { 40 | type: DataTypes.INTEGER, 41 | primaryKey: true, 42 | autoIncrement: true // 自动转换为 PostgreSQL 的 SERIAL 43 | } 44 | }, { sequelize }); 45 | 46 | class Collection extends Model {} 47 | Collection.init({ 48 | uuid: { 49 | type: DataTypes.UUID, 50 | primaryKey: true 51 | } 52 | }, { sequelize }); 53 | ``` 54 | 55 | 如果你的模型根本没有主键,则可以使用 `Model.removeAttribute('id');` 56 | 57 | 仍然可以使用 `Model.findOne` 和 `Model.findAll` 检索没有主键的实例. 58 | 虽然目前可以使用它们的实例方法(`instance.save`, `instance.update` 等), 但这样做会导致细微的错误, 并计划在未来的更新中删除. 59 | 60 | **注意** 61 | 62 | 如果你的模型没有主键, 则需要使用以下实例方法的静态等效项, 并提供你自己的 "where" 参数: 63 | 64 | - `instance.save`: `Model.update` 65 | - `instance.update`: `Model.update` 66 | - `instance.reload`: `Model.findOne` 67 | - `instance.destroy`: `Model.destroy` 68 | - `instance.restore`: `Model.restore` 69 | - `instance.decrement`: `Model.decrement` 70 | - `instance.increment`: `Model.increment` 71 | 72 | ## 外键 73 | 74 | ```js 75 | // 1:1 76 | Organization.belongsTo(User, { foreignKey: 'owner_id' }); 77 | User.hasOne(Organization, { foreignKey: 'owner_id' }); 78 | 79 | // 1:M 80 | Project.hasMany(Task, { foreignKey: 'tasks_pk' }); 81 | Task.belongsTo(Project, { foreignKey: 'tasks_pk' }); 82 | 83 | // N:M 84 | User.belongsToMany(Role, { through: 'user_has_roles', foreignKey: 'user_role_user_id' }); 85 | Role.belongsToMany(User, { through: 'user_has_roles', foreignKey: 'roles_identifier' }); 86 | ``` 87 | -------------------------------------------------------------------------------- /other-topics/migrations.md: -------------------------------------------------------------------------------- 1 | # Migrations - 迁移 2 | 3 | 就像你使用[版本控制](https://en.wikipedia.org/wiki/Version_control) 如 [Git](https://en.wikipedia.org/wiki/Git) 来管理源代码的更改一样,你可以使用迁移来跟踪数据库的更改. 通过迁移,你可以将现有的数据库转移到另一个状态,反之亦然:这些状态转换将保存在迁移文件中,它们描述了如何进入新状态以及如何还原更改以恢复旧状态. 4 | 5 | 你将需要 [Sequelize CLI](https://github.com/sequelize/cli). CLI支持迁移和项目引导. 6 | 7 | Sequelize 中的 Migration 是一个 javascript 文件,它导出两个函数 `up` 和 `down`,这些函数指示如何执行迁移和撤消它. 你可以手动定义这些功能,但不必手动调用它们; 它们将由 CLI 自动调用. 在这些函数中,你应该借助 `sequelize.query` 以及 Sequelize 提供给你的其他任何方法,简单地执行所需的任何查询. 除此之外,没有其他神奇的事情. 8 | 9 | ## 安装 CLI 10 | 11 | 要安装 Sequelize CLI,请执行以下操作: 12 | 13 | ```bash 14 | # using npm 15 | npm install --save-dev sequelize-cli 16 | # using yarn 17 | yarn add sequelize-cli --dev 18 | ``` 19 | 20 | 有关详细信息,请参见 [CLI GitHub 库](https://github.com/sequelize/cli). 21 | 22 | ## 项目启动 23 | 24 | 要创建一个空项目,你需要执行 `init` 命令 25 | 26 | ```bash 27 | # using npm 28 | npx sequelize-cli init 29 | # using yarn 30 | yarn sequelize-cli init 31 | ``` 32 | 33 | 这将创建以下文件夹 34 | 35 | - `config`, 包含配置文件,它告诉CLI如何连接数据库 36 | - `models`,包含你的项目的所有模型 37 | - `migrations`, 包含所有迁移文件 38 | - `seeders`, 包含所有种子文件 39 | 40 | ### 结构 41 | 42 | 在继续进行之前,我们需要告诉 CLI 如何连接到数据库. 为此,可以打开默认配置文件 `config/config.json`. 看起来像这样: 43 | 44 | ```json 45 | { 46 | "development": { 47 | "username": "root", 48 | "password": null, 49 | "database": "database_development", 50 | "host": "127.0.0.1", 51 | "dialect": "mysql" 52 | }, 53 | "test": { 54 | "username": "root", 55 | "password": null, 56 | "database": "database_production", 57 | "host": "127.0.0.1", 58 | "dialect": "mysql" 59 | }, 60 | "production": { 61 | "username": "root", 62 | "password": null, 63 | "database": "database_test", 64 | "host": "127.0.0.1", 65 | "dialect": "mysql" 66 | } 67 | } 68 | ``` 69 | 70 | 请注意,默认情况下,Sequelize CLI 假定使用 mysql. 如果你使用其他方言,则需要更改 `"dialect"` 参数的内容. 71 | 72 | 现在编辑此文件并设置正确的数据库凭据和方言.对象的键(例如 "development")用于 `model/index.js` 以匹配 `process.env.NODE_ENV`(当未定义时,默认值是 "development"). 73 | 74 | Sequelize 将为每个方言使用默认的连接端口(例如,对于postgres,它是端口5432). 如果需要指定其他端口,请使用 `port` 字段(默认情况下它不在 config/config.js 中,但你可以简单地添加它). 75 | 76 | **注意:** _如果你的数据库还不存在,你可以调用 `db:create` 命令. 通过正确的访问,它将为你创建该数据库._ 77 | 78 | ## 创建第一个模型(和迁移) 79 | 80 | 一旦你正确配置了CLI配置文件,你就可以首先创建迁移. 它像执行一个简单的命令一样简单. 81 | 82 | 我们将使用 `model:generate` 命令. 此命令需要两个选项 83 | 84 | - `name`: 模型的名称 85 | - `attributes`: 模型的属性列表 86 | 87 | 让我们创建一个名叫 `User` 的模型 88 | 89 | ```bash 90 | # using npm 91 | npx sequelize-cli model:generate --name User --attributes firstName:string,lastName:string,email:string 92 | # using yarn 93 | yarn sequelize-cli model:generate --name User --attributes firstName:string,lastName:string,email:string 94 | ``` 95 | 96 | 这将发生以下事情 97 | 98 | - 在 `models` 文件夹中创建了一个 `user` 模型文件; 99 | - 在 `migrations` 文件夹中创建了一个名字像 `XXXXXXXXXXXXXX-create-user.js` 的迁移文件. 100 | 101 | **注意:** _Sequelize 将只使用模型文件,它是表描述.另一边,迁移文件是该模型的更改,或更具体的是说 CLI 所使用的表. 处理迁移,如提交或日志,以进行数据库的某些更改. _ 102 | 103 | ## 编写迁移 104 | 105 | 以下框架显示了一个典型的迁移文件. 106 | 107 | ```js 108 | module.exports = { 109 | up: (queryInterface, Sequelize) => { 110 | // 转变为新状态的逻辑 111 | }, 112 | down: (queryInterface, Sequelize) => { 113 | // 恢复更改的逻辑 114 | } 115 | } 116 | ``` 117 | 118 | 我们可以使用 `migration:generate` 生成该文件. 这将在你的迁移文件夹中创建 `xxx-migration-example.js`. 119 | 120 | ```bash 121 | # using npm 122 | npx sequelize-cli migration:generate --name migration-example 123 | # using yarn 124 | yarn sequelize-cli migration:generate --name migration-example 125 | ``` 126 | 127 | 传递的 `queryInterface` 对象可以用来修改数据库. `Sequelize` 对象存储可用的数据类型,如 `STRING` 或 `INTEGER`. 函数 `up` 或 `down` 应该返回一个 `Promise` . 让我们来看一个例子 128 | 129 | ```js 130 | module.exports = { 131 | up: (queryInterface, Sequelize) => { 132 | return queryInterface.createTable('Person', { 133 | name: Sequelize.DataTypes.STRING, 134 | isBetaMember: { 135 | type: Sequelize.DataTypes.BOOLEAN, 136 | defaultValue: false, 137 | allowNull: false 138 | } 139 | }); 140 | }, 141 | down: (queryInterface, Sequelize) => { 142 | return queryInterface.dropTable('Person'); 143 | } 144 | }; 145 | ``` 146 | 147 | 以下是一个迁移示例,该迁移使用自动管理的事务来在数据库中执行两次更改,以确保成功执行所有指令或在发生故障时回滚所有指令: 148 | 149 | ```js 150 | module.exports = { 151 | up: (queryInterface, Sequelize) => { 152 | return queryInterface.sequelize.transaction(t => { 153 | return Promise.all([ 154 | queryInterface.addColumn('Person', 'petName', { 155 | type: Sequelize.DataTypes.STRING 156 | }, { transaction: t }), 157 | queryInterface.addColumn('Person', 'favoriteColor', { 158 | type: Sequelize.DataTypes.STRING, 159 | }, { transaction: t }) 160 | ]); 161 | }); 162 | }, 163 | down: (queryInterface, Sequelize) => { 164 | return queryInterface.sequelize.transaction(t => { 165 | return Promise.all([ 166 | queryInterface.removeColumn('Person', 'petName', { transaction: t }), 167 | queryInterface.removeColumn('Person', 'favoriteColor', { transaction: t }) 168 | ]); 169 | }); 170 | } 171 | }; 172 | ``` 173 | 174 | 下一个是具有外键的迁移示例. 你可以使用 references 来指定外键: 175 | 176 | ```js 177 | module.exports = { 178 | up: (queryInterface, Sequelize) => { 179 | return queryInterface.createTable('Person', { 180 | name: Sequelize.DataTypes.STRING, 181 | isBetaMember: { 182 | type: Sequelize.DataTypes.BOOLEAN, 183 | defaultValue: false, 184 | allowNull: false 185 | }, 186 | userId: { 187 | type: Sequelize.DataTypes.INTEGER, 188 | references: { 189 | model: { 190 | tableName: 'users', 191 | schema: 'schema' 192 | }, 193 | key: 'id' 194 | }, 195 | allowNull: false 196 | }, 197 | }); 198 | }, 199 | down: (queryInterface, Sequelize) => { 200 | return queryInterface.dropTable('Person'); 201 | } 202 | } 203 | ``` 204 | 205 | 下一个是使用 async/await 的迁移示例, 其中你通过手动管理的事务在新列上创建唯一索引: 206 | 207 | ```js 208 | module.exports = { 209 | async up(queryInterface, Sequelize) { 210 | const transaction = await queryInterface.sequelize.transaction(); 211 | try { 212 | await queryInterface.addColumn( 213 | 'Person', 214 | 'petName', 215 | { 216 | type: Sequelize.DataTypes.STRING, 217 | }, 218 | { transaction } 219 | ); 220 | await queryInterface.addIndex( 221 | 'Person', 222 | 'petName', 223 | { 224 | fields: 'petName', 225 | unique: true, 226 | transaction, 227 | } 228 | ); 229 | await transaction.commit(); 230 | } catch (err) { 231 | await transaction.rollback(); 232 | throw err; 233 | } 234 | }, 235 | async down(queryInterface, Sequelize) { 236 | const transaction = await queryInterface.sequelize.transaction(); 237 | try { 238 | await queryInterface.removeColumn('Person', 'petName', { transaction }); 239 | await transaction.commit(); 240 | } catch (err) { 241 | await transaction.rollback(); 242 | throw err; 243 | } 244 | } 245 | }; 246 | ``` 247 | 248 | 下一个示例, 该迁移创建具多个字段组成的唯一索引, 该索引允许一个关系存在多次, 但只有一个满足条件: 249 | 250 | ```js 251 | module.exports = { 252 | up: (queryInterface, Sequelize) => { 253 | queryInterface.createTable('Person', { 254 | name: Sequelize.DataTypes.STRING, 255 | bool: { 256 | type: Sequelize.DataTypes.BOOLEAN, 257 | defaultValue: false 258 | } 259 | }).then((queryInterface, Sequelize) => { 260 | queryInterface.addIndex( 261 | 'Person', 262 | ['name', 'bool'], 263 | { 264 | type: 'UNIQUE', 265 | where: { bool : 'true' }, 266 | } 267 | ); 268 | }); 269 | }, 270 | down: (queryInterface, Sequelize) => { 271 | return queryInterface.dropTable('Person'); 272 | } 273 | } 274 | ``` 275 | 276 | ## 运行迁移 277 | 278 | 直到这一步,CLI没有将任何东西插入数据库. 我们刚刚为我们的第一个模型 `User` 创建了必需的模型和迁移文件. 现在要在数据库中实际创建该表,需要运行 `db:migrate` 命令. 279 | 280 | ```bash 281 | # using npm 282 | npx sequelize-cli db:migrate 283 | # using yarn 284 | yarn sequelize-cli db:migrate 285 | ``` 286 | 287 | 此命令将执行这些步骤 288 | 289 | - 将在数据库中确保一个名为 `SequelizeMeta` 的表. 此表用于记录在当前数据库上运行的迁移 290 | - 开始寻找尚未运行的任何迁移文件. 这可以通过检查 `SequelizeMeta` 表. 在这个例子中,它将运行我们在最后一步中创建的 `XXXXXXXXXXXXXX-create-user.js` 迁移,. 291 | - 创建一个名为 `Users` 的表,其中包含其迁移文件中指定的所有列. 292 | 293 | ## 撤消迁移 294 | 295 | 现在我们的表已创建并保存在数据库中. 通过迁移,只需运行命令即可恢复为旧状态. 296 | 297 | 你可以使用 `db:migrate:undo`,这个命令将会恢复最近的迁移. 298 | 299 | ```bash 300 | # using npm 301 | npx sequelize-cli db:migrate:undo 302 | # using yarn 303 | yarn sequelize-cli db:migrate:undo 304 | ``` 305 | 306 | 通过使用 `db:migrate:undo:all` 命令撤消所有迁移,可以恢复到初始状态. 你还可以通过将其名称传递到 `--to` 选项中来恢复到特定的迁移. 307 | 308 | ```bash 309 | # using npm 310 | npx sequelize-cli db:migrate:undo:all --to XXXXXXXXXXXXXX-create-posts.js 311 | # using yarn 312 | yarn sequelize-cli db:migrate:undo:all --to XXXXXXXXXXXXXX-create-posts.js 313 | ``` 314 | 315 | ### 创建第一个种子 316 | 317 | 假设我们希望在默认情况下将一些数据插入到几个表中. 如果我们跟进前面的例子,我们可以考虑为 `User` 表创建演示用户. 318 | 319 | 要管理所有数据迁移,你可以使用 `seeders`. 种子文件是数据的一些变化,可用于使用样本数据或测试数据填充数据库表. 320 | 321 | 让我们创建一个种子文件,它会将一个演示用户添加到我们的 `User` 表中. 322 | 323 | ```bash 324 | # using npm 325 | npx sequelize-cli seed:generate --name demo-user 326 | # using yarn 327 | yarn sequelize-cli seed:generate --name demo-user 328 | ``` 329 | 330 | 这个命令将会在 `seeders` 文件夹中创建一个种子文件.文件名看起来像是 `XXXXXXXXXXXXXX-demo-user.js`,它遵循相同的 `up/down` 语义,如迁移文件. 331 | 332 | 现在我们应该编辑这个文件,将演示用户插入`User`表. 333 | 334 | ```js 335 | module.exports = { 336 | up: (queryInterface, Sequelize) => { 337 | return queryInterface.bulkInsert('Users', [{ 338 | firstName: 'John', 339 | lastName: 'Doe', 340 | email: 'example@example.com', 341 | createdAt: new Date(), 342 | updatedAt: new Date() 343 | }]); 344 | }, 345 | down: (queryInterface, Sequelize) => { 346 | return queryInterface.bulkDelete('Users', null, {}); 347 | } 348 | }; 349 | ``` 350 | 351 | ## 运行种子 352 | 353 | 在上一步中,你创建了一个种子文件. 但它还没有保存到数据库. 为此,我们需要运行一个简单的命令. 354 | 355 | ```bash 356 | # using npm 357 | npx sequelize-cli db:seed:all 358 | # using yarn 359 | yarn sequelize-cli db:seed:all 360 | ``` 361 | 362 | 这将执行该种子文件,你将有一个演示用户插入 `User` 表. 363 | 364 | **注意:** _与使用 `SequelizeMeta` 表的迁移不同,`Seeder` 执行历史记录不会存储在任何地方. 如果你想更改此行为,请阅读 `存储` 部分_ 365 | 366 | ## 撤销种子 367 | 368 | Seeders 如果使用了任何存储那么就可以被撤消. 有两个可用的命令 369 | 370 | 如果你想撤消最近的种子 371 | 372 | ```bash 373 | # using npm 374 | npx sequelize-cli db:seed:undo 375 | # using yarn 376 | yarn sequelize-cli db:seed:undo 377 | ``` 378 | 379 | 如果你想撤消特定的种子 380 | 381 | ```bash 382 | # using npm 383 | npx sequelize-cli db:seed:undo --seed name-of-seed-as-in-data 384 | # using yarn 385 | yarn sequelize-cli db:seed:undo --seed name-of-seed-as-in-data 386 | ``` 387 | 388 | 如果你想撤消所有的种子 389 | 390 | ```bash 391 | # using npm 392 | npx sequelize-cli db:seed:undo:all 393 | # using yarn 394 | yarn sequelize-cli db:seed:undo:all 395 | ``` 396 | 397 | ### `.sequelizerc` 文件 398 | 399 | 这是一个特殊的配置文件. 它允许你指定通常作为参数传递给CLI的各种选项: 400 | 401 | - `env`: 在其中运行命令的环境 402 | - `config`: 配置文件的路径 403 | - `options-path`: 带有其他参数的 JSON 文件的路径 404 | - `migrations-path`: migrations 文件夹的路径 405 | - `seeders-path`: seeders 文件夹的路径 406 | - `models-path`: models 文件夹的路径 407 | - `url`: 要使用的数据库连接字符串. 替代使用 --config 文件 408 | - `debug`: 可用时显示各种调试信息 409 | 410 | 在某些情况下,你可以使用它: 411 | 412 | - 你想要覆盖到 `migrations`, `models`, `seeders` 或 `config` 文件夹的路径. 413 | - 你想要重命名 `config.json` 成为别的名字比如 `database.json` 414 | 415 | 还有更多的, 让我们看一下如何使用这个文件进行自定义配置. 416 | 417 | 首先,让我们在项目的根目录中创建 `.sequelizerc` 文件,其内容如下: 418 | 419 | ```js 420 | // .sequelizerc 421 | 422 | const path = require('path'); 423 | 424 | module.exports = { 425 | 'config': path.resolve('config', 'database.json'), 426 | 'models-path': path.resolve('db', 'models'), 427 | 'seeders-path': path.resolve('db', 'seeders'), 428 | 'migrations-path': path.resolve('db', 'migrations') 429 | }; 430 | ``` 431 | 432 | 通过这个配置你告诉CLI: 433 | 434 | - 使用 `config/database.json` 文件来配置设置; 435 | - 使用 `db/models` 作为模型文件夹; 436 | - 使用 `db/seeders` 作为种子文件夹; 437 | - 使用 `db/migrations` 作为迁移文件夹; 438 | 439 | ### 动态配置 440 | 441 | 默认情况下,配置文件是一个名为 `config.json` 的 JSON 文件. 但是有时你需要动态配置,例如访问环境变量或执行其他代码来确定配置. 442 | 443 | 值得庆幸的是,Sequelize CLI 可以从 `.json` 和 `.js` 文件中读取. 可以使用 `.sequelizerc` 文件来设置. 你只需提供 `.js` 文件的路径作为导出对象的 `config` 参数即可: 444 | 445 | ```js 446 | const path = require('path'); 447 | 448 | module.exports = { 449 | 'config': path.resolve('config', 'config.js') 450 | } 451 | ``` 452 | 453 | 现在,Sequelize CLI 将加载 `config/config.js` 以获取配置参数. 454 | 455 | 一个 `config/config.js` 文件的例子 456 | 457 | ```js 458 | const fs = require('fs'); 459 | 460 | module.exports = { 461 | development: { 462 | username: 'database_dev', 463 | password: 'database_dev', 464 | database: 'database_dev', 465 | host: '127.0.0.1', 466 | port: 3306, 467 | dialect: 'mysql', 468 | dialectOptions: { 469 | bigNumberStrings: true 470 | } 471 | }, 472 | test: { 473 | username: process.env.CI_DB_USERNAME, 474 | password: process.env.CI_DB_PASSWORD, 475 | database: process.env.CI_DB_NAME, 476 | host: '127.0.0.1', 477 | port: 3306, 478 | dialect: 'mysql', 479 | dialectOptions: { 480 | bigNumberStrings: true 481 | } 482 | }, 483 | production: { 484 | username: process.env.PROD_DB_USERNAME, 485 | password: process.env.PROD_DB_PASSWORD, 486 | database: process.env.PROD_DB_NAME, 487 | host: process.env.PROD_DB_HOSTNAME, 488 | port: process.env.PROD_DB_PORT, 489 | dialect: 'mysql', 490 | dialectOptions: { 491 | bigNumberStrings: true, 492 | ssl: { 493 | ca: fs.readFileSync(__dirname + '/mysql-ca-main.crt') 494 | } 495 | } 496 | } 497 | }; 498 | ``` 499 | 500 | 上面的示例还显示了如何向配置中添加自定义方言参数. 501 | 502 | ### 使用 Babel 503 | 504 | 为了在你的迁移和 seeder 中实现更现代的构造,你可以简单地安装 `babel-register` 并在 `.sequelizerc` 开始时 require 它: 505 | 506 | ```bash 507 | # using npm 508 | npm i --save-dev babel-register 509 | # using yarn 510 | yarn add babel-register --dev 511 | ``` 512 | 513 | ```js 514 | // .sequelizerc 515 | 516 | require("babel-register"); 517 | 518 | const path = require('path'); 519 | 520 | module.exports = { 521 | 'config': path.resolve('config', 'config.json'), 522 | 'models-path': path.resolve('models'), 523 | 'seeders-path': path.resolve('seeders'), 524 | 'migrations-path': path.resolve('migrations') 525 | } 526 | ``` 527 | 528 | 当然,结果将取决于你的 babel 配置(例如在 `.babelrc` 文件中). 在[babeljs.io](https://babeljs.io)了解更多信息 529 | 530 | ### 安全提示 531 | 532 | 使用环境变量进行配置设置. 这是因为诸如密码之类的机密绝不应该是源代码的一部分(尤其是不要提交给版本控制). 533 | 534 | ### 存储 535 | 536 | 你可以使用三种类型的存储:`sequelize`,`json`和`none`. 537 | 538 | - `sequelize` : 将迁移和种子存储在 sequelize 数据库的表中 539 | - `json` : 将迁移和种子存储在 json 文件中 540 | - `none` : 不存储任何迁移/种子 541 | 542 | #### 迁移存储 543 | 544 | 默认情况下,CLI 将在数据库中创建一个名为 `SequelizeMeta` 的表,其中包含每个执行的迁移的条目. 要更改此行为,可以将三个参数添加到配置文件中. 使用 `migrationStorage`,你可以选择用于迁移的存储类型. 如果选择 `json`,则可以使用 `migrationStoragePath` 指定文件的路径,否则 CLI 将写入文件 `sequelize-meta.json`. 如果你想使用 `sequelize` 将信息保留在数据库中,但又想使用其他表,则可以使用 `migrationStorageTableName` 来更改表名. 你还可以通过提供 `migrationStorageTableSchema` 属性来为 `SequelizeMeta` 表定义不同的架构. 545 | 546 | ```json 547 | { 548 | "development": { 549 | "username": "root", 550 | "password": null, 551 | "database": "database_development", 552 | "host": "127.0.0.1", 553 | "dialect": "mysql", 554 | 555 | // 使用其他存储类型. 默认: sequelize 556 | "migrationStorage": "json", 557 | 558 | // 使用其他文件名. 默认: sequelize-meta.json 559 | "migrationStoragePath": "sequelizeMeta.json", 560 | 561 | // 使用其他表格名称. 默认: SequelizeMeta 562 | "migrationStorageTableName": "sequelize_meta", 563 | 564 | // 对 SequelizeMeta 表使用其他架构 565 | "migrationStorageTableSchema": "custom_schema" 566 | } 567 | } 568 | ``` 569 | 570 | **Note:** _不建议将 `none` 存储作为迁移存储. 如果你决定使用它,请注意没有任何迁移进行或未运行的记录的含义._ 571 | 572 | #### 种子储存 573 | 574 | 默认情况下,CLI 不会保存任何已执行的种子. 如果选择更改此行为(!),则可以在配置文件中使用 `seederStorage` 来更改存储类型. 如果选择 `json`,则可以使用 `seederStoragePath` 指定文件的路径,否则 CLI 将写入文件 `sequelize-data.json`. 如果要使用 `sequelize` 将信息保留在数据库中,则可以使用 `seederStorageTableName` 指定表名,否则它将默认为 `SequelizeData`. 575 | 576 | ```json 577 | { 578 | "development": { 579 | "username": "root", 580 | "password": null, 581 | "database": "database_development", 582 | "host": "127.0.0.1", 583 | "dialect": "mysql", 584 | // 使用其他存储. 默认: none 585 | "seederStorage": "json", 586 | // 使用其他文件名称. 默认: sequelize-data.json 587 | "seederStoragePath": "sequelizeData.json", 588 | // 使用其他表格名称. 默认: SequelizeData 589 | "seederStorageTableName": "sequelize_data" 590 | } 591 | } 592 | ``` 593 | 594 | ### 配置连接字符串 595 | 596 | 配置连接字符串作为配置文件定义数据库的 `--config` 参数的替代方法,可以使用 `--url` 参数来传递连接字符串. 例如: 597 | 598 | ```bash 599 | # using npm 600 | npx sequelize-cli db:migrate --url 'mysql://root:password@mysql_host.com/database_name' 601 | # using yarn 602 | yarn sequelize-cli db:migrate --url 'mysql://root:password@mysql_host.com/database_name' 603 | ``` 604 | 605 | 如果将 `package.json` 脚本与 npm 一起使用, 请确保在使用标志时在命令中使用额外的 `--`. 606 | 607 | 示例: 608 | 609 | ```json 610 | // package.json 611 | 612 | ... 613 | "scripts": { 614 | "migrate:up": "npx sequelize-cli db:migrate", 615 | "migrate:undo": "npx sequelize-cli db:migrate:undo" 616 | }, 617 | ... 618 | ``` 619 | 620 | 像这样使用命令: `npm run migrage:up -- --url ` 621 | 622 | ### 程序用法 623 | 624 | 程序用法 Sequelize 有一个名为 [umzug](https://github.com/sequelize/umzug) 的姊妹库,用于以编程方式处理迁移任务的执行和日志记录. -------------------------------------------------------------------------------- /other-topics/naming-strategies.md: -------------------------------------------------------------------------------- 1 | # Naming Strategies - 命名策略 2 | 3 | ## `underscored` 参数 4 | 5 | Sequelize 为模型提供了 `underscored` 参数. 设为 `true` 时,此参数会将所有属性的 `field` 参数设置为其名称的 [snake_case](https://en.wikipedia.org/wiki/Snake_case) 版本(除非手动设置). 这也适用于关联自动生成的表名和外键以及其他自动生成的字段. 6 | 7 | 示例: 8 | 9 | ```js 10 | const User = sequelize.define('User', { username: DataTypes.STRING }, { 11 | underscored: true 12 | }); 13 | const Task = sequelize.define('Task', { title: DataTypes.STRING }, { 14 | underscored: true 15 | }); 16 | User.hasMany(Task); 17 | Task.belongsTo(User); 18 | ``` 19 | 20 | 上面我们有模型 User 和 Task,都使用了 `underscored` 的参数. 他们之间也有一对多的关系. 另外,回想一下,由于默认情况下 `timestamps` 为 `true`,因此我们应该期望 `createedAt` 和 `updatedAt` 字段也将自动创建. 21 | 22 | 如果没有 `underscored` 参数,Sequelize 会自动定义: 23 | 24 | * User 模型的 `Users` 表和 Task 模型的 `Tasks` 表. 25 | * 每个模型的 `createdAt` 属性,指向每个表中名为 `createdAt` 的列 26 | * 每个模型的 `updatedAt` 属性,指向每个表中名为 `updatedAt` 的列 27 | * `Task` 模型中的 `userId` 属性,指向任务表中名为 `userId` 的列 28 | 29 | 启用 `underscored` 参数后,Sequelize 将改为定义: 30 | 31 | * User 模型的 `users` 表和 Task 模型的 `tasks` 表. 32 | * 每个模型的 `createdAt` 属性,指向每个表中名为 `created_at ` 的列 33 | * 每个模型的 `updatedAt` 属性,指向每个表中名为 `updated_at ` 的列 34 | * `Task` 模型中的 `userId` 属性,指向任务表中名为 `user_id ` 的列 35 | 36 | 请注意,在这两种情况下,JavaScript 字段均仍为 [camelCase](https://en.wikipedia.org/wiki/Camel_case); 此参数仅更改这些字段如何映射到数据库本身. 每个属性的 `field` 参数都设置为它们的 snake_case 版本,但属性本身仍为 camelCase. 37 | 38 | 这样,在上面的代码上调用 `sync()` 将会生成以下内容: 39 | 40 | ```sql 41 | CREATE TABLE IF NOT EXISTS "users" ( 42 | "id" SERIAL, 43 | "username" VARCHAR(255), 44 | "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, 45 | "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, 46 | PRIMARY KEY ("id") 47 | ); 48 | CREATE TABLE IF NOT EXISTS "tasks" ( 49 | "id" SERIAL, 50 | "title" VARCHAR(255), 51 | "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, 52 | "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, 53 | "user_id" INTEGER REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE, 54 | PRIMARY KEY ("id") 55 | ); 56 | ``` 57 | 58 | ## 单数与复数 59 | 60 | 乍看之下,在 Sequelize 中是否应使用名称的单数形式或复数形式可能会造成混淆. 本节旨在澄清这一点. 61 | 62 | 回想一下 Sequelize 在后台使用了一个名为 [inflection](https://www.npmjs.com/package/inflection) 的库,以便正确计算不规则的复数形式(例如 `person -> people`). 但是,如果你使用的是另一种语言,则可能需要直接定义名称的单数和复数形式. sequelize 允许你通过一些参数来执行此操作. 63 | 64 | ### 定义模型时 65 | 66 | 模型应以单词的单数形式定义. 例: 67 | 68 | ```js 69 | sequelize.define('foo', { name: DataTypes.STRING }); 70 | ``` 71 | 72 | 上面的模型名称是 `foo`(单数),表名称是 `foos`,因为 Sequelize 会自动获取表名称的复数形式. 73 | 74 | ### 在模型中定义参考键时 75 | 76 | ```js 77 | sequelize.define('foo', { 78 | name: DataTypes.STRING, 79 | barId: { 80 | type: DataTypes.INTEGER, 81 | allowNull: false, 82 | references: { 83 | model: "bars", 84 | key: "id" 85 | }, 86 | onDelete: "CASCADE" 87 | }, 88 | }); 89 | ``` 90 | 91 | 在上面的示例中,我们手动定义了引用另一个模型的键. 这不是通常的做法,但是如果必须这样做,则应在此使用表名. 这是因为引用是根据引用的表名创建的. 在上面的示例中,使用了复数形式(`bars`),假设 `bar` 模型是使用默认设置创建的(使其基础表自动复数). 92 | 93 | ### 从预先加载中检索数据时 94 | 95 | 当你在查询中执行 `include` 时,包含的数据将根据以下规则添加到返回对象的额外字段中: 96 | 97 | * 当包含来自单个关联(`hasOne` 或 `belongsTo`)的内容时,字段名称将是模型名称的单数形式; 98 | * 当包含来自多个关联(`hasMany` 或 `belongsToMany`)的内容时,字段名称将是模型的复数形式. 99 | 100 | 简而言之,在每种情况下,字段名称将采用最合乎逻辑的形式. 101 | 102 | 示例: 103 | 104 | ```js 105 | // 假设 Foo.hasMany(Bar) 106 | const foo = Foo.findOne({ include: Bar }); 107 | // foo.bars 将是一个数组 108 | // foo.bar 将不存在,因为它没有意义 109 | 110 | // 假设 Foo.hasOne(Bar) 111 | const foo = Foo.findOne({ include: Bar }); 112 | // foo.bar 将是一个对象(如果没有关联的模型,则可能为 null) 113 | // foo.bars 将不存在,因为它没有意义 114 | 115 | // 等等. 116 | ``` 117 | 118 | ### 定义别名时覆盖单数和复数 119 | 120 | 在为关联定义别名时,你可以传递一个对象以指定单数和复数形式,而不仅仅是使用 `{ as: 'myAlias' }`. 121 | 122 | ```js 123 | Project.belongsToMany(User, { 124 | as: { 125 | singular: 'líder', 126 | plural: 'líderes' 127 | } 128 | }); 129 | ``` 130 | 131 | 如果你知道模型在关联中将始终使用相同的别名,则可以将单数和复数形式直接提供给模型本身: 132 | 133 | ```js 134 | const User = sequelize.define('user', { /* ... */ }, { 135 | name: { 136 | singular: 'líder', 137 | plural: 'líderes', 138 | } 139 | }); 140 | Project.belongsToMany(User); 141 | ``` 142 | 143 | 添加到用户实例的混入文件将使用正确的形式. 例如,Sequelize 将代替 `project.addUser()` 来提供 `project.getLíder()`. 另外,Sequelize 将代替 `project.setUsers()` 来提供 `project.setLíderes()`. 144 | 145 | 注意:记得使用 `as` 来改变关联的名字也会改变外键的名字. 因此,建议也指定在这种情况下直接涉及的外键. 146 | 147 | ```js 148 | // 错误示例 149 | Invoice.belongsTo(Subscription, { as: 'TheSubscription' }); 150 | Subscription.hasMany(Invoice); 151 | ``` 152 | 153 | 上面的第一个调用将在 `Invoice` 上建立一个名为 `theSubscriptionId` 的外键. 但是,第二个调用也会在 `Invoice` 上建立外键(因为众所周知, `hasMany` 调用会将外键放置在目标模型中)-但是,它将被命名为 `subscriptionId`. 这样,你将同时具有 `subscriptionId` 和 `theSubscriptionId` 列. 154 | 155 | 最好的方法是为外键选择一个名称,并将其显式放置在两个调用中. 例如,如果选择了 `subscription_id`: 156 | 157 | ```js 158 | // 修正示例 159 | Invoice.belongsTo(Subscription, { as: 'TheSubscription', foreignKey: 'subscription_id' }); 160 | Subscription.hasMany(Invoice, { foreignKey: 'subscription_id' }); 161 | ``` -------------------------------------------------------------------------------- /other-topics/optimistic-locking.md: -------------------------------------------------------------------------------- 1 | # Optimistic Locking - 乐观锁定 2 | 3 | Sequelize 内置支持通过模型实例版本计数进行乐观锁定. 4 | 5 | 乐观锁定默认情况下处于禁用状态,可以通过在特定模型定义或全局模型配置中将 `version` 属性设置为 true 来启用. 6 | 7 | 有关更多详细信息,请参见[模型基础](../core-concepts/model-basics.md). 8 | 9 | 乐观锁定允许并发访问模型记录以进行编辑,并防止冲突覆盖数据. 它通过检查自从读取以来另一个进程是否对记录进行了更改,并在检测到冲突时抛出 OptimisticLockError 来执行此操作. -------------------------------------------------------------------------------- /other-topics/query-interface.md: -------------------------------------------------------------------------------- 1 | # Query Interface - 查询接口 2 | 3 | Sequelize 实例使用一种称为 **查询接口** 的东西来以与方言无关的方式与数据库进行通信. 你在本手册中学到的大多数方法都是通过查询接口中的几种方法来实现的. 4 | 5 | 因此,查询接口中的方法是较低级的方法; 仅当找不到其他方法来使用 Sequelize 的高级 API 时,才应使用它们. 当然,它们比直接运行原始查询(即,手工编写SQL)的级别更高. 6 | 7 | 本指南展示了一些示例,但是要获取其功能的完整列表以及每种方法的详细用法,请查看[查询接口 API](/api/v7/classes/QueryInterface.html). 8 | 9 | ## 获取查询界面 10 | 11 | 从现在开始,我们将 `queryInterface` 称为 [查询接口](/api/v7/classes/QueryInterface.html) 类的单例实例,该实例可在你的 Sequelize 实例上使用: 12 | 13 | ```js 14 | const { Sequelize, DataTypes } = require('@sequelize/core'); 15 | const sequelize = new Sequelize(/* ... */); 16 | const queryInterface = sequelize.getQueryInterface(); 17 | ``` 18 | 19 | ## 创建一个表 20 | 21 | ```js 22 | queryInterface.createTable('Person', { 23 | name: DataTypes.STRING, 24 | isBetaMember: { 25 | type: DataTypes.BOOLEAN, 26 | defaultValue: false, 27 | allowNull: false 28 | } 29 | }); 30 | ``` 31 | 32 | 生成 SQL (使用 SQLite): 33 | 34 | ```SQL 35 | CREATE TABLE IF NOT EXISTS `Person` ( 36 | `name` VARCHAR(255), 37 | `isBetaMember` TINYINT(1) NOT NULL DEFAULT 0 38 | ); 39 | ``` 40 | 41 | **注意:** 考虑定义一个模型,然后调用 `YourModel.sync()`,这是一个较高级别的方法. 42 | 43 | ## 向表添加列 44 | 45 | ```js 46 | queryInterface.addColumn('Person', 'petName', { type: DataTypes.STRING }); 47 | ``` 48 | 49 | 生成 SQL (使用 SQLite): 50 | 51 | ```sql 52 | ALTER TABLE `Person` ADD `petName` VARCHAR(255); 53 | ``` 54 | 55 | ## 更改列的数据类型 56 | 57 | ```js 58 | queryInterface.changeColumn('Person', 'foo', { 59 | type: DataTypes.FLOAT, 60 | defaultValue: 3.14, 61 | allowNull: false 62 | }); 63 | ``` 64 | 65 | 生成 SQL (使用 MySQL): 66 | 67 | ```sql 68 | ALTER TABLE `Person` CHANGE `foo` `foo` FLOAT NOT NULL DEFAULT 3.14; 69 | ``` 70 | 71 | ## 删除列 72 | 73 | ```js 74 | queryInterface.removeColumn('Person', 'petName', { /* 查询参数 */ }); 75 | ``` 76 | 77 | 生成 SQL (使用 PostgreSQL): 78 | 79 | ```SQL 80 | ALTER TABLE "public"."Person" DROP COLUMN "petName"; 81 | ``` 82 | 83 | ## 更改和删除 SQLite 中的列 84 | 85 | SQLite 不支持直接更改和删除列. 但是,Sequelize 将通过受[这些说明](https://www.sqlite.org/lang_altertable.html#otheralter)的启发在备份表的帮助下重新创建整个表,以解决此问题. 86 | 87 | 示例: 88 | 89 | ```js 90 | // 假设我们在 SQLite 中创建了一个表,如下所示: 91 | queryInterface.createTable('Person', { 92 | name: DataTypes.STRING, 93 | isBetaMember: { 94 | type: DataTypes.BOOLEAN, 95 | defaultValue: false, 96 | allowNull: false 97 | }, 98 | petName: DataTypes.STRING, 99 | foo: DataTypes.INTEGER 100 | }); 101 | 102 | // 我们改变一列: 103 | queryInterface.changeColumn('Person', 'foo', { 104 | type: DataTypes.FLOAT, 105 | defaultValue: 3.14, 106 | allowNull: false 107 | }); 108 | ``` 109 | 110 | 为 SQLite 生成了以下 SQL 调用: 111 | 112 | ```sql 113 | PRAGMA TABLE_INFO(`Person`); 114 | 115 | CREATE TABLE IF NOT EXISTS `Person_backup` ( 116 | `name` VARCHAR(255), 117 | `isBetaMember` TINYINT(1) NOT NULL DEFAULT 0, 118 | `foo` FLOAT NOT NULL DEFAULT '3.14', 119 | `petName` VARCHAR(255) 120 | ); 121 | 122 | INSERT INTO `Person_backup` 123 | SELECT 124 | `name`, 125 | `isBetaMember`, 126 | `foo`, 127 | `petName` 128 | FROM `Person`; 129 | 130 | DROP TABLE `Person`; 131 | 132 | CREATE TABLE IF NOT EXISTS `Person` ( 133 | `name` VARCHAR(255), 134 | `isBetaMember` TINYINT(1) NOT NULL DEFAULT 0, 135 | `foo` FLOAT NOT NULL DEFAULT '3.14', 136 | `petName` VARCHAR(255) 137 | ); 138 | 139 | INSERT INTO `Person` 140 | SELECT 141 | `name`, 142 | `isBetaMember`, 143 | `foo`, 144 | `petName` 145 | FROM `Person_backup`; 146 | 147 | DROP TABLE `Person_backup`; 148 | ``` 149 | 150 | ## 其它 151 | 152 | 如本指南开头所述,Sequelize 中的查询接口还有很多! 查看 [查询接口 API](/api/v7/classes/QueryInterface.html),以获取可以完成的操作的完整列表. -------------------------------------------------------------------------------- /other-topics/read-replication.md: -------------------------------------------------------------------------------- 1 | # Read Replication - 读取复制 2 | 3 | Sequelize 支持 [读取复制](https://en.wikipedia.org/wiki/Replication_%28computing%29#Database_replication), 即,当你要执行 SELECT 查询时,可以连接多个服务器. 当你执行读取复制时,你可以指定一台或多台服务器充当读取副本,并指定一台服务器充当写入主机,该主机处理所有写入和更新并将它们传播到副本(请注意,实际复制过程 *不是* 由 Sequelize 处理,而应由数据库后端设置). 4 | 5 | ```js 6 | const sequelize = new Sequelize('database', null, null, { 7 | dialect: 'mysql', 8 | port: 3306, 9 | replication: { 10 | read: [ 11 | { host: '8.8.8.8', username: 'read-1-username', password: process.env.READ_DB_1_PW }, 12 | { host: '9.9.9.9', username: 'read-2-username', password: process.env.READ_DB_2_PW } 13 | ], 14 | write: { host: '1.1.1.1', username: 'write-username', password: process.env.WRITE_DB_PW } 15 | }, 16 | pool: { // 如果要覆盖用于 读/写 池的参数,可以在此处执行 17 | max: 20, 18 | idle: 30000 19 | }, 20 | }) 21 | ``` 22 | 23 | 如果你有适用于所有副本的常规设置,则无需为每个实例提供它们. 在上面的代码中,数据库名称和端口将传播到所有副本. 如果将用户名和密码留给任何副本,则同样会生效. 每个副本具有以下参数:`host`, `port`, `username`, `password`, `database`. 24 | 25 | Sequelize 使用池来管理与副本的连接. 在内部,Sequelize 将维护使用 `pool` 配置创建的两个池. 26 | 27 | 如果要修改这些设置,可以在实例化 Sequelize 时将 pool 作为参数传递,如上所示. 28 | 29 | 每个 `write` 或 `useMaster: true` 查询都将使用写入池. 对于 `SELECT`,将使用读取池. 使用基本的循环调度来切换只读副本. -------------------------------------------------------------------------------- /other-topics/resources.md: -------------------------------------------------------------------------------- 1 | # Resources - 资源 2 | 3 | 使用 Sequelize 的精选项目列表. 4 | 5 | ## Integrations 6 | 7 | * [sequelize-typescript](https://www.npmjs.com/package/sequelize-typescript) - Decorators and some other features for sequelize (built-in as of Sequelize 7). 8 | * [Sequelize-Nest](https://docs.nestjs.com/recipes/sql-sequelize) - Sequelize integration in [nest](https://github.com/nestjs/nest). 9 | * [sequelizejs-decorators](https://www.npmjs.com/package/sequelizejs-decorators) decorators for composing sequelize models. 10 | 11 | ## Code generation & visualisers 12 | 13 | * [sequelize-ui](https://github.com/tomjschuster/sequelize-ui) - Online tool for building models, relations and more. 14 | * [sequelizer](https://github.com/andyforever/sequelizer) - A GUI Desktop App for generating Sequelize models. Support for Mysql, Mariadb, Postgres, Sqlite, Mssql. 15 | * [sequelize-auto](https://github.com/sequelize/sequelize-auto) Generating models for SequelizeJS via the command line is another choice. 16 | * [pg-generator](https://pg-generator.com/v4/builtin-templates--nc,d1/sequelize.html) - Auto generate/scaffold Sequelize models for PostgreSQL database. 17 | * [meteor modeler](https://www.datensen.com/) - Desktop tool for visual definition of Sequelize models and asssociations. 18 | * [sequel-ace-typescript-bundles](https://github.com/binlabs/sequel-ace-typescript-bundles) - A plugin for Sequel Ace that allows generation of Sequelize models from selected database tables. 19 | 20 | ## Performance 21 | 22 | * [dataloader-sequelize](https://www.npmjs.com/package/dataloader-sequelize) - Batching, caching and simplification of Sequelize with facebook/dataloader 23 | * [sequelize-transparent-cache](https://github.com/DanielHreben/sequelize-transparent-cache) 24 | 25 | ## Migrations 26 | 27 | * [umzug](https://github.com/sequelize/umzug) - framework-agnostic migration tool for Node. 28 | * [sequelize-cli](https://github.com/sequelize/cli) - The Sequelize Command Line Interface. Includes umzug-based migrations. 29 | * [sequelize-mig](https://github.com/MRVMV/sequelize-mig) - Sequelize migration generator 30 | * [sequelizemm](https://github.com/hasinoorit/sequelizemm) - CLI tool to generate a migration script from models 31 | 32 | ## Miscellaneous 33 | 34 | * [sequelize-pg-utilities](https://github.com/davesag/sequelize-pg-utilities) - Opinionated set of database utilities. 35 | * [sequelize-test-helpers](https://github.com/davesag/sequelize-test-helpers) - A collection of utilities to help with unit-testing Sequelize models and code that needs those models. 36 | * [Sequelize-fixtures](https://github.com/domasx2/sequelize-fixtures) - Simple lib to load data to database using sequelize. 37 | * [SequelizeGuard](https://github.com/lotivo/sequelize-acl) - Role, Permission based Authorization for Sequelize. 38 | * [sequelize-bcrypt](https://github.com/mattiamalonni/sequelize-bcrypt) - Utility to integrate bcrypt into sequelize models 39 | * [sequelize-temporal](https://github.com/bonaval/sequelize-temporal) - Temporal tables (aka historical records) 40 | * [sequelize-joi](https://github.com/mattiamalonni/sequelize-joi) - Allows specifying [Joi](https://github.com/sideway/joi) validation schema for model attributes in Sequelize. 41 | * [sequelize-slugify](https://www.npmjs.com/package/sequelize-slugify) - Add slugs to sequelize models 42 | * [sequelize-tokenify](https://github.com/pipll/sequelize-tokenify) - Add unique tokens to sequelize models 43 | * [sqlcommenter-sequelize](https://github.com/google/sqlcommenter/tree/master/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-sequelize) A [sqlcommenter](https://google.github.io/sqlcommenter/) plugin with [support for Sequelize](https://google.github.io/sqlcommenter/node/sequelize/) to augment SQL statements with comments that can be used later to correlate application code with SQL statements. 44 | * [@rematter/sequelize-paranoid-delete](https://www.npmjs.com/package/@rematter/sequelize-paranoid-delete) - Enables onDelete when using paranoid mode. 45 | 46 | ## Outdated 47 | 48 | These libraries haven't been updated in a long time, and may not work with newer versions of Sequelize 49 | 50 | * [sequelize-transforms](https://www.npmjs.com/package/sequelize-transforms) - Add configurable attribute transforms. 51 | * [Sequelize-fixture](https://github.com/xudejian/sequelize-fixture) 52 | * [Fixer](https://github.com/olalonde/fixer) 53 | * [ssacl](https://github.com/pumpupapp/ssacl) 54 | * [ssacl-attribute-roles](https://github.com/mickhansen/ssacl-attribute-roles) 55 | * [sequelize-hierarchy](https://www.npmjs.com/package/sequelize-hierarchy) - Nested hierarchies for Sequelize. 56 | * [sequelize-autoload](https://github.com/boxsnake-nodejs/sequelize-autoload) - An autoloader for Sequelize, inspired by [PSR-0](https://www.php-fig.org/psr/psr-0/) and [PSR-4](https://www.php-fig.org/psr/psr-4/). 57 | * [sequelize-deep-update](https://www.npmjs.com/package/sequelize-deep-update) - Update a sequelize instance and its included associated instances with new properties. 58 | * [sequelize-noupdate-attributes](https://www.npmjs.com/package/sequelize-noupdate-attributes) - Adds no update/readonly attributes support to models. 59 | -------------------------------------------------------------------------------- /other-topics/scopes.md: -------------------------------------------------------------------------------- 1 | # Scopes - 作用域 2 | 3 | 作用域用于帮助你重用代码. 你可以定义常用查询,并指定诸如 `where`, `include`, `limit` 等参数. 4 | 5 | 本指南涉及模型作用域. 你可能也对[关联作用域指南](../advanced-association-concepts/association-scopes.md)感兴趣,它们相似但又不同. 6 | 7 | ## 定义 8 | 9 | 作用域在模型定义中定义,可以是查找器对象,也可以是返回查找器对象的函数 - 默认作用域除外,该作用域只能是一个对象: 10 | 11 | ```js 12 | class Project extends Model {} 13 | Project.init({ 14 | // 属性 15 | }, { 16 | defaultScope: { 17 | where: { 18 | active: true 19 | } 20 | }, 21 | scopes: { 22 | deleted: { 23 | where: { 24 | deleted: true 25 | } 26 | }, 27 | activeUsers: { 28 | include: [ 29 | { model: User, where: { active: true } } 30 | ] 31 | }, 32 | random() { 33 | return { 34 | where: { 35 | someNumber: Math.random() 36 | } 37 | } 38 | }, 39 | accessLevel(value) { 40 | return { 41 | where: { 42 | accessLevel: { 43 | [Op.gte]: value 44 | } 45 | } 46 | } 47 | }, 48 | sequelize, 49 | modelName: 'project' 50 | } 51 | }); 52 | ``` 53 | 54 | 你也可以在定义模型后通过调用 [`YourModel.addScope`](/api/v7/classes/Model.html#addScope) 添加作用域. 这对于具有包含的作用域特别有用,其中在定义另一个模型时可能未定义包含中的模型. 55 | 56 | 始终应用默认作用域. 这意味着,使用上面的模型定义,`Project.findAll()` 将创建以下查询: 57 | 58 | ```sql 59 | SELECT * FROM projects WHERE active = true 60 | ``` 61 | 62 | 可以通过调用 `.unscoped()`, `.scope(null)`, 或调用另一个作用域来删除默认作用域: 63 | 64 | ```js 65 | await Project.scope('deleted').findAll(); // 删除默认作用域 66 | ``` 67 | 68 | ```sql 69 | SELECT * FROM projects WHERE deleted = true 70 | ``` 71 | 72 | 也可以在作用域定义中包括作用域模型. 这样可以避免重复 `include`, `attributes` 或 `where` 定义. 使用上面的示例,并在包含的用户模型上调用 `active` 作用域(而不是直接在该包含对象中指定条件): 73 | 74 | ```js 75 | // 上例中定义的 `activeUsers` 作用域也可以通过以下方式定义: 76 | Project.addScope('activeUsers', { 77 | include: [ 78 | { model: User.scope('active') } 79 | ] 80 | }); 81 | ``` 82 | 83 | ## 使用 84 | 85 | 通过在模型定义上调用 `.scope`,并传递一个或多个作用域的名称来应用作用域.`.scope` 返回具有所有常规方法的功能齐全的模型实例:`.findAll`, `.update`, `.count`, `.destroy` 等.你可以保存此模型实例并在以后重用: 86 | 87 | ```js 88 | const DeletedProjects = Project.scope('deleted'); 89 | await DeletedProjects.findAll(); 90 | 91 | // 以上相当于: 92 | await Project.findAll({ 93 | where: { 94 | deleted: true 95 | } 96 | }); 97 | ``` 98 | 99 | 作用域适用于 `.find`, `.findAll`, `.count`, `.update`, `.increment` 和 `.destroy`. 100 | 101 | 作用域可以通过两种方式调用. 如果作用域不带任何参数,则可以正常调用它. 如果作用域接受参数,则传递一个对象: 102 | 103 | ```js 104 | await Project.scope('random', { method: ['accessLevel', 19] }).findAll(); 105 | ``` 106 | 107 | 生成 SQL: 108 | 109 | ```sql 110 | SELECT * FROM projects WHERE someNumber = 42 AND accessLevel >= 19 111 | ``` 112 | 113 | ## 合并 114 | 115 | 通过将作用域数组传递给 `.scope` 或将作用域作为连续参数传递,可以同时应用多个作用域. 116 | 117 | ```js 118 | // 这两个是等效的 119 | await Project.scope('deleted', 'activeUsers').findAll(); 120 | await Project.scope(['deleted', 'activeUsers']).findAll(); 121 | ``` 122 | 123 | 生成 SQL: 124 | 125 | ```sql 126 | SELECT * FROM projects 127 | INNER JOIN users ON projects.userId = users.id 128 | WHERE projects.deleted = true 129 | AND users.active = true 130 | ``` 131 | 132 | 如果要在默认作用域之外应用另一个作用域,请将关键字 `defaultScope` 传递给`.scope`: 133 | 134 | ```js 135 | await Project.scope('defaultScope', 'deleted').findAll(); 136 | ``` 137 | 138 | 生成 SQL: 139 | 140 | ```sql 141 | SELECT * FROM projects WHERE active = true AND deleted = true 142 | ``` 143 | 144 | 调用多个合并作用域时,后续合并作用域中的键将覆盖先前合并作用域中的键(类似于 [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)),除了将合并的 `where` 和 `include` 之外. 考虑两个作用域: 145 | 146 | ```js 147 | YourMode.addScope('scope1', { 148 | where: { 149 | firstName: 'bob', 150 | age: { 151 | [Op.gt]: 20 152 | } 153 | }, 154 | limit: 2 155 | }); 156 | YourMode.addScope('scope2', { 157 | where: { 158 | age: { 159 | [Op.lt]: 30 160 | } 161 | }, 162 | limit: 10 163 | }); 164 | ``` 165 | 166 | 使用 `.scope('scope1', 'scope2')` 将产生以下 WHERE 子句: 167 | 168 | ```sql 169 | WHERE firstName = 'bob' AND age > 20 AND age < 30 LIMIT 10 170 | ``` 171 | 172 | 注意 `limit` 是如何被 `scope2` 覆盖的, 而 `firstName` 和 `age` 的两个条件都被保留了下来. `limit`、`offset`、`order`、`paranoid`、`lock` 和 `raw` 字段被覆盖, 而 `where` 字段使用 `AND` 运算符合并. `include` 的合并策略将在后面讨论. 173 | 174 | 注意,多个应用作用域的 `attributes` 键以始终保留 `attributes.exclude` 的方式合并. 这允许合并多个合并作用域,并且永远不会泄漏最终合并作用域中的敏感字段. 175 | 176 | 当将查找对象直接传递给作用域模型上的 `findAll`(和类似的查找器)时,适用相同的合并逻辑: 177 | 178 | ```js 179 | Project.scope('deleted').findAll({ 180 | where: { 181 | firstName: 'john' 182 | } 183 | }) 184 | ``` 185 | 186 | 生成的 where 子句: 187 | 188 | ```sql 189 | WHERE deleted = true AND firstName = 'john' 190 | ``` 191 | 192 | 在这里, `deleted` 作用域与查找器合并. 如果我们将 `where: { firstName: 'john', deleted: false }` 传递给查找器,则 `deleted` 作用域将被覆盖. 193 | 194 | ### 合并 Include 195 | 196 | Include 将基于所包含的模型进行递归合并. 这是 v5 上添加的功能非常强大的合并,并通过示例更好地理解. 197 | 198 | 考虑模型 `Foo`, `Bar`, `Baz` 和 `Qux`,它们具有一对多关联,如下所示: 199 | 200 | ```js 201 | const Foo = sequelize.define('Foo', { name: DataTypes.STRING }); 202 | const Bar = sequelize.define('Bar', { name: DataTypes.STRING }); 203 | const Baz = sequelize.define('Baz', { name: DataTypes.STRING }); 204 | const Qux = sequelize.define('Qux', { name: DataTypes.STRING }); 205 | Foo.hasMany(Bar, { foreignKey: 'fooId' }); 206 | Bar.hasMany(Baz, { foreignKey: 'barId' }); 207 | Baz.hasMany(Qux, { foreignKey: 'bazId' }); 208 | ``` 209 | 210 | 现在,考虑在 Foo 上定义的以下四个作用域: 211 | 212 | ```js 213 | Foo.addScope('includeEverything', { 214 | include: { 215 | model: Bar, 216 | include: [{ 217 | model: Baz, 218 | include: Qux 219 | }] 220 | } 221 | }); 222 | 223 | Foo.addScope('limitedBars', { 224 | include: [{ 225 | model: Bar, 226 | limit: 2 227 | }] 228 | }); 229 | 230 | Foo.addScope('limitedBazs', { 231 | include: [{ 232 | model: Bar, 233 | include: [{ 234 | model: Baz, 235 | limit: 2 236 | }] 237 | }] 238 | }); 239 | 240 | Foo.addScope('excludeBazName', { 241 | include: [{ 242 | model: Bar, 243 | include: [{ 244 | model: Baz, 245 | attributes: { 246 | exclude: ['name'] 247 | } 248 | }] 249 | }] 250 | }); 251 | ``` 252 | 253 | 这四个作用域可以很容易地进行深度合并,例如,通过调用 `Foo.scope('includeEverything', 'limitedBars', 'limitedBazs', 'excludeBazName').findAll()`,这完全等同于调用以下代码: 254 | 255 | ```js 256 | await Foo.findAll({ 257 | include: { 258 | model: Bar, 259 | limit: 2, 260 | include: [{ 261 | model: Baz, 262 | limit: 2, 263 | attributes: { 264 | exclude: ['name'] 265 | }, 266 | include: Qux 267 | }] 268 | } 269 | }); 270 | 271 | // 以上等同于: 272 | await Foo.scope([ 273 | 'includeEverything', 274 | 'limitedBars', 275 | 'limitedBazs', 276 | 'excludeBazName' 277 | ]).findAll(); 278 | ``` 279 | 280 | 观察如何将四个作用域合并为一个. 作用域的 include 根据所包含的模型进行合并. 如果一个作用域包含模型 A,另一个作用域包含模型 B,则合并结果将同时包含模型 A 和 B.另一方面,如果两个作用域都包含相同的模型 A,但具有不同的参数(例如嵌套包含或其他属性) ,这些将被递归合并,如上所示. 281 | 282 | 上面说明的合并以完全相同的方式工作,而不管应用于作用域的顺序如何. 如果某个选项由两个不同的作用域设置,则顺序只会有所不同 - 上面的示例不是这种情况,因为每个作用域执行的操作都不相同. 283 | 284 | 这种合并策略还可以与传递给 `.findAll`, `.findOne` 等的参数完全相同. -------------------------------------------------------------------------------- /other-topics/sub-queries.md: -------------------------------------------------------------------------------- 1 | # Sub Queries - 子查询 2 | 3 | 考虑你有两个模型,即 `Post` 和 `Reaction`,它们之间建立了一对多的关系,因此一个 post 有很多 reactions: 4 | 5 | ```js 6 | const Post = sequelize.define('post', { 7 | content: DataTypes.STRING 8 | }, { timestamps: false }); 9 | 10 | const Reaction = sequelize.define('reaction', { 11 | type: DataTypes.STRING 12 | }, { timestamps: false }); 13 | 14 | Post.hasMany(Reaction); 15 | Reaction.belongsTo(Post); 16 | ``` 17 | 18 | *注意: 我们已禁用时间戳,只是为了缩短下一个示例的查询时间.* 19 | 20 | 让我们用一些数据填充表格: 21 | 22 | ```js 23 | async function makePostWithReactions(content, reactionTypes) { 24 | const post = await Post.create({ content }); 25 | await Reaction.bulkCreate( 26 | reactionTypes.map(type => ({ type, postId: post.id })) 27 | ); 28 | return post; 29 | } 30 | 31 | await makePostWithReactions('Hello World', [ 32 | 'Like', 'Angry', 'Laugh', 'Like', 'Like', 'Angry', 'Sad', 'Like' 33 | ]); 34 | await makePostWithReactions('My Second Post', [ 35 | 'Laugh', 'Laugh', 'Like', 'Laugh' 36 | ]); 37 | ``` 38 | 39 | 现在,我们已经准备好子查询功能的示例. 40 | 41 | 假设我们要通过 SQL 为每个帖子计算一个 `laughReactionsCount `. 我们可以通过子查询来实现,例如: 42 | 43 | ```sql 44 | SELECT 45 | *, 46 | ( 47 | SELECT COUNT(*) 48 | FROM reactions AS reaction 49 | WHERE 50 | reaction.postId = post.id 51 | AND 52 | reaction.type = "Laugh" 53 | ) AS laughReactionsCount 54 | FROM posts AS post 55 | ``` 56 | 57 | 如果我们通过 Sequelize 运行上面的原始 SQL 查询,我们将得到: 58 | 59 | ```json 60 | [ 61 | { 62 | "id": 1, 63 | "content": "Hello World", 64 | "laughReactionsCount": 1 65 | }, 66 | { 67 | "id": 2, 68 | "content": "My Second Post", 69 | "laughReactionsCount": 3 70 | } 71 | ] 72 | ``` 73 | 74 | 那么,如何在 Sequelize 的帮助下实现这一目标,而不必手工编写整个原始查询呢? 75 | 76 | 答案是: 通过将 finder 方法(例如,`findAll `)的 `attributes ` 参数与 `sequelize.literal` 实用程序功能结合使用,可以直接在查询中插入任意内容,而不会自动转义. 77 | 78 | 这意味着 Sequelize 将帮助你进行较大的主要查询,但是你仍然必须自己编写该子查询: 79 | 80 | ```js 81 | Post.findAll({ 82 | attributes: { 83 | include: [ 84 | [ 85 | // 注意下面的调用中的括号! 86 | sequelize.literal(`( 87 | SELECT COUNT(*) 88 | FROM reactions AS reaction 89 | WHERE 90 | reaction.postId = post.id 91 | AND 92 | reaction.type = "Laugh" 93 | )`), 94 | 'laughReactionsCount' 95 | ] 96 | ] 97 | } 98 | }); 99 | ``` 100 | 101 | *重要提示:由于 `sequelize.literal` 会插入任意内容而不进行转义,因此,它可能是(主要)安全漏洞的来源,因此值得特别注意. 它不应该在用户生成的内容上使用.*但是在这里,我们使用自己编写的带有固定字符串的 `sequelize.literal`.因为我们知道我们在做什么. 102 | 103 | 上面给出了以下输出: 104 | 105 | ```json 106 | [ 107 | { 108 | "id": 1, 109 | "content": "Hello World", 110 | "laughReactionsCount": 1 111 | }, 112 | { 113 | "id": 2, 114 | "content": "My Second Post", 115 | "laughReactionsCount": 3 116 | } 117 | ] 118 | ``` 119 | 120 | 成功! 121 | 122 | ## 使用子查询进行复杂排序 123 | 124 | 这个想法可用于实现复杂的排序,例如根据 post 具有的 laugh 数量来排序帖子: 125 | 126 | ```js 127 | Post.findAll({ 128 | attributes: { 129 | include: [ 130 | [ 131 | sequelize.literal(`( 132 | SELECT COUNT(*) 133 | FROM reactions AS reaction 134 | WHERE 135 | reaction.postId = post.id 136 | AND 137 | reaction.type = "Laugh" 138 | )`), 139 | 'laughReactionsCount' 140 | ] 141 | ] 142 | }, 143 | order: [ 144 | [sequelize.literal('laughReactionsCount'), 'DESC'] 145 | ] 146 | }); 147 | ``` 148 | 149 | 结果: 150 | 151 | ```json 152 | [ 153 | { 154 | "id": 2, 155 | "content": "My Second Post", 156 | "laughReactionsCount": 3 157 | }, 158 | { 159 | "id": 1, 160 | "content": "Hello World", 161 | "laughReactionsCount": 1 162 | } 163 | ] 164 | ``` -------------------------------------------------------------------------------- /other-topics/transactions.md: -------------------------------------------------------------------------------- 1 | # Transactions - 事务 2 | 3 | 默认情况下,Sequelize 不使用[事务](https://en.wikipedia.org/wiki/Database_transaction). 但是,对于 Sequelize 的生产环境使用,你绝对应该将 Sequelize 配置为使用事务. 4 | 5 | Sequelize 支持两种使用事务的方式: 6 | 7 | 1. **非托管事务:** 提交和回滚事务应由用户手动完成(通过调用适当的 Sequelize 方法).请注意, innoDB (MariaDB 和 MySQL) 在出现死锁时仍会自动回滚事务. [在此处阅读更多信息](https://github.com/sequelize/sequelize/pull/12841). 8 | 9 | 2. **托管事务**: 如果引发任何错误,Sequelize 将自动回滚事务,否则将提交事务. 另外,如果启用了CLS(连续本地存储),则事务回调中的所有查询将自动接收事务对象. 10 | 11 | ## 非托管事务 12 | 13 | 让我们从一个例子开始: 14 | 15 | ```js 16 | // 首先,我们开始一个事务并将其保存到变量中 17 | const t = await sequelize.transaction(); 18 | 19 | try { 20 | 21 | // 然后,我们进行一些调用以将此事务作为参数传递: 22 | 23 | const user = await User.create({ 24 | firstName: 'Bart', 25 | lastName: 'Simpson' 26 | }, { transaction: t }); 27 | 28 | await user.addSibling({ 29 | firstName: 'Lisa', 30 | lastName: 'Simpson' 31 | }, { transaction: t }); 32 | 33 | // 如果执行到此行,且没有引发任何错误. 34 | // 我们提交事务. 35 | await t.commit(); 36 | 37 | } catch (error) { 38 | 39 | // 如果执行到达此行,则抛出错误. 40 | // 我们回滚事务. 41 | await t.rollback(); 42 | 43 | } 44 | ``` 45 | 46 | 如上所示,*非托管事务* 方法要求你在必要时手动提交和回滚事务. 47 | 48 | ## 托管事务 49 | 50 | 托管事务会自动处理提交或回滚事务. 通过将回调传递给 `sequelize.transaction` 来启动托管事务. 这个回调可以是 `async`(通常是)的. 51 | 52 | 在这种情况下,将发生以下情况: 53 | 54 | * Sequelize 将自动开始事务并获得事务对象 `t` 55 | * 然后,Sequelize 将执行你提供的回调,并在其中传递 `t` 56 | * 如果你的回调抛出错误,Sequelize 将自动回滚事务 57 | * 如果你的回调成功,Sequelize 将自动提交事务 58 | * 只有这样,`sequelize.transaction` 调用才会解决: 59 | * 解决你的回调的决议 60 | * 或者,如果你的回调引发错误,则拒绝并抛出错误 61 | 62 | 示例代码: 63 | 64 | ```js 65 | try { 66 | 67 | const result = await sequelize.transaction(async (t) => { 68 | 69 | const user = await User.create({ 70 | firstName: 'Abraham', 71 | lastName: 'Lincoln' 72 | }, { transaction: t }); 73 | 74 | await user.setShooter({ 75 | firstName: 'John', 76 | lastName: 'Boothe' 77 | }, { transaction: t }); 78 | 79 | return user; 80 | 81 | }); 82 | 83 | // 如果执行到此行,则表示事务已成功提交,`result`是事务返回的结果 84 | // `result` 就是从事务回调中返回的结果(在这种情况下为 `user`) 85 | 86 | } catch (error) { 87 | 88 | // 如果执行到此,则发生错误. 89 | // 该事务已由 Sequelize 自动回滚! 90 | 91 | } 92 | ``` 93 | 94 | 注意,`t.commit()` 和 `t.rollback()` 没有被直接调用. 95 | 96 | ### 抛出错误以回滚 97 | 98 | 使用托管事务时,你 *不应* 手动提交或回滚事务. 如果所有查询都成功(就不引发任何错误而言),但是你仍然想回滚事务,那么你应该自己引发一个错误: 99 | 100 | ```js 101 | await sequelize.transaction(async t => { 102 | const user = await User.create({ 103 | firstName: 'Abraham', 104 | lastName: 'Lincoln' 105 | }, { transaction: t }); 106 | 107 | // 查询成功,但我们仍要回滚! 108 | // 我们手动引发错误,以便 Sequelize 自动处理所有内容. 109 | throw new Error(); 110 | }); 111 | ``` 112 | 113 | ### 自动将事务传递给所有查询 114 | 115 | 在上面的示例中,仍然通过传递 `{ transaction: t }` 作为第二个参数来手动传递事务. 要将事务自动传递给所有查询,你必须安装 [cls-hooked](https://github.com/Jeff-Lewis/cls-hooked) (CLS) 模块,并在自己的代码中实例化命名空间: 116 | 117 | ```js 118 | const cls = require('cls-hooked'); 119 | const namespace = cls.createNamespace('my-very-own-namespace'); 120 | ``` 121 | 122 | 要启用 CLS,你必须通过使用 sequelize 构造函数的静态方法来告诉 sequelize 使用哪个命名空间: 123 | 124 | ```js 125 | const Sequelize = require('@sequelize/core'); 126 | Sequelize.useCLS(namespace); 127 | 128 | new Sequelize(....); 129 | ``` 130 | 131 | 注意,`useCLS()` 方法在 *构建器* 上,而不在 sequelize 实例上. 这意味着所有实例将共享相同的命名空间,并且 CLS 是全有或全无 - 你不能仅对某些实例启用它. 132 | 133 | CLS 的工作方式类似于用于回调的线程本地存储. 实际上,这意味着不同的回调链可以使用 CLS 命名空间访问局部变量. 启用 CLS 时,sequelize 将在创建新事务时在命名空间上设置 `transaction` 属性. 由于在回调链中设置的变量是该链的私有变量,因此可以同时存在多个并发事务: 134 | 135 | ```js 136 | sequelize.transaction((t1) => { 137 | namespace.get('transaction') === t1; // true 138 | }); 139 | 140 | sequelize.transaction((t2) => { 141 | namespace.get('transaction') === t2; // true 142 | }); 143 | ``` 144 | 145 | 在大多数情况下,你不需要直接访问 `namespace.get('transaction')`,因为所有查询都会自动在命名空间上查找事务: 146 | 147 | ```js 148 | sequelize.transaction((t1) => { 149 | // 启用 CLS 后,将在事务内部创建用户 150 | return User.create({ name: 'Alice' }); 151 | }); 152 | ``` 153 | 154 | ## 并发/部分事务 155 | 156 | 你可以在一系列查询中进行并发事务,也可以将某些事务排除在任何事务之外. 使用 `transaction` 参数来控制查询属于哪个事务: 157 | 158 | **注意:** *SQLite 不支持同时多个事务.* 159 | 160 | ### 启用 CLS 161 | 162 | ```js 163 | sequelize.transaction((t1) => { 164 | return sequelize.transaction((t2) => { 165 | // 启用 CLS 后,此处的查询默认情况下将使用 t2. 166 | // 传递 `transaction` 参数以定义/更改它们所属的事务. 167 | return Promise.all([ 168 | User.create({ name: 'Bob' }, { transaction: null }), 169 | User.create({ name: 'Mallory' }, { transaction: t1 }), 170 | User.create({ name: 'John' }) // 这将默认为 t2 171 | ]); 172 | }); 173 | }); 174 | ``` 175 | 176 | ## 传递参数 177 | 178 | `sequelize.transaction` 方法接受参数. 179 | 180 | 对于非托管事务,只需使用 `sequelize.transaction(options)`. 181 | 182 | 对于托管交易,请使用 `sequelize.transaction(options, callback)`. 183 | 184 | ## 隔离级别 185 | 186 | 启动事务时可能使用的隔离级别: 187 | 188 | ```js 189 | const { Transaction } = require('@sequelize/core'); 190 | 191 | // 以下是有效的隔离级别: 192 | Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED // "READ UNCOMMITTED" 193 | Transaction.ISOLATION_LEVELS.READ_COMMITTED // "READ COMMITTED" 194 | Transaction.ISOLATION_LEVELS.REPEATABLE_READ // "REPEATABLE READ" 195 | Transaction.ISOLATION_LEVELS.SERIALIZABLE // "SERIALIZABLE" 196 | ``` 197 | 198 | 默认情况下,sequelize 使用数据库的隔离级别. 如果要使用其他隔离级别,请传入所需的级别作为第一个参数: 199 | 200 | ```js 201 | const { Transaction } = require('@sequelize/core'); 202 | 203 | await sequelize.transaction({ 204 | isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE 205 | }, async (t) => { 206 | // 你的代码 207 | }); 208 | ``` 209 | 210 | 你还可以使用 Sequelize 构造函数中的一个参数来全局覆盖 `isolationLevel` 设置: 211 | 212 | ```js 213 | const { Sequelize, Transaction } = require('@sequelize/core'); 214 | 215 | const sequelize = new Sequelize('sqlite::memory:', { 216 | isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE 217 | }); 218 | ``` 219 | 220 | **MSSQL 注意:** _因为指定的 `isolationLevel` 被直接传递给 `tedious `,所以没有记录 `SET ISOLATION LEVEL` 查询._ 221 | 222 | ## 与其他 sequelize 方法一起使用 223 | 224 | `transaction` 参数与大多数其他参数一起使用,通常是方法的第一个参数. 225 | 226 | 对于带有值的方法,例如 `.create`,`.update()` 等.`transaction` 应该传递给第二个参数. 227 | 228 | 如果不确定,请参考你使用的方法的 API 文档以确保正确. 229 | 230 | 示例: 231 | 232 | ```js 233 | await User.create({ name: 'Foo Bar' }, { transaction: t }); 234 | 235 | await User.findAll({ 236 | where: { 237 | name: 'Foo Bar' 238 | }, 239 | transaction: t 240 | }); 241 | ``` 242 | 243 | ## `afterCommit` hook 244 | 245 | 一个 `transaction` 对象允许跟踪它是否以及何时被提交. 246 | 247 | 可以将 `afterCommit` hook 添加到托管和非托管事务对象中: 248 | 249 | ```js 250 | // 托管事务: 251 | await sequelize.transaction(async (t) => { 252 | t.afterCommit(() => { 253 | // 你的代码 254 | }); 255 | }); 256 | 257 | // 非托管事务: 258 | const t = await sequelize.transaction(); 259 | t.afterCommit(() => { 260 | // 你的代码 261 | }); 262 | await t.commit(); 263 | ``` 264 | 265 | 传递给 `afterCommit` 的回调可以是 `async`. 在这种情况下: 266 | 267 | * 对于托管交易:`sequelize.transaction` 调用将在完成之前等待它; 268 | * 对于非托管交易:`t.commit` 调用将在完成之前等待它. 269 | 270 | 注意: 271 | 272 | * 如果事务回滚,则不会引发 `afterCommit` hook; 273 | * `afterCommit` hook 不修改事务的返回值(与大多数 hook 不同) 274 | 275 | 你可以将 `afterCommit` hook 与模型 hook 结合使用,以了解何时保存实例并在事务外部可用 276 | 277 | ```js 278 | User.hooks.addListener('afterSave', (instance, options) => { 279 | if (options.transaction) { 280 | // 在事务中保存完成, 281 | // 等待事务提交以通知侦听器实例已保存 282 | options.transaction.afterCommit(() => /* 通知 */) 283 | return; 284 | } 285 | // 在事务外保存完成,使调用者可以安全地获取更新的模型 286 | // 通知 287 | }); 288 | ``` 289 | 290 | ## 锁 291 | 292 | 可以使用锁执行 `transaction` 中的查询: 293 | 294 | ```js 295 | return User.findAll({ 296 | limit: 1, 297 | lock: true, 298 | transaction: t1 299 | }); 300 | ``` 301 | 302 | 事务中的查询可以跳过锁定的行: 303 | 304 | ```js 305 | return User.findAll({ 306 | limit: 1, 307 | lock: true, 308 | skipLocked: true, 309 | transaction: t2 310 | }); 311 | ``` 312 | -------------------------------------------------------------------------------- /other-topics/typescript.md: -------------------------------------------------------------------------------- 1 | # TypeScript 2 | 3 | Sequelize 提供了自己的 TypeScript 定义. 4 | 5 | 请注意, 仅支持 **TypeScript >= 4.5**. 6 | 我们对 TypeScript 的支持不遵循 SemVer. 我们将支持 TypeScript 版本至少一年, 之后它们可能会在 SemVer MINOR 版本中被删除. 7 | 8 | 由于 Sequelize 严重依赖于运行时属性分配, 因此 TypeScript 无法立即开箱即用. 9 | 10 | 为了使模型可用, 需要大量的手动类型声明. 11 | 12 | ## 安装 13 | 14 | 为了避免与不同的 Node 版本发生冲突, 不包括 Node 的类型. 你必须手动安装 [`@types/node`](https://www.npmjs.com/package/@types/node). 15 | 16 | ## 使用 17 | 18 | **重要**: 你必须在类属性类型上使用 `declare` 以确保 TypeScript 不会触发这些类属性. 19 | 参阅 [公共类字段的注意事项](../core-concepts/model-basics.md) 20 | 21 | Sequelize Models 接受两种通用类型来定义模型的属性和创建属性是什么样的: 22 | 23 | ```typescript 24 | import { Model, Optional } from '@sequelize/core'; 25 | 26 | // 我们不建议这样做. 继续阅读声明模型类型的新方法. 27 | 28 | type UserAttributes = { 29 | id: number, 30 | name: string, 31 | // 其他属性... 32 | }; 33 | 34 | // 我们告诉模型在创建模型实例时 "id" 是可选的(例如使用 Model.create()) 35 | type UserCreationAttributes = Optional; 36 | 37 | class User extends Model { 38 | declare id: number; 39 | declare string: number; 40 | // 其他属性... 41 | } 42 | ``` 43 | 44 | 这个解决方案很冗长. Sequelize >=6.14.0 提供了新的实用程序类型, 将大大减少数量 45 | 必需的样板: [`InferAttributes`](/api/v7/index.html#InferAttributes), 和 [`InferCreationAttributes`](/api/v7/index.html#InferCreationAttributes). 他们将提取属性类型 46 | 直接来自模型: 47 | 48 | ```typescript 49 | import { Model, InferAttributes, InferCreationAttributes, CreationOptional } from '@sequelize/core'; 50 | 51 | // InferAttributes 和 InferCreationAttributes 的顺序很重要. 52 | class User extends Model, InferCreationAttributes> { 53 | // 'CreationOptional' 是一种特殊类型, 54 | // 在创建模型实例时将字段标记为可选(例如使用 Model.create()) 55 | declare id: CreationOptional; 56 | declare string: number; 57 | // 其他属性... 58 | } 59 | ``` 60 | 61 | 关于 [`InferAttributes`](/api/v7/index.html#InferAttributes) & [`InferCreationAttributes`](/api/v7/index.html#InferCreationAttributes) 工作需要了解的重要事项: 它们将选择类的所有声明属性, 除了: 62 | 63 | - 静态字段和方法. 64 | - 方法(任何类型为函数的东西). 65 | - 类型使用铭记类型 [`NonAttribute`](/api/v7/index.html#NonAttribute) 的那些. 66 | - 像这样使用 InferAttributes 排除的那些: `InferAttributes`. 67 | - 由 Model 超类声明的那些(但不是中间类!). 68 | 如果你的属性之一与 [`Model`](/api/v7/classes/Model.html) 的属性之一同名, 请更改其名称. 69 | 无论如何, 这样做可能会导致问题. 70 | - Getter & setter 不会被自动排除. 将它们的 return / parameter 类型设置为 [`NonAttribute`](/api/v7/index.html#NonAttribute), 71 | 或将它们添加到 `omit` 以排除它们. 72 | 73 | [`InferCreationAttributes`](/api/v7/index.html#InferCreationAttributes) 与 [`InferAttributes`](/api/v7/index.html#InferAttributes) 的工作方式相同 74 | 但有一个例外:使用 [`CreationOptional`](/api/v7/index.html#CreationOptional) 类型键入的属性将被标记为可选. 75 | 请注意, 接受 `null` 或 `undefined` 的属性不需要使用 [`CreationOptional`](/api/v7/index.html#CreationOptional) 76 | 77 | ```typescript 78 | class User extends Model, InferCreationAttributes> { 79 | declare firstName: string; 80 | 81 | // 不需要在 firstName 上使用 CreationOptional 82 | // 因为可为 null 的属性在 User.create() 中始终是可选的 83 | declare lastName: string | null; 84 | } 85 | 86 | // ... 87 | 88 | await User.create({ 89 | firstName: 'Zoé', 90 | // 省略姓氏, 但这仍然有效! 91 | }); 92 | ``` 93 | 94 | 你只需要在类实例字段或 getter 上使用 `CreationOptional` 和 `NonAttribute`. 95 | 96 | 对属性进行严格类型检查的最小 TypeScript 项目示例: 97 | 98 | ``` 99 | .sequelize/v7/packages/core/test/types/typescript-docs/model-init.ts 100 | ``` 101 | 102 | ### `Model.init` 的案例 103 | 104 | `Model.init` 需要为 typings 中声明的每个属性进行属性配置. 105 | 106 | 有些属性实际上不需要传递给 `Model.init`, 下面就是让这个静态方法知道它们的方式: 107 | 108 | - 用于定义关联的方法(`Model.belongsTo`, `Model.hasMany`, 等..)已经处理了必要外键属性的配置. 没有必要配置这些外键使用 `Model.init`. 使用 `ForeignKey<>` 标记类型让 `Model.init` 意识到不需要配置外键这一事实: 109 | 110 | ```typescript 111 | import { Model, InferAttributes, InferCreationAttributes, DataTypes, ForeignKey } from '@sequelize/core'; 112 | 113 | class Project extends Model, InferCreationAttributes> { 114 | id: number; 115 | userId: ForeignKey; 116 | } 117 | 118 | // 这配置了 `userId` 属性. 119 | Project.belongsTo(User); 120 | 121 | // 因此, 这里不需要指定 `userId`. 122 | Project.init({ 123 | id: { 124 | type: DataTypes.INTEGER, 125 | primaryKey: true, 126 | autoIncrement: true, 127 | }, 128 | }, { sequelize }); 129 | ``` 130 | 131 | - 由 Sequelize 管理的时间戳属性(默认情况下, `createdAt`、`updatedAt` 和 `deletedAt`)不需要使用 `Model.init` 配置, 不幸的是, `Model.init` 无法知道这一点. 我们建议你使用最低限度的必要配置来消除此错误: 132 | 133 | ```typescript 134 | import { Model, InferAttributes, InferCreationAttributes, DataTypes } from 'sequelize'; 135 | 136 | class User extends Model, InferCreationAttributes> { 137 | id: number; 138 | createdAt: Date; 139 | updatedAt: Date; 140 | } 141 | 142 | User.init({ 143 | id: { 144 | type: DataTypes.INTEGER, 145 | primaryKey: true, 146 | autoIncrement: true, 147 | }, 148 | // 从技术上讲, `createdAt` 和 `updatedAt` 是由 Sequelize 添加的, 149 | // 不需要在 Model.init 中配置, 但是 Model.init 的类型不知道这一点. 添加以下内容以消除打字错误: 150 | createdAt: DataTypes.DATE, 151 | updatedAt: DataTypes.DATE, 152 | }, { sequelize }); 153 | ``` 154 | 155 | ### 不使用严格类型的属性 156 | 157 | Sequelize v5 的类型允许你在不指定属性类型的情况下定义模型. 158 | 这对于向后兼容仍然是可能的, 并且在你觉得对属性进行严格类型化是不值得的情况下. 159 | 160 | ``` 161 | .sequelize/v7/packages/core/test/types/typescript-docs/model-init-no-attributes.ts 162 | ``` 163 | 164 | ## 使用 `Sequelize#define` 165 | 166 | 在 v5 之前的 Sequelize 版本中, 定义模型的默认方式涉及使用 [`Sequelize#define`](/api/v7/classes/Sequelize.html#define). 仍然可以用它来定义模型, 你还可以使用接口向这些模型添加类型. 167 | 168 | ``` 169 | .sequelize/v7/packages/core/test/types/typescript-docs/define.ts 170 | ``` 171 | 172 | ## 实用程序类型 173 | 174 | ### 请求模型类 175 | 176 | [`ModelStatic`](/api/v7/index.html#ModelStatic) 旨在用于键入模型 *class*. 177 | 178 | 以下是请求模型类并返回该类中定义的主键列表的实用方法示例: 179 | 180 | ```typescript 181 | import { ModelStatic, ModelAttributeColumnOptions, Model, InferAttributes, InferCreationAttributes, CreationOptional } from '@sequelize/core'; 182 | 183 | /** 184 | * 返回属于模型主键一部分的属性列表. 185 | */ 186 | export function getPrimaryKeyAttributes(model: ModelStatic): ModelAttributeColumnOptions[] { 187 | const attributes: ModelAttributeColumnOptions[] = []; 188 | 189 | for (const attribute of Object.values(model.rawAttributes)) { 190 | if (attribute.primaryKey) { 191 | attributes.push(attribute); 192 | } 193 | } 194 | 195 | return attributes; 196 | } 197 | 198 | class User extends Model, InferCreationAttributes> { 199 | id: CreationOptional; 200 | } 201 | 202 | User.init({ 203 | id: { 204 | type: DataTypes.INTEGER.UNSIGNED, 205 | autoIncrement: true, 206 | primaryKey: true 207 | }, 208 | }, { sequelize }); 209 | 210 | const primaryAttributes = getPrimaryKeyAttributes(User); 211 | ``` 212 | 213 | ### 获取模型的属性 214 | 215 | 如果你需要访问给定模型的属性列表, 你需要使用 [`Attributes`](/api/v7/index.html#Attributes) 和 [`CreationAttributes`](/api/v7/index.html#CreationAttributes). 216 | 217 | 它们将返回作为参数传递的模型的属性(和创建属性). 218 | 219 | 不要将它们与 [`InferAttributes`](/api/v7/index.html#InferAttributes) 和 [`InferCreationAttributes`](/api/v7/index.html#InferCreationAttributes) 混淆. 这两种实用程序类型应该只被使用在模型的定义中自动从模型的公共类字段创建属性列表. 它们仅适用于基于类的模型定义(使用 [`Model.init`](/api/v7/classes/Model.html#init) 时). 220 | 221 | [`Attributes`](/api/v7/index.html#Attributes) 和 [`CreationAttributes`](/api/v7/index.html#CreationAttributes) 将返回任何模型的属性列表, 无论它们是如何创建的(无论是 [`Model.init`](/api/v7/classes/Model.html#init) 还是 [`Sequelize#define`](/api/v7/classes/Sequelize.html#define)). 222 | 223 | 这是一个请求模型类和属性名称的实用函数示例; 并返回相应的属性元数据. 224 | 225 | ```typescript 226 | import { 227 | ModelStatic, 228 | ModelAttributeColumnOptions, 229 | Model, 230 | InferAttributes, 231 | InferCreationAttributes, 232 | CreationOptional, 233 | Attributes 234 | } from '@sequelize/core'; 235 | 236 | export function getAttributeMetadata(model: ModelStatic, attributeName: keyof Attributes): ModelAttributeColumnOptions { 237 | const attribute = model.rawAttributes[attributeName]; 238 | if (attribute == null) { 239 | throw new Error(`Attribute ${attributeName} does not exist on model ${model.name}`); 240 | } 241 | 242 | return attribute; 243 | } 244 | 245 | class User extends Model, InferCreationAttributes> { 246 | id: CreationOptional; 247 | } 248 | 249 | User.init({ 250 | id: { 251 | type: DataTypes.INTEGER.UNSIGNED, 252 | autoIncrement: true, 253 | primaryKey: true 254 | }, 255 | }, { sequelize }); 256 | 257 | const idAttributeMeta = getAttributeMetadata(User, 'id'); // 可用! 258 | 259 | // @ts-expect-error 260 | const nameAttributeMeta = getAttributeMetadata(User, 'name'); // 失败, 因为 'name' 不是 User 的属性 261 | ``` 262 | -------------------------------------------------------------------------------- /other-topics/upgrade-to-v6.md: -------------------------------------------------------------------------------- 1 | # Upgrade to v6 - 升级到 V6 2 | 3 | Sequelize v6 是 v5 之后的下一个主要版本. 以下列出了一些重大更改以帮助你进行升级. 4 | 5 | ## 重大变化 6 | 7 | ### 支持 Node 10 以及更高 8 | 9 | Sequelize v6 将支持 Node 10 及更高版本 [#10821](https://github.com/sequelize/sequelize/issues/10821). 10 | 11 | ### CLS 12 | 13 | 你现在应该使用 [cls-hooked](https://github.com/Jeff-Lewis/cls-hooked) 软件包来支持 CLS. 14 | 15 | ```js 16 | const cls = require('cls-hooked'); 17 | const namespace = cls.createNamespace('....'); 18 | const Sequelize = require('sequelize'); 19 | 20 | Sequelize.useCLS(namespace); 21 | ``` 22 | 23 | ### 数据库引擎支持 24 | 25 | 我们已经更新了最低支持的数据库引擎版本. 使用较旧的数据库引擎将显示 `SEQUELIZE0006` 弃用警告. 26 | 27 | ### Sequelize 28 | 29 | - Bluebird 已被移除. 内部所有方法现在都使用 async/await. 公共 API 现在返回原生 promises. 感谢 [Andy Edwards](https://github.com/jedwards1211) 的重构工作. 30 | - `Sequelize.Promise` 不再被提供. 31 | - `sequelize.import` 方法已被删除. CLI 用户应更新到 `sequelize-cli@6`. 32 | - QueryInterface 和 QueryGenerator 的所有实例都已重命名为它们的小写驼峰别名形式, 例如: queryInterface 和 queryGenerator 用作 '模型' 和 '方言' 上的属性名称时, 类名称保持不变. 33 | 34 | ### 模型 35 | 36 | #### `options.returning` 37 | 38 | 参数 `returning: true` 将不再返回模型中未定义的属性. 之前旧的行为可以通过使用 `returning: ['*']` 来实现. 39 | 40 | #### `Model.changed()` 41 | 42 | 现在,此方法使用 [`_.isEqual`](https://lodash.com/docs/4.17.15#isEqual) 进行相等性测试,并且现在可以识别 JSON 对象. 修改 JSON 对象的嵌套值不会将其标记为已更改(因为它仍然是同一对象). 43 | 44 | ```js 45 | const instance = await MyModel.findOne(); 46 | 47 | instance.myJsonField.someProperty = 12345; // 更改为 12345 48 | console.log(instance.changed()); // false 49 | 50 | await instance.save(); // 这不会保存任何东西 51 | 52 | instance.changed("myJsonField", true); 53 | console.log(instance.changed()); // ['myJsonField'] 54 | 55 | await instance.save(); // 将会保存 56 | ``` 57 | 58 | #### `Model.bulkCreate()` 59 | 60 | 这个方法现在抛出 `Sequelize.AggregateError` 而不是 `Bluebird.AggregateError`. 现在, 所有错误都显示为 `errors` 标识. 61 | 62 | #### `Model.upsert()` 63 | 64 | 现在所有语言都支持原生 upsert. 65 | 66 | ```js 67 | const [instance, created] = await MyModel.upsert({}); 68 | ``` 69 | 70 | 此方法的签名已更改为 `Promise`. 第一个索引包含被 upsert 的 `instance`, 第二个索引包含一个布尔值(或`null`), 指示记录是创建还是更新. 对于SQLite/Postgres, `created` 值将始终为 `null`. 71 | 72 | - MySQL - 使用 ON DUPLICATE KEY UPDATE 实现 73 | - PostgreSQL - 使用 ON CONFLICT DO UPDATE 实现 74 | - SQLite - 使用 ON CONFLICT DO UPDATE 实现 75 | - MSSQL - 使用 MERGE 语句实现 76 | 77 | _Postgres 用户需要注意:_ 如果 upsert 有效负载包含 PK 字段, 则 PK 将用作冲突目标. 否则, 将选择第一个唯一约束作为冲突键. 78 | 79 | ### 查询接口 80 | 81 | #### `addConstraint` 82 | 83 | 现在, 此方法仅使用2个参数, 即 `tableName` 和 `options`. 以前, 第二个参数是要应用约束的列名列表, 此列表现在必须作为 `options.fields` 属性传递. -------------------------------------------------------------------------------- /other-topics/upgrade-to-v7.md: -------------------------------------------------------------------------------- 1 | # Upgrade to v7 - 升级到 V7 2 | 3 | Sequelize v7 是 v6 之后的下一个主要版本. 以下列出了一些重大更改以帮助你进行升级. 4 | 5 | ## 重大变化 6 | 7 | ### 支持 Node 12 以及更高 8 | 9 | Sequelize v7 将只支持那些与 ES 模块规范兼容的 Node.js 版本, 10 | 版本 12 及更高版本[#5](https://github.com/sequelize/meetings/issues/5). 11 | 12 | ### TypeScript 转换 13 | 14 | v7 的主要基础代码更改之一是迁移到 TypeScript. 15 | 结果就是, 以前在 JavaScript 代码库之上的尽力而为猜测的手动类型, 16 | 已被删除, 所有类型现在都直接从实际的 TypeScript 代码中检索. 17 | 你可能会发现许多微小的差异, 但它们应该很容易修复. 18 | 19 | ### 对 `ConnectionManager` 的更改 20 | 21 | *仅当你直接使用 `ConnectionManager` 时, 这才会对你产生影响.* 22 | 23 | `ConnectionManager#getConnection`: `type` 选项现在接受 `'read' | 'write'` 而不是 `'SELECT' | any`. 24 | 它已经在 v6 中记录, 但实现与文档不匹配. 25 | 26 | ```typescript 27 | // 而不是这样做: 28 | sequelize.connectionManager.getConnection({ type: 'SELECT' }); 29 | 30 | // 这样做: 31 | sequelize.connectionManager.getConnection({ type: 'read' }); 32 | ``` 33 | 34 | ### Microsoft SQL Server 支持 35 | 36 | Sequelize v7 完全支持 MS SQL Server 2017(版本 14), Sequelize v6 从 2012(版本 13)开始 37 | , 符合微软自己的[主流支持]( 38 | https://docs.microsoft.com/en-us/sql/sql-server/end-of-support/sql-server-end-of-life-overview?view=sql-server-ver15#lifecycle-dates). 39 | 40 | ### 不会在内部调用重写的模型方法 41 | 42 | `Model.findOne` 和 `Model.findAll` 分别被 `Model.findByPk` 和 `Model.findOne` 使用. 43 | 这被认为是一个细节实现, 因此, 从 Sequelize v7 开始, 44 | `Model.findByPk` 或 `Model.findOne` 不会覆盖任何一个在这些方法中的内部调用. 45 | 46 | 换句话说, 这样做不会破坏: 47 | 48 | ```typescript 49 | class User extends Model { 50 | static findOne() { 51 | throw new Error('Do not call findOne'); 52 | } 53 | } 54 | 55 | // 这会在 v6 中抛出 "Do not call findOne" 56 | // 但它在 v7 中有效 57 | User.findByPk(1); 58 | ``` 59 | --------------------------------------------------------------------------------