Egg之数据持久化

各位好久不见,这一章主要讲的是数据持久化,那么,如何持久化,大家都会觉得,存入数据库的数据,所以为了方便大家学会创建数据库,给大家带来了MySQL的教学,不会的小伙伴,欢迎和我一起学习数据库。这一章节,会显得代码有点多,有点长,希望大家多敲代码,加深理解。

一、数据持久化

1、应用程序的数据通常存储在数据库中。
2、我们使用MySQL数据库实现数据的持久化。
3、为了更方便的操作mysql,我们使用sequelize(ORM框架)管理数据层的代码。

二、ORM(对象关系映射)

1、将数据从对象的形式,转换成表格的形式。
2、sequelize是一个基于node的orm框架。
3、通过egg-sequelize,可以直接使用sequelize提供的方法操作数据库,而不需要动手写SQL语句。

三、安装sequelize

1、下载egg-sequelize:npm install --save egg-sequelize mysql2
2、在plugin.js文件中引入插件

module.exports = {
   sequelize:{
        enable:true,
        package:'egg-sequelize'
    }
}

3、在config.default.js文件中配置数据库连接

config.sequelize = {
    dialect:'mysql',
    database:'demo',
    host:'localhost',
    port:3306,
    username:'root',
    password:'123456'
}

4、在app/model文件中创建数据模型
在app下的model文件夹下创建clazz.js文件

module.exports = app => {
  const {
    STRING
  } = app.Sequelize
  //默认情况下,sequelize将自动将所有传递的模型名称(define的第一个参数)转为负数
  const Clazz = app.model.define('clazz', {
    name: STRING
  })
  return Clazz
}

5、添加app.js文件,初始化数据库

module.exports = app => {
  app.beforeStart(async function () {
    //await app.model.sync({force:true});//开发环境使用,会删除数据
    //sync 方法会根据模型去创建表
    await app.model.sync({})
  })
}
通过代码创建的clazzs表

ps:这个为什么会在表名后加个s呢,因为数据库会给我们自动加上s,所以不管你取什么名字,都会在后面加上s。

三、数据类型

MySQL数据类型与sequelize数据类型对应如下
1、STRING => varchar(255)
2、INTEGER => int
3、DOUBLE => double
4、DATE => datetime
5、TEXT => text

四、数据操作

在controller中实现数据的增删改查
说明:在真实的项目中,controller和操作数据的逻辑要分离,以便于项目的扩展与维护

this.app.model.Clazz.findAll();//查询数据
this.app.model.Clazz.findAll({where:{id:1}}) //通过where设置查询条件
this.app.model.Clazz.create({name:"xx"})  //添加数据
this.app.model.Clazz.update({name:"xx"},{where:{id:1}}) //通过条件修改数据
this.app.model.Clazz.destory({where:{id:1}}) //通过条件删除数据

五、实际操作(为了方便不做异常处理)

来举个例子实际操作一下
题目:制作学生成绩管理功能,数据存储在mysql中,要求如下:
1、实现班级列表的查看、添加、删除功能
2、实现学生列表的查看、添加、删除功能
3、学生字段包括,id、姓名、成绩、班级名称

  • 首先,路由里面写上两个egg给我们封装好的接口形式,增删改查,样样都有,我们需要一个班级的,一个学生的
 router.resources('clazz', '/clazz', controller.clazz)
 router.resources('students', '/students', controller.students)
  • 接下来,我们就要去画vue的页面了,第一个要把班级列表画出来


    班级管理页面
