我只想卷死各位,或者被各位卷死,在此特别感谢黑马程序员的JavaWeb教程

>项目源码:链接:https://pan.baidu.com/s/1iyXBAU4ct4dPDxQM52TCEA?pwd=y02r 提取码:y02r

本文摘要:

  • 用Element组件编写一个好看的页面
  • 能够完成查询所有功能
  • 能够完成添加功能
  • 能够理解 BaseServlet 思想
  • 能够完成批量删除功能
  • 能够完成分页查询功能
  • 能够完成条件查询功能
  • 完善了课程中没讲的修改和删除功能

案例页面

我们要完成如下页面效果
Element案例.png

那我们现在就开始从上往下找组件来拼凑这个页面,在此之前,我们先创建一个brand_case的项目,并在webapp下新建brand.html,引入Element 的css、js文件 和 Vue.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">

</div>

<script src="js/vue.js"></script>
<script src="element-ui/lib/index.js"></script>
<link rel="stylesheet" href="element-ui/lib/theme-chalk/index.css">

<script>
new Vue({
el:"#app"
})
</script>
</body>
</html>

搜索表单的展示

Element-组件-Form表单-行内表单,这个组件可以完成我们的搜索表单,直接把代码Copy过来进行修改

活动区域换成当前状态,并将区域一,区域二换成启用,禁用审批人换成企业名称,然后在复制一份,改成品牌名称
data和methods都放到我们创建的vue对象中,同时将绑定的对象也改一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!--这里的model要改成brand_->
<el-form :inline="true" :model="formInline" class="demo-form-inline">
<!--审批人换成企业名称,复制一份再换成品牌名称-->
<el-form-item label="审批人">
<el-input v-model="formInline.user" placeholder="审批人"></el-input>
</el-form-item>
<!--这个有下拉选择框,刚好可以换成启用和禁用-->
<el-form-item label="活动区域">
<el-select v-model="formInline.region" placeholder="活动区域">
<!--记得将启用和禁用的value设为1和0-->
<el-option label="区域一" value="shanghai"></el-option>
<el-option label="区域二" value="beijing"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">查询</el-button>
</el-form-item>
</el-form>
<script>
export default {
data() {
return {
//这里改成我们需要的brand
formInline: {
user: '',
region: ''
}
}
},
methods: {
onSubmit() {
//在浏览器控制台输出 this.brand,看看提交之后,控制台有没有预期的数据
console.log('submit!');
}
}
}
</script>

表单部分的修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<el-form :inline="true" :model="brand" class="demo-form-inline">
<el-form-item label="当前状态">
<el-select v-model="brand.status" placeholder="当前状态">
<el-option label="启用" value="1"></el-option>
<el-option label="禁用" value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item label="企业名称">
<el-input v-model="brand.companyName" placeholder="企业名称"></el-input>
</el-form-item>
<el-form-item label="品牌名称">
<el-input v-model="brand.brandName" placeholder="品牌名称"></el-input>
</el-form-item>

<el-form-item>
<el-button type="primary" @click="onSubmit">查询</el-button>
</el-form-item>
</el-form>

data部分的修改
1
2
3
4
5
6
7
8
brand: {
id:'',
brandName: '',
companyName: '',
ordered: '',
description: '',
status: ''
}

methods部分的修改
1
2
3
onSubmit() {
console.log(this.brand);
}

打开浏览器测试,状态选启用,企业名称写华为技术有限公司,品牌名称写华为,控制台输出如下,符合我们的预期

id:””
brandName: “华为”
companyName: “华为技术有限公司”
description: “”
ordered: “”
status: “1”

批量删除和修改的按钮

Element-组件-Button按钮-基础用法,这个组件可以给我们提供好看的按钮,直接Copy过来

1
2
3
4
<el-row>
<el-button type="danger" plain>批量删除</el-button>
<el-button type="primary" plain>新增</el-button>
</el-row>

新增对话框展示

删除按钮我们暂时不动,但是当我们点击新增按钮时,需要弹出一个对话框让我们填写信息

编辑品牌.png

Element-组件-Dialog对话框,这个组件可以帮我们达到目的

需要设置visible属性,它接收Boolean,当为true时显示 Dialog。Dialog 分为两个部分:body和footer,footer需要具名为footer的slot。title属性用于定义标题,它是可选的,默认值为空。最后,本例还展示了before-close的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!--这里绑定了一个点击事件,当点击时,将dialogVisible设为true,
然后我们就可以看到对话框了,所以我们要给新增按钮绑定这个点击事件-->
<el-button type="text" @click="dialogVisible = true">点击打开 Dialog</el-button>

<!--这里的title换成编辑品牌-->
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose">
<!--将span换成我们需要的表单-->
<span>这是一段信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>

<script>
export default {
data() {
return {
//这个data可以原封不动的加入到我们的代码中
dialogVisible: false
};
},
methods: {
//确认关闭这个method我们就不用了
handleClose(done) {
this.$confirm('确认关闭?')
.then(_ => {
done();
})
.catch(_ => {});
}
}
};
</script>

由于我们要在对话框中再搞一个表单,所以我们继续去官网看看有没有合适的组件

Element-组件-典型表单可以完成我们的需求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<!--绑定的model可以用我们之前写的brand_->
<el-form ref="form" :model="form" label-width="80px">
<!--活动名称改成品牌名称,企业名称和排序-->
<el-form-item label="活动名称">
<el-input v-model="form.name"></el-input>
</el-form-item>
<!--暂时不需要这个下拉选择框-->
<el-form-item label="活动区域">
<el-select v-model="form.region" placeholder="请选择活动区域">
<el-option label="区域一" value="shanghai"></el-option>
<el-option label="区域二" value="beijing"></el-option>
</el-select>
</el-form-item>
<!--这个也暂时不需要-->
<el-form-item label="活动时间">
<el-col :span="11">
<el-date-picker type="date" placeholder="选择日期" v-model="form.date1" style="width: 100%;"></el-date-picker>
</el-col>
<el-col class="line" :span="2">-</el-col>
<el-col :span="11">
<el-time-picker placeholder="选择时间" v-model="form.date2" style="width: 100%;"></el-time-picker>
</el-col>
</el-form-item>
<!--这个可以改成启用和禁用的状态,并给启用和禁用赋值,active-text和inactive-text-->
<el-form-item label="即时配送">
<el-switch v-model="form.delivery"></el-switch>
</el-form-item>
<!--复选框也不需要-->
<el-form-item label="活动性质">
<el-checkbox-group v-model="form.type">
<el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
<el-checkbox label="地推活动" name="type"></el-checkbox>
<el-checkbox label="线下主题活动" name="type"></el-checkbox>
<el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<!--单选框也不要-->
<el-form-item label="特殊资源">
<el-radio-group v-model="form.resource">
<el-radio label="线上品牌商赞助"></el-radio>
<el-radio label="线下场地免费"></el-radio>
</el-radio-group>
</el-form-item>
<!--这个文本域可以改成备注-->
<el-form-item label="活动形式">
<el-input type="textarea" v-model="form.desc"></el-input>
</el-form-item>
<el-form-item>
<!--这俩按钮也不要了,改成提交和取消就行,
提交的点击事件改为"addBrand",在methods中改,
取消绑定点击事件,@click="dialogVisible = false",这样就能关闭对话框-->
<el-button type="primary" @click="onSubmit">立即创建</el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
<script>
export default {
data() {
return {
//这个就不需要了,用前面写好的brand就行
form: {
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
resource: '',
desc: ''
}
}
},
methods: {
//改成addBrand,在控制台输出this.brand,看看是否有预期的结果
onSubmit() {
console.log('submit!');
}
}
}
</script>

其实这里偷懒了,点击提交后,对话框不会自动关闭(后面会修改addBrand函数,就可以自动关闭了),而且在对话框中输入数据,搜索栏也会跟着变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 <el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%">
<el-form ref="form" :model="brand" label-width="80px">
<el-form-item label="企业名称">
<el-input v-model="brand.companyName"></el-input>
</el-form-item>
<el-form-item label="品牌名称">
<el-input v-model="brand.brandName"></el-input>
</el-form-item>
<el-form-item label="排序">
<el-input v-model="brand.ordered"></el-input>
</el-form-item>
<el-form-item label="状态">
<el-switch v-model="brand.status" active-value="1" inactive-value="0"></el-switch>
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="brand.description"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="addBrand">提交</el-button>
<el-button @click="dialogVisible = true">取消</el-button>
</el-form-item>
</el-form>
</span>
</el-dialog>

在浏览器访问页面,输入数据,点击提交之前,打开浏览器的控制台,得到以下输出,符合预期

