數(shù)據(jù)持久化 - MySQL

回顧

  • http協(xié)議
  • websocket
  • socket.io

課堂目標

  • 掌握node.js中實現(xiàn)持久化的多種方法
  • 掌握mysql下載、安裝和配置
  • 掌握node.js中原生mysql驅動模塊的應用
  • 掌握node.js中的ORM模塊Sequelize的應用
  • 掌握Sequelize的應用案例

node.js中實現(xiàn)持久化的多種方法

  • 文件系統(tǒng) fs
  • 數(shù)據(jù)庫
    關系型數(shù)據(jù)庫-mysql
    文檔型數(shù)據(jù)庫-mongodb
    鍵值對數(shù)據(jù)庫-redis

文件系統(tǒng)數(shù)據(jù)庫

// fsdb.js
    // 實現(xiàn)一個文件系統(tǒng)讀寫數(shù)據(jù)庫
    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安裝、配置

菜鳥教程 http://www.runoob.com/mysql/mysql-tutorial.html

node.js原生驅動

  • 安裝mysql模塊: npm i mysql --save
  • mysql模塊基本使用
// mysql.js
    const mysql = require("mysql");
    // 連接配置
    const cfg = {
        host: "localhost",
        user: "root",
        password: "example", // 修改為你的密碼
        database: "kaikeba" // 請確保數(shù)據(jù)庫存在
    };
    // 創(chuàng)建連接對象
    const conn = mysql.createConnection(cfg);
    // 連接
    conn.connect(err => {
        if (err) {
            throw err;
        } else {
            console.log("連接成功!");
        }
    });
    // 查詢 conn.query()
    // 創(chuàng)建表
    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;
        }
        // 插入數(shù)據(jù)
        conn.query(INSERT_SQL, "hello,world", (err, result) => {
            if (err) {
                throw err;
            }
            console.log(result);
            conn.query(SELECT_SQL, (err, results) => {
                console.log(results);
                conn.end(); // 若query語句有嵌套,則end需在此執(zhí)行
            })
        });
    });

ES2017寫法

 // mysql2.js
    (async () => {
        // get the client
        const mysql = require('mysql2/promise');
        // 連接配置
        const cfg = {
            host: "localhost",
            user: "root",
            password: "example", // 修改為你的密碼
            database: "kaikeba" // 請確保數(shù)據(jù)庫存在
        };
        // create the connection
        const connection = await mysql.createConnection(cfg);
        // 查詢 conn.query()
        // 創(chuàng)建表
        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`;
        // query database
        let ret = await connection.execute(CREATE_SQL);
        console.log('create:', ret)
        ret = await connection.execute(INSERT_SQL, ['abc']);
        console.log('insert:', ret)
        const [rows, fields] = await connection.execute(SELECT_SQL);
        console.log('select:', rows)
    })()

Node.js ORM - Sequelize

  • 概述:基于Promise的ORM(Object Relation Mapping),是一種數(shù)據(jù)庫中間件 支持多種數(shù)據(jù)庫、事務、關聯(lián)等

中間件是介于應用系統(tǒng)和系統(tǒng)軟件之間的一類軟件,它使用系統(tǒng)軟件所提供的基礎服務(功
能),銜接網(wǎng)絡上應用系統(tǒng)的各個部分或不同的應用,能夠達到資源共享、功能共享的目
的。目前,它并沒有很嚴格的定義,但是普遍接受IDC的定義:中間件是一種獨立的系統(tǒng)軟
件服務程序,分布式應用軟件借助這種軟件在不同的技術之間共享資源,中間件位于客戶機
服務器的操作系統(tǒng)之上,管理計算資源和網(wǎng)絡通信。從這個意義上可以用一個等式來表示中
間件:中間件=平臺+通信,這也就限定了只有用于分布式系統(tǒng)中才能叫中間件,同時也把它
與支撐軟件和實用軟件區(qū)分開來。

  • 安裝: npm i sequelize mysql2 -S
  • 基本使用:
(async () => {
        const Sequelize = require("sequelize");
        // 建立連接
        const sequelize = new Sequelize("kaikeba", "root", "example", {
            host: "localhost",
            dialect: "mysql",
            operatorsAliases: false // 仍可通過傳入 operators map 至 operatorsAliases 的方式來使用字符串運算符,但會返回棄用警告
        });
        // 定義模型
        const Fruit = sequelize.define("Fruit", {
            name: { type: Sequelize.STRING(20), allowNull: false },
            price: { type: Sequelize.FLOAT, allowNull: false },
            stock: { type: Sequelize.INTEGER, defaultValue: 0 }
        });
        // 同步數(shù)據(jù)庫,force: true則會刪除已存在表
        let ret = await Fruit.sync()
        console.log('sync', ret)
        ret = await Fruit.create({
            name: "香蕉",
            price: 3.5
        })
        console.log('create', ret)
        ret = await Fruit.findAll()
        await Fruit.update(
            { price: 4 },
            { where: { name: '香蕉' } }
        )
        console.log('findAll', JSON.stringify(ret))
        const Op = Sequelize.Op;
        ret = await Fruit.findAll({
            // where: { price: { [Op.lt]:4 }, stock: { [Op.gte]: 100 } }
            where: { price: { [Op.lt]: 4, [Op.gt]: 2 } }
        })
        console.log('findAll', JSON.stringify(ret, '', '\t'))
    })()
  • 強制同步:創(chuàng)建表之前先刪除已存在的表
Fruit.sync({force: true})
  • 避免自動生成時間戳字段
const Fruit = sequelize.define("Fruit", {}, {
timestamps: false
});
  • 指定表名: freezeTableName: true 或 tableName:'xxx'

設置前者則以modelName作為表名;設置后者則按其值作為表名。蛇形命名 underscored: true,默認駝峰命名

  • UUID-主鍵
id: {
   type: Sequelize.DataTypes.UUID,
   defaultValue: Sequelize.DataTypes.UUIDV1,
   primaryKey: true
},
  • Getters & Setters:可用于定義偽屬性或映射到數(shù)據(jù)庫字段的保護屬性
// 定義為屬性的一部分
    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);
            }
        }
    }
    // 通過模型實例觸發(fā)setterMethods
    Fruit.findAll().then(fruits => {
        console.log(JSON.stringify(fruits));
        // 修改amount,觸發(fā)setterMethods
        fruits[0].amount = '150kg';
        fruits[0].save();
    });

校驗:可以通過校驗功能驗證模型字段格式、內容,校驗會在 create 、 update 和 save 時自動運行

price: {
        validate: {
            isFloat: { msg: "價格字段請輸入數(shù)字" },
            min: { args: [0], msg: "價格字段必須大于0" }
        }
    },
    stock: {
        validate: {
            isNumeric: { msg: "庫存字段請輸入數(shù)字" }
        }
    }
  • 模型擴展:可添加模型實例方法或類方法擴展模型
// 添加類級別方法
    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)}`);
    });
  • 數(shù)據(jù)查詢
