JavaWeb---综合案例
我只想卷死各位,或者被各位卷死,在此特别感谢黑马程序员的JavaWeb教程
>项目源码:链接:https://pan.baidu.com/s/1iyXBAU4ct4dPDxQM52TCEA?pwd=y02r 提取码:y02r
本文摘要:
- 用Element组件编写一个好看的页面
- 能够完成查询所有功能
- 能够完成添加功能
- 能够理解 BaseServlet 思想
- 能够完成批量删除功能
- 能够完成分页查询功能
- 能够完成条件查询功能
- 完善了课程中没讲的修改和删除功能
案例页面
我们要完成如下页面效果
那我们现在就开始从上往下找组件来拼凑这个页面,在此之前,我们先创建一个brand_case
的项目,并在webapp
下新建brand.html,引入Element 的css、js文件 和 Vue.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<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
8brand: {
id:'',
brandName: '',
companyName: '',
ordered: '',
description: '',
status: ''
}
methods部分的修改1
2
3onSubmit() {
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>
新增对话框展示
删除按钮我们暂时不动,但是当我们点击新增
按钮时,需要弹出一个对话框让我们填写信息
Element-组件-Dialog对话框,这个组件可以帮我们达到目的
需要设置visible属性,它接收Boolean,当为true时显示 Dialog。Dialog 分为两个部分:body和footer,footer需要具名为footer的slot。title属性用于定义标题,它是可选的,默认值为空。最后,本例还展示了before-close的用法。
1 | <!--这里绑定了一个点击事件,当点击时,将dialogVisible设为true, |
由于我们要在对话框中再搞一个表单,所以我们继续去官网看看有没有合适的组件
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 | <template> |
1 | <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 | <template> |
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 |
|
环境准备
工程准备
在Java目录下创建com.blog.mapper
,com.blog.pojo
,com.blog.service
,com.blog.util
,com.blog.web
这五个包
在mapper包下新建
BrandMapper
接口1
2
3
4
5import java.util.List;
public interface BrandMapper {
}在resource目录下新建
com/blog/mapper
路径,新建一个BrandMapper.xml
文件1
2
3
4
5
6
7
8
9
10
11
<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
<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&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
82public 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;
}
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
19public 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
<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 | -- 删除tb_brand表 |
查询所有功能
后端实现
dao方法实现
由于表中有些字段名和实体类中的属性名没有对应,所以需要在
com/itheima/mapper/BrandMapper.xml
映射配置文件中定义结果映射 ,使用resultMap
标签。映射配置文件内容如下:1
2
3
4
5
6
7
8
9
10
11
<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
*/
List<Brand> selectAll();
service方法实现
在
com.itheima.service
包下创建BrandService
接口,在该接口中定义查询所有的抽象方法1
2
3
4
5
6
7
8public 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
17public class BrandServiceImpl implements BrandService {
//1. 创建SqlSessionFactory工厂对象
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();
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实现类的对象
这里使用多态是因为方便解除Servlet
和service
的耦合。从上面的代码我们可以看到SelectAllServlet
类和BrandServiceImpl
类之间是耦合在一起的,如果后期BrandService
有其它的实现类(例如叫BrandServiceImpl1
),那就需要修改SelectAllServlet
类中的代码。后面学习了Spring
框架后就可以解除SelectAllServlet
类和BrandServiceImpl
的代码耦合。但现在还做不到解耦合,在这里只需要理解为什么定义接口即可。
- 因为service定义了接口后,在 servlet 中就可以使用多态的形式创建Service实现类的对象
servlet方法实现
- 在
com.itheima.web.servlet
包下定义名为SelectAllServlet
的查询所有的servlet
。该servlet
逻辑如下:- 调用service的
selectAll()
方法查询所有的品牌数据,并接口返回结果 - 将返回的结果转换为 json 数据
- 响应 json 数据
- 调用service的
1 |
|
测试后端程序
在浏览器输入访问 servlet 的资源路径 http://localhost:8080/brand_case/selectAllServlet
,如果没有报错,并能看到如下信息表明后端程序没有问题
[{“brandName”:”华为”,”companyName”:”华为技术有限公司”,”description”:”万物互联”,”id”:1,”ordered”:100,”status”:1,”statusStr”:”启用”},
前端实现
前端需要在页面加载完毕后发送 ajax 请求,所以发送请求的逻辑应该放在 mounted()
钩子函数中。而响应回来的数据需要赋值给表格绑定的数据模型
1 | mounted(){ |
添加功能
上图是添加数据的对话框,当点击 提交
按钮后就需要将数据提交到后端,并将数据保存到数据库中。
页面发送请求时,需要将输入框输入的内容提交给后端程序,而这里是以 json 格式进行传递的。
后端实现
dao方法实现
- 在
BrandMapper
接口中定义add()
添加方法,并使用@Insert
注解编写sql语句1
2
3
4
5
6
7/**
* 添加数据
* @param brand
*/
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
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
public class AddServlet extends HttpServlet {
private BrandService brandService = new BrandServiceImpl();
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");
}
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
17addBrand() {
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
表示数据添加成功。那么添加成功之后有哪些操作呢?
- 先将对话框关闭,设置dialogVisible为false,就可以关闭对话框了
1
_this.dialogVisible = false
- 重新查询数据,这部分代码其实就是刚刚查询所有的前端代码,将其封装成一个selectAll()函数
1
2
3
4
5
6
7var _this = this;
axios({
method:"get",
url:"http://localhost:8080/brand_case/selectAllServlet"
}).then(function (resp) {
_this.tableData = resp.data;
}) - 给出提示信息,这部分其实是直接从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
public class BrandServlet {
//查询所有
public void selectAll(...) {}
//添加数据
public void add(...) {}
//修改数据
public void update(...) {}
//删除删除
public void delete(...) {}
}
而我们知道发送请求 servlet
,tomcat
会自动的调用 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
37protected 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
6public class BaseServlet extends HttpServlet {
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
BrandServlet定义修改如下1
2
3
4
5
6
7
8
9
10
11
12
13public 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
21public class BaseServlet extends HttpServlet {
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
对象,所以指定方法的参数为 HttpServletRequest
和 HttpServletResponse
,那么 BrandServlet
代码就可以改进为:1
2
3
4
5
6
7
8
9
10
11
12
13
14public 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
26public class BaseServlet extends HttpServlet {
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
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/selectAll
和http://localhost:8080/brand_case/brand/add
1 | selectAll(){ |
批量删除
点击多条数据前的复选框就意味着要删除这些数据,而点击了 批量删除
按钮后,需要让用户确认一下,因为有可能是用户误操作的,当用户确定后需要给后端发送请求并携带者需要删除数据的多个id值,前端发送请求时需要将要删除的多个id值以json格式提交给后端
后端实现
dao方法实现
- 接口方法声明如下
1
2
3
4
5
6/**
* 批量删除
*
* @param ids
*/
void deleteByIdIs(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
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
3handleSelectionChange(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
30deleteByIds() {
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
*/
List<Brand> selectByPage(int begin, int size); - 在
BrandMapper
接口中定义selectTotalCount()
方法进行统计记录数,代码如下:1
2
3
4
5
6/**
* 查询总记录数
* @return
*/
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
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
17public 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
,而 currentPage
和 pageSize
是需要携带的参数,分别是 当前页码
和 每页显示的条目数
。
刚才我们对后端代码进行测试可以看出响应回来的数据,所以在异步请求的成功回调函数(then
中的匿名函数)中给页面表格的数据模型赋值。整体代码如下1
2
3
4
5
6
7
8
9
10
11
12
13selectAll() {
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
9handleSizeChange(val) {
//重新设置每页的条目数
this.pageSize=val;
this.selectAll();
},
handleCurrentChange(val) {
this.currentPage=val;
this.selectAll();
}
条件查询
要做条件查询功能,先明确以下三个问题
- 3个条件之间什么关系?
- 同时满足,所用 SQL 中多个条件需要使用 and 关键字连接
- 3个条件必须全部填写吗?
- 不需要。想根据哪儿个条件查询就写那个,所以这里需要使用动态 sql 语句
- 条件查询需要分页吗?
- 需要
后端实现
dao实现
在 BrandMapper
接口中定义 selectByPageAndCondition()
方法 和 selectTotalCountByCondition
方法,用来进行条件分页查询功能,方法如下:
1 | /** |
参数:
begin
分页查询的起始索引size
分页查询的每页条目数brand
用来封装条件的对象
由于这是一个复杂的查询语句,需要使用动态sql;所以我们在映射配置文件中书写 sql 语句。brand_name
字段和 company_name
字段需要进行模糊查询,所以需要使用 %
占位符。映射配置文件中 statement 书写如下:
1 | <!--查询满足条件的数据并进行分页--> |
service实现
在 BrandService
接口中定义 selectByPageAndCondition()
分页查询数据的业务逻辑方法
1 | /** |
在 BrandServiceImpl
类中重写 selectByPageAndCondition()
方法,并进行业务逻辑实现
1 |
|
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 | /** |
前端实现
前端代码我们从以下几方面实现:
- 查询表单绑定查询条件对象模型
这一步在页面上已经实现了,页面代码如下: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> 点击查询按钮查询数据
从上面的代码可以看到给查询
按钮绑定了onSubmit()
函数,而在onSubmit()
函数中只需要调用selectAll()
函数进行条件分页查询。改进 selectAll() 函数
子页面加载完成后发送异步请求,需要携带当前页码、每页显示条数、查询条件对象。接下来先对携带的数据进行说明:当前页码
和每页显示条数
这两个参数我们会拼接到 URL 的后面查询条件对象
这个参数需要以 json 格式提交给后端程序- 修改
selectAll()
函数逻辑为1
2
3
4
5
6
7
8
9
10
11
12
13var _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 | var _this = this; |
需要在成功的回调函数(也就是then
函数中的匿名函数)中使用this,都需要在外边使用 _this
记录一下 this
所指向的对象;因为在外边的 this
表示的是 Vue 对象,而回调函数中的 this
表示的不是 vue 对象。这里我们可以使用 ECMAScript6
中的新语法(箭头函数)来简化这部分代码,如上面的代码可以简化为:
1 | axios({ |
箭头函数语法:
1 | (参数) => { |
箭头函数的作用:
替换(简化)匿名函数。
补充
由于课程中没有删除和修改的功能,所以我这里完善了一下
删除功能
删除功能的难点在于如果获取本行数据的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
33deleteById() {
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
3handleCurrentChangeRow(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
4echo(){
this.updateVisible = true;
this.brand = this.currentRow;
}
修改功能
## dao实现
- 接口方法声明如下
1
2
3
4
5
6
7/**
* 更新数据
* @param brand
*/
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
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
29public 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
17update() {
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巩固基础再往后学。