id: “”
brandName: “EA”
companyName: “Apex”
description: “大逃杀”
ordered: “10”
status: “1”

表格展示

Element-组件-Table表格可以完成我们的需求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<template>
<el-table
:data="tableData"
style="width: 100%"
:row-class-name="tableRowClassName">
<!--把列名和列宽改成我们需要的-->
<el-table-column
prop="date"
label="日期"
width="180">
</el-table-column>
<el-table-column
prop="name"
label="姓名"
width="180">
</el-table-column>
<el-table-column
prop="address"
label="地址">
</el-table-column>
</el-table>
</template>

<!--CSS样式放在head里,别忘了-->
<style>
.el-table .warning-row {
background: oldlace;
}

.el-table .success-row {
background: #f0f9eb;
}
</style>

<script>
export default {
methods: {
//这个直接照抄
tableRowClassName({row, rowIndex}) {
if (rowIndex === 1) {
return 'warning-row';
} else if (rowIndex === 3) {
return 'success-row';
}
return '';
}
},
data() {
return {
//这个换成品牌数据
tableData: [{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}]
}
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<template>
<el-table
:data="tableData"
style="width: 100%"
:row-class-name="tableRowClassName">
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column
type="index"
width="50">
</el-table-column>
<el-table-column
align="center"
prop="brandName"
label="品牌名称"
width="180">
</el-table-column>
<el-table-column
align="center"
prop="companyName"
label="企业名称"
width="180">
</el-table-column>
<el-table-column
align="center"
prop="ordered"
label="排序"
width="180">
</el-table-column>
<el-table-column
align="center"
prop="status"
label="当前状态"
width="180">
</el-table-column>
<el-table-column
align="center"
label="操作">
<el-row>
<el-button type="primary">修改</el-button>
<el-button type="danger">删除</el-button>
</el-row>
</el-table-column>
</el-table>
</template>

data部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 tableData: [{
brandName: '华为',
companyName: '华为技术有限公司',
ordered: '100',
status: '1',
handle: '修改'
}, {
brandName: '华为',
companyName: '华为技术有限公司',
ordered: '100',
status: '1',
handle: '修改'
}, {
brandName: '华为',
companyName: '华为技术有限公司',
ordered: '100',
status: '1',
handle: '修改'
}, {
brandName: '华为',
companyName: '华为技术有限公司',
ordered: '100',
status: '1',
handle: '修改'
}]

method部分直接照抄就行

1
2
3
4
5
6
7
8
 tableRowClassName({row, rowIndex}) {
if (rowIndex === 1) {
return 'warning-row';
} else if (rowIndex === 3) {
return 'success-row';
}
return '';
}

分页条展示

Element-组件-Pagination 分页可以帮我们完成需求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<template>
<div class="block">
<span class="demonstration">显示总数</span>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page.sync="currentPage1"
:page-size="100"
layout="total, prev, pager, next"
:total="1000">
</el-pagination>
</div>
<div class="block">
<span class="demonstration">调整每页显示条数</span>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page.sync="currentPage2"
:page-sizes="[100, 200, 300, 400]"
:page-size="100"
layout="sizes, prev, pager, next"
:total="1000">
</el-pagination>
</div>
<div class="block">
<span class="demonstration">直接前往</span>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page.sync="currentPage3"
:page-size="100"
layout="prev, pager, next, jumper"
:total="1000">
</el-pagination>
</div>
<div class="block">
<span class="demonstration">完整功能</span>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage4"
:page-sizes="[100, 200, 300, 400]"
:page-size="100"
layout="total, sizes, prev, pager, next, jumper"
:total="400">
</el-pagination>
</div>
</template>
<script>
export default {
methods: {
handleSizeChange(val) {
console.log(`每页 ${val} 条`);
},
handleCurrentChange(val) {
console.log(`当前页: ${val}`);
}
},
data() {
return {
currentPage1: 5,
currentPage2: 5,
currentPage3: 5,
currentPage4: 4
};
}
}
</script>

HTML部分

1
2
3
4
5
6
7
8
9
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5, 10, 15, 20]"
:page-size="100"
layout="total, sizes, prev, pager, next, jumper"
:total="400">
</el-pagination>

data部分

1
currentPage: 1

method部分直接照抄

完整代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>

<style>
.el-table .warning-row {
background: oldlace;
}

.el-table .success-row {
background: #f0f9eb;
}
</style>

</head>
<body>
<div id="app">
<!--搜索表单的-->
<el-form :inline="true" :model="brand" class="demo-form-inline">
<el-form-item label="当前状态">
<el-select v-model="brand.status" placeholder="当前状态">
<el-option label="启用" value="1"></el-option>
<el-option label="禁用" value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item label="企业名称">
<el-input v-model="brand.companyName" placeholder="企业名称"></el-input>
</el-form-item>
<el-form-item label="品牌名称">
<el-input v-model="brand.brandName" placeholder="品牌名称"></el-input>
</el-form-item>

<el-form-item>
<el-button type="primary" @click="onSubmit">查询</el-button>
</el-form-item>
</el-form>

<!--删除和新增的按钮-->
<el-row>
<el-button type="danger" plain>批量删除</el-button>
<el-button @click="dialogVisible = true" type="primary" plain>新增</el-button>
</el-row>

<!--对话框弹出编辑品牌-->
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%">
<el-form ref="form" :model="brand" label-width="80px">
<el-form-item label="企业名称">
<el-input v-model="brand.companyName"></el-input>
</el-form-item>
<el-form-item label="品牌名称">
<el-input v-model="brand.brandName"></el-input>
</el-form-item>
<el-form-item label="排序">
<el-input v-model="brand.ordered"></el-input>
</el-form-item>
<el-form-item label="状态">
<el-switch v-model="brand.status" active-value="1" inactive-value="0"></el-switch>
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="brand.description"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="addBrand">提交</el-button>
<el-button @click="dialogVisible = true">取消</el-button>
</el-form-item>
</el-form>
</span>
</el-dialog>

<!--表单数据-->
<template>
<el-table
:data="tableData"
style="width: 100%"
:row-class-name="tableRowClassName">
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column
type="index"
width="50">
</el-table-column>
<el-table-column
align="center"
prop="brandName"
label="品牌名称"
width="180">
</el-table-column>
<el-table-column
align="center"
prop="companyName"
label="企业名称"
width="180">
</el-table-column>
<el-table-column
align="center"
prop="ordered"
label="排序"
width="180">
</el-table-column>
<el-table-column
align="center"
prop="status"
label="当前状态"
width="180">
</el-table-column>
<el-table-column
align="center"
label="操作">
<el-row>
<el-button type="primary">修改</el-button>
<el-button type="danger">删除</el-button>
</el-row>
</el-table-column>
</el-table>
</template>

<!--分页条-->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5, 10, 15, 20]"
:page-size="5"
layout="total, sizes, prev, pager, next, jumper"
:total="400">
</el-pagination>

</div>

<script src="js/vue.js"></script>
<script src="element-ui/lib/index.js"></script>
<link rel="stylesheet" href="element-ui/lib/theme-chalk/index.css">

<script>
new Vue({
el: "#app",
data() {
return {
//品牌数据
brand: {
id:'',
brandName: '',
companyName: '',
ordered: '',
description: '',
status: ''
},
//新增对话框是否显示
dialogVisible: false,
//表格数据
tableData: [{
brandName: '华为',
companyName: '华为技术有限公司',
ordered: '100',
status: '1',
handle: '修改'
}, {
brandName: '华为',
companyName: '华为技术有限公司',
ordered: '100',
status: '1',
handle: '修改'
}, {
brandName: '华为',
companyName: '华为技术有限公司',
ordered: '100',
status: '1',
handle: '修改'
}, {
brandName: '华为',
companyName: '华为技术有限公司',
ordered: '100',
status: '1',
handle: '修改'
}],
currentPage: 1
}
},
methods: {
onSubmit() {
console.log(this.brand);
},
addBrand() {
console.log(this.brand);
},
tableRowClassName({row, rowIndex}) {
if (rowIndex === 1) {
return 'warning-row';
} else if (rowIndex === 3) {
return 'success-row';
}
return '';
},
handleSizeChange(val) {
console.log(`每页 ${val} 条`);
},
handleCurrentChange(val) {
console.log(`当前页: ${val}`);
}

}
})
</script>
</body>
</html>

环境准备

工程准备