<template>
  <div>
    <div class="container">
      <el-tabs v-model="activeName"
               @tab-click="handleClick">
        <el-tab-pane label="班级管理"
                     name="class">
          <div class="container_search">
            <el-input placeholder="请输入班级名称"
                      v-model="className"
                      class="input-with-select">
              <el-button slot="append"
                         @click="searchClass"
                         icon="el-icon-search"></el-button>
            </el-input>
          </div>
          <el-button type="text"
                     @click="classVisible = true">添加班级</el-button>
          <el-table :data="classList"
                    :border="true"
                    style="width: 50%">
            <el-table-column prop="id"
                             label="ID">
            </el-table-column>
            <el-table-column prop="name"
                             label="班级">
            </el-table-column>
            <el-table-column fixed="right"
                             label="操作"
                             width="100">
              <template slot-scope="scope">
                <el-button @click="editClick(scope.row)"
                           type="text"
                           size="small">编辑</el-button>
                <el-button @click="deleteClick(scope.row)"
                           type="text"
                           size="small">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
        </el-tab-pane>
        <el-tab-pane label="学生管理"
                     name="student">学生管理</el-tab-pane>
      </el-tabs>
    </div>
    <!-- 班级弹窗 -->
    <el-dialog :title="title"
               :visible.sync="classVisible"
               width="30%">
      <el-form ref="formClass"
               :model="formClass"
               label-width="80px">
        <el-form-item label="班级名">
          <el-input v-model="formClass.name"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer"
            class="dialog-footer">
        <el-button @click="classVisible = false">取 消</el-button>
        <el-button type="primary"
                   @click="addClass(title)">确 定</el-button>
      </span>
    </el-dialog>
  </div>

</template>
<script>
import axios from 'axios'
export default {
  data () {
    return {
      activeName: 'class',
      classList: [],
      classVisible: false,
      formClass: {},
      className: '',
      title: '新增班级',
      classId: ''
    }
  },
  mounted () {
    this.getClasslist()
  },
  methods: {
    //班级列表
    getClasslist () {
      axios.get("http://127.0.0.1:7001/clazz", { params: { name: this.className } }).then((res) => {
        this.classList = res.data
      })
    },
    //搜索班级
    searchClass () {
      this.getClasslist()
    },
    //添加班级
    addClass () {
      this.classVisible = false
      if (this.title === '编辑班级') {
        axios.put(`http://127.0.0.1:7001/clazz/${this.classId}`, { data: this.formClass }).then(() => {
          this.$message.success('添加成功');
          this.getClasslist()
        })
      } else {
        axios.post("http://127.0.0.1:7001/clazz", { data: this.formClass }).then(() => {
          this.$message.success('修改成功');
          this.getClasslist()
        })
      }


    },
    //编辑班级
    editClick (item) {
      this.classVisible = true
      this.title = '编辑班级'
      this.$nextTick(() => {
        this.formClass = Object.assign({}, item)
      })
      this.classId = item.id
    },
    //删除班级
    deleteClick (item) {
      this.$confirm('此操作将永久删除该班级, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        axios.delete(`http://127.0.0.1:7001/clazz/${item.id}`).then(() => {
          this.$message.success('删除成功');
          this.getClasslist()
        })
      })
    },
    handleClick () {

    }

  }
}
</script>
<style lang="stylus" scoped>
.container {
  width: 100%;
  height: 60vh;
  margin-top: 20px;
  &_search {
    width: 30%;
  }
}
</style>
  • 画好页面写好逻辑,就要看后台是如何操作的了
const Controller = require('egg').Controller; //Controller类
class clazzController extends Controller {

  //班级列表
  async index() {
    let name = this.ctx.request.query.name
    if (name) {
      let clazzList = await this.app.model.Clazz.findAll({
        where: {
          name
        }
      })
      this.ctx.body = clazzList
    } else {
      let clazzList = await this.app.model.Clazz.findAll()
      this.ctx.body = clazzList
    }
  }

  //新增班级
  async create() {
    let name = this.ctx.request.body.data.name
    await this.app.model.Clazz.create({
      name
    })
    this.ctx.body = "添加成功"
  }

  //删除班级
  async destroy() {
    let id = this.ctx.params.id
    await this.app.model.Clazz.destroy({
      where: {
        id: id
      }
    })
    this.ctx.body = "删除成功"
  }

