前言
ORM即Object Relational Mapping,中文翻譯過來就是對象關係映射,是一種爲了解決面向對象與關係數據庫存在的互不匹配的現象的技術。簡單的說,ORM是通過使用描述對象和數據庫之間映射的元數據,將程序中的對象自動持久化到關係數據庫中。在nodejs中也有比較好用的ORM框架,比如TypeORM,Sequelize等等,但基本上都是重量級的,如果自己先實現一個簡單的對數據庫的增刪改查,使用它們就感覺有點大材小用了。因此本文用簡短的一百多行代碼實現一個簡單的ORM框架,使用TypeScript以及mysql包。
開始之前
安裝typescript
npm install -g typescript
新建nodejs項目
npm init
安裝mysql包
npm install --save mysql
在項目目錄下,新建tsconfig.json文件,添加文件內容如下
{
"compilerOptions": {
"module": "commonjs",
"target": "es2018",
"sourceMap": true,
"outDir": "./dist",
"pretty": true,
"baseUrl": "./lib",
"moduleResolution": "node"
},
"include": [
"lib/**/*.ts",
"lib/*.ts"
],
"exclude": [
"node_modules"
]
}
在項目目錄新建lib文件夾來存放.ts文件
注:在tsconfig.json目錄下使用tsc命令能將ts文件打包成js文件,然後再使用node命令來運行js文件,或者使用node-ts來運行ts文件,需要安裝node-ts包
npm install -g node-ts
ORM的簡單實現
mysql.ts文件,主要實現連接mysql,以及一下基本的增刪改查函數
import * as MySql from "mysql";
// 使用連接池
const mysqlPool = MySql.createPool({
host: "localhost",
user: "root",
password: "",
port: 3306,
database: "blogapp",
connectionLimit: 10000
});
const timeout = 4000;
export interface ISqlResults {
results: Array<any>;
fields: Object;
}
interface IQueryObject {
[name: string]: string | number
}
// 實現增刪改查基本方法
export const sqlQuery = (sql: MySql.QueryOptions) => new Promise((resolve, reject) =>
mysqlPool.query(sql,
(err, results, fields) => err ? reject(err) : resolve(<ISqlResults>{ results, fields })
)
);
export const and = (dataObject: IQueryObject) => {
let queryString = "";
Object.keys(dataObject).forEach(key => {
queryString += MySql.escape(key) + "=" + dataObject[key] + " and "
});
return queryString.replace(/\sand\s$/, "")
};
export const tableQuery = (table: string, condition: IQueryObject | string) => sqlQuery({
sql: typeof condition === "string" ?
MySql.format("select * from ?? where ??", [table, condition]) :
MySql.format("select * from ?? where ?", [table, condition]),
timeout
});
export const insertTable = (table: string, data: IQueryObject | Array<string | number>) => sqlQuery({
sql: Array.isArray(data) ?
MySql.format("insert into ?? values(??)", [table, (<Array<string | number>>data).join(", ")]) :
MySql.format("insert into ?? set ?", [table, data]),
timeout
});
export const updateTable = (table: string, update: IQueryObject, condition: IQueryObject | string) => sqlQuery({
sql: typeof condition === "string" ?
MySql.format("update ?? set ? where ??", [table, update, condition]) :
MySql.format("update ?? set ? where ?", [table, update, condition]),
timeout
});
export const deleteRow = (table: string, condition: IQueryObject | string) => sqlQuery({
sql: typeof condition === "string" ?
MySql.format("delete from ?? where ??", [table, condition]) :
MySql.format("delete from ?? where ?", [table, condition]),
timeout
});
export default mysqlPool;
ORM.ts,封裝ORM類
import { sqlQuery, insertTable, deleteRow, tableQuery, updateTable, ISqlResults } from "./mysql";
import { format } from "mysql";
export interface IField {
[name: string]: string | number
}
export interface IError {
err: boolean;
message?: string;
}
export interface IORM<TableType, TableTypeRest> {
fieldMap: IField;
map: IField;
table: string;
defaultValue: TableType;
fetchAll (condition: TableTypeRest | string): Promise<Array<TableType>>;
fetch (query: TableTypeRest | string): Promise<TableType>;
insert (data: TableType): Promise<IError>;
update (data: TableTypeRest, condition: TableTypeRest | string): Promise<IError>;
delete (condition: TableTypeRest | string): Promise<IError>;
}
//ORM framework
export default class ORM<Type, TypeRest> implements IORM<Type, TypeRest> {
public fieldMap: IField;
public map: IField;
public table: string;
public defaultValue: Type;
public async delete (condition: TypeRest | string): Promise<IError> {
const dbCondition: IField | string = typeof condition === "string" ? condition :
Object.keys(condition).reduce((acc, curKey) => {
acc[this.fieldMap[curKey]] = condition[curKey];
return acc;
}, {});
const { results, fields } = <ISqlResults>await deleteRow(this.table, dbCondition);
return results ? <IError>{ err: false } :
<IError>{
err: true,
message: "you have err on delete from " + this.table
};
}
public async fetch (query: TypeRest | string): Promise<Type> {
const dbQuery: IField = typeof query === "string" ? query :
Object.keys(query).reduce((acc, curKey) => {
acc[this.fieldMap[curKey]] = query[curKey];
return acc;
}, {});
let { results, fields } = <ISqlResults>await tableQuery(this.table, dbQuery);
results.length || (results = [{}]);
Array.isArray(results) && (results = results[0]);
return <Type>Object.keys(results).reduce((acc, curKey) => {
acc[this.map[curKey]] = results[curKey];
return acc
}, {});
}
public async fetchAll (condition?: TypeRest | string): Promise<Array<Type>> {
const { results, fields } = <ISqlResults>await sqlQuery({
sql: !condition ? format("select * from ??", [this.table]) :
typeof condition === "string" ?
format("select * from ?? where ??", [this.table, condition]) :
format("select * from ?? where ?", [this.table, condition]),
timeout: 2000
});
return <Array<Type>>results.map(value => Object.keys(value).reduce((acc, curKey) => {
acc[this.map[curKey]] = value[curKey];
return acc;
}, {}));
}
public async insert (data: Type): Promise<IError> {
const dbData: IField = Object.keys(data).reduce((acc, curKey) => {
acc[this.fieldMap[curKey]] = data[curKey];
return acc;
}, {});
const { results } = <ISqlResults>await insertTable(this.table, dbData);
return results ? <IError>{ err: false } : <IError>{ err: true, message: "you have some error in insert data" };
}
public async update (data: TypeRest, condition: TypeRest | string): Promise<IError> {
const dbData: IField = Object.keys(data).reduce((acc, curKey) => {
acc[this.fieldMap[curKey]] = data[curKey];
return acc;
}, {});
const dbCondition: IField | string = typeof condition === "string" ? condition : Object.keys(condition).reduce((acc, curKey) => {
acc[this.fieldMap[curKey]] = condition[curKey];
return acc;
}, {});
console.log(dbData,dbCondition);
const { results } = <ISqlResults>await updateTable(this.table, dbData, dbCondition);
return results ? <IError>{ err: false } : { err: true, message: "you have message in update" };
}
}
至此已經簡單的實現了一個ORM,接下來看看如何使用,以一個User實體爲例
user表
User.ts
import ORM from "./ORM";
export interface IUserType {
readonly id: string;
readonly nickname: string;
readonly email: string;
readonly password: string;
readonly picture: string;
readonly position: string;
readonly company: string;
readonly description: string;
readonly text: string;
readonly createTime: string;
}
export interface IUserTypeRest {
readonly id?: string;
readonly nickname?: string;
readonly email?: string;
readonly password?: string;
readonly picture?: string;
readonly position?: string;
readonly company?: string;
readonly description?: string;
readonly text?: string;
readonly createTime?: string;
}
// 只需要繼承ORM類即可
export default class User extends ORM<IUserType, IUserTypeRest> {
// 類屬性與數據庫列字段的映射
public fieldMap = {
id: "user_id",
nickname: "user_nickname",
email: "user_email",
password: "user_password",
picture: "user_picture",
position: "user_position",
company: "user_company",
description: "user_description",
text: "user_text",
createTime: "user_create_time"
};
// 默認值
public defaultValue: IUserType = {
id: "",
nickname: "",
email: "",
password: "",
picture: "/static/img/test-head.jpg",
position: "",
company: "",
description: "",
text: "",
createTime: Date.now().toString()
};
public table: string = "user"; // 指定數據庫表名
public map = Object.keys(this.fieldMap).reduce((acc, curKey) => {
acc[this.fieldMap[curKey]] = curKey;
return acc;
}, {});
}
使用如下
const user = new User();
async function main() {
const user = new User();
await user.insert(<IUserType>{
id: Date.now().toString(),
nickname: "sundial dreams",
email: "[email protected]",
password: "abcd",
picture: "/static/img/test-head.jpg",
position: "china",
company: "bytedunce",
description: "i am sundial dreams",
text: "() => {}",
createTime: Date.now().toString()
})
const data = await user.fetchAll()
console.log(data)
}
main()