在Java目录下创建com.blog.mappercom.blog.pojocom.blog.servicecom.blog.utilcom.blog.web这五个包

  • 在mapper包下新建BrandMapper接口

    1
    2
    3
    4
    5
    import java.util.List;

    public interface BrandMapper {

    }
  • 在resource目录下新建com/blog/mapper路径,新建一个BrandMapper.xml文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.blog.mapper.BrandMapper">

    <resultMap id="brandResultMap" type="brand">
    <result property="brandName" column="brand_name" />
    <result property="companyName" column="company_name" />
    </resultMap>
    </mapper>
  • 在resource目录下新建mybatis-config.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    <!--起别名-->
    <typeAliases>
    <package name="com.blog.pojo"/>
    </typeAliases>

    <environments default="development">
    <environment id="development">
    <transactionManager type="JDBC"/>
    <dataSource type="POOLED">
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:13306/db1?useSSL=false&amp;useServerPrepStmts=true"/>
    <property name="username" value="root"/>
    <property name="password" value="PASSWORD"/>
    </dataSource>
    </environment>
    </environments>
    <mappers>
    <!--扫描mapper-->
    <package name="com.blog.mapper"/>
    </mappers>
    </configuration>
  • 在pojo包下新建Brand

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    public class Brand {
    // id 主键
    private Integer id;
    // 品牌名称
    private String brandName;
    // 企业名称
    private String companyName;
    // 排序字段
    private Integer ordered;
    // 描述信息
    private String description;
    // 状态:0:禁用 1:启用
    private Integer status;


    public Integer getId() {
    return id;
    }

    public void setId(Integer id) {
    this.id = id;
    }

    public String getBrandName() {
    return brandName;
    }

    public void setBrandName(String brandName) {
    this.brandName = brandName;
    }

    public String getCompanyName() {
    return companyName;
    }

    public void setCompanyName(String companyName) {
    this.companyName = companyName;
    }

    public Integer getOrdered() {
    return ordered;
    }

    public void setOrdered(Integer ordered) {
    this.ordered = ordered;
    }

    public String getDescription() {
    return description;
    }

    public void setDescription(String description) {
    this.description = description;
    }

    public Integer getStatus() {
    return status;
    }
    //逻辑视图
    public String getStatusStr(){
    if (status == null){
    return "未知";
    }
    return status == 0 ? "禁用":"启用";
    }

    public void setStatus(Integer status) {
    this.status = status;
    }

    @Override
    public String toString() {
    return "Brand{" +
    "id=" + id +
    ", brandName='" + brandName + '\'' +
    ", companyName='" + companyName + '\'' +
    ", ordered=" + ordered +
    ", description='" + description + '\'' +
    ", status=" + status +
    '}';
    }
    }
  • 在util包下导入工具类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class SqlSessionFactoryUtils {

    private static SqlSessionFactory sqlSessionFactory;

    static {
    //静态代码块会随着类的加载而自动执行,且只执行一次
    try {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    public static SqlSessionFactory getSqlSessionFactory(){
    return sqlSessionFactory;
    }
    }
  • 在pom.xml中导入坐标

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>brand_demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
    <!-- mybatis -->
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.5</version>
    </dependency>

    <!--mysql-->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.34</version>
    </dependency>

    <!--servlet-->
    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
    </dependency>

    <!--fastjson-->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
    </dependency>
    </dependencies>

    <build>
    <plugins>
    <plugin>
    <groupId>org.apache.tomcat.maven</groupId>
    <artifactId>tomcat7-maven-plugin</artifactId>
    <version>2.2</version>
    </plugin>
    </plugins>
    </build>
    </project>

创建表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
-- 删除tb_brand表
drop table if exists tb_brand;
-- 创建tb_brand表
create table tb_brand
(
-- id 主键
id int primary key auto_increment,
-- 品牌名称
brand_name varchar(20),
-- 企业名称
company_name varchar(20),
-- 排序字段
ordered int,
-- 描述信息
description varchar(100),
-- 状态:0:禁用 1:启用
status int
);
-- 添加数据
insert into tb_brand (brand_name, company_name, ordered, description, status)
values
('华为', '华为技术有限公司', 100, '万物互联', 1),
('小米', '小米科技有限公司', 50, 'are you ok', 1),
('格力', '格力电器股份有限公司', 30, '让世界爱上中国造', 1),
('阿里巴巴', '阿里巴巴集团控股有限公司', 10, '买买买', 1),
('腾讯', '腾讯计算机系统有限公司', 50, '玩玩玩', 0),
('百度', '百度在线网络技术公司', 5, '搜搜搜', 0),
('京东', '北京京东世纪贸易有限公司', 40, '就是快', 1),
('小米', '小米科技有限公司', 50, 'are you ok', 1),
('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0),
('华为', '华为技术有限公司', 100, '万物互联', 1),
('小米', '小米科技有限公司', 50, 'are you ok', 1),
('格力', '格力电器股份有限公司', 30, '让世界爱上中国造', 1),
('阿里巴巴', '阿里巴巴集团控股有限公司', 10, '买买买', 1),
('腾讯', '腾讯计算机系统有限公司', 50, '玩玩玩', 0),
('百度', '百度在线网络技术公司', 5, '搜搜搜', 0),
('京东', '北京京东世纪贸易有限公司', 40, '就是快', 1),
('华为', '华为技术有限公司', 100, '万物互联', 1),
('小米', '小米科技有限公司', 50, 'are you ok', 1),
('格力', '格力电器股份有限公司', 30, '让世界爱上中国造', 1),
('阿里巴巴', '阿里巴巴集团控股有限公司', 10, '买买买', 1),
('腾讯', '腾讯计算机系统有限公司', 50, '玩玩玩', 0),
('百度', '百度在线网络技术公司', 5, '搜搜搜', 0),
('京东', '北京京东世纪贸易有限公司', 40, '就是快', 1),
('小米', '小米科技有限公司', 50, 'are you ok', 1),
('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0),
('华为', '华为技术有限公司', 100, '万物互联', 1),
('小米', '小米科技有限公司', 50, 'are you ok', 1),
('格力', '格力电器股份有限公司', 30, '让世界爱上中国造', 1),
('阿里巴巴', '阿里巴巴集团控股有限公司', 10, '买买买', 1),
('腾讯', '腾讯计算机系统有限公司', 50, '玩玩玩', 0),
('百度', '百度在线网络技术公司', 5, '搜搜搜', 0),
('京东', '北京京东世纪贸易有限公司', 40, '就是快', 1),
('华为', '华为技术有限公司', 100, '万物互联', 1),
('小米', '小米科技有限公司', 50, 'are you ok', 1),
('格力', '格力电器股份有限公司', 30, '让世界爱上中国造', 1),
('阿里巴巴', '阿里巴巴集团控股有限公司', 10, '买买买', 1),
('腾讯', '腾讯计算机系统有限公司', 50, '玩玩玩', 0),
('百度', '百度在线网络技术公司', 5, '搜搜搜', 0),
('京东', '北京京东世纪贸易有限公司', 40, '就是快', 1),
('小米', '小米科技有限公司', 50, 'are you ok', 1),
('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0),
('华为', '华为技术有限公司', 100, '万物互联', 1),
('小米', '小米科技有限公司', 50, 'are you ok', 1),
('格力', '格力电器股份有限公司', 30, '让世界爱上中国造', 1),
('阿里巴巴', '阿里巴巴集团控股有限公司', 10, '买买买', 1),
('腾讯', '腾讯计算机系统有限公司', 50, '玩玩玩', 0),
('百度', '百度在线网络技术公司', 5, '搜搜搜', 0),
('京东', '北京京东世纪贸易有限公司', 40, '就是快', 1)
;


SELECT * FROM tb_brand;

查询所有功能

后端实现

dao方法实现

  • 由于表中有些字段名和实体类中的属性名没有对应,所以需要在 com/itheima/mapper/BrandMapper.xml 映射配置文件中定义结果映射 ,使用resultMap 标签。映射配置文件内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.blog.mapper.BrandMapper">

    <resultMap id="brandResultMap" type="brand">
    <result property="brandName" column="brand_name" />
    <result property="companyName" column="company_name" />
    </resultMap>
    </mapper>
  • com.blog.mapper.BrandMapper接口中定义抽象方法,并使用@Select注解编写SQL语句

    1
    2
    3
    4
    5
    6
    7
    /**
    * 查询所有
    * @return
    */
    @Select("select * from tb_brand")
    @ResultMap("brandResultMap")
    List<Brand> selectAll();

service方法实现

  • com.itheima.service 包下创建 BrandService 接口,在该接口中定义查询所有的抽象方法

    1
    2
    3
    4
    5
    6
    7
    8
    public interface BrandService {
    /**
    * 查询所有
    * @return
    */
    List<Brand> selectAll();
    }

  • 并在 com.itheima.service 下再创建 impl 包;impl 表示是放 service 层接口的实现类的包。 在该包下创建名为 BrandServiceImpl

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class BrandServiceImpl implements BrandService {
    //1. 创建SqlSessionFactory工厂对象
    SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();

    @Override
    public List<Brand> selectAll() {
    //2. 获取SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //3. 获取BrandMapper
    BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
    //4. 调用方法
    List<Brand> brands = mapper.selectAll();
    //5. 关闭资源
    sqlSession.close();
    return brands;
    }
    }
  • 为什么要创建BrandServiceImpl?

    • 因为service定义了接口后,在 servlet 中就可以使用多态的形式创建Service实现类的对象
      这里使用多态是因为方便解除 Servletservice 的耦合。从上面的代码我们可以看到 SelectAllServlet 类和 BrandServiceImpl 类之间是耦合在一起的,如果后期 BrandService 有其它的实现类(例如叫 BrandServiceImpl1),那就需要修改 SelectAllServlet 类中的代码。后面学习了 Spring 框架后就可以解除 SelectAllServlet 类和BrandServiceImpl的代码耦合。但现在还做不到解耦合,在这里只需要理解为什么定义接口即可。

servlet方法实现

  • com.itheima.web.servlet 包下定义名为 SelectAllServlet 的查询所有的 servlet。该 servlet 逻辑如下:
    • 调用service的 selectAll() 方法查询所有的品牌数据,并接口返回结果
    • 将返回的结果转换为 json 数据
    • 响应 json 数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@WebServlet("/selectAllServlet")
public class SelectAllServlet extends HttpServlet {
private BrandServiceImpl brandService = new BrandServiceImpl();

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 调用方法,返回结果
List<Brand> brands = brandService.selectAll();
//2. 将返回的结果转换为json数据
String jsonString = JSON.toJSONString(brands);
//告知浏览器响应的数据是什么, 告知浏览器使用什么字符集进行解码
response.setContentType("text/json;charset=utf-8");
//3. 响应json数据
response.getWriter().write(jsonString);
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}

测试后端程序

在浏览器输入访问 servlet 的资源路径 http://localhost:8080/brand_case/selectAllServlet ,如果没有报错,并能看到如下信息表明后端程序没有问题

[{“brandName”:”华为”,”companyName”:”华为技术有限公司”,”description”:”万物互联”,”id”:1,”ordered”:100,”status”:1,”statusStr”:”启用”},

前端实现

前端需要在页面加载完毕后发送 ajax 请求,所以发送请求的逻辑应该放在 mounted() 钩子函数中。而响应回来的数据需要赋值给表格绑定的数据模型

1
2
3
4
5
6
7
8
9
10
mounted(){
//当页面加载完成后,发送异步请求,获取数据
var _this = this;
axios({
method:"get",
url:"http://localhost:8080/brand_case/selectAllServlet"
}).then(function (resp) {
_this.tableData = resp.data;
})
}

添加功能

编辑品牌.png

上图是添加数据的对话框,当点击 提交 按钮后就需要将数据提交到后端,并将数据保存到数据库中。
页面发送请求时,需要将输入框输入的内容提交给后端程序,而这里是以 json 格式进行传递的。

后端实现

dao方法实现

  • BrandMapper 接口中定义 add() 添加方法,并使用 @Insert 注解编写sql语句
    1
    2
    3
    4
    5
    6
    7
    /**
    * 添加数据
    * @param brand
    */
    @Insert("insert into tb_brand values(null,#{brandName},#{companyName},#{ordered},#{description},#{status})")
    @ResultMap("brandResultMap")
    void add(Brand brand);

service方法实现

  • BrandService 接口中定义 add() 添加数据的业务逻辑方法

    1
    2
    3
    4
    5
    /**
    * 添加数据
    * @param brand
    */
    void add(Brand brand);
  • BrandServiceImpl 类中重写 add() 方法,并进行业务逻辑实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Override
    public void add(Brand brand) {
    //2. 获取SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //3. 获取BrandMapper
    BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
    //4. 调用方法
    mapper.add(brand);
    sqlSession.commit();//提交事务
    //5. 释放资源
    sqlSession.close();
    }

    注意:增删改操作一定要提交事务。

servlet方法实现

  • com.blog.web.servlet 包写定义名为 AddServlet 的 Servlet。该 Servlet 的逻辑如下:

    • 接收页面提交的数据。页面到时候提交的数据是 json 格式的数据,所以此处需要使用输入流读取数据
    • 将接收到的数据转换为 Brand 对象
    • 调用 service 的 add() 方法进行添加的业务逻辑处理
    • 给浏览器响应添加成功的标识,这里直接给浏览器响应 success 字符串表示成功
  • servlet 代码实现如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    @WebServlet("/addServlet")
    public class AddServlet extends HttpServlet {
    private BrandService brandService = new BrandServiceImpl();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //解决乱码问题,一定要把它放在第一行
    request.setCharacterEncoding("utf-8");
    //获取输入流
    BufferedReader br = request.getReader();
    //读取数据
    String params = br.readLine();
    //将json格式的数据转换为一个Brand对象
    Brand brand = JSON.parseObject(params, Brand.class);
    //调用方法
    brandService.add(brand);
    //如果能顺利执行到此行,则说明添加成功,给浏览器响应一个success字符串表示成功
    response.getWriter().write("success");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }

前端实现

我们之前给提交按钮的点击事件绑定了一个addBrand函数,所以添加数据功能的逻辑代码应该写在 addBrand() 函数中。在此方法中需要发送异步请求并将表单中输入的数据作为参数进行传递。如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
addBrand() {
var _this = this;
axios({
method:"post",
url:"http://localhost:8080/brand_case/addServlet",
data:_this.brand
}).then(function (resp){
if(resp.data == "success"){
_this.dialogVisible = false
_this.selectAll();
_this.$message({
message: '恭喜你,添加成功',
type: 'success'
});
}
})
}

then 函数中的匿名函数是成功后的回调函数,而 resp.data 就可以获取到响应回来的数据,如果值是 success 表示数据添加成功。那么添加成功之后有哪些操作呢?

  1. 先将对话框关闭,设置dialogVisible为false,就可以关闭对话框了
    1
    _this.dialogVisible = false
  2. 重新查询数据,这部分代码其实就是刚刚查询所有的前端代码,将其封装成一个selectAll()函数
    1
    2
    3
    4
    5
    6
    7
    var _this = this;
    axios({
    method:"get",
    url:"http://localhost:8080/brand_case/selectAllServlet"
    }).then(function (resp) {
    _this.tableData = resp.data;
    })
  3. 给出提示信息,这部分其实是直接从ElementUI官网扒的代码,想用花里胡哨的可以戳我
    1
    2
    3
    4
    _this.$message({
    message: '恭喜你,添加成功',
    type: 'success'
    });

servlet优化

问题导入

Web 层的 Servlet 个数太多了,不利于管理和编写。
每一个功能都需要定义一个 servlet,一个模块需要实现增删改查功能,就需要4个 servlet,模块一多就会造成servlet 泛滥。此时我们就想 servlet 能不能像 service 一样,一个模块只定义一个 servlet,而每一个功能只需要在该 servlet 中定义对应的方法。例如下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@WebServlet("/brand/*")
public class BrandServlet {
//查询所有
public void selectAll(...) {}

//添加数据
public void add(...) {}

//修改数据
public void update(...) {}

//删除删除
public void delete(...) {}
}

而我们知道发送请求 servlettomcat 会自动的调用 service() 方法,HttpServlet的service()方法如下,当我们访问该 servlet 时会根据请求方式将请求分发给 doGet() 或者 doPost() 等方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}

那么我们也可以仿照这样请求分发的思想,在 service() 方法中根据具体的操作调用对应的方法,如:查询所有就调用 selectAll() 方法,添加企业信息就调用 add() 方法。

为了做到通用,我们定义一个通用的 servlet 类,再定义其他的 servlet 就不用继承 HttpServlet,而是继承我们自定义的 BaseServlet,在BaseServlet 中调用具体 servlet(如BrandServlet)中的对应方法。

1
2
3
4
5
6
public class BaseServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

}
}

BrandServlet定义修改如下

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BrandServlet extends BaseServlet{
//用户实现分页查询
public void selectAll() {}

//添加企业信息
public void add() {}

//修改企业信息
public void update() {}

//删除企业信息
public void delete() {}
}

那么如何在 BaseServlet 中调用对应的方法呢?比如查询所有就调用 selectAll() 方法。

可以规定在发送请求时,请求资源的二级路径(/brandServlet/selectAll)和需要调用的方法名相同,如:
查询所有数据的路径以后就需要写成: http://localhost:8080/brand_case/brand/selectAll
添加数据的路径以后就需要写成: http://localhost:8080/brand_case/brand/add
修改数据的路径以后就需要写成: http://localhost:8080/brand_case/brand/update
删除数据的路径以后就需要写成: http://localhost:8080/brand_case/brand/delete

这样的话,在 BaseServlet 中就需要获取到资源的二级路径作为方法名,然后调用该方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class BaseServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求路径
String requestURI = req.getRequestURI();
//找到最后一个斜杠,它后面就是我们要的方法名
int index = requestURI.lastIndexOf('/');
//取子串就是斜杠后面的字符串
String methodName = requestURI.substring(index + 1);
//获取当前对象的字节码文件
Class<? extends BaseServlet> clazz = this.getClass();
try {
//根据前面的方法名,获取方法
Method method = clazz.getMethod(methodName,...);
//执行方法
method.invoke(this,...)
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}

通过上面代码发现根据方法名获取对应方法的 Method 对象时需要指定方法参数的字节码对象。解决这个问题,可以将方法的参数类型规定死,而方法中可能需要用到 request 对象和 response 对象,所以指定方法的参数为 HttpServletRequestHttpServletResponse,那么 BrandServlet 代码就可以改进为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BrandServlet extends BaseServlet{
//用户实现分页查询
public void selectAll(HttpServletRequest req, HttpServletResponse resp) {}

//添加企业信息
public void add(HttpServletRequest req, HttpServletResponse resp) {}

//修改企业信息
public void update(HttpServletRequest req, HttpServletResponse resp) {}

//删除企业信息
public void delete(HttpServletRequest req, HttpServletResponse resp) {}
}

BaseServlet代码可以改进为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class BaseServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求路径
String uri = req.getRequestURI(); // 例如路径为:/brand_case/brand/selectAll
// 找到最后一个 / ,以此来划分方法名
int index = uri.lastIndexOf('/');
// 获取到资源的二级路径 selectAll
String methodName = uri.substring(index + 1);

//获取BrandServlet /UserServlet 字节码对象 Class
Class<? extends BaseServlet> cls = this.getClass();
//获取根据上面获取的方法名,来Method对象
try {
Method method = cls.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
//执行方法
method.invoke(this,req,resp);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}

代码优化

后端优化

定义了 BaseServlet 后,针对品牌模块我们定义一个 BrandServlet 的 Servlet,并使其继承 BaseServlet 。在BrandServlet中定义 以下功能的方法:

  • 查询所有 功能:方法名声明为 selectAll ,并将之前的 SelectAllServlet 中的逻辑代码拷贝到该方法中
  • 添加数据 功能:方法名声明为 add ,并将之前的 AddServlet 中的逻辑代码拷贝到该方法中

具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@WebServlet("/brand/*")
public class BrandServlet extends BaseServlet{
private BrandService brandService = new BrandServiceImpl();

public void selectAll(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 调用service查询
List<Brand> brands = brandService.selectAll();

//2. 转为JSON
String jsonString = JSON.toJSONString(brands);
//3. 写数据
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(jsonString);
}

public void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//处理乱码问题
request.setCharacterEncoding("utf-8");
//1. 获取输入流,接收品牌数据json字符串
BufferedReader br = request.getReader();
String params = br.readLine();

//将json字符串转为Brand对象
Brand brand = JSON.parseObject(params, Brand.class);

//2. 调用service添加
brandService.add(brand);

//3. 响应成功的标识
response.getWriter().write("success");
}
}

前端优化

页面中之前发送的请求的路径都需要进行修改,selectAll()addBrand()函数中发送异步请求的 url 应该改为http://localhost:8080/brand_case/brand/selectAllhttp://localhost:8080/brand_case/brand/add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 selectAll(){
var _this = this;
axios({
method:"get",
url:"http://localhost:8080/brand_case/brand/selectAll"
}).then(function (resp){
_this.tableData = resp.data;
})
},
addBrand() {
var _this = this;
axios({
method:"post",
url:"http://localhost:8080/brand_case/brand/add",
data:_this.brand
}).then(function (resp){
if(resp.data == "success"){
_this.dialogVisible = false
_this.selectAll();
_this.$message({
message: '恭喜你,添加成功',
type: 'success'
});
}
})

批量删除

点击多条数据前的复选框就意味着要删除这些数据,而点击了 批量删除 按钮后,需要让用户确认一下,因为有可能是用户误操作的,当用户确定后需要给后端发送请求并携带者需要删除数据的多个id值,前端发送请求时需要将要删除的多个id值以json格式提交给后端

后端实现

dao方法实现

  • 接口方法声明如下
    1
    2
    3
    4
    5
    6
    /**
    * 批量删除
    *
    * @param ids
    */
    void deleteByIdIs(@Param("ids") int[] ids);
  • BrandMapper.xml 映射配置文件中添加 statement,需要用到动态sql的知识,在前面的文章已经详细说过了
    1
    2
    3
    4
    5
    <delete id="deleteByIdIs">
    delete from tb_brand
    where id in
    <foreach collection="ids" item="id" separator="," open="(" close=")">#{id}</foreach>
    </delete>

service方法实现

  • BrandService 接口中定义 deleteByIds() 批量删除的业务逻辑方法

    1
    2
    3
    4
    5
    /**
    * 批量删除
    * @param ids
    */
    void deleteByIds(int[] ids);
  • BrandServiceImpl 类中重写 deleteByIds() 方法,并进行业务逻辑实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Override
    public void deleteByIds(int[] ids) {
    //2. 获取SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //3. 获取BrandMapper
    BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);

    //4. 调用方法
    mapper.deleteByIds(ids);

    sqlSession.commit();//提交事务

    //5. 释放资源
    sqlSession.close();
    }

servlet方法实现

  • BrandServlet 类中定义 deleteByIds() 方法。而该方法的逻辑如下:

    • 接收页面提交的数据。页面到时候提交的数据是 json 格式的数据,所以此处需要使用输入流读取数据
    • 将接收到的数据转换为 int[] 数组
    • 调用 service 的 deleteByIds() 方法进行批量删除的业务逻辑处理
    • 给浏览器响应添加成功的标识,这里直接给浏览器响应 success 字符串表示成功
  • servlet 中 deleteByIds() 方法代码实现如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
    * 批量删除
    *
    * @param request
    * @param response
    * @throws ServletException
    * @throws IOException
    */
    public void deleteByIds(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //处理乱码问题
    request.setCharacterEncoding("utf-8");
    //1. 获取输入流,接收品牌数据json字符串
    BufferedReader br = request.getReader();
    String params = br.readLine();

    //将json字符串转为int[]数组
    int[] ids = JSON.parseObject(params, int[].class);

    //2. 调用service添加
    brandService.deleteByIds(ids);

    //3. 响应成功的标识
    response.getWriter().write("success");
    }

前端实现

  • 先去ElementUI官网看看复选框都有什么属性,发现表单绑定了一个事件@selection-change="handleSelectionChange
    1
    2
    3
    handleSelectionChange(val) {
    this.multipleSelection = val;
    }
  • 该事件是当选择项发生变化时会触发。该事件绑定了 handleSelectionChange 函数,而该函数有一个参数 val ,该参数是获取选中行的数据,选中多行也就是相当于选中了一个brand数组,我们可以通过遍历brand数组来获取其id,然后使用 axios 发送异步请求并经刚刚获取到的存储所有的 id 数组作为请求参数,那么现在就分析完毕了,开始敲代码

  • 给批量删除绑定一个点击事件,并绑定触发时的调用函数deleteByIds

    1
    <el-button @click="deleteByIds" type="danger" plain>批量删除</el-button>
  • 我们要在deleteByIds函数中遍历选中的brand数组,也就是遍历this.multipleSelection,获取brand.id,将其加入到id数组,所以我们先在Vue对象的data中添加selectedIds: []

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //获取选中的id值,并加入到selectedIds数组中
    for (let i = 0; i < this.multipleSelection.length; i++) {
    this.selectedIds[i] = this.multipleSelection[i].id;
    }
    var _this = this;
    axios({
    //发送异步请求提交json数据,用post
    method: "post",
    url: "http://localhost:8080/test_brand/brand/deleteByIds", //地址就是BrandServlet中的批量删除方法
    data: _this.selectedIds //提交的数据就是的selectedIds
    }).then(function (resp) {
    if (resp.data == "success") { //获取响应标识
    _this.selectAll(); //如果成功添加了,就重新查询数据,删除完毕,不用手动刷新页面就能看到删除结果
    _this.$message({ //给出提示信息
    message: '恭喜你,删除成功',
    type: 'success'
    });
    }
    })
  • 由于删除操作是一个危险的操作,所以在删除之前,我们需要让用户确认一下是否删除,要完成这个需求,我们继续去ElementUI中找个组件,MessageBox弹框

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    <template>
    <el-button type="text" @click="open">点击打开 Message Box</el-button>
    </template>

    <script>
    export default {
    methods: {
    open() {
    //这里的文案也可以改一下
    this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
    }).then(() => {
    //当点击确定,就执行这里,将我们刚刚写的删除操作放到这里就可以了
    this.$message({
    type: 'success',
    message: '删除成功!'
    });
    }).catch(() => {
    //点击取消就不用管了
    this.$message({
    type: 'info',
    message: '已取消删除'
    });
    });
    }
    }
    }
    </script>
  • 直接把这个open函数的函数体搬走就可以,修改完成的deleteByIds函数代码如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
     deleteByIds() {
    this.$confirm('此操作将删除数据, 是否继续?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
    }).then(() => {
    for (let i = 0; i < this.multipleSelection.length; i++) {
    this.selectedIds[i] = this.multipleSelection[i].id;
    }
    var _this = this;
    axios({
    method: "post",
    url: "http://localhost:8080/test_brand/brand/deleteByIds",
    data: _this.selectedIds
    }).then(function (resp) {
    if (resp.data == "success") {
    _this.selectAll();
    _this.$message({
    message: '恭喜你,删除成功',
    type: 'success'
    });
    }
    })
    }).catch(() => {
    this.$message({
    type: 'info',
    message: '已取消删除'
    });
    });
    }

