猿实战08——属性库实现之属性关系绑定

tech2022-08-26  132

猿实战是一个原创系列文章,通过实战的方式,采用前后端分离的技术结合SpringMVC Spring Mybatis,手把手教你撸一个完整的电商系统,跟着教程走下来,变身猿人找到工作不是问题。更多精彩内容,敬请大家关注公主号猿人工厂,点击猿人养成获取!

上一章节,猿人君带你完成了属性库的设计以及实现了属性组的管理功能。

今天,猿人工厂君就继续和你一起来实现属性库剩余的功能——属性和属性值。

 

属性和属性值以及属性组

   在上一章节中,我们说商品的区分是由属性和属性值来构成的:

属性和属性值,看上去很不起眼,数据粒度也很小,但是正式因为数据粒度小,灵活多变,组织得当可以强有力的区分千变万化的商品。

你一定很好奇,既然已经有属性和属性值了,为什么还需要属性组的存在呢。这个道理很简单,属性组相当于对粒度过小的属性提供了组织管理功能。可以将一些共性组合起来,更加便利的去描述商品之间的区别。

功能概览

在上一章节,我们已经实际上已经列出了属性和属性值管理的相关功能图了,但是为了方便你查看文章,我们还是再看一下。

属性和属性组的维护都需要提供,列表以及新增/编辑功能,在属性列表,点击管理按钮,进入到当前属性的属性值列表页面。在属性列表,点击“组管理”则切换到属性组管理页面。在属性组和属性值列表页面,分别提供对应的新增/编辑功能。

数据库设计

为了方便您的阅读,不妨再看一下属性和属性值的数据库设计。

 

属性和属性组的后端实现

由于之前已经给出了我们自己定义的代码生成器,属性组的实现也相对简单,考虑到篇幅问题,这一部分我们给出Controller层面的功能实现,service、和dao,还是希望你自行实现,在初学时期,多谢代码,对你熟悉掌握代码编写的技巧,是一个必不可少的环节。

/** * Copyright(c) 2004-2020 pangzi *com.pz.basic.mall.controller.product.property.MallPropertyController.java */ package com.pz.basic.mall.controller.product.property; import com.pz.basic.mall.domain.base.Result; import com.pz.basic.mall.domain.product.property.MallProperty; import com.pz.basic.mall.domain.product.property.query.QueryMallProperty; import com.pz.basic.mall.service.product.property.MallPropertyService; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * * @author pangzi * @date 2020-06-22 20:47:27 * * */ @RestController @RequestMapping("/property") public class MallPropertyController { private MallPropertyService mallPropertyService; public void setMallPropertyService(MallPropertyService mallPropertyService) { this.mallPropertyService =mallPropertyService; } /** * 新增属性 * @param mallProperty * @return */ @RequestMapping("/addMallProperty") public Result<MallProperty> addMallProperty(@RequestBody MallPropertymallProperty){ try{ return mallPropertyService.addMallProperty(mallProperty); }catch(Exception e){ e.printStackTrace(); return new Result(false); } } /** * 修改属性 * @param mallProperty * @return */ @RequestMapping("/updateMallProperty") public Result updateMallProperty(@RequestBody MallProperty mallProperty){ try{ return mallPropertyService.updateMallPropertyById(mallProperty); }catch(Exception e){ e.printStackTrace(); return new Result(false); } } /** * 分页返回属性列表 * @param queryMallProperty * @return */ @RequestMapping("/findByPage") public Result<List<MallProperty>> findByPage(@RequestBodyQueryMallProperty queryMallProperty){ return mallPropertyService.getMallPropertysByPage(queryMallProperty); } } /** * Copyright(c) 2004-2020 pangzi *com.pz.basic.mall.controller.sys.MallPropertyValueController.java */ package com.pz.basic.mall.controller.product.property; import com.pz.basic.mall.domain.base.Result; import com.pz.basic.mall.domain.product.category.MallCategoryProperty; import com.pz.basic.mall.domain.product.category.MallCategoryPropertyValue; import com.pz.basic.mall.domain.product.property.MallPropertyValue; import com.pz.basic.mall.domain.product.property.query.QueryMallPropertyValue; import com.pz.basic.mall.service.product.property.MallPropertyValueService; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * * @author pangzi * @date 2020-06-22 20:47:27 * * */ @RestController @RequestMapping("/propertyValue") public class MallPropertyValueController { private MallPropertyValueServicemallPropertyValueService; public void setMallPropertyValueService(MallPropertyValueService mallPropertyValueService){ this.mallPropertyValueService =mallPropertyValueService; } /** * 新增属性 * * @param mallPropertyValue * @return */ @RequestMapping("/addMallPropertyValue") public Result<MallPropertyValue>addMallPropertyValue(@RequestBody MallPropertyValue mallPropertyValue) { try { return mallPropertyValueService.addMallPropertyValue(mallPropertyValue); } catch (Exception e) { e.printStackTrace(); return new Result(false); } } /** * 修改属性 * * @param mallPropertyValue * @return */ @RequestMapping("/updateMallPropertyValue") public Result updateMallPropertyValue(@RequestBody MallPropertyValue mallPropertyValue) { try { return mallPropertyValueService.updateMallPropertyValueById(mallPropertyValue); } catch (Exception e) { e.printStackTrace(); return new Result(false); } } /** * 分页返回属性列表 * * @param queryMallPropertyValue * @return */ @RequestMapping("/findByPage") public Result<List<MallPropertyValue>> findByPage(@RequestBodyQueryMallPropertyValue queryMallPropertyValue) { return mallPropertyValueService.getMallPropertyValuesByPage(queryMallPropertyValue); } /** * 删除属性 * * @param mallPropertyValue * @return */ @RequestMapping("/deleteMallPropertyValue") public Result deleteMallPropertyValue(@RequestBody MallPropertyValue mallPropertyValue) { try { return mallPropertyValueService.deleteMallPropertyValueById(mallPropertyValue.getPropertyValueId()); } catch (Exception e) { e.printStackTrace(); return new Result(false); } } }