  //更新班级
  async update() {
    let id = this.ctx.params.id
    let name = this.ctx.request.body.data.name
    await this.app.model.Clazz.update({
      name: name
    }, {
      where: {
        id: id
      }
    })
    this.ctx.body = '修改成功'
  }

}
module.exports = clazzController

解析:我知道很多刚接触这种的小伙伴一时间没办法理解,这里目前只是一个表的增删改查,如果涉及到后面多表联查,多个表之间建立联系,就会复杂起来,但是没关系,我们一个一个消化,搞懂一个才能去搞懂下一个。
班级的建表逻辑就是id,是自动生成的,主要是存储name(班级名称),vue页面的逻辑还是非常简单的,查询,列表,删除,修改,新增。后台的逻辑,新增的时候去获取前台传来的name的值,并存入数据库,获取班级列表这里,稍微有点不一样了,获取输入的name,根据名称是查询列表,但是有一点,如果name传的是空的值,我们就查询全部就行了,这里要做一个简单的判断就行了。删除根据id删除,至于这个更新,同学们容易犯的错误,这里不是post了,是put了,就像是删除是delete一样,千万别写错了,根据id去更新,把新输入的name更新掉。

  • 讲完简单的班级列表,我们需要在班级里面加上学生的信息,这里就比较难了。首先要去写一个student数据模型,去建一个学生表,以及这个联查函数。
module.exports = app => {
  const {
    STRING,
    DOUBLE
  } = app.Sequelize;
  const Student = app.model.define('student', {
    name: STRING,
    achievement: DOUBLE
  })

  Student.associate = function () { //所属哪个班级,指向班级主键
    app.model.Student.belongsTo(app.model.Clazz, {
      foreignKey: 'clazz_id',
      as: 'clazz'
    })
  }
  return Student
}

之后的学生的增删改查,都是一样的,这里就放上全部的代码了,唯一要注意的就是这个clazz_id是作为连接两个表的桥梁的。

const Controller = require('egg').Controller; //Controller类
class studentController extends Controller {

  //学生列表
  async index() {
    let studentList = await this.app.model.Student.findAll()
    this.ctx.body = studentList
  }

  //新增学生
  async create() {
    let name = this.ctx.request.body.data.name
    let achievement = this.ctx.request.body.data.achievement
    let clazz_id = this.ctx.request.body.data.clazz_id
    await this.app.model.Student.create({
      name,
      achievement,
      clazz_id
    })
    this.ctx.body = '添加成功'
  }

  //删除班级
  async destroy() {
    let id = this.ctx.params.id
    await this.app.model.Student.destroy({
      where: {
        id: id
      }
    })
    this.ctx.body = "删除成功"
  }