分页查询

我们之前做的 查询所有 功能中将数据库中所有的数据查询出来并展示到页面上,试想如果数据库中的数据有很多(假设有十几万条)的时候,将数据全部展示出来肯定不现实,那如何解决这个问题呢?几乎所有的网站都会使用分页解决这个问题。每次只展示一页的数据,比如一页展示10条数据,如果还想看其他的数据,可以通过点击页码进行查询

分析

分页查询sql

  • 分页查询也是从数据库进行查询的,所以我们要分页对应的SQL语句应该怎么写。分页查询使用 LIMIT 关键字,格式为:LIMIT 开始索引 每页显示的条数。以后前端页面在发送请求携带参数时,它并不明确开始索引是什么,但是它知道查询第几页。所以 开始索引 需要在后端进行计算,计算的公式是 :开始索引 = (当前页码 - 1)* 每页显示条数
  • 比如查询第一页的数据的 SQL 语句是:
    1
    select * from tb_brand  limit 0,5;
  • 查询第二页的数据的 SQL 语句是:
    1
    select * from tb_brand  limit 5,5;
  • 查询第三页的数据的 SQL 语句是:
    1
    select * from tb_brand  limit 10,5;

前后端数据分析

分页查询功能时候比较复杂的,所以我们要先分析清楚以下两个问题:

  • 前端需要传递什么参数给后端
    根据上一步对分页查询 SQL 语句分析得出,前端需要给后端两个参数

    • 当前页码 :currentPage
    • 每页显示条数:pageSize
  • 后端需要响应什么数据给前端

    • 当前页需要展示的数据。我们在后端一般会存储到 List 集合中
    • 总共记录数。在上图页面中需要展示总的记录数,所以这部分数据也需要。总的页面 ElementUI 的分页组件会自动计算,我们不需要关心
    • 将以上两部分封装到 PageBean 对象中,并将该对象转换为 json 格式的数据响应回给浏览器
  • 通过上面的分析我们需要先在 pojo 包下创建 PageBean 类,为了做到通用性会将其定义成泛型类,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    //分页查询的JavaBean
    public class PageBean<T> {
    // 总记录数
    private int totalCount;
    // 当前页数据
    private List<T> rows;


    public int getTotalCount() {
    return totalCount;
    }

    public void setTotalCount(int totalCount) {
    this.totalCount = totalCount;
    }

    public List<T> getRows() {
    return rows;
    }

    public void setRows(List<T> rows) {
    this.rows = rows;
    }
    }