注意噢,由于我们需要在属性列表上,展示属性对应的属性组名称,而我们在设计属性表时,仅仅记录了属性组ID。有很多人在实现这类功能的时候,采用的是和属性组表进行关联查询的方式。这种方式在数据量较大时,容易出现问题,要尽量避免大表间的关联查询。

考虑到目前属性组的数据量不大,以及兼容后续扩展和优化,我们可以采取另一种实现思路——将属性组的数据查出来,通过前端去匹配数据(怎么优化?以后我们再慢慢来解决)。

因为需要检索属性组的数据,所以需要让属性组增加一个返回所有属性组数据的功能,打开MallPropertyGroupController,增加如下代码:

/** * 返回所有启用状态的属性组列表 * @param queryMallPropertyGroup * @return */ @RequestMapping("/findAll") public Result<List<MallPropertyGroup>> findAll(@RequestBodyQueryMallPropertyGroup queryMallPropertyGroup){ queryMallPropertyGroup.setStatus(DataStatusEnum.STATUS_ENABLE.getStatusValue()); return mallPropertyGroupService.getMallPropertyGroupsByQuery(queryMallPropertyGroup); }

 

属性维护前端实现

聊完了后端数据接口的事情,我们一起来看看前端实现的问题,我们先看看属性维护功能的前端实现。

还是老规矩,先封装API:

export functionfetchPropertyList(data) { return request({ url: '/property/findByPage', method: 'post', data }) } export functioncreateProperty(data) { return request({ url: '/property/addMallProperty', method: 'post', data: data }) } export functionupdateProperty(data) { return request({ url: '/property/updateMallProperty', method: 'post', data: data }) }

不要忘记,我们还需要访问属性组的数据噢,所以还是需要封装属性组的新api的。

export function fetchAllPropertyGroupList(data) { return request({ url: '/propertyGroup/findAll', method: 'post', data }) }

然后在组件里引用它。

考虑到你还是不太熟悉整个页面的开发,再次将前端页的整体代码给到你。

