nodejs - mqsql

本文探讨了在Node.js中通过文件系统fs和数据库(包括关系型MySQL、文档型MongoDB及键值对Redis)实现数据持久化的方法,涉及fsdb.js示例、MySQL原生驱动和ORM框架Sequelize的应用。
摘要由CSDN通过智能技术生成

node.js中实现持久化的多种⽅法

node.js中实现持久化的2种⽅法

  1. 文件系统fs
  2. 数据库
    1.关系型数据库 - mysql
    2.文档型数据库 - mongodb
    3.键值对数据库-redis

⽂件系统数据库

//  fsdb.js
// 实现一个文件系统读写数据库

const  fs = require(fs)
function get(key) {
	fs.readFile("./db.json", (err, data) => {
		const json = JSON.parse(data);
		console.log(json[key]);
	})
}

function set(key, value) {
	fs.readFile(",.db.json", (err, data) => {
		// 可能是空⽂件,则设置为空对象
		const json = data ? JSON.parse(data) : {};
		json[key] = value; // 设置值
		// 重新写⼊⽂件
		fs.writeFile("./db.json", JSON.stringify(json), err => {
			if(err) console.log(err)
			console.log("写入成功")
		});
	})
}


// 命令行部分
const readline = require("readline");
const rl = readline..createInterface({ input: process.stdin, output: process.stdout })
rl.on("line", function (input) {
	const [op, key, value] = input.split(" ");
	if (op ==='get') {
		get(key)
	} else if (op === 'set') {
		set(key, value)
	} else if (op === 'quit') {
		rl.close();
	} else {
		console.log('没有该操作');
	}
})

rl.on('close', function () {
	console.log("程序结束");
	process.exit(0);
})

MySQL安装、配置

菜鸟教程:https://www.runoob.com/mysql/mysql-tutorial.html

node.js原⽣驱动

  1. 安装mysql模块 npm i mysql --save
  2. mysql模块基本使⽤
const mysql = require("mysql");
// 连接配置
const cfg = {
  host: "localhost",
  user: "root",
  password: "12345678", // 修改为你的密码
  database: "kaikeba" // 请确保数据库存在
};
// 创建连接对象
const conn = mysql.createConnection(cfg);

// 连接
conn.connect(err => {
  if (err) {
    throw err;
  } else {
    console.log("连接成功!");
  }
});

// 查询 conn.query()
// 创建表
const CREATE_SQL = `CREATE TABLE IF NOT EXISTS test (
                    id INT NOT NULL AUTO_INCREMENT,
                    message VARCHAR(45) NULL,
                    PRIMARY KEY (id))`;
const INSERT_SQL = `INSERT INTO test(message) VALUES(?)`;
const SELECT_SQL = `SELECT * FROM test`;
conn.query(CREATE_SQL, err => {
  if (err) {
    throw err;
  }
  // 插入数据
  conn.query(INSERT_SQL, "hello,world", (err, result) => {
    if (err) {
      throw err;
    }
    console.log(result);
    conn.query(SELECT_SQL, (err, results) => {
        console.log(JSON.stringify(results));
        conn.end(); // 若query语句有嵌套,则end需在此执行
    })
  });
});

es2017写法:

(async () => {
    const mysql = require('mysql2/promise')

    // 连接配置
    const cfg = {
        host: "localhost",
        user: "root",
        password: "12345678", // 修改为你的密码
        database: "kaikeba" // 请确保数据库存在
    }

    const connection = await mysql.createConnection(cfg)

    let ret = await connection.execute(`
        CREATE TABLE IF NOT EXISTS test (
            id INT NOT NULL AUTO_INCREMENT,
            message VARCHAR(45) NULL,
        PRIMARY KEY (id))
    `)
    console.log('create', ret)

    ret = await connection.execute(`
            INSERT INTO test(message)
            VALUES(?)
    `, ['ABC'])
    console.log('insert:', ret)


    ret = await connection.execute(`
            SELECT * FROM test
    `)
    console.log(JSON.stringify(ret[0]))
    // console.log(ret[1])

    connection.end()

})()    

Node.js ORM

Sequelize: https://sequelize.org/

  1. 概述:基于Promise的ORM(Object Relation Mapping),⽀持多种数据库、事务、关联等。
  2. 安装: npm i sequelize mysql2 -S
  3. 基本使用