后端实现

dao方法实现

  • BrandMapper 接口中定义 selectByPage() 方法进行分页查询,代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * 分页查询
    * @param begin
    * @param size
    * @return
    */
    @Select("select * from tb_brand limit #{begin} , #{size}")
    @ResultMap("brandResultMap")
    List<Brand> selectByPage(@Param("begin") int begin,@Param("size") int size);
  • BrandMapper 接口中定义 selectTotalCount() 方法进行统计记录数,代码如下:
    1
    2
    3
    4
    5
    6
    /**
    * 查询总记录数
    * @return
    */
    @Select("select count(*) from tb_brand ")
    int selectTotalCount();

service方法实现

BrandService 接口中定义 selectByPage() 分页查询数据的业务逻辑方法

1
2
3
4
5
6
7
/**
* 分页查询
* @param currentPage 当前页码
* @param pageSize 每页展示条数
* @return
*/
PageBean<Brand> selectByPage(int currentPage,int pageSize);

BrandServiceImpl 类中重写 selectByPage() 方法,并进行业务逻辑实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public PageBean<Brand> selectByPage(int currentPage, int pageSize) {
//获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取BrandMapper
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
//计算索引
int begin = (currentPage - 1) * pageSize;
//计算当前条目数
int size = pageSize;
//查询当前页数据
List<Brand> rows = mapper.selectByPage(begin, size);
//查询总记录数
int totalCount = mapper.selectTotalCount();
//封装成PageBean对象
PageBean<Brand> brandPageBean = new PageBean<Brand>();
brandPageBean.setRows(rows);
brandPageBean.setTotalCount(totalCount);
//释放资源
sqlSession.close();
return brandPageBean;
}