<template> <div id="attributeListSearchDiv"class="app-container"> <el-cardclass="filter-container" shadow="never"> <div> <el-form ref="listQuery":model="listQuery" :inline="true"> <el-form-item label="属性ID:" prop="propertyId"> <el-inputv-model="listQuery.propertyId" placeholder="请输入属性ID"clearable /> </el-form-item> <el-form-item label="属性名:"prop="propertyNameLike"> <el-inputv-model="listQuery.propertyNameLike" placeholder="请输入属性中文名"clearable /> </el-form-item> <el-form-item label="状态:"prop="value"> <el-selectv-model="listQuery.value" placeholder="请选择"> <el-option key="0" label="全部" value="" /> <el-option v-for="item invalueList" :key="item.value +'^-^'" :label="item.label" :value="item.value" /> </el-select> </el-form-item> <el-form-item> <el-buttontype="primary" icon="el-icon-search"@click="fetchData()">查询</el-button> <el-buttontype="primary" icon="el-icon-edit"@click="addDate()">新增属性</el-button> <el-buttonicon="el-icon-s-tools" @click="resetForm('listQuery')">重置</el-button> </el-form-item> </el-form> </div> </el-card> <div style="height:20px;"/> <divclass="table-container"> <el-table ref="table" v-loading="listLoading" :data="list" style="width: 100%" border > <!-- @selection-change="handleSelectionChange"--> <!-- <el-table-columntype="selection" width="60" />--> <!-- <el-table-columnlabel="编号"> <templateslot-scope="scope">{{ scope.row.id }}</template> </el-table-column> --> <el-table-column label="属性ID"align="center"> <templateslot-scope="scope">{{ scope.row.propertyId }}</template> </el-table-column> <el-table-column label="属性名"align="center"> <templateslot-scope="scope">{{ scope.row.propertyName }}</template> </el-table-column> <el-table-column label="排序"align="center"> <templateslot-scope="scope">{{ scope.row.sortOrder }}</template> </el-table-column> <el-table-column label="所属组"align="center"> <templateslot-scope="scope">{{ getGroupName(scope.row.groupId)}}</template> </el-table-column> <el-table-column label="状态"align="center"> <template slot-scope="scope">{{scope.row.status == 1 ? "启用" : "停用"}}</template> </el-table-column> <el-table-column label="属性值管理"align="center"> <templateslot-scope="scope"> <astyle="color:#4395ff" @click="handleManage(scope.row)"> 管理</a> </template> </el-table-column> <el-table-column label="操作"width="200"> <templateslot-scope="scope"> <el-button type="primary" size="mini" @click="handleUpdate(scope.row)" >修改 </el-button> </template> </el-table-column> </el-table> </div> <paginationv-show="total>0" :total="total":page.sync="listQuery.page" :limit.sync="listQuery.limit"@pagination="getList" /> <!-- 新增/编辑弹框 --> <el-dialog:title="textMap[dialogStatus]":visible.sync="dialogFormVisible"> <el-form ref="dataForm":rules="rules" :model="temp"label-position="right" label-width="120px"style="width: 320px; margin-left:50px;"> <el-form-item label="属性名:"prop="propertyName"> <el-inputv-model="temp.propertyName" placeholder="请输入属性名"/> </el-form-item> <el-form-item label="属性排序:"prop="sortOrder"> <el-input-number v-model="temp.sortOrder" :min="0" :max="100" placeholder="请输入属性排序" /> </el-form-item> <el-form-item label="所属组:"> <el-selectv-model="temp.groupId" filterable allow-create placeholder="请选择"> <el-option v-for="(item,index) ingroupList" :key="index" :label="item.groupName" :value="item.groupId" /> </el-select> </el-form-item> <el-form-item label="属性状态:"prop="status"> <el-selectv-model="temp.status" placeholder="请选择"> <el-option v-for="(item,index) invalueList" :key="index" :label="item.label" :value="item.value" /> </el-select> </el-form-item> </el-form> <div slot="footer"class="dialog-footer"> <el-button@click="dialogFormVisible = false"> 取消 </el-button> <el-button type="primary"@click="dialogStatus==='create'?createData():updateData()"> 确定 </el-button> </div> </el-dialog> </div> </template> <script> import Pagination from'@/components/Pagination' // secondary package based on el-pagination import { fetchPropertyList,updateProperty, createProperty, fetchAllPropertyGroupList } from'@/api/propertyManage/propertyManage' export default { components: { Pagination }, data() { return { // 弹框校验规则 rules: { propertyName: [{ required: true,message: '请填写属性名', trigger: 'change' }], status: [{ required: true, message: '请选择状态',trigger: 'change' }], sortOrder: [{ required: true, message:'请输入排序',trigger: 'blur' }] }, temp: { propertyId: undefined, // 属性中文名: propertyName: '', // 属性状态 status: 1, // 排序 sortOrder: 0, // 所属组 groupId: 0 }, // 弹框是否显示 dialogFormVisible: false, dialogStatus: '', textMap: { update: '属性修改', create: '属性新增' }, // table集合 list: null, multipleSelection: [], // 状态 valueList: [{ value: 1, label: '启用' }, { value: 0, label: '停用' }], // 所属组 groupList: null, // 分页 total: 0, // loading listLoading: true, listQuery: { page: 1, pageSize: 10, groupType: 1 } } }, created() { // 初始化属性组 this.initPropertyGroupList() }, methods: { // 更新保存方法 updateData() { this.$refs['dataForm'].validate((valid)=> { if (valid) { const tempData = Object.assign({},this.temp) updateProperty(tempData).then(()=> { const index = this.list.findIndex(v=> v.id === this.temp.id) this.list.splice(index, 1,this.temp) this.dialogFormVisible = false this.$notify({ title: 'Success', message: 'Update Successfully', type: 'success', duration: 2000 }) }) } }) }, // 创建保存方法 createData() { this.$refs['dataForm'].validate((valid)=> { if (valid) { createProperty(this.temp).then((res)=> { this.temp.propertyId =res.model.propertyId this.list.unshift(this.temp) this.dialogFormVisible = false this.$notify({ title: 'Success', message: 'Created Successfully', type: 'success', duration: 2000 }) }) } }) }, // 管理 handleManage(row) { this.$emit('selectedAttribute', row) }, // 编辑 handleUpdate(row) { this.temp = Object.assign({}, row) //copy obj this.dialogStatus = 'update' this.dialogFormVisible = true this.$nextTick(() => { this.$refs['dataForm'].clearValidate() }) }, // 列表查询 getList() { this.listLoading = true fetchPropertyList(this.listQuery).then(response => { this.list = response.model this.total = response.totalItem // Just to simulate the time of therequest setTimeout(() => { this.listLoading = false }, 1.5 * 1000) }) }, // 重置 resetTemp() { this.temp = { id: undefined, // 属性值中文名: attributeChineseName: '', // 属性值英文名 attributeEnglishName: '', // 属性值状态 value: '', // 排序 sort: 0, // 所属组 subordinate: '' } }, // 新增 addDate() { this.resetTemp() this.dialogStatus = 'create' this.dialogFormVisible = true this.$nextTick(() => { this.$refs['dataForm'].clearValidate() }) }, // 查询方法 fetchData() { this.listQuery.page = 1 this.listQuery.pageSize = 20 this.getList() }, // 重置表单 resetForm(formName) { this.$refs[formName].resetFields() }, // handleSelectionChange(val) { this.multipleSelection = val }, initPropertyGroupList() { this.listLoading = true fetchAllPropertyGroupList(this.listQuery).then(response => { this.groupList = response.model // Just to simulate the time of therequest setTimeout(() => { this.listLoading = false }, 1.5 * 1000) // 列表查询 this.getList() }) }, getGroupName(groupId) { var rowData = this.groupList.filter(itmer=> { if (itmer.groupId === groupId) { return itmer.groupName } }) if (undefined !== rowData[0]) { return (rowData[0].groupName) } } } } </script> <style scoped> </style>