  //更新班级
  async update() {
    let id = this.ctx.params.id
    let name = this.ctx.request.body.data.name
    let achievement = this.ctx.request.body.data.achievement
    let clazz_id = this.ctx.request.body.data.clazz_id
    await this.app.model.Student.update({
      name,
      achievement,
      clazz_id
    }, {
      where: {
        id: id
      }
    })
    this.ctx.body = '修改成功'
  }

}
module.exports = studentController
<template>
  <div>
    <el-button @click="getMessage"
               type="primary">请求数据</el-button>
    <el-button @click="logout"
               type="primary">注销</el-button>
    <div class="container">
      <el-tabs v-model="activeName"
               @tab-click="handleClick">
        <el-tab-pane label="班级管理"
                     name="class">
          <!-- 班级内容 -->
          <div class="container_search">
            <el-input placeholder="请输入班级名称"
                      v-model="className"
                      class="input-with-select">
              <el-button slot="append"
                         @click="searchClass"
                         icon="el-icon-search"></el-button>
            </el-input>
          </div>
          <el-button type="text"
                     @click="classVisible = true">添加班级</el-button>
          <el-table :data="classList"
                    :border="true"
                    style="width: 50%">
            <el-table-column prop="id"
                             label="ID">
            </el-table-column>
            <el-table-column prop="name"
                             label="班级">
            </el-table-column>
            <el-table-column fixed="right"
                             label="操作"
                             width="100">
              <template slot-scope="scope">
                <el-button @click="editClick(scope.row)"
                           type="text"
                           size="small">编辑</el-button>
                <el-button @click="deleteClick(scope.row)"
                           type="text"
                           size="small">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
        </el-tab-pane>
        <el-tab-pane label="学生管理"
                     name="student">
          <!-- 学生内容 -->
          <el-button type="text"
                     @click="model.studentVisible = true">添加学生</el-button>
          <el-table :data="studentList"
                    :border="true"
                    style="width: 50%">
            <el-table-column prop="id"
                             label="ID">
            </el-table-column>
            <el-table-column prop="name"
                             label="姓名">
            </el-table-column>
            <el-table-column prop="achievement"
                             label="成绩">
            </el-table-column>
            <el-table-column fixed="right"
                             label="操作"
                             width="100">
              <template slot-scope="scope">
                <el-button @click="editStuClick(scope.row)"
                           type="text"
                           size="small">编辑</el-button>
                <el-button @click="deleteStuClick(scope.row)"
                           type="text"
                           size="small">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
        </el-tab-pane>

      </el-tabs>
    </div>
    <!-- 班级弹窗 -->
    <el-dialog :title="title"
               :visible.sync="classVisible"
               width="30%">
      <el-form ref="formClass"
               :model="formClass"
               label-width="80px">
        <el-form-item label="班级名">
          <el-input v-model="formClass.name"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer"
            class="dialog-footer">
        <el-button @click="classVisible = false">取 消</el-button>
        <el-button type="primary"
                   @click="addClass(title)">确 定</el-button>
      </span>
    </el-dialog>
    <!-- 学生弹窗 -->
    <el-dialog :title="model.type === 'add' ? '添加学生信息':'修改学生信息'"
               :visible.sync="model.studentVisible"
               width="30%">
      <el-form ref="formStudent"
               :model="formStudent"
               label-width="80px">
        <el-form-item label="姓名">
          <el-input v-model="formStudent.name"></el-input>
        </el-form-item>
        <el-form-item label="成绩">
          <el-input v-model="formStudent.achievement"></el-input>
        </el-form-item>
        <el-form-item label="班级">
          <el-select v-model="formStudent.clazz_id"
                     placeholder="请选择班级">
            <el-option v-for="option in classList"
                       :key="option.id"
                       :label="option.name"
                       :value="option.id" />
          </el-select>
        </el-form-item>
      </el-form>
      <span slot="footer"
            class="dialog-footer">
        <el-button @click="model.studentVisible = false">取 消</el-button>
        <el-button type="primary"
                   @click="addStudent()">确 定</el-button>
      </span>
    </el-dialog>
  </div>