servlet方法实现

  • BrandServlet 类中定义 selectByPage() 方法。而该方法的逻辑如下:
    • 获取页面提交的 当前页码每页显示条目数 两个数据。这两个参数是在url后进行拼接的,格式是 url?currentPage=1&pageSize=5。获取这 样的参数需要使用 requet.getparameter() 方法获取。
    • 调用 service 的 selectByPage() 方法进行分页查询的业务逻辑处理
    • 将查询到的数据转换为 json 格式的数据
    • 响应 json 数据
  • servlet 中 selectByPage() 方法代码实现如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public void selectByPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1. 接收 当前页码 和 每页展示条数 url?currentPage=1&pageSize=5
    String _currentPage = request.getParameter("currentPage");
    String _pageSize = request.getParameter("pageSize");

    int currentPage = Integer.parseInt(_currentPage);
    int pageSize = Integer.parseInt(_pageSize);

    //2. 调用service查询
    PageBean<Brand> pageBean = brandService.selectByPage(currentPage, pageSize);

    //2. 转为JSON
    String jsonString = JSON.toJSONString(pageBean);
    //3. 写数据
    response.setContentType("text/json;charset=utf-8");
    response.getWriter().write(jsonString);
    }

测试

在浏览器上地址栏输入 http://localhost:8080/brand_case/brand/selectByPage?currentPage=1&pageSize=5 ,可以查询到数据

前端实现

selectAll 代码改进

selectAll() 函数之前是查询所有数据,现需要改成分页查询。 请求路径应改为 http://localhost:8080/brand_case/brand/selectByPage?currentPage=1&pageSize=5 ,而 currentPagepageSize 是需要携带的参数,分别是 当前页码每页显示的条目数

刚才我们对后端代码进行测试可以看出响应回来的数据,所以在异步请求的成功回调函数(then 中的匿名函数)中给页面表格的数据模型赋值。整体代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
selectAll() {
var _this = this;
axios({
method:"post",
url:"http://localhost:8080/test_brand/brand/selectByPageAndCondition?currentPage="+this.currentPage+"&pageSize="+this.pageSize,
data:this.brand
}).then(function (resp) {
//设置表格数据
_this.tableData = resp.data.rows; // {rows:[],totalCount:100}
//设置总记录数
_this.totalCount = resp.data.totalCount;
})
}

改变当前页的条目数和当前页码

先来看看分页条的代码
@size-change 就是每页显示的条目数发生变化时会触发的事件,而该事件绑定了一个 handleSizeChange 函数
@current-change 就是页码发生变化时会触发的事件,而该事件绑定了一个 handleSizeChange 函数

1
2
3
4
5
6
7
8
9
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5, 10, 15, 20]"
:page-size="5"
layout="total, sizes, prev, pager, next, jumper"
:total="totalCount">
</el-pagination>

我们只需要在这两个函数中重新设置当前页的条目数和当前页码,然后调用selectAll函数重新分页查询数据

1
2
3
4
5
6
7
8
9
handleSizeChange(val) {
//重新设置每页的条目数
this.pageSize=val;
this.selectAll();
},
handleCurrentChange(val) {
this.currentPage=val;
this.selectAll();
}