(async () => {
    const Sequelize = require("sequelize");

    // 建立连接
    const sequelize = new Sequelize("kaikeba", "root", "example", {
        host: "localhost",
        dialect: "mysql",
        operatorsAliases: false
    });

    // 定义模型
    const Fruit = sequelize.define("Fruit", {
        name: {
            type: Sequelize.STRING(20),
            allowNull: false,
            // get() {
            //     const fname = this.getDataValue("name");
            //     const price = this.getDataValue("price");
            //     const stock = this.getDataValue("stock");
            //     return `${fname}(价格:¥${price} 库存:${stock}kg)`;
            // }
        },
        price: {
            type: Sequelize.FLOAT,
            allowNull: false,
            validate: {
                isFloat: { msg: "价格字段请输入数字" },
                min: { args: [0], msg: "价格字段必须大于0" }
            }
        },
        stock: { type: Sequelize.INTEGER, defaultValue: 0 },

    },
        {
            timestamps: false,
            getterMethods: {
                amount() {
                    return this.getDataValue("stock") + "kg";
                }
            },
            setterMethods: {
                amount(val) {
                    const idx = val.indexOf('kg');
                    const v = val.slice(0, idx);
                    this.setDataValue('stock', v);
                }
            }
        });

    Fruit.classify = function (name) {
        const tropicFruits = ['香蕉', '芒果', '椰子']; // 热带水果
        return tropicFruits.includes(name) ? '热带水果' : '其他水果';
    };
    Fruit.prototype.totalPrice = function (count) {
        return (this.price * count).toFixed(2);
    };

    ['香蕉', '草莓'].forEach(f => console.log(f + '是' + Fruit.classify(f)));

    // 同步数据库,force: true则会删除已存在表
    let ret = await Fruit.sync({ force: false })
    // console.log('sync', ret)
    ret = await Fruit.create({
        name: "香蕉",
        price: 3.5
    })
    // console.log('create', ret)
    // ret = await Fruit.findAll()

    // 使用实例方法
    // Fruit.findAll().then(fruits => {
    //     const [f1] = fruits;
    //     console.log(`买5kg${f1.name}需要¥${f1.totalPrice(5)}`);      
    // });

    // Fruit.findOne({ where: { name: "香蕉" } }).then(fruit => {
    //     // fruit是首个匹配项,若没有则为null
    //     console.log(fruit.get());
    // });

    // console.log('findAll', ret.amount, JSON.stringify(ret))

    // Fruit.findAll().then(fruits => {
    //     console.log(JSON.stringify(fruits));
    //     // 修改amount,触发setterMethods
    //     fruits[0].amount = '150kg';
    //     fruits[0].save();
    // });

    // Fruit.findOne({ attributes: ['name'] }).then(fruit => {
    //     // fruit是首个匹配项,若没有则为null
    //     console.log(fruit.get());
    // });

    // ret = await Fruit.findAll({
    //     offset: 3,
    //     limit: 3,
    // })
    // console.log('ret:', JSON.stringify(ret))

    const Op = Sequelize.Op;
    Fruit.findAll({
        // where: { price: { [Op.lt]:4 }, stock: { [Op.gte]: 100 } }
        where: { id: { [Op.lt]: 4, [Op.gt]: 2 } }
    }).then(fruits => {
        console.log(JSON.stringify(fruits))
        console.log(fruits.length);
    });

    // Fruit.destroy({ where: { id: 1 } }).then(r => console.log(r));
})()


  1. 强制同步:创建表之前先删除已存在的表: Fruit.sync({force: true})

  2. 避免⾃动⽣成时间戳字段: const Fruit = sequelize.define("Fruit", {}, { timestamps: false });

  3. 指定表名: freezeTableName: true 或 tableName:‘xxx’

设置前者则以modelName作为表名;设置后者则按其值作为表名。
蛇形命名 underscored: true,
默认驼峰命名

  1. UUID-主键
id: {
 type: Sequelize.DataTypes.UUID,
 defaultValue: Sequelize.DataTypes.UUIDV1,
 primaryKey: true
 },
  1. Getters & Setters:可⽤于定义伪属性或映射到数据库字段的保护属性
// 定义为属性的⼀部分
name: {
	type: Sequelize.STRING,
	allowNull: false,
	get() {
		const fname = this.getDataValue("name")
		const price =this.getDataValue("price")
		const stock = this.getDataValue('stock')
		return `${fname}(价格:¥${price} 库存:${stock}kg)`;
	}
}


// 定义模型选项
// options中
{
	getterMethods: {
		amount() {
			return  this.getDataValue("stock") + 'kg'
		}
	},
	setterMethods: {
		amount(val) {
			const idx = val.indexof("kg")
			const v = val.slice(0, idx);
			this.setDataValue('stock', v);
		}
	}
}