</template>
<script>
import axios from 'axios'
export default {
  data () {
    return {
      activeName: 'class',
      classList: [],
      classVisible: false,
      formClass: {},
      className: '',
      title: '新增班级',
      classId: '',
      studentList: [],
      model: {
        studentVisible: false,
        type: 'add'
      },
      formStudent: {},
      studentId: ''
    }
  },
  mounted () {
    this.getClasslist()
    this.getStudentList()
  },
  methods: {
    //请求数据
    getMessage () {
      let token = localStorage.getItem('token')
      axios.get("http://127.0.0.1:7001/jwtmessage", { headers: { token: token } }).then((res) => {
        console.log(res.data)
      })
    },
    //注销
    logout () {
      localStorage.setItem('token', '')
      location.reload()
    },
    //班级列表
    getClasslist () {
      axios.get("http://127.0.0.1:7001/clazz", { params: { name: this.className } }).then((res) => {
        this.classList = res.data
      })
    },
    //搜索班级
    searchClass () {
      this.getClasslist()
    },
    //添加/编辑 班级
    addClass () {
      this.classVisible = false
      if (this.title === '编辑班级') {
        axios.put(`http://127.0.0.1:7001/clazz/${this.classId}`, { data: this.formClass }).then(() => {
          this.$message.success('修改成功');
          this.getClasslist()
        })
      } else {
        axios.post("http://127.0.0.1:7001/clazz", { data: this.formClass }).then(() => {
          this.$message.success('添加成功');
          this.getClasslist()
        })
      }


    },
    //编辑班级
    editClick (item) {
      this.classVisible = true
      this.title = '编辑班级'
      this.$nextTick(() => {
        this.formClass = Object.assign({}, item)
      })
      this.classId = item.id
    },
    //删除班级
    deleteClick (item) {
      this.$confirm('此操作将永久删除该班级, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        axios.delete(`http://127.0.0.1:7001/clazz/${item.id}`).then(() => {
          this.$message.success('删除成功');
          this.getClasslist()
        })
      })
    },
    //学生列表
    getStudentList () {
      axios.get("http://127.0.0.1:7001/students").then((res) => {
        this.studentList = res.data
      })
    },
    //编辑学生
    editStuClick (item) {
      this.model.type = 'update'
      this.model.studentVisible = true
      this.$nextTick(() => {
        this.formStudent = Object.assign({}, item)
      })
      this.studentId = item.id
    },
    //添加、编辑 学生
    addStudent () {
      this.model.studentVisible = false
      const { type } = this.model
      if (type === 'add') {
        axios.post("http://127.0.0.1:7001/students", { data: this.formStudent }).then(() => {
          this.$message.success('添加成功');
          this.activeName = 'student'
          this.getStudentList()
        })
      } else {
        axios.put(`http://127.0.0.1:7001/students/${this.studentId}`, { data: this.formStudent }).then(() => {
          this.$message.success('修改成功');
          this.getStudentList()
        })
      }
    },
    //删除学生
    deleteStuClick (item) {
      this.$confirm('此操作将永久删除该学生, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        axios.delete(`http://127.0.0.1:7001/students/${item.id}`).then(() => {
          this.$message.success('删除成功');
          this.getStudentList()
        })
      })
    },
    handleClick () {

    }

  }
}
</script>
<style lang="stylus" scoped>
.container {
  width: 100%;
  height: 60vh;
  margin-top: 20px;
  &_search {
    width: 30%;
  }
}
</style>
学生管理

学生弹窗

班级弹窗

那么本文到这里也就结束了,此代码,有些地方可能会存在bug没有考虑到,所以博主,会上传git,大家可以去看看最新完整的代码进行查询,感谢你们的观看。顺便,博主推荐一首歌《爱的回归线》,没错,看过《爱情公寓》的小伙伴一定是非常熟悉的歌,我想也许我要像婉瑜给展播的心里那样,虽然我不是很想结束,但是新的生活总要开始,那一段开心的时光,会变成回忆,但是未来的人生还要自己去开启。现在看来,我已经不想逃了,重新开始人生,比救赎更重要吧。以及我想对这几天也经历过我一样经历的好朋友说,我们虽然都是失败者,但是失败并不意味着自己人生的结束,我们都值得变得更好,振作一点。

传送门:
1、第一章 Egg框架概述:https://www.jianshu.com/p/bfdaecb5a18c
2、第二章 路由与控制器:https://www.jianshu.com/p/62edeb088d76
3、第三章 插件:https://www.jianshu.com/p/e8d39f446f46
4、第四章 用户登录状态(上) :https://www.jianshu.com/p/a43759eab484
5、第五章 用户登录状态(下) :https://www.jianshu.com/p/06937d8d1241
6、第六章 中间件:https://www.jianshu.com/p/e369798c0e42

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容