条件查询

要做条件查询功能,先明确以下三个问题

  • 3个条件之间什么关系?
    • 同时满足,所用 SQL 中多个条件需要使用 and 关键字连接
  • 3个条件必须全部填写吗?
    • 不需要。想根据哪儿个条件查询就写那个,所以这里需要使用动态 sql 语句
  • 条件查询需要分页吗?
    • 需要

后端实现

dao实现

BrandMapper 接口中定义 selectByPageAndCondition() 方法 和 selectTotalCountByCondition 方法,用来进行条件分页查询功能,方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 分页条件查询
* @param begin
* @param size
* @return
*/
List<Brand> selectByPageAndCondition(@Param("begin") int begin,@Param("size") int size,@Param("brand") Brand brand);

/**
* 根据条件查询总记录数
* @return
*/
int selectTotalCountByCondition(Brand brand);

参数:

  • begin 分页查询的起始索引
  • size 分页查询的每页条目数
  • brand 用来封装条件的对象

由于这是一个复杂的查询语句,需要使用动态sql;所以我们在映射配置文件中书写 sql 语句。brand_name 字段和 company_name 字段需要进行模糊查询,所以需要使用 % 占位符。映射配置文件中 statement 书写如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!--查询满足条件的数据并进行分页-->
<select id="selectByPageAndCondition" resultMap="brandResultMap">
select *
from tb_brand
<where>
<if test="brand.brandName != null and brand.brandName != '' ">
and brand_name like #{brand.brandName}
</if>

<if test="brand.companyName != null and brand.companyName != '' ">
and company_name like #{brand.companyName}
</if>

<if test="brand.status != null">
and status = #{brand.status}
</if>
</where>
limit #{begin} , #{size}
</select>

<!--查询满足条件的数据条目数-->
<select id="selectTotalCountByCondition" resultType="java.lang.Integer">
select count(*)
from tb_brand
<where>
<if test="brandName != null and brandName != '' ">
and brand_name like #{brandName}
</if>

<if test="companyName != null and companyName != '' ">
and company_name like #{companyName}
</if>

<if test="status != null">
and status = #{status}
</if>
</where>
</select>

service实现

BrandService 接口中定义 selectByPageAndCondition() 分页查询数据的业务逻辑方法

1
2
3
4
5
6
7
8
 /**
* 分页条件查询
* @param currentPage
* @param pageSize
* @param brand
* @return
*/
PageBean<Brand> selectByPageAndCondition(int currentPage,int pageSize,Brand brand);

BrandServiceImpl 类中重写 selectByPageAndCondition() 方法,并进行业务逻辑实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Override
public PageBean<Brand> selectByPageAndCondition(int currentPage, int pageSize, Brand brand) {
//2. 获取SqlSession对象
SqlSession sqlSession = factory.openSession();
//3. 获取BrandMapper
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);


//4. 计算开始索引
int begin = (currentPage - 1) * pageSize;
// 计算查询条目数
int size = pageSize;

// 处理brand条件,模糊表达式
String brandName = brand.getBrandName();
if (brandName != null && brandName.length() > 0) {
brand.setBrandName("%" + brandName + "%");
}

String companyName = brand.getCompanyName();
if (companyName != null && companyName.length() > 0) {
brand.setCompanyName("%" + companyName + "%");
}

//5. 查询当前页数据
List<Brand> rows = mapper.selectByPageAndCondition(begin, size, brand);

//6. 查询总记录数
int totalCount = mapper.selectTotalCountByCondition(brand);

//7. 封装PageBean对象
PageBean<Brand> pageBean = new PageBean<>();
pageBean.setRows(rows);
pageBean.setTotalCount(totalCount);

//8. 释放资源
sqlSession.close();

return pageBean;
}

注意:brandName 和 companyName 属性值到时候需要进行模糊查询,所以前后需要拼接上 %

servlet实现

BrandServlet 类中定义 selectByPageAndCondition() 方法。而该方法的逻辑如下:

  • 获取页面提交的 当前页码每页显示条目数 两个数据。这两个参数是在url后进行拼接的,格式是 url?currentPage=1&pageSize=5。获取这样的参数需要使用 requet.getparameter() 方法获取。

  • 获取页面提交的 条件数据 ,并将数据封装到一个Brand对象中。由于这部分数据到时候是需要以 json 格式进行提交的,所以我们需要通过流获取数据,具体代码如下:

    1
    2
    3
    4
    5
    6
    // 获取查询条件对象
    BufferedReader br = request.getReader();
    String params = br.readLine();//json字符串

    //转为 Brand
    Brand brand = JSON.parseObject(params, Brand.class);
  • 调用 service 的 selectByPageAndCondition() 方法进行分页查询的业务逻辑处理

  • 将查询到的数据转换为 json 格式的数据

  • 响应 json 数据

servlet 中 selectByPageAndCondition() 方法代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 分页条件查询
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/

public void selectByPageAndCondition(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 接收 当前页码 和 每页展示条数 url?currentPage=1&pageSize=5
String _currentPage = request.getParameter("currentPage");
String _pageSize = request.getParameter("pageSize");

int currentPage = Integer.parseInt(_currentPage);
int pageSize = Integer.parseInt(_pageSize);

// 获取查询条件对象
BufferedReader br = request.getReader();
String params = br.readLine();//json字符串

//转为 Brand
Brand brand = JSON.parseObject(params, Brand.class);


//2. 调用service查询
PageBean<Brand> pageBean = brandService.selectByPageAndCondition(currentPage,pageSize,brand);

//2. 转为JSON
String jsonString = JSON.toJSONString(pageBean);
//3. 写数据
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(jsonString);
}

前端实现

前端代码我们从以下几方面实现:

  1. 查询表单绑定查询条件对象模型
    这一步在页面上已经实现了,页面代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <el-form :inline="true" :model="brand" class="demo-form-inline">
    <el-form-item label="当前状态">
    <el-select v-model="brand.status" placeholder="当前状态">
    <el-option label="启用" value="1"></el-option>
    <el-option label="禁用" value="0"></el-option>
    </el-select>
    </el-form-item>
    <el-form-item label="企业名称">
    <el-input v-model="brand.companyName" placeholder="企业名称"></el-input>
    </el-form-item>
    <el-form-item label="品牌名称">
    <el-input v-model="brand.brandName" placeholder="品牌名称"></el-input>
    </el-form-item>

    <el-form-item>
    <el-button type="primary" @click="onSubmit">查询</el-button>
    </el-form-item>
    </el-form>
  2. 点击查询按钮查询数据
    从上面的代码可以看到给 查询 按钮绑定了 onSubmit() 函数,而在 onSubmit() 函数中只需要调用 selectAll() 函数进行条件分页查询。

  3. 改进 selectAll() 函数
    子页面加载完成后发送异步请求,需要携带当前页码、每页显示条数、查询条件对象。接下来先对携带的数据进行说明:

    • 当前页码每页显示条数 这两个参数我们会拼接到 URL 的后面
    • 查询条件对象 这个参数需要以 json 格式提交给后端程序
    • 修改 selectAll() 函数逻辑为
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      var _this = this;

      axios({
      method:"post",
      url:"http://localhost:8080/brand_case/brand/selectByPageAndCondition?currentPage="+this.currentPage+"&pageSize="+this.pageSize,
      data:this.brand
      }).then(function (resp) {

      //设置表格数据
      _this.tableData = resp.data.rows; // {rows:[],totalCount:100}
      //设置总记录数
      _this.totalCount = resp.data.totalCount;
      })

前端代码优化

咱们已经将所有的功能实现完毕。而针对前端代码中的发送异步请求的代码,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
var _this = this;

axios({
method:"post",
url:"http://localhost:8080/brand_case/brand/selectByPageAndCondition?currentPage="+this.currentPage+"&pageSize="+this.pageSize,
data:this.brand
}).then(function (resp) {

//设置表格数据
_this.tableData = resp.data.rows; // {rows:[],totalCount:100}
//设置总记录数
_this.totalCount = resp.data.totalCount;
})

需要在成功的回调函数(也就是then 函数中的匿名函数)中使用this,都需要在外边使用 _this 记录一下 this 所指向的对象;因为在外边的 this 表示的是 Vue 对象,而回调函数中的 this 表示的不是 vue 对象。这里我们可以使用 ECMAScript6 中的新语法(箭头函数)来简化这部分代码,如上面的代码可以简化为:

1
2
3
4
5
6
7
8
9
10
11
axios({
method:"post",
url:"http://localhost:8080/brand_case/brand/selectByPageAndCondition?currentPage="+this.currentPage+"&pageSize="+this.pageSize,
data:this.brand
}).then((resp) => {

//设置表格数据
this.tableData = resp.data.rows; // {rows:[],totalCount:100}
//设置总记录数
this.totalCount = resp.data.totalCount;
})