// 通过模型实例触发setterMethods
Fruit.findAll().then((fruits) => {
	console.log(JSON.stringify(fruits));
	// 修改amount,触发setterMethods
	fruits[0].amount = '150kg';
	fruits[0].save();
})
  • 校验: 可以通过校验功能验证模型字段格式,内容,校验会在create,update和dave时自动运行。
price: {
 	validate: {
		isFloat: { msg: "价格字段请输⼊数字" },
		min: { args: [0], msg: "价格字段必须⼤于0" }
	}
}

stock: {
	validate: {
		isNumeric: { msg: "库存字段请输⼊数字" }
	}
}
  • 模型扩展:可添加模型实例⽅法或类⽅法扩展模型
// 添加类级别⽅法
Fruit.classify = function (name) {
	const tropicFruits = ['⾹蕉', '芒果', '椰⼦']; // 热带⽔果
	return tropicFruits.includes(name) ? '热带⽔果':'其他⽔果'; 
}

// 添加实例级别方法
Fruit.prototype.totalPrice = function(count) {
	return (this.price * count).toFixed(2);
};

// 使用方法:
['⾹蕉','草莓'].forEach(f => console.log(f+'是'+Fruit.classify(f)));

// 使用实例方法:
Fruit.findAll() .then((fruits) => {
	const [f1] = fruits
	console.log(`买5kg${f1.name}需要¥${f1.totalPrice(5)}`);
})
  • 查询数据
//  通过id查询(不⽀持了)
Fruit.findById(1).then(fruit => {
// fruit是⼀个Fruit实例,若没有则为null
console.log(fruit.get());
});

// 通过属性查询
Fruit.findOne({ where: {name: '香蕉'} }).then((fruit) => {
	// fruit是首个匹配选项,若没有则为null
	console.log(fruit.get())
})

// 指定查询字段
Fruit.findOne({ attributes: ['name'] }).then((fruit) => {
	// fruit是首个匹配选项,若没有则为null
	console.log(fruit.get())
})

// 获取数据和总条数
Fruit.findAndCountAll().then((result) => {
	console.log(result.count)
	console.log(result.rows.length)
})