// 通過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());
    });
    // 獲取數(shù)據(jù)和總條數(shù)
    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]: 4, [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,
        limit: 2,
    })
    // 排序
    Fruit.findAll({
        order: [['price', 'DESC']],
    })
    // 聚合
    Fruit.max("price").then(max => {
        console.log("max", max);
    });
    Fruit.sum("price").then(sum => {
        console.log("sum", sum);
    });
  • 更新
Fruit.findById(1).then(fruit => {
        // 方式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));

實體關系圖和與域模型 ERD


model.jpg
  • 初始化數(shù)據(jù)庫
// 初始化數(shù)據(jù)庫
    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');
    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
    });
  • 同步數(shù)據(jù)
// 同步數(shù)據(jù)
    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"));
        })
  • 中間件鑒權
app.use(async (ctx, next) => {
    const user = await User.findByPk(1)
    ctx.user = user;
    await next();
});
  • 功能實現(xiàn)
const router = require('koa-router')()
    /**
    * 查詢產(chǎn)品
    */
    router.get('/admin/products', async (ctx, next) => {
        // const products = await ctx.user.getProducts()
        const products = await Product.findAll()
        ctx.body = { prods: products }
    })
    /**
    * 創(chuàng)建產(chǎn)品
    */
    router.post('/admin/product', async ctx => {
        const body = ctx.request.body
        const res = await ctx.user.createProduct(body)
        ctx.body = { success: true }
    })
    /**
    * 刪除產(chǎn)品
    */
    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
        const prodId = body.id
        let newQty = 1
        const cart = await ctx.user.getCart()
        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
        } else {
            product = await Product.findByPk(prodId)
        }
        await cart.addProduct(product, {
            through: {
                quantity: newQty
            }
        })
        ctx.body = { success: true }
    })
    /**
    * 添加訂單
    */
    router.post('/orders', async ctx => {
        const cart = await ctx.user.getCart()
        const products = await cart.getProducts()
        const order = await ctx.user.createOrder()
        const result = await order.addProduct(
            products.map(p => {
                p.orderItem = {
                    quantity: p.cartItem.quantity
                }
                return p
            })
        )
        await cart.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())

Restful服務
實踐指南 http://www.ruanyifeng.com/blog/2014/05/restful_api.html
原理 http://www.ruanyifeng.com/blog/2011/09/restful.html

TODO List范例 https://github.com/BayliSade/TodoList
關于新版本的警告問題https://segmentfault.com/a/1190000011583806

資料來源:開課吧

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容