egg中数据库的应用
Opened this issue · 0 comments
什么是 ORM ?
对象关系映射(英语:Object Relational Mapping,简称 ORM,或 O/RM,或 O/R mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。
类似于 J2EE 中的 DAO 设计模式,将程序中的数据对象自动地转化为关系型数据库中对应的表和列,数据对象间的引用也可以通过这个工具转化为表。这样就可以很好的解决我遇到的那个问题,对于表结构修改和数据对象操作是两个独立的部分,从而使得代码更好维护。其实是否选择 ORM 框架,和以前前端是选择模板引擎还是手动拼字符串一样,ORM 框架避免了在开发的时候手动拼接 SQL 语句,可以防止 SQL 注入,另外也将数据库和数据 CRUD 解耦,更换数据库也相对更容易。
sequelize / egg-sequelize
sequelize 是 Node.js 社区比较流行的一个 ORM 框架
定义 Model :
const User = sequelize.define("user", {
username: {
type: Sequelize.STRING
},
password: {
type: Sequelize.STRING
}
});
Sequelize 默认会添加 createdAt 和 updatedAt,这样可以很方便的知道数据创建和更新的时间。如果不想使用可以通过设置 attributes 的 timestamps: false;
Sequelize 支持丰富的数据类型,例如:STRING、CHAR、TEXT、INTEGER、FLOAT、DOUBLE、BOOLEAN、DATE、UUID、JSON 等多种不同的数据类型
Model 对象
find:搜索数据库中的一个特定元素,可以通过 findById 或 findOne;
findOrCreate:搜索特定元素或在不可用时创建它;
findAndCountAll:搜索数据库中的多个元素,返回数据和总数;
findAll:在数据库中搜索多个元素;
复杂的过滤/ OR / NOT 查询;
使用 limit(限制),offset(偏移量),order(顺序)和 group(组)操作数据集;
count:计算数据库中元素的出现次数;
max:获取特定表格中特定属性的最大值;
min:获取特定表格中特定属性的最小值;
sum:特定属性的值求和;
create:创建数据库 Model 实例;
update:更新数据库 Model 实例;
destroy:销毁数据库 Model 实例。
具体用法: Sequelize-Model
Migrations
在数据库操作设计中,我们一般是通过脚本提前生成表结构,如果手动写创建表的 SQL,每次修改表结构其实是一件麻烦事。Sequelize 提供了Migrations 帮助创建或迁移数据库,egg-sequelize 里面也提供了方便的方法。
egg中mysql使用
由于egg-sequelize是支持mysql的,所以可以结合一起使用,即可以使用上mysql数据库,又可以通过egg-sequelize操作数据库,屏蔽了写sql的麻烦(对于大多数前端来说,写sql很麻烦、容易出错、也不安全).
- 1.安装
npm i -S egg-sequelize
npm i -S mysql2
- 2.配置
// /config/config.local.js
// ORM base configuration
exports.sequelize = {
dialect: 'mysql', // 链接的数据库类型,也可以是mongodb等其他数据库
host: '127.0.0.1',
port: 3306,
username: 'root',
password: 'root',
database: 'test_db',
define: { // model的全局配置
// timestamps: true, // 添加create,update,delete时间戳
// paranoid: true, // 添加软删除
// freezeTableName: true, // 防止修改表名为复数
underscored: false, // 防止驼峰式字段被默认转为下划线
},
timezone: '+8:00', // 由于orm用的UTC时间,这里必须加上东八区,否则取出来的时间相差8小时
dialectOptions: { // 让读取date类型数据时返回字符串而不是UTC时间
dateStrings: true,
typeCast(field, next) {
if (field.type === 'DATETIME') {
return field.string();
}
return next();
},
},
};
- 3.定义model(即表结构)
// 如:/app/model/user.js
module.exports = app => {
const { STRING, INTEGER } = app.Sequelize;
const User = app.model.define('user', {
id: { type: INTEGER, allowNull: false, autoIncrement: true, primaryKey: true },
username: { type: STRING(30), allowNull: false, unique: true, field: 'user_name' },
email: { type: STRING(30), allowNull: false },
}, {
createdAt: 'created_at',
updatedAt: 'updated_at',
});
return User;
};
注意:定义的名称为user,但是必须先在数据库中创建表users,即表默认必须是负数名,结构为单数名。
- 4.Service中使用
// /app/service/user.js
async getUsers() {
const { ctx } = this;
return ctx.model.User.findAndCountAll();
}
egg中oracle使用
egg-sequelize是不支持oracle,而且市面上也没有支持它的orm模型,所以只能自己手写了。
- 1.安装
npm i -S egg-oracle
npm i -S oracledb
- 2.配置
// /config/config.local.js
config.oracle = {
user: 'xx',
password: 'xx',
connectString: '//xxxx/ORCL', //oracle db
};
- 3.新建oracle目录,并定义基类,相当于之前的model
// /app/oracle/index.js
// https://oracle.github.io/node-oracledb/doc/api.html
const oracledb = require('oracledb');
class Oracle {
constructor(app) {
this.config = app.config.oracle;
}
/**
* @description sql语句执行
* @param sqlFuncName 自定义sql语句函数名
* @param dialectData 数据
*/
async excute(sqlFuncName, dialectData) {
oracledb.autoCommit = true;
const connection = await oracledb.getConnection(this.config);
const sql = this[sqlFuncName](dialectData);
const result = await connection.execute(sql);
connection.close();
const data = [];
const { metaData, rows, rowsAffected } = result;
// 非查询操作
if (!rows) {
return !!rowsAffected;
}
// 查询操作
for (let i = 0; i < rows.length; i++) {
const row = {};
for (let j = 0; j < metaData.length; j++) {
const key = this.camelKey(metaData[j].name);
row[key] = rows[i][j];
}
data.push(row);
}
return data;
}
/**
* @description 工具函数 将key转为驼峰
* @param "str"
*/
camelKey(str) {
const strArr = str.toLowerCase().split('_');
for (let i = 1; i < strArr.length; i++) {
strArr[i] = strArr[i].charAt(0).toUpperCase() + strArr[i].substring(1);
}
return strArr.join('');
}
}
module.exports = Oracle;
- 4.定义oracle的model
这里以user为例,定义它的CRUD模型操作
// /app/oracle/user.js
const Oracle = require('../index.js');
const SQL_MAP = {
name: 'NAME',
age: 'AGE',
male: 'MALE',
};
class userDialect extends Oracle {
create({ id, name, age, male, updateTime }) {
return `INSERT INTO BGT_AUTH ("ID","NAME","AGE","MALE","UPDATE_TIME") VALUES ('${id}','${name}','${age}','${male}',TO_DATE('${createTime}', 'yyyy-mm-dd HH24:mi:ss'))`;
}
// 查询总数
searchAll() {
return 'SELECT e."ID",e."NAME",e."AGE",e."MALE",e."UPDATE_TIME", COUNT(*) OVER () total FROM USER e';
}
search(data) {
const keys = Object.keys(data);
let { pageSize = 20, pageIndex = 1 } = data;
pageSize = parseInt(pageSize);
pageIndex = parseInt(pageIndex) < 1 ? 1 : parseInt(pageIndex);
let sql = 'SELECT emp.* FROM ( SELECT e."ID",e."NAME",e."AGE",e."MALE",e."UPDATE_TIME", ROWNUM rowno FROM USER e';
keys.forEach((key, index) => {
if (!data[key] || key === 'pageSize' || key === 'pageIndex') return;
let signal = '=';
// 模糊查找类name的用户
let value;
if (key === 'name') {
signal = ' LIKE ';
value = `'%${data[key]}%'`;
} else {
value = `'${data[key]}'`;
}
sql += ` ${index === 0 ? 'WHERE' : 'AND'} "${SQL_MAP[key]}"${signal}${value}`;
});
sql += ` ${sql.indexOf('WHERE') > 0 ? 'AND' : 'WHERE'} ROWNUM <= ${pageSize * pageIndex} ) emp WHERE emp.rowno > ${pageSize * (pageIndex - 1)}`;
return sql;
}
del({ id }) {
return `DELETE FROM USER WHERE "ID"='${id}'`;
}
edit({ id, name, age, male, updateTime }) {
return `UPDATE USER SET ${name ? `"NAME"='${name}',` : ''}${age ? `"AGE"='${age}',` : ''}${male ? `"MALE"='${male}',` : ''}${updateTime ? `"UPDATE_TIME"=TO_DATE('${updateTime}', 'yyyy-mm-dd HH24:mi:ss')` : ''} WHERE ID='${id}'`;
}
}
module.exports = userDialect;
注意:
1.查询时,最好使用主表-子表方式去查,分页和查询总数是要分开的。
2.对于时间,需要使用oracle自带函数:TO_DATE('${updateTime}', 'yyyy-mm-dd HH24:mi:ss')
3.sql语句中注意字段是string还是int,为string必须加上单引号,为int不能加,sql是强类型的,类型不对就会报错。
- 5.引入到全局context中
// /app.js
async didLoad() {
const { app } = this;
// 例如:加载自定义的目录
app.config.loaderDirs.forEach(dir => {
app.loader.loadToContext(
path.join(__dirname, `app/${dir}`),
`${dir}`,
{
fieldClass: `${dir}Classes`,
initializer(factory) {
if (typeof factory === 'function') {
return new factory(app);
}
},
}
);
});
}
现在就可以通过 this.ctx.oracle.user访问到user模型了。
-
- Service中使用
// /app/service/user.js
const Service = require('egg').Service;
class UserService extends Service {
// 新建账号
async create(body) {
return await this.ctx.oracle.user.excute('create', body);
}
async search(body) {
const dialect = this.ctx.oracle.user;
return dialect.excute('search', body)
}
async del(id) {
const dialect = this.ctx.oracle.user;
return await dialect.excute('del', { id });
}
async edit(body) {
const dialect = this.ctx.oracle.user;
const { id, name, age, male } = body;
const { format } = this.ctx.helper;
return await dialect.excute('edit', { id, name, age, male, updateTime: format(null, 'yyyy-MM-dd hh:mm:ss') });
}
}
module.exports = UserService;
Oracle深坑
oracle在insert插入时,必须commit一下,否则执行的命令是成功的,但数据库中却没有插进去。
具体解决:const oracledb = require('oracledb'); oracledb.autoCommit = true; // 关键是这句 const connection = await oracledb.getConnection({ user: '', password: '', connectString: '',// connect url }); const result = await connection.execute(sql); connection.close();
分页和总数目查询
SELECT
emp.*
FROM
(
SELECT
temp.*, ROWNUM AS rowIndex
FROM
(
SELECT
E .*, COUNT (*) OVER () AS total
FROM
USERS E
WHERE
(NAME = 'kebin' AND AGE = 20)
OR
(NAME = 'kate' AND AGE = 24)
AND ROWNUM > 0
ORDER BY
CREATE_TIME DESC
) temp
WHERE
ROWNUM > 0
) emp
WHERE
emp.rowIndex > 0
AND emp.rowIndex <= 20
代码演示
// /app/model/user.js
findDetail(data) {
const { pageSize = 20, pageIndex = 1, name, age } = data;
return `SELECT emp.*
FROM (
SELECT temp.*, ROWNUM AS rowIndex
FROM (
SELECT E.*, COUNT(*) OVER () AS total
FROM USERS E
WHERE
${name ? `NAME = '${name}' AND` : ''}
${age ? `AGE LIKE '${age}' AND` : ''}
ROWNUM > 0 ORDER BY CREATE_TIME DESC
) temp
WHERE ROWNUM > 0
) emp
WHERE
emp.rowIndex > ${pageSize * (pageIndex - 1)} AND
emp.rowIndex <= ${pageSize * pageIndex}`;
}
// /app/service/user.js
async search(body) {
const UserModel = this.ctx.model.user;
return new Promise(async (resolve, reject) => {
const result = await UserModel.findDeail(body).catch(e => {
reject(e);
});
resolve(data);
});
}
// /app/controller/user.js
async search() {
const { ctx } = this;
const { body } = ctx.request;
const { pageSize = 20, pageIndex = 1 } = body;
const result = await ctx.service.user.search(body);
// 拿到总数
let total = 0;
if (result[0]) {
total = result[0].total;
}
return ctx.success({
data: {
pageSize,
pageIndex,
total,
list: result,
},
});
}
参考
基于 Egg.js 框架的 Node.js 服务构建之用户管理设计
egg商城--权限管理篇
权限管理设计
rbac之管理员crud