// 查询操作符
const Op = Sequelize.Op
Fruit.findAll({
 // where: { price: { [Op.lt]:4 }, stock: { [Op.gte]: 100 } }
 where: { price: { [Op.lt], [Op.gt]: 2 } }
}.then(fruits => {
	console.log(fruits.length)
})

// 或语句
Fruit.findAll({
	// where: { [Op.or]:[{price: { [Op.lt]:4 }}, {stock: { [Op.gte]: 100 }}] }
	where: { price: { [Op.or]:[{[Op.gt]:3 }, {[Op.lt]:2 }]}}
}).then(fruits => {
	console.log(fruits[0].get());
});

// 分页:
Fruit.findAll({ offset: 0, linit: 2 })

// 排序
Fruit.findAll({ order: [['price', 'DESC']] })

// 聚合
Fruit.max('price').then(max => {
	console.log("max", max);
})

Fruit.sum("price").then((sum) => {
	console.log(sum)
})
  • 更新
Fruit.findById(1).then(() => {
	// 方式1
	fruit.price = 4
	fruit.save().then(() => {console.log('update!!!')})
})

// 方式2
Fruit.update({ price: 4 }, { where: { id: 1 } }).then(r => {
	console.log(r);
	console.log('update!!!!')
})
  • 删除
// 方式1
Fruit.findOne({ where: { id: 1 } }).then( r => r.destroy() )

// 方式2
Fruit.destroy({ where: { id: 1 } }).then(r => console.log(r));

1:N关系

(async () => {

    // 1:N关系
    const Sequelize = require("sequelize");

    // 建立连接
    const sequelize = new Sequelize("kaikeba", "root", "example", {
        host: "localhost",
        dialect: "mysql",
        operatorsAliases: false
    });
    // Player
    const Player = sequelize.define('player', { name: Sequelize.STRING });
	
	// Team
    const Team = sequelize.define('team', { name: Sequelize.STRING });
    
    Player.belongsTo(Team); // 1端建立关系
    Team.hasMany(Player); // N端建立关系

    // 同步数据库,force: true则会删除已存在表
    sequelize.sync({ force: true }).then(async () => {
        await Team.create({ name: '火箭' });
        await Player.bulkCreate([{ name: '哈登', teamId: 1 }, { name: '保罗', teamId: 1 }]);

        // 1端关联查询
        const players = await Player.findAll({ include: [Team] });
        console.log(JSON.stringify(players, null, 2));

        // N端关联查询
        const team = await Team.findOne({ where: { name: '火箭' }, include: [Player] });
        console.log(JSON.stringify(team, null, 2));
    });


})()

(async () => {

    // 1:N关系
    const Sequelize = require("sequelize");

    // 建立连接
    const sequelize = new Sequelize("kaikeba", "root", "example", {
        host: "localhost",
        dialect: "mysql",
    });

    const Fruit = sequelize.define("fruit", { name: Sequelize.STRING });
    const Category = sequelize.define("category", { name: Sequelize.STRING });
    
    Fruit.FruitCategory = Fruit.belongsToMany(Category, {
        through: "FruitCategory"
    });

    // 插入测试数据
    sequelize.sync({ force: true }).then(async () => {
        await Fruit.create(
            {
                name: "香蕉",
                categories: [{ id: 1, name: "热带" }, { id: 2, name: "温带" }]
            },
            {
                include: [Fruit.FruitCategory]
            }
        );
        // 多对多联合查询
        const fruit = await Fruit.findOne({
            where: { name: "香蕉" }, // 通过through指定条件、字段等
            include: [{ model: Category, through: { attributes: ['id', 'name'] } }]
        });
    })

})()


案例:

目录结构:
请添加图片描述

package.json

{
  "name": "expressjs",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "dependencies": {
    "body-parser": "^1.18.3",
    "dotenv": "^6.2.0",
    "ejs": "^2.6.1",
    "express": "^4.16.4",
    "express-handlebars": "^3.0.0",
    "http-proxy-middleware": "^0.19.1",
    "mongodb": "^3.1.13",
    "mysql2": "^1.6.5",
    "nodemon": "^1.18.10",
    "sequelize": "^4.42.0"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon app.js"
  },
  "author": "Sourav",
  "license": "ISC"
}

data/cart.json

{"products":[],"totalPrice":0}

data/products.json

[
  {
    "id": "8QngKP18c",
    "title": "Fixing international domains",
    "imageUrl": "https://www.listchallenges.com/f/lists/7c6a48ca-d523-409e-ad51-5e652ee208ed.jpg",
    "description": "pok",
    "price": "77"
  },
  {
    "id": "gNoFUbWka",
    "title": "Optimizing get open-identities call",
    "imageUrl": "https://www.listchallenges.com/f/lists/7c6a48ca-d523-409e-ad51-5e652ee208ed.jpg",
    "description": "wes",
    "price": "96"
  }
]

models/cart-item.js

const Sequelize = require('sequelize');
const sequelize = require('../util/database');

const CartItem = sequelize.define('cartItem', {
    id: {
        type: Sequelize.INTEGER,
        autoIncrement: true,
        allowNull: false,
        primaryKey: true
    },
    quantity: Sequelize.INTEGER
});

module.exports = CartItem;

models/cart.js

const Sequelize = require('sequelize');
const sequelize = require('../util/database');

const Cart = sequelize.define('cart', {
    id: {
        type: Sequelize.INTEGER,
        autoIncrement: true,
        primaryKey: true,
        allowNull: false
    }
});

module.exports = Cart;

models/order-item.js

const Sequelize = require('sequelize');
const sequelize = require('../util/database');

const OrderItem = sequelize.define('orderItem', {
    id: {
        type: Sequelize.INTEGER,
        autoIncrement: true,
        allowNull: false,
        primaryKey: true
    },
    quantity: Sequelize.INTEGER
});

module.exports = OrderItem;

models/order.js

const Sequelize = require('sequelize');
const sequelize = require('../util/database');

const Order = sequelize.define('order', {
    id: {
        type: Sequelize.INTEGER,
        autoIncrement: true,
        allowNull: false,
        primaryKey: true
    }
});

module.exports = Order;

models/product.js

const Sequelize = require('sequelize');

const sequelize = require('../util/database');

const Product = sequelize.define('product', {
    id: {
        type: Sequelize.INTEGER,
        autoIncrement: true,
        allowNull: false,
        primaryKey: true
    },
    title: {
        type: Sequelize.STRING,
        allowNull: false
    },
    price: {
        type: Sequelize.DOUBLE,
        allowNull: false
    },
    imageUrl: {
        type: Sequelize.STRING,
        allowNull: false
    },
    description: {
        type: Sequelize.STRING,
        allowNull: false
    }
});

module.exports = Product;

models/user.js

const Sequelize = require('sequelize');
const sequelize = require('../util/database');

const User = sequelize.define('user', {
    id : {
        type: Sequelize.INTEGER,
        autoIncrement: true,
        allowNull: false,
        primaryKey: true
    },
    name: Sequelize.STRING,
    email: Sequelize.STRING
});

module.exports = User;

util/conf.js

module.exports = {
    database: 'shop',
    username: 'root',
    password:'example',
    host:'localhost'
 };

util/database.js

const Sequelize = require("sequelize");
const env = require("dotenv");
const conf = require("./conf")

env.config();
const sequelize = new Sequelize(conf.database, conf.username, conf.password, {
  dialect: "mysql",
  host: conf.host,
  operatorsAliases: false
});

module.exports = sequelize;

util/path.js

const path = require('path');

module.exports = path.dirname(process.mainModule.filename);

docker-compose.yml

version: '3.1'
services:
  mysql:
    image: mysql
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: example
    ports:
      - 3306:3306
  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080
  

index.js

const Koa = require('koa')
const app = new Koa()
const bodyParser = require('koa-bodyparser')
app.use(require('koa-static')(__dirname + '/'))
app.use(bodyParser())

// 初始化数据库
const sequelize = require('./util/database');
const Product = require('./models/product');
const User = require('./models/user');
const Cart = require('./models/cart');
const CartItem = require('./models/cart-item');
const Order = require('./models/order');
const OrderItem = require('./models/order-item');


// 加载用户 - 代替鉴权
app.use(async (ctx, next) => {
    const user = await User.findByPk(1)
    ctx.user = user;
    await next();
});

const router = require('koa-router')()
router.get('/admin/products', async (ctx, next) => {
    // const products = await ctx.user.getProducts()
    const products = await Product.findAll()
    ctx.body = { prods: products }
})

router.post('/admin/product', async ctx => {
    const body = ctx.request.body
    const res = await ctx.user.createProduct(body)
    ctx.body = { success: true }
})

router.delete('/admin/product/:id', async (ctx, next) => {
    const id = ctx.params.id
    const res = await Product.destroy({
        where: {
            id
        }
    })
    ctx.body = { success: true }
})

router.get('/cart', async ctx => {
    const cart = await ctx.user.getCart()
    const products = await cart.getProducts()
    ctx.body = { products }
})
/**
 * 添加购物车
 */
router.post('/cart', async ctx => {
    const body = ctx.request.body
    console.log('ctx.body', ctx.request.body)
    const prodId = body.id;
    let fetchedCart;
    let newQty = 1;

    // 获取购物车
    const cart = await ctx.user.getCart()
    console.log('cart', cart)
    fetchedCart = cart;
    const products = await cart.getProducts({
        where: {
            id: prodId
        }
    });

    let product;
    // 判断购物车数量
    if (products.length > 0) {
        product = products[0];
    }
    if (product) {
        const oldQty = product.cartItem.quantity;
        newQty = oldQty + 1;
        console.log("newQty", newQty);
    } else {
        product = await Product.findByPk(prodId);
    }

    await fetchedCart.addProduct(product, {
        through: {
            quantity: newQty
        }
    });
    ctx.body = { success: true }
})

router.post('/orders', async ctx => {
    let fetchedCart;
    const cart = await ctx.user.getCart();
    fetchedCart = cart;
    const products = await cart.getProducts();
    const order = await ctx.user.createOrder();
    const result = await order.addProducts(
        products.map(p => {
            p.orderItem = {
                quantity: p.cartItem.quantity
            };
            return p;
        })
    );
    await fetchedCart.setProducts(null);
    ctx.body = { success: true }
})
router.delete('/cartItem/:id', async ctx => {
    const id = ctx.params.id
    const cart = await ctx.user.getCart()
    const products = await cart.getProducts({
        where: { id }
    })
    const product = products[0]
    await product.cartItem.destroy()
    ctx.body = { success: true }
})
router.get('/orders', async ctx => {
    const orders = await ctx.user.getOrders({ include: ['products'], order: [['id', 'DESC']] })
    ctx.body = { orders }
})


app.use(router.routes())

// app.use('/admin', adminRoutes.routes);
// app.use(shopRoutes);


Product.belongsTo(User, {
    constraints: true,
    onDelete: 'CASCADE'
});
User.hasMany(Product);
User.hasOne(Cart);
Cart.belongsTo(User);
Cart.belongsToMany(Product, {
    through: CartItem
});
Product.belongsToMany(Cart, {
    through: CartItem
});
Order.belongsTo(User);
User.hasMany(Order);
Order.belongsToMany(Product, {
    through: OrderItem
});
Product.belongsToMany(Order, {
    through: OrderItem
});

sequelize.sync().then(
    async result => {
        let user = await User.findByPk(1)
        if (!user) {
            user = await User.create({
                name: 'Sourav',
                email: 'sourav.dey9@gmail.com'
            })
            await user.createCart();
        }
        app.listen(3000, () => console.log("Listening to port 3000"));
    })

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值