javaweb
本文最后更新于:2 年前
Javaweb基础项目
学完 javaweb 做的小项目
综合案例需求与准备
今日目标:
- 能够完成查询所有功能
- 能够完成添加功能
- 能够理解 BaseServlet 思想
- 能够完成批量删除功能
- 能够完成分页查询功能
- 能够完成条件查询功能
1,功能介绍
以上是我们在综合案例要实现的功能。对数据的除了对数据的增删改查功能外,还有一些复杂的功能,如 批量删除
、分页查询
、条件查询
等功能
批量删除
功能:每条数据前都有复选框,当我选中多条数据并点击批量删除
按钮后,会发送请求到后端并删除数据库中指定的多条数据。分页查询
功能:当数据库中有很多数据时,我们不可能将所有的数据展示在一页里,这个时候就需要分页展示数据。条件查询
功能:数据库量大的时候,我们就需要精确的查询一些想看到的数据,这个时候就需要通过条件查询。
这里的 修改品牌
和 删除品牌
功能在课程上不做讲解,留作同学来下的练习。
2,环境准备
环境准备我们主要完成以下两件事即可
- 将资料的 brand-case 模块导入到 idea中
- 执行资料中提供的 tb_brand.sql脚本
在没有资料的情况下在idea中创建maven
的web项目可以使用下面的方法
file
-> new
-> module
->Maven
->Create from archetype
->maven archetype
->next
2.1 工程准备
将 04-资料\01-初始工程
中的 brand-case
工程导入到我们自己的 idea 中。工程结构如下:
2.2 创建表
下面是创建表的语句
-- 删除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);
2.3 配置信息
mybatis
将mybatis的配置文件mybatis-config.xml
copy到src/main/resource
目录下
<?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.itheima.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<!-- 配置数据名 javastudy 用户名和密码-->
<property name="url" value="jdbc:mysql:///javastudy?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--扫描mapper 这里根据项目的mapper目录配置-->
<package name="com.itheima.mapper"/>
</mappers>
</configuration>
maven
pom.xml
<?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-case</artifactId>
<version>1.0-SNAPSHOT</version>
<!--web项目将打包成war格式
普通项目则为jar
-->
<packaging>war</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--Servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--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>
<!--fastjson json转object时用到-->
<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>
<!-- 这里可以配置tomcat的port和根路径 -->
</plugin>
</plugins>
</build>
</project>
包
在/scr/main/java
中新建pojo
、mapper
、service
、web
、util
等包
包名 | 作用 |
---|---|
pojo | 存放实体类,比如Brand、User等 |
mapper | 存放对应的Mapper接口 比如BrandMapper |
service | 存放提供服务的类BrandService |
web | 存放web层使用到的Servlet |
util | 存放工具包 |
创建src/main/resources/com/itheima/mapper/
目录,该目录存放BrandMapper.xml
该文件在tomcat启动后自动保存到src/main/java/com/itheima/mapper/BrandMapper.xml
前端页面
登录和注册页面使用html和javascript设计
展示页面使用Vue和element-ui设计出一个简单的展示页面
功能实现
1.查询所有
需求分析
- 当页面加载完成后,数据需要展示到出来,所以需要在Vue中
mounted
构造函数中写发送异步请求的代码 - 并且发送请求时无需携带参数
- 服务器返回所有数据,其格式为json格式,返回的参数绑定到vue的模型中,显示到页面中
后端实现
dao
在com.itheima.mapper.BrandMapper
接口中添加selectAll()
方法
/**
* 查询所有
* @return
*/
@Select("select * from tb_brand")
@ResultMap("brandResultMap")
List<Brand> selectAll();
由于表中有些字段名和实体类中的属性名没有对应,所以需要在 com/itheima/mapper/BrandMapper.xml
映射配置文件中定义结果映射 ,使用resultMap
标签。映射配置文件内容如下:
<resultMap id="brandResultMap" type="brand">
<result property="brandName" column="brand_name"/>
<result property="companyName" column="company_name"/>
</resultMap>
service
在com.itheima.mapper
中创建BrandService
类
// 获取sqlSessionFactory
// 该类 存放在util包中,封装后方便使用
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();
/**
* 查询所有
* @return
*/
public List<Brand> selectAll(){
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取 BrandMapper
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
// 调用方法
List<Brand> brands = mapper.selectAll();
sqlSession.close();
// 返回查询到的数据
return brands;
}
servlet
在com.itheima.web
中创建SelectAllServlet
可以使用Idea提供的模板创建自动继承HttpServlet
package com.itheima.web.servlet;
import com.alibaba.fastjson.JSON;
import com.itheima.pojo.Brand;
import com.itheima.service.BrandService;
import jdk.nashorn.internal.ir.CallNode;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.util.List;
@WebServlet(value = "/selectAllServlet")
public class SelectAllServlet extends HttpServlet {
// 创建BrandService
private BrandService service = new BrandService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 调用BrandService中的selectAll方法
List<Brand> brandList = service.selectAll();
// 将brand对象转换为json字符串
String string = JSON.toJSONString(brandList);
// 设置响应数据类型为json
response.setContentType("text/json;charset=utf-8");
//响应数据
response.getWriter().write(string);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
前端实现
前端需要在页面加载完毕后发送 ajax 请求,所以发送请求的逻辑应该放在 mounted()
钩子函数中。而响应回来的数据需要赋值给表格绑定的数据模型,从代码中可以看出表格绑定的数据模型是 brandlist
<!--表格-->
<template>
<el-table
:data="brandlist"
style="width: 100%"
:row-class-name="tableRowClassName"
@selection-change="handleSelectionChange"
>
ajax请求为
methods:{
selectAll(){
var _this = this;
axios({
method: 'GET',
url: 'http://localhost:8080/brand-case/brand/selectAll'
}).then(function(response){
var brandlist = response.data;
_this.brandlist = brandlist;
})
}
}
mounted(){
this.selectAll;
}
2.添加功能
需求分析
- 用户请求后台时需要携带数据,且为post请求,携带的数据类型为json类型
- 后端服务器获取json数据后将其转换为Brand类型
- 添加成功后响应前端
后端实现
dao
在BrandMapper
中添加addBrand()方法
/**
* 添加
* @param brand
*/
@Insert("insert into tb_brand values (null, #{brandName}, #{companyName}, #{ordered},#{description}, #{status})")
void addBrand(Brand brand);
service
在BrandService中实现addBrand()业务逻辑方法
/**
* 添加
* @param brand
*/
public void addBrand(Brand brand){
SqlSession sqlSession = sqlSessionFactory.openSession();
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
mapper.addBrand(brand);
// 对数据库的增删改 记得提交事务
sqlSession.commit();
sqlSession.close();
}
servlet
前端请求时携带json数据,request.getParamter()
无法获取,需要使用获取请求体的方法request.getReader()
package com.itheima.web.servlet;
import com.alibaba.fastjson.JSON;
import com.itheima.pojo.Brand;
import com.itheima.service.BrandService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.List;
@WebServlet(value = "/addServlet")
public class AddServlet extends HttpServlet {
private BrandService service = new BrandService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// getParamter无法获取json数据
BufferedReader reader = request.getReader();
String line = reader.readLine();
System.out.println(line);
// 将json数据转换为Brand对象
Brand brand = JSON.parseObject(line, Brand.class);
System.out.println(brand);
service.addBrand(brand);
// 返回的响应数据
response.getWriter().write("success");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
前端实现
添加的对话框
初始时该对话框为隐藏状态,其状态有dialogVisible
控制,默认情况下为false
<el-dialog
title="添加品牌"
:visible.sync="dialogVisible"
width="30%"
>
当点击添加按钮时,将dialogVisible
设置为true
,对话框弹出,便可以编辑数据
<el-button type="primary" plain @click="dialogVisible = true">新增</el-button>
提交数据
对话框实际上是一个form表单,该表单数据与brand
模型绑定,所以请求后端时提交brand即可
new Vue({
data(){
return {
// 品牌模型数据
brand: {
status: '',
brandName: '',
companyName: '',
id:"",
ordered:"",
description:""
},
}
}
})
提交和取消按钮
<el-button type="primary" @click="addBrand">提交</el-button>
<!--取消 只需要将dialogVisble设置为false即可隐藏对话框-->
<el-button @click="dialogVisible = false">取消</el-button>
addBrand
// 添加数据
addBrand(){
var _this = this;
this.currentPage=1
axios({
method: 'post',
url: 'http://localhost:8080/brand-case/brand/add',
data: brand = _this.brand
}).then(function(response){
if(response.data == "success"){
// 隐藏对话框
_this.dialogVisible = false;
// 重新加载全部数据
_this.selectAll();
_this.msgbox("添加成功!");
}
})
}
Servlet优化
===Web 层的 Servlet 个数太多了,不利于管理和编写===
通过之前的两个功能,我们发现每一个功能都需要定义一个 servlet
,一个模块需要实现增删改查功能,就需要4个 servlet
,模块一多就会造成servlet
泛滥。此时我们就想 servlet
能不能像 service
一样,一个模块只定义一个 servlet
,而每一个功能只需要在该 servlet
中定义对应的方法。例如下面代码:
@WebServlet("/brand/*")
public class BrandServlet {
//查询所有
public void selectAll(...) {}
//添加数据
public void add(...) {}
//修改数据
public void update(...) {}
//删除删除
public void delete(...) {}
}
而我们知道发送请求 servlet
,tomcat
会自动的调用 service()
方法,之前我们在自定义的 servlet
中重写 doGet()
方法和 doPost()
方法,当我们访问该 servlet
时会根据请求方式将请求分发给 doGet()
或者 doPost()
方法
那么我们也可以仿照这样请求分发的思想,在 service()
方法中根据具体的操作调用对应的方法,如:查询所有就调用 selectAll()
方法,添加企业信息就调用 add()
方法。
为了做到通用,我们定义一个通用的 servlet
类,在定义其他的 servlet
是不需要继承 HttpServlet
,而继承我们定义的 BaseServlet
,在 BaseServlet
中调用具体 servlet
(如BrandServlet
)中的对应方法。
public class BaseServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//进行请求的分发
}
}
BrandServlet
定义就需要修改为如下:
@WebServlet("/brand/*")
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/brandServlet/selectAll
添加数据的路径以后就需要写成: http://localhost:8080/brand-case/brandServlet/add
修改数据的路径以后就需要写成: http://localhost:8080/brand-case/brandServlet/update
删除数据的路径以后就需要写成: http://localhost:8080/brand-case/brandServlet/delete
这样的话,在 BaseServlet
中就需要获取到资源的二级路径作为方法名,然后调用该方法
public class BaseServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1. 获取请求路径
String uri = req.getRequestURI(); // 例如路径为:/brand-case/brand/selectAll
//2. 获取最后一段路径,方法名
int index = uri.lastIndexOf('/');
String methodName = uri.substring(index + 1); // 获取到资源的二级路径 selectAll
//2. 执行方法
//2.1 获取BrandServlet /UserServlet 字节码对象 Class
//System.out.println(this);
Class<? extends BaseServlet> cls = this.getClass();
//2.2 获取方法 Method对象
try {
Method method = cls.getMethod(methodName,???);
//4,调用该方法
method.invoke(this,???);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
通过上面代码发现根据方法名获取对应方法的 Method
对象时需要指定方法参数的字节码对象。解决这个问题,可以将方法的参数类型规定死,而方法中可能需要用到 request
对象和 response
对象,所以指定方法的参数为 HttpServletRequest
和 HttpServletResponse
,那么 BrandServlet
代码就可以改进为:
@WebServlet("/brand/*")
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代码可以改进为:
public class BaseServlet extends HttpServlet {
//根据请求的最后一段路径来进行方法分发
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1. 获取请求路径
String uri = req.getRequestURI(); // 例如路径为:/brand-case/brand/selectAll
//2. 获取最后一段路径,方法名
int index = uri.lastIndexOf('/');
String methodName = uri.substring(index + 1); // 获取到资源的二级路径 selectAll
//2. 执行方法
//2.1 获取BrandServlet /UserServlet 字节码对象 Class
//System.out.println(this);
Class<? extends BaseServlet> cls = this.getClass();
//2.2 获取方法 Method对象
try {
Method method = cls.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
//2.3 执行方法
method.invoke(this,req,resp);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
前端需要改只有几个访问路径,这里不做具体实现
3.批量删除
需求分析
- 批量删除请求,携带参数为
[1,2,3...]
类型的json字符串 - 后端接收到json字符串后需要将其转为int数组
- 调用BrandService中的方法删除数据
后端实现
dao
BrandMapper
中添加deleteByIds()
方法
/**
* 批量删除
* @param ids
*/
void deleteByIds(@Param("ids") int[] ids);
service
在BrandService
中实现批量删除的业务逻辑代码
/**
* 批量删除
* @param ids
*/
public void deleteByIds(int[] ids){
SqlSession sqlSession = sqlSessionFactory.openSession();
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
mapper.deleteByIds(ids);
sqlSession.commit();
sqlSession.close();
}
servlet
src/main/java/com/itheima/web/servlet
的BrandServlet
添加deleteByIds
方法
/**
* 批量删除操作
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void deleteByIds(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
BufferedReader reader = request.getReader();
// 获取json字符串
String line = reader.readLine();
System.out.println(line);
int[] ids = JSON.parseObject(line, int[].class);
System.out.println(ids);
service.deleteByIds(ids);
response.getWriter().write("success");
}
前端实现
获取选中数据的id值
handleSelectionChange
<el-table
:data="brandlist"
style="width: 100%"
:row-class-name="tableRowClassName"
@selection-change="handleSelectionChange"
>
可以看到当数据被选中后,multipleSelection
会存放被选择的数据对象
// 复选框选中后执行的方法
handleSelectionChange(val) {
this.multipleSelection = val;
console.log(this.multipleSelection)
// console.log(this.multipleSelection)
},
而我们所需要的只是对象的id值,因此需要遍历multipleSelection
获取id值
// 从_this.multipleSelection获取brand的id值
for(let i=0; i<_this.multipleSelection.length;i++){
let selectItem = _this.multipleSelection[i];
_this.selectedIds[i] = selectItem.id;
}
批量删除提示
删除操作一般比较危险因此需要让用户确认是否删除,在element-ui中找到$confirm
组件实现该功能
deleteByIds(){
this.$confirm('确定删除吗?', '确认信息', {
distinguishCancelAndClose: true,
confirmButtonText: '确定',
cancelButtonText: '取消'
}).then(() => {
var _this = this;
// 从_this.multipleSelection获取brand的id值
for(let i=0; i<_this.multipleSelection.length;i++){
let selectItem = _this.multipleSelection[i];
_this.selectedIds[i] = selectItem.id;
}
axios({
method: 'post',
url:"http://localhost:8080/brand-case/brand/deleteByIds",
data: _this.selectedIds
}).then(function(response){
if(response.data == "success"){
// 重新查询数据
_this.selectAll();
//显示提示框
_this.$message({
type: 'success',
message: '删除成功'
});
}
})
}).catch(() => {
this.$message({
type: 'info',
message: "取消删除"
})
});
}
4.分页查询
需求分析
分页查询sql
分页查询也是从数据库进行查询的,所以我们要分页对应的SQL语句应该怎么写。分页查询使用 LIMIT
关键字,格式为:==LIMIT 开始索引 每页显示的条数
==。以后前端页面在发送请求携带参数时,它并不明确开始索引是什么,但是它知道查询第几页。所以 开始索引
需要在后端进行计算,计算的公式是 :==开始索引 = (当前页码 - 1)* 每页显示条数==
比如查询第一页的数据的 SQL 语句是:
select * from tb_brand limit 0,5;
查询第二页的数据的 SQL 语句是:
select * from tb_brand limit 5,5;
查询第三页的数据的 SQL 语句是:
select * from tb_brand limit 10,5;
前后端数据分析
分页查询功能时候比较复杂的,所以我们要先分析清楚以下两个问题:
前端需要传递什么参数给后端
根据上一步对分页查询 SQL 语句分析得出,前端需要给后端两个参数
- 当前页码 : currentPage
- 每页显示条数:pageSize
后端需要响应什么数据给前端
上图是分页查询页面展示的效果,从上面我们可以看出需要响应以下联股份数据
- 当前页需要展示的数据。我们在后端一般会存储到 List 集合中
- 总共记录数。在上图页面中需要展示总的记录数,所以这部分数据也需要。总的页面 elementUI 的分页组件会自动计算,我们不需要关心
而这两部分需要封装到 PageBean 对象中,并将该对象转换为 json 格式的数据响应回给浏览器
通过上面的分析我们需要先在 pojo
包下创建 PageBean
类,为了做到通过会将其定义成泛型类,代码如下:
//分页查询的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
/**
* 分页查询
* @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);
/**
* 查询总记录数
* @return
*/
@Select("select count(*) from tb_brand")
int selectTotalCount();
service
/**
* 分页查询
* @param currentPage
* @param pageSize
* @return
*/
public PageBean<Brand> selectByPage(int currentPage, int pageSize){
SqlSession sqlSession = sqlSessionFactory.openSession();
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> pageBean = new PageBean<>();
pageBean.setRows(rows);
pageBean.setTotalCount(totalCount);
sqlSession.close();
return pageBean;
}
servlet
/**
* 分页查询
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void selectByPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
// 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.转为json
PageBean<Brand> pageBean = service.selectByPage(currentpage, pagesize);
String string = JSON.toJSONString(pageBean);
// 响应数据
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(string);
}
前端实现
分页工具条
放在表单下面
<!--分页工具条-->
<el-pagination
@size-change="handleSizeChange" <!--设置每页显示条数-->
@current-change="handleCurrentChange" <!--修改当前页码-->
:current-page="currentPage" <!--绑定currentPage模型-->
:page-sizes="[5, 10, 15, 20]"
:page-size="5"
layout="total, sizes, prev, pager, next, jumper"
:total="totalCount"> <!--绑定totalCount模型-->
</el-pagination>
修改handleSizeChange
和handleCurrentChange
handleSizeChange(val) {
// console.log(`每页 ${val} 条`);
// 设置每页显示条数
this.pageSize = val;
// 重新查询
this.selectAll();
// this.currentPage=1
},
handleCurrentChange(val) {
this.currentPage = val;
},
修改selectAll()异步请求方法
主要修改url和获取响应数据后的处理方式
selectAll(){
// var _this = this;
// axios({
// method: 'GET',
// url: 'http://localhost:8080/brand-case/brand/selectAll'
// }).then(function(response){
// var brandlist = response.data;
// _this.brandlist = brandlist;
// })
var _this = this;
axios({
method: 'GET',
url: 'http://localhost:8080/brand-case/brand/selectByPage?currentpage='+ _this.currentPage + '&pagesize=' + _this.pageSize
}).then(function(response){
// 获取数据 {rows: { },totalCount:47}
var brandlist = response.data.rows;
// 设置表格数据
_this.brandlist = brandlist;
// 设置总记录数
_this.totalCount = response.data.totalCount;
})
},
5.多条件分页查询
需求分析
- 用户提交数据 状态 品牌名称 企业名称, 绑定到brand模型后作为参数提交为后端
- 三个条件的查询为and关系,并且对品牌和企业的查询需要使用模糊查询即需要like关键字
- 查询后依然需要分页显示,因此需要返回符合条件的数据外,还要返回对应的数量
- 可以根据分页查询修改代码
后端实现
dao
需要两个查询,selelctByConditions
和selectByConditionsCount
/**
* 多条件查询
* @param brand
* @param begin
* @param size
* @return
*/
List<Brand> selectByConditions(@Param("brand") Brand brand, @Param("begin") int begin, @Param("size") int size);
/**
* 多条件查询的记录条数
* @param brand
* @return
*/
int selectByConditionsCount(Brand brand);
在对应的BrandMapper.xml
中实现具体的查询
<select id="selectByConditions" resultType="com.itheima.pojo.Brand" resultMap="brandResultMap">
select * from tb_brand
<where>
<!--这里有多个参数,访问brand数据实时需要使用brand.brandName-->
<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="selectByConditionsCount" resultType="java.lang.Integer">
select count(*) from tb_brand
<where>
<!--这里为单个参数,访问brand数据实时需要使用#{brandName}-->
<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
中实现多条件分页查询的业务逻辑
/**
* 多条件分页查询
* @param currentPage
* @param pageSize
* @return
*/
public PageBean<Brand> selectByConditionsPage(Brand brand, int currentPage, int pageSize){
SqlSession sqlSession = sqlSessionFactory.openSession();
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
int begin = (currentPage - 1) * pageSize;
int size = pageSize;
// 对数据进行处理加上 % 模糊查询表达式
if(brand.getBrandName() != null && brand.getBrandName().length() > 0 ){
brand.setBrandName("%" + brand.getBrandName() + "%");
}
if(brand.getCompanyName() != null && brand.getCompanyName().length() > 0){
brand.setCompanyName("%" + brand.getCompanyName() + "%");
}
System.out.println(brand);
List<Brand> rows = mapper.selectByConditions(brand, begin, size);
int totalCount = mapper.selectByConditionsCount(brand);
PageBean<Brand> pageBean = new PageBean<>();
pageBean.setRows(rows);
pageBean.setTotalCount(totalCount);
sqlSession.close();
return pageBean;
}
servlet
/**
* 搜索
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void search(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.获取json字符串
BufferedReader reader = request.getReader();
String line = reader.readLine();
System.out.println(line);
// 3.将json字符串转为brand对象
Brand brand = JSON.parseObject(line, Brand.class);
System.out.println(brand);
// 4.调用service方法 获取pageBean对象
PageBean<Brand> pageBean = service.selectByConditionsPage(brand, currentpage, pagesize);
// 5.将PageBean转为Json字符串
String string = JSON.toJSONString(pageBean);
// 6.发送响应数据
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(string);
}
前端实现
获取查询条件
查询表单绑定的模型为searchBrand
<el-form :inline="true" :model="searchBrand" class="demo-form-inline">
searchBrand
数据如下
// 搜索数据
searchBrand:{
status: '',
brandName: '',
companyName: '',
id:"",
ordered:"",
description:""
},
当用户输入条件后,数据自动绑定到searchBrand
模型上,向后台提交数据时,提交searchBrand
即可
查询的异步请求
search(){
var _this = this;
axios({
method: 'POST',
url: 'http://localhost:8080/brand-case/brand/search?currentpage='+ _this.currentPage + '&pagesize=' + _this.pageSize,
data: this.searchBrand
}).then(function(response){
// 获取数据 {rows: { } }
// 设置表格数据
_this.brandlist = response.data.rows;
// 设置总记录数
_this.totalCount = response.data.totalCount;
_this.currentPage = 1;
})
}
6.删除单条数据
需求分析
- 删除单条数据时需要携带该数据的id,使用get请求既可
- 获取id,可以使用
scope.row.id
后端实现
dao
/**
* 删除指定商品
* @param id
*/
@Delete("delete from tb_brand where id = #{id}")
void deleteById(@Param("id") int id);
service
/**
* 删除指定数据
* @param id
*/
public void deleteById(int id){
SqlSession sqlSession = sqlSessionFactory.openSession();
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
mapper.deleteById(id);
sqlSession.commit();
sqlSession.close();
}
servlet
/**
* 删除指定数据
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void deleteById(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String _id = request.getParameter("id");
int id = Integer.parseInt(_id);
service.deleteById(id);
response.getWriter().write("success");
}
前端实现
获取id
在修改和删除按钮位置添加 slot-scope="scope"
<el-row slot-scope="scope">
<el-button type="primary" @click="update(scope.row)" >修改</el-button>
<el-button type="danger" @click="deleteById(scope.row.id)" >删除</el-button>
</el-row>
使用scope.row.id
即可获取到每行数据的id
异步请求
同样使用confirm组件提示用户是否真的想删除
deleteById(id){
this.$confirm('确定删除吗?', '确认信息', {
distinguishCancelAndClose: true,
confirmButtonText: '确定',
cancelButtonText: '取消'
}).then(() => {
this.currentPage=1
var _this = this;
var deleteId = id;
axios({
method: 'get',
url: 'http://localhost:8080/brand-case/brand/deleteById?id='+deleteId,
}).then(function(response){
if(response.data == "success"){
// 重新查询数据
_this.selectAll();
//显示提示框
_this.$message({
type: 'success',
message: '删除成功'
});
}
})
}).catch(() => {
this.$message({
type: 'info',
message: "取消删除"
})
})
},
7.修改
需求分析
- 修改与添加类似,需要弹出对话框后进行数据的编辑
- 如何获取每行的数据,使用
scope.row
即可 - 请求数据为json数据,后台接收后将其转换为Brand类型后进行更新操作
后端实现
dao
/**
* 更新数据
* @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
/**
* 更新数据
* @param brand
*/
public void update(Brand brand){
SqlSession sqlSession = sqlSessionFactory.openSession();
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
mapper.update(brand);
sqlSession.commit();
sqlSession.close();
}
servlet
/**
* 更新
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
BufferedReader reader = request.getReader();
String line = reader.readLine();
System.out.println(line);
Brand brand = JSON.parseObject(line, Brand.class);
System.out.println(brand);
service.update(brand);
response.getWriter().write("success");
}
前端实现
修改对话框
该对话框与增加对话的实现相同,不过该对话框初始时需要显示数据
<el-button type="primary" @click="update(scope.row)" >修改</el-button>
update函数的实现
update(data){
var _this = this;
this.dialogUpdateVisible = true;
this.updateBrand.brandName = data.brandName;
this.updateBrand.companyName = data.companyName;
this.updateBrand.status = data.status;
this.updateBrand.ordered = data.ordered;
this.updateBrand.description = data.description;
this.updateBrand.statusStr = data.statusStr;
},
updateBrand定义
data() {
return {
//带修改的数据
updateBrand:{
"brandName": "",
"companyName": "",
"description": "",
"id": 1,
"ordered": 0,
"status": 0,
"statusStr": ""
},
}
异步请求实现
修改对话框的提交按钮位置
<el-form-item>
<el-button type="primary" @click="updateSubmit">提交</el-button>
<!--取消 只需要将dialogVisble设置为false即可隐藏对话框-->
<el-button @click="dialogUpdateVisible = false">取消</el-button>
</el-form-item>
// 修改
updateSubmit(){
var _this = this;
axios({
method:'POST',
url: 'http://localhost:8080/brand-case/brand/update',
data: _this.updateBrand
}).then(function(response){
if(response.data == "success"){
_this.dialogUpdateVisible = false;
_this.selectAll();
_this.msgbox("修改成功!");
}
})
},
登录注册功能实现
登录功能
后端实现
dao
在src/main/java/com/itheima/mapper
创建UserMapper
接口
package com.itheima.mapper;
import com.itheima.pojo.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
public interface UserMapper {
/**
* 用户登录
* 检查用户名和密码是否正确
* @param username
* @param password
* @return
*/
@Select("select * from tb_user where username = #{username} and password = #{password}")
User select(@Param("username") String username, @Param("password") String password);
}
service
在src/main/java/com/itheima/service
中创建UserService
类
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();
/**
* 用户登录方法
* @param username
* @param password
* @return
*/
public User Login(String username, String password){
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.select(username, password);
if(user != null){
return user;
}else {
return null;
}
}
servlet
在src/main/java/com/itheima/servler/web
中创建LoginServlet
package com.itheima.web.servlet;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
@WebServlet(value = "/login")
public class LoginServlet extends HttpServlet {
// 因为BrandService可能多次用到 所以变成成员变量 减少创建的次数
UserService userservice = new UserService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("login");
//解决POST请求中文乱码问题
request.setCharacterEncoding("UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = userservice.Login(username, password);
if(user != null){
// 登录成功,页面跳转到了 brand.jsp
// 保存用户的session信息
// 检查是否勾选记住密码
String remember = request.getParameter("remember");
if("1".equals(remember)){
Cookie c_username = new Cookie("username", username);
Cookie c_password = new Cookie("password", password);
c_username.setMaxAge(3600*24*7);
c_password.setMaxAge(3600*24*7);
// 发送Cookie
response.addCookie(c_username);
response.addCookie(c_password);
}
HttpSession session = request.getSession();
session.setAttribute("user", user);
// 获取项目的虚拟路径
String contextPath = request.getContextPath();
request.getRequestDispatcher( "/brand.html").forward(request,response);
}else {
// 登录失败,在页面显示登录失败的提示信息
request.setAttribute("login_msg","用户名或密码错误");
request.getRequestDispatcher("/login.jsp").forward(request,response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
前端实现
<div id="loginDiv" style="height: 350px">
<form action="/brand-case/login" method="post"id="form">
<h1 id="loginMsg">LOGIN IN</h1>
<div id="errorMsg">${login_msg} ${register_msg}</div>
<p>Username:<input id="username" name="username" value="${cookie.username.value}" type="text"></p>
<p>Password:<input id="password" name="password" value="${cookie.password.value}"type="password"></p>
<p>Remember:<input id="remember" name="remember" value="1" type="checkbox"></p>
<div id="subDiv">
<input type="submit" class="button" value="login up">
<input type="reset" class="button" value="reset">
<a href="register.jsp">没有账号?</a>
</div>
</form>
</div>
注册功能
后端实现
dao
/**
* 查询是否存在相同的用户名
* @param username
* @return
*/
@Select("select * from tb_user where username = #{username}")
User selectUser(@Param("username") String username);
/**
* 用户注册
* @param user
*/
@Insert("insert into tb_user values (null, #{username}, #{password})")
void addUser(User user);
service
/**
* 注册功能
* @param user
* @return
*/
public boolean Register(User user){
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User u = mapper.selectUser(user.getUsername());
if(u == null){
mapper.addUser(user);
session.commit();
}
session.commit();
return u == null;
}
servlet
package com.itheima.web.servlet;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet(value = "/register")
public class RegisterServlet extends HttpServlet {
// 因为UserService可能多次用到 所以变成成员变量 减少创建的次数
UserService userservice = new UserService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//解决POST请求中文乱码问题
request.setCharacterEncoding("UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
String checkCode1 = request.getParameter("checkCode");
User user = new User();
user.setUsername(username);
user.setPassword(password);
// 获取生成的验证码信息
HttpSession session = request.getSession();
String checkCode =(String) session.getAttribute("checkCode");
// 比较用户输入的验证码
// 细节:checkCode为程序中定义的变量 比较时放在前面,否则可能出现空指针错误
if(!checkCode.equals(checkCode1)){
request.setAttribute("register_msg", "验证码错误,请重新输入!");
request.getRequestDispatcher("/register.jsp").forward(request,response);
// 禁止注册
return ;
}
boolean flag = userservice.Register(user);
if(flag){
request.setAttribute("register_msg","注册成功,请登录!");
request.getRequestDispatcher("/login.jsp").forward(request,response);
}else{
request.setAttribute("register_msg","注册成功,请重试!");
request.getRequestDispatcher("/regsiter.jsp").forward(request,response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
前端实现
<div class="form-div">
<div class="reg-content">
<h1>欢迎注册</h1>
<span>已有帐号?</span> <a href="login.jsp">登录</a>
</div>
<form id="reg-form" action="/register" method="post">
<table>
<tr>
<td>用户名</td>
<td class="inputs">
<input name="username" type="text" id="username">
<br>
<span id="username_err" class="err_msg">${register_msg}</span>
</td>
</tr>
<tr>
<td>密码</td>
<td class="inputs">
<input name="password" type="password" id="password">
<br>
<span id="password_err" class="err_msg" style="display: none">密码格式有误</span>
</td>
</tr>
<tr>
<td>验证码</td>
<td class="inputs">
<input name="checkCode" type="text" id="checkCode">
<img id="checkCodeImg" src="/checkCode">
<a href="#" id="changeImg">看不清?</a>
</td>
</tr>
</table>
<div class="buttons">
<input value="注 册" type="submit" id="reg_btn">
</div>
<br class="clear">
</form>
</div>
</body>
<script>
document.getElementById("changeImgg").onclick = function() {
//路径后面添加时间戳的目的是避免浏览器进行缓存静态资源
document.getElementById("checkCodeImg").src = "/checkCode?"+new Date().getMilliseconds();
}
</script>
验证码模块
借助验证码生成工具
在src/main/java/com/itheima/util
引入生成验证码的类
将验证码显示在页面上
在src/main/java/com/itheima/servler/web
创建CheckCodeServlet
package com.itheima.web.servlet;
import com.itheima.service.BrandService;
import com.itheima.util.CheckCodeUtil;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet(value = "/checkCode")
public class CheckCodeServlet extends HttpServlet {
// 因为BrandService可能多次用到 所以变成成员变量 减少创建的次数
private BrandService brandService = new BrandService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 生成验证码
// 获取响应输出流
ServletOutputStream os = response.getOutputStream();
// 生成验证码图片显示在jsp页面上
String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4);
// 将生成的验证码信息发送到 /register 用于验证用户输入的验证码是否正确
// 将验证码存入session
HttpSession codeSession = request.getSession();
codeSession.setAttribute("checkCode", checkCode);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
权限验证
使用javaweb的三大组件 filer
实现
在src/main/java/com/itheima/servler/web/filter
中创建LoginFilter
package com.sunzy.web.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
//@WebFilter("/*")
public class LoginFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
// 放行登录相关的资源
String urls[] = {"/login.jsp", "/css/", "/imgs/", "register.jsp","/checkCode","/register","/login"};
HttpServletRequest req = (HttpServletRequest) request;
StringBuffer requestURL = req.getRequestURL();
String url = requestURL.toString();
for(String u :urls ){
if(url.contains(u)){
chain.doFilter(request,response);
return ;
}
}
HttpSession session = req.getSession();
Object user = session.getAttribute("user");
if(user != null){
chain.doFilter(request, response);
}else {
request.setAttribute("login_msg", "您尚未登录!");
request.getRequestDispatcher("/login.jsp").forward(req, response);
}
}
}
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!