有几个点你需要好好注意一下噢。首先,再页面初始化时,你需要初始化属性组的数据:

其次,在页面输出属性名称时,你需要定义自己的函数。

在新增/编辑属性时,需要做状态的下拉选择功能,像这类功能的实现,我们往往采用定义状态数组结合v-for指令的方式来实现。

 点击管理,需要进入属性对应的属性值页面,需要绑定事件噢。

属性值维护前端实现

属性值维护的前端实现相对于属性维护的实现要简单一些,现在就给到你,自行参考。

<template> <divid="attributeValueListSearchDiv"> <el-cardclass="filter-container" shadow="never"> <div> <el-form ref="listQuery":model="listQuery" :inline="true"> <el-form-itemv-if="!showFlag" label="属性值ID:" prop="propertyValueId"> <el-inputv-model="listQuery.propertyValueId" placeholder="请输入属性值ID"clearable /> </el-form-item> <el-form-itemv-if="!showFlag" label="属性值:"prop="valueData"> <el-inputv-model="listQuery.valueData" placeholder="请输入属性值中文名"clearable /> </el-form-item> <el-form-itemv-if="!showFlag" label="状态:"prop="value"> <el-selectv-model="listQuery.value" placeholder="请选择"> <el-option key="0" label="全部" value="" /> <el-option v-for="item invalueList" :key="item.value +'^-^'" :label="item.label" :value="item.value" /> </el-select> </el-form-item> <div style="float:right"class="mouseMark"@click="returnBack">&lt;&nbsp;<spanstyle="font-size:15px;margin-right:70px;margin-top:50px;">返回列表</span></div> <el-form-item> <el-buttonv-if="!showFlag" type="primary" icon="el-icon-search"@click="fetchData()">查询</el-button> <el-buttontype="primary" icon="el-icon-edit"@click="addDate()">新增属性值</el-button> <el-buttonv-if="!showFlag" icon="el-icon-s-tools"@click="resetForm('listQuery')">重置</el-button> </el-form-item> </el-form> </div> </el-card> <div style="height:20px;"/> <divclass="table-container"> <el-table ref="table" v-loading="listLoading" :data="list" style="width: 100%" border > <el-table-column label="属性值ID"align="center"> <templateslot-scope="scope">{{ scope.row.propertyValueId}}</template> </el-table-column> <el-table-column label="所属属性ID"align="center"> <templateslot-scope="scope">{{ scope.row.propertyId }}</template> </el-table-column> <el-table-column label="属性值值"align="center"> <templateslot-scope="scope">{{ scope.row.valueData }}</template> </el-table-column> <el-table-column label="排序"align="center"> <templateslot-scope="scope">{{ scope.row.sortOrder }}</template> </el-table-column> <el-table-column label="状态"align="center"> <templateslot-scope="scope">{{ scope.row.status == 1 ? "启用" :"停用" }}</template> </el-table-column> <el-table-column label="所属组"align="center"> <templateslot-scope="scope">{{ getGroupName(scope.row.groupId)}}</template> </el-table-column> <el-table-column label="操作"width="200"> <templateslot-scope="scope"> <el-button type="primary" size="mini" @click="handleUpdate(scope.row)" >修改 </el-button> <el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)" >删除 </el-button> </template> </el-table-column> </el-table> </div> <paginationv-show="total>0" :total="total":page.sync="listQuery.page" :limit.sync="listQuery.limit"@pagination="getList" /> <!-- 新增/编辑弹框 --> <el-dialog:title="textMap[dialogStatus]":visible.sync="dialogFormVisible"> <el-form ref="dataForm":rules="rules" :model="temp"label-position="right" label-width="120px"style="width: 320px; margin-left:50px;"> <el-form-item label="属性值值:"prop="valueData"> <el-inputv-model="temp.valueData" placeholder="请输入属性值值"/> </el-form-item> <el-form-item label="属性值排序:"prop="sortOrder"> <el-input-number v-model="temp.sortOrder" :min="0" :max="100" placeholder="请输入属性值排序" /> </el-form-item> <el-form-item label="所属组:"> <el-selectv-model="temp.groupId" placeholder="请选择"> <el-option v-for="(item,index) ingroupList" :key="index" :label="item.groupName" :value="item.groupId" /> </el-select> </el-form-item> <el-form-item label="属性值状态:"prop="status"> <el-selectv-model="temp.status" placeholder="请选择"> <el-option v-for="(item,index) invalueList" :key="index" :label="item.label" :value="item.value" /> </el-select> </el-form-item> </el-form> <div slot="footer"class="dialog-footer"> <el-button@click="dialogFormVisible = false"> 取消 </el-button> <el-button type="primary"@click="dialogStatus==='create'?createData():updateData()"> 确定 </el-button> </div> </el-dialog> </div> </template> <script> import Pagination from'@/components/Pagination' // secondary package based on el-pagination import {fetchPropertyValueList, updatePropertyValue, createPropertyValue,deletePropertyValue, fetchAllPropertyGroupList } from'@/api/propertyManage/propertyManage' export default { components: { Pagination }, props: ['pid'], data() { return { showFlag: false, // 弹框校验规则 rules: { valueData: [{ required: true, message:'请输入属性值名称',trigger: 'change' }], status: [{ required: true, message: '请选择状态',trigger: 'change' }], sortOrder: [{ required: true, message:'请输入排序',trigger: 'blur' }] }, temp: { propertyValueId: undefined, // 属性值名称: propertyData: '', // 属性值状态 value: 1, // 排序 sort: 0, // 所属组 groupId: 0 }, // 弹框是否显示 dialogFormVisible: false, dialogStatus: '', textMap: { update: '属性值新增', create: '属性值修改' }, // table集合 list: null, multipleSelection: [], // 状态 valueList: [{ value: 1, label: '启用' }, { value: 2, label: '停用' }], // 所属组 groupList: [], // 分页 total: 0, // loading listLoading: true, listQuery: { page: 1, pageSize: 10, groupType: 2, propertyId: this.pid } } }, created() { // 判断一些按钮是否显示 if (this.showflag !== undefined) { this.showFlag = this.showflag } // 初始化属性组 this.initPropertyGroupList() }, methods: { // 删除 handleDelete(index, row) { this.temp.propertyValueId =row.propertyValueId deletePropertyValue(this.temp).then(()=> { this.$notify({ title: 'Success', message: 'Delete Successfully', type: 'success', duration: 2000 }) this.getList() }) }, /** * 回退 */ returnBack() { this.$emit('returnBack') }, // 更新保存方法 updateData() { this.$refs['dataForm'].validate((valid)=> { if (valid) { const tempData = Object.assign({},this.temp) updatePropertyValue(tempData).then(()=> { const index = this.list.findIndex(v=> v.id === this.temp.id) this.list.splice(index, 1,this.temp) this.dialogFormVisible = false this.$notify({ title: 'Success', message: 'Update Successfully', type: 'success', duration: 2000 }) }) } }) }, // 创建保存方法 createData() { this.$refs['dataForm'].validate((valid) =>{ if (valid) { createPropertyValue(this.temp).then((res) => { this.temp.propertyValueId =res.model.propertyValueId this.list.unshift(this.temp) this.dialogFormVisible = false this.$notify({ title: 'Success', message: 'Created Successfully', type: 'success', duration: 2000 }) }) } }) }, // 管理 handleManage(row) { }, // 编辑 handleUpdate(row) { this.temp = Object.assign({}, row) //copy obj this.dialogStatus = 'update' this.dialogFormVisible = true this.$nextTick(() => { this.$refs['dataForm'].clearValidate() }) }, // 列表查询 getList() { this.listLoading = true fetchPropertyValueList(this.listQuery).then(response => { this.list = response.model this.total = response.totalItem // Just to simulate the time of therequest setTimeout(() => { this.listLoading = false }, 1.5 * 1000) }) }, // 重置 resetTemp() { this.temp = { propertyValueId: undefined, propertyId: this.pid, // 属性值: valueData: '', // 属性值状态 status: 1, // 排序 sortOrder: 0, // 所属组 groupId: 0 } }, // 新增 addDate() { this.resetTemp() this.dialogStatus = 'create' this.dialogFormVisible = true this.$nextTick(() => { this.$refs['dataForm'].clearValidate() }) }, // 查询方法 fetchData() { this.getList() }, // 重置表单 resetForm(formName) { this.$refs[formName].resetFields() }, // handleSelectionChange(val) { this.multipleSelection = val }, initPropertyGroupList() { this.listLoading = true fetchAllPropertyGroupList(this.listQuery).then(response => { this.groupList = response.model // Just to simulate the time of therequest setTimeout(() => { this.listLoading = false }, 1.5 * 1000) // 列表查询 this.getList() }) }, getGroupName(groupId) { var rowData = this.groupList.filter(itmer=> { if (itmer.groupId === groupId) { return itmer.groupName } }) if (undefined !== rowData[0]) { return (rowData[0].groupName) } } } } </script> <style scoped> .mouseMark:hover { cursor: pointer; } </style>

有很多朋友一直在问猿人君所要,service和dao层面的代码。实话讲,由于你刚刚初学,有很多代码需要自己去编写,不要觉得mapper文件容易出错,就放弃了。如果真的什么都不写,技术可不是看会的噢。

 

最新回复(0)