箭头函数语法:

1
2
3
(参数) => {
逻辑代码
}

箭头函数的作用:

替换(简化)匿名函数。

补充

由于课程中没有删除和修改的功能,所以我这里完善了一下

删除功能

删除功能的难点在于如果获取本行数据的id,但成功获取id之后的操作就与批量删除的操作一样了
批量删除是选中多行数据,然后将多行数据的id放到ids数组中,那么单行数据就只需要将当前行的id放到ids数组中
具体操作就是this.selectedIds[0] = this.currentRow.id;,前端代码也只需要将for循环替换掉

那现在我们只需要复制一份deleteByIds,然后改个名,将for循环替换掉,就大功告成了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
deleteById() {
this.$confirm('此操作将删除数据, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
//唯一的区别就是不用这个for循环来获取id了
/*for (let i = 0; i < this.multipleSelection.length; i++) {
this.selectedIds[i] = this.multipleSelection[i].id;
}
*/
this.selectedIds[0] = this.currentRow.id;
var _this = this;
axios({
method: "post",
url: "http://localhost:8080/test_brand/brand/deleteByIds",
data: _this.selectedIds
}).then(function (resp) {
if (resp.data == "success") {
_this.selectAll();
_this.$message({
message: '恭喜你,删除成功',
type: 'success'
});
}
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}

如何获取当前行数据

其实我们在写前端页面的时候,就已经遇到过了,那现在我们仔细看看

Table 组件提供了单选的支持,只需要配置highlight-current-row属性即可实现单选。之后由current-change事件来管理选中时触发的事件,它会传入currentRow,oldCurrentRow。如果需要显示索引,可以增加一列el-table-column,设置type属性为index即可显示从 1 开始的索引号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<template>
<el-table
ref="singleTable"
:data="tableData"
highlight-current-row
@current-change="handleCurrentChange"
style="width: 100%">
<el-table-column
type="index"
width="50">
</el-table-column>
<el-table-column
property="date"
label="日期"
width="120">
</el-table-column>
<el-table-column
property="name"
label="姓名"
width="120">
</el-table-column>
<el-table-column
property="address"
label="地址">
</el-table-column>
</el-table>
<div style="margin-top: 20px">
<el-button @click="setCurrent(tableData[1])">选中第二行</el-button>
<el-button @click="setCurrent()">取消选择</el-button>
</div>
</template>

<script>
export default {
data() {
return {
tableData: [{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}],
currentRow: null
}
},

methods: {
setCurrent(row) {
this.$refs.singleTable.setCurrentRow(row);
},
//这个方法直接搬走
handleCurrentChange(val) {
this.currentRow = val;
}
}
}
</script>

提取一下信息就是我们要在table中配置highlight-current-row属性,current-change事件,和事件绑定的handleCurrentChange函数,然后在Vue的data中配置currentRow属性(默认为null),然后就大功告成了

  • table标签的修改如下
    1
    2
    3
    4
    5
    6
    7
    8
    <el-table
    :data="tableData"
    style="width: 100%"
    :row-class-name="tableRowClassName"
    highlight-current-row
    @current-change="handleCurrentChangeRow"
    @selection-change="handleSelectionChange">
    <!--我这里给绑定的事件改了个名-->
  • data中新增属性(示例代码中已经提供好了)
    1
    currentRow: null
  • methods中新增方法(示例代码中已经提供好了,直接CV,我这里改了个名)
    1
    2
    3
    handleCurrentChangeRow(val) {
    this.currentRow = val;
    }

修改功能

  • 修改功能依旧是分为回显修改两部分
  • 修改功能的需求就是当我们点击修改按钮(给修改按钮绑定echo()回显函数)时,会弹出一个对话框,里面是已经填好了的品牌信息,我们只需要修改我们需要改的部分,其他部分不用动,然后点击提交按钮(给提交按钮绑定update()更新函数),完成修改功能

前端页面

  • 前端页面我们直接照抄新增页面即可,注意将设置:visible.sync="updateVisible",同时在Vue对象的data中,新增updateVisible属性updateVisible: false,该属性默认为false,用来控制修改对话框是否显示
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <!--对话框弹出修改品牌-->
    <el-dialog
    title="修改品牌"
    :visible.sync="updateVisible"
    width="30%">
    <el-form ref="form" :model="brand" label-width="80px">
    </el-form-item>
    <el-form-item label="企业名称">
    <el-input v-model="brand.companyName"></el-input>
    </el-form-item>
    <el-form-item label="品牌名称">
    <el-input v-model="brand.brandName"></el-input>
    </el-form-item>
    <el-form-item label="排序">
    <el-input v-model="brand.ordered"></el-input>
    </el-form-item>
    <el-form-item label="状态">
    <el-switch v-model="brand.status" active-value=1 inactive-value=0></el-switch>
    </el-form-item>
    <el-form-item label="备注">
    <el-input type="textarea" v-model="brand.description"></el-input>
    </el-form-item>
    <el-form-item>
    <el-button type="primary" @click="update">提交</el-button>
    <el-button @click="updateVisible = false">取消</el-button>
    </el-form-item>
    </el-form>
    </el-dialog>

回显功能

  • 回显功能是要当我们点击修改按钮时,弹出对话框,同时显示原有数据,那我们就给修改按钮的点击事件绑定一个回显函数
  • 而回显函数的功能就是刚刚说的,弹出对话框(设置updateVisible为true),显示原有数据(将本行数据赋给brand即可,因为修改对话框已经绑定了brand模型)
  • 代码如下
    1
    2
    3
    4
    echo(){
    this.updateVisible = true;
    this.brand = this.currentRow;
    }

修改功能

## dao实现
  • 接口方法声明如下
    1
    2
    3
    4
    5
    6
    7
    /**
    * 更新数据
    * @param brand
    */
    @Update("update tb_brand set brand_name=#{brandName},company_name=#{companyName},ordered=#{ordered},`description`=#{description},`status`=#{status} where id =#{id}")
    @ResultMap("brandResultMap")
    void update(Brand brand);
    ## service实现
  • BrandService 接口中定义 update() 更新数据的业务逻辑方法
    1
    2
    3
    4
    5
    /**
    * 更新数据
    * @param brand
    */
    void update(Brand brand);
  • BrandServiceImpl 类中重写update() 方法,并进行业务逻辑实现
    1
    2
    3
    4
    5
    6
    7
    8
    @Override
    public void update(Brand brand) {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
    mapper.update(brand);
    sqlSession.commit();
    sqlSession.close();
    }
    ## servlet实现
  • servlet 中 update() 方法代码实现如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //处理乱码问题
    request.setCharacterEncoding("utf-8");
    //1. 获取输入流,接收品牌数据json字符串
    BufferedReader br = request.getReader();
    String params = br.readLine();

    //将json字符串转为brand对象
    Brand brand = JSON.parseObject(params, Brand.class);
    Integer id = brand.getId();
    String brandName = brand.getBrandName();
    String companyName = brand.getCompanyName();
    String description = brand.getDescription();
    Integer ordered = brand.getOrdered();
    Integer status = brand.getStatus();

    Brand b = new Brand();
    b.setId(id);
    b.setBrandName(brandName);
    b.setCompanyName(companyName);
    b.setDescription(description);
    b.setOrdered(ordered);
    b.setStatus(status);
    //2. 调用service更新
    brandService.update(brand);

    //3. 响应成功的标识
    response.getWriter().write("success");
    }
## 前端实现
  • 前面当我们点击修改按钮时,已经帮我们弹出对话框,且对话框中有原有的数据,当我们修改完毕之后,点击提交按钮,就会帮我们完成更新操作
    那现在我们只需要来写一个update()函数就完事儿了,而update()函数和addBrand()函数几乎没有区别,只需要将url改成update,然后提示信息改一下,就大功告成了(区别就是底层的SQL语句不一样,addBrand()执行insert,update()执行update)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    update() {
    var _this = this;
    axios({
    method: "post",
    url: "http://localhost:8080/test_brand/brand/update",
    data: _this.brand
    }).then(function (resp) {
    if (resp.data == "success") {
    _this.updateVisible = false
    _this.selectAll();
    _this.$message({
    message: '恭喜你,修改成功',
    type: 'success'
    });
    }
    })
    }
  • 至此我们就完成了课程中没讲的修改和删除功能啦

完结撒花

JavaWeb的学习到此就结束了,后面就是学一些主流的框架了,最近先好好复盘一遍JavaWeb巩固基础再往后学。