JavaWeb综合案例

综合案例

1 查询所有

在这里插入图片描述

1.1 后端实现
1.1.1 dao方法实现

com.itheima.mapper.BrandMapper 接口中定义抽象方法,并使用 @Select 注解编写 sql 语句

/**
     * 查询所有
     * @return
     */
@Select("select * from tb_brand")
List<Brand> selectAll();

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

<?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.itheima.mapper.BrandMapper">

    <resultMap id="brandResultMap" type="brand">
        <result property="brandName" column="brand_name" />
        <result property="companyName" column="company_name" />
    </resultMap>
</mapper>

定义完结果映射关系后,在接口 selectAll() 方法上引用该结构映射。使用 @ResultMap("brandResultMap") 注解

完整接口的 selectAll() 方法如下:

/**
     * 查询所有
     * @return
     */
@Select("select * from tb_brand")
@ResultMap("brandResultMap")
List<Brand> selectAll();
1.1.2 service方法实现

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

public interface BrandService {

    /**
     * 查询所有
     * @return
     */
    List<Brand> selectAll();
}

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

public class BrandServiceImpl implements BrandService {

    @Override
    public List<Brand> selectAll() {
    }
}

此处为什么要给 service 定义接口呢?因为service定义了接口后,在 servlet 中就可以使用多态的形式创建Service实现类的对象,如下:

在这里插入图片描述

这里使用多态是因为方便我们后期解除 Servletservice 的耦合。从上面的代码我们可以看到 SelectAllServlet 类和 BrandServiceImpl 类之间是耦合在一起的,如果后期 BrandService 有其它更好的实现类(例如叫 BrandServiceImpl),那就需要修改 SelectAllServlet 类中的代码。后面我们学习了 Spring 框架后就可以解除 SelectAllServlet 类和红色框括起来的代码耦合。而现在咱们还做不到解除耦合,在这里只需要理解为什么定义接口即可。

BrandServiceImpl 类代码如下:

public class BrandServiceImpl implements BrandService {
    //1. 创建SqlSessionFactory 工厂对象
    SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory();

    @Override
    public List<Brand> selectAll() {
        //2. 获取SqlSession对象
        SqlSession sqlSession = factory.openSession();
        //3. 获取BrandMapper
        BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);

        //4. 调用方法
        List<Brand> brands = mapper.selectAll();

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

        return brands;
    }
}
1.1.3 servlet实现

com.itheima.web.servlet 包下定义名为 SelectAllServlet 的查询所有的 servlet。该 servlet 逻辑如下:

  • 调用service的 selectAll() 方法查询所有的品牌数据,并接口返回结果
  • 将返回的结果转换为 json 数据
  • 响应 json 数据

代码如下:

@WebServlet("/selectAllServlet")
public class SelectAllServlet extends HttpServlet {

    private BrandService brandService = new BrandServiceImpl();

    @Override
    protected void doGet(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);
    }

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

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

在这里插入图片描述

前端代码如下:

 mounted(){
     //当页面加载完成后,发送异步请求,获取数据
     var _this = this;

     axios({
         method:"get",
         url:"http://localhost:8080/brand-case/selectAllServlet"
     }).then(function (resp) {
         _this.tableData = resp.data;
     })
 }

2 添加功能

在这里插入图片描述

2.1 后端实现
2.1.1 dao方法实现

BrandMapper 接口中定义 add() 添加方法,并使用 @Insert 注解编写sql语句

/**
     * 添加数据
     * @param brand
     */
@Insert("insert into tb_brand values(null,#{brandName},#{companyName},#{ordered},#{description},#{status})")
void add(Brand brand);
2.1.2 service方法实现

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

/**
     * 添加数据
     * @param brand
     */
void add(Brand brand);

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

@Override
public void add(Brand brand) {
    //2. 获取SqlSession对象
    SqlSession sqlSession = factory.openSession();
    //3. 获取BrandMapper
    BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);

    //4. 调用方法
    mapper.add(brand);
    sqlSession.commit();//提交事务

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

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

2.1.3 servlet实现

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

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

servlet 代码实现如下:

@WebServlet("/addServlet")
public class AddServlet extends HttpServlet {

    private BrandService brandService = new BrandServiceImpl();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //1. 接收品牌数据
        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");
    }

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

上图左边是页面效果,里面的 提交 按钮可以通过上图右边看出绑定了一个 单击事件,而该事件绑定的是 addBrand 函数,所以添加数据功能的逻辑代码应该写在 addBrand() 函数中。在此方法中需要发送异步请求并将表单中输入的数据作为参数进行传递。如下

// 添加数据
addBrand() {
	//console.log(this.brand);
	//发送ajax请求
	axios({
        method:"post",
        url:"http://localhost:8080/brand-case/addServlet",
        data:this.brand
    }).then(resp=> {
       	//响应数据的处理逻辑
    })
}

then 函数中的匿名函数是成功后的回调函数,而 resp.data 就可以获取到响应回来的数据,如果值是 success 表示数据添加成功。成功后我们需要做一下逻辑处理:

  1. 关闭新增对话框窗口

    如下图所示是添加数据的对话框代码,从代码中可以看到此对话框绑定了 dialogVisible 数据模型,只需要将该数据模型的值设置为 false,就可以关闭新增对话框窗口了。

  2. 重新查询数据

    数据添加成功与否,用户只要能在页面上查看到数据说明添加成功。而此处需要重新发送异步请求获取所有的品牌数据,而这段代码在 查询所有 功能中已经实现,所以我们可以将此功能代码进行抽取,抽取到一个 selectAll() 函数中

    // 查询所有数据
    selectAll(){
        axios({
            method:"get",
            url:"http://localhost:8080/brand-case/selectAllServlet"
        }).then(resp=> {
            this.tableData = resp.data;
        })
    }
    

    那么就需要将 mounted() 钩子函数中代码改进为

    mounted(){
        //当页面加载完成后,发送异步请求,获取数据
        this.selectAll();
    }
    

    同时在新增响应的回调中调用 selectAll() 进行数据的重新查询。

  3. 弹出消息给用户提示添加成功
    (../../../../../传智播客/2021年/web阶段文档编写/JavaWeb课程文档/day14-综合案例/assets/image-20210825224958220.png)]

    上图左边就是 elementUI 官网提供的成功提示代码,而上图右边是具体的效果。

    注意:上面的this需要的是表示 VUE 对象的this。

综上所述,前端代码如下:

			// 添加数据
			addBrand() {
                //console.log(this.brand);
                //发送ajax请求
                axios({
                    method: "post",
                    url: "http://localhost:8080/brand-case/addServlet",
                    data: this.brand
                }).then(resp => {
                    if (resp.data == "success") {
                        //添加成功
                        //关闭窗口
                        this.dialogVisible = false;

                        //重新查询所有
                        this.selectAll();
                        //弹出消息提示
                        this.$message({
                            message: '恭喜你,添加成功',
                            type: 'success'
                        });
                    }
                })
            },

3. Servlet代码优化

3.1 问题导入

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(...) {}
}

而我们知道发送请求 servlettomcat 会自动的调用 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 对象,所以指定方法的参数为 HttpServletRequestHttpServletResponse,那么 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();
        }
    }
}

4. 批量删除

在这里插入图片描述
注意:

前端发送请求时需要将要删除的多个id值以json格式提交给后端,而该json格式数据如下:

[1,2,3,4]
4.1 后端实现
4.1.1 dao方法实现

BrandMapper 接口中定义 deleteByIds() 添加方法,由于这里面要用到动态 sql ,属于复杂的sql操作,建议使用映射配置文件。

接口方法声明如下:

 /**
     * 批量删除
     * @param ids
     */
void deleteByIds(@Param("ids") int[] ids);

BrandMapper.xml 映射配置文件中添加 statement

<delete id="deleteByIds">
    delete from tb_brand where id in
    <foreach collection="ids" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
</delete>
4.1.2 service方法实现

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

/**
     * 批量删除
     * @param ids
     */
void deleteByIds( int[] ids);

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

@Override
public void deleteByIds(int[] ids) {
    //2. 获取SqlSession对象
    SqlSession sqlSession = factory.openSession();
    //3. 获取BrandMapper
    BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);

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

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

    //5. 释放资源
    sqlSession.close();
}
4.1.3 servlet实现

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

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

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

public void deleteByIds(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1. 接收数据 json  [1,2,3]
    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");
}
4.2 前端实现

此功能的前端代码实现稍微有点麻烦,分为以下几步实现

4.2.1 获取选中的id值

从上图可以看出表格复选框绑定了一个 selection-change 事件,该事件是当选择项发生变化时会触发。该事件绑定了 handleSelectionChange 函数,而该函数有一个参数 val ,该参数是获取选中行的数据,如下

而我们只需要将所有选中数据的id值提交给服务端即可,获取id的逻辑我们书写在 批量删除 按钮绑定的函数中。

批量删除 按钮绑定单击事件,并给绑定触发时调用的函数,如下

并在Vue对象中的 methods 中定义 deleteByIds() 函数,在该函数中从 multipleSelection 数据模型中获取所选数据的id值。要完成这个功能需要在 Vue 对象中定义一个数据模型 selectedIds:[],在 deleteByIds() 函数中遍历 multipleSelection 数组,并获取到每一个所选数据的id值存储到 selectedIds 数组中,代码实现如下:

//1. 创建id数组 [1,2,3], 从 this.multipleSelection 获取即可
for (let i = 0; i < this.multipleSelection.length; i++) {
    let selectionElement = this.multipleSelection[i];
    this.selectedIds[i] = selectionElement.id;
}
4.2.2 发送异步请求

使用 axios 发送异步请求并经上一步获取到的存储所有的 id 数组作为请求参数

//2. 发送AJAX请求
var _this = this;

// 发送ajax请求,添加数据
axios({
    method:"post",
    url:"http://localhost:8080/brand-case/brand/deleteByIds",
    data:_this.selectedIds
}).then(function (resp) {
    if(resp.data == "success"){
        //删除成功
        // 重新查询数据
        _this.selectAll();
        // 弹出消息提示
        _this.$message({
            message: '恭喜你,删除成功',
            type: 'success'
        });
    }
})
4.2.3 确定框实现

由于删除操作是比较危险的;有时候可能是由于用户的误操作点击了 批量删除 按钮,所以在点击了按钮后需要先给用户确认提示。而确认框在 elementUI 中也提供了,如下图

(assets/image-20210826225742852.png)]

而在点击 确定 按钮后需要执行之前删除的逻辑。因此前端代码实现如下:

 // 批量删除
deleteByIds(){
    // 弹出确认提示框
    this.$confirm('此操作将删除该数据, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
    }).then(() => {
        //用户点击确认按钮
        //1. 创建id数组 [1,2,3], 从 this.multipleSelection 获取即可
        for (let i = 0; i < this.multipleSelection.length; i++) {
            let selectionElement = this.multipleSelection[i];
            this.selectedIds[i] = selectionElement.id;
        }
        //2. 发送AJAX请求
        var _this = this;
        // 发送ajax请求,添加数据
        axios({
            method:"post",
            url:"http://localhost:8080/brand-case/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: '已取消删除'
        });
    });
}

5,分页查询

5.1 分析
5.1.1 分页查询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;
5.1.2 前后端数据分析

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

  • 前端需要传递什么参数给后端

    根据上一步对分页查询 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;
    }
}
5.1.3 流程分析

后端需要响应总记录数当前页的数据 两部分数据给前端,所以在 BrandMapper 接口中需要定义两个方法:

  • selectByPage() :查询当前页的数据的方法
  • selectTotalCount() :查询总记录的方法

整体流程如下:

[(img-KDRtUxNs-1681189849288)(assets/image-20210826232620404.png)]

5.2 后端实现
5.2.1 dao方法实现

BrandMapper 接口中定义 selectByPage() 方法进行分页查询,代码如下:

/**
     * 分页查询
     * @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() 方法进行统计记录数,代码如下:

/**
     * 查询总记录数
     * @return
     */
@Select("select count(*) from tb_brand ")
int selectTotalCount();
5.2.2 service方法实现

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

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

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

@Override
public PageBean<Brand> selectByPage(int currentPage, int pageSize) {
    //2. 获取SqlSession对象
    SqlSession sqlSession = factory.openSession();
    //3. 获取BrandMapper
    BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
    //4. 计算开始索引
    int begin = (currentPage - 1) * pageSize;
    // 计算查询条目数
    int size = pageSize;
    //5. 查询当前页数据
    List<Brand> rows = mapper.selectByPage(begin, size);
    //6. 查询总记录数
    int totalCount = mapper.selectTotalCount();
    //7. 封装PageBean对象
    PageBean<Brand> pageBean = new PageBean<>();
    pageBean.setRows(rows);
    pageBean.setTotalCount(totalCount);

    //8. 释放资源
    sqlSession.close();
    return pageBean;
}
5.2.3 servlet实现

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

  • 获取页面提交的 当前页码每页显示条目数 两个数据。这两个参数是在url后进行拼接的,格式是 url?currentPage=1&pageSize=5。获取这样的参数需要使用 requet.getparameter() 方法获取。
  • 调用 service 的 selectByPage() 方法进行分页查询的业务逻辑处理
  • 将查询到的数据转换为 json 格式的数据
  • 响应 json 数据

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

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);
}
5.2.4 测试

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pmrd9y2T-1681189849289)(assets/image-20210828184610060.png)]

5.3 前端实现
5.3.1 selectAll 代码改进

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

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

var _this = this;
axios({
    method:"post",
    url:"http://localhost:8080/brand-case/brand/selectByPage?currentPage=1&pageSize=5"
}).then(resp =>{
    //设置表格数据
    _this.tableData = resp.data.rows; // {rows:[],totalCount:100}
})

响应的数据中还有总记录数,要进行总记录数展示需要在页面绑定数据模型

注意:该数据模型需要在Vue对象中声明出来。

那异步请求的代码就可以优化为

var _this = this;
axios({
    method:"post",
    url:"http://localhost:8080/brand-case/brand/selectByPage?currentPage=1&pageSize=5"
}).then(resp =>{
    //设置表格数据
    _this.tableData = resp.data.rows; // {rows:[],totalCount:100}
    //设置总记录数
    _this.totalCount = resp.data.totalCount;
})

而页面中分页组件给 当前页码每页显示的条目数 都绑定了数据模型

所以 selectAll() 函数中发送异步请求的资源路径中不能将当前页码和 每页显示条目数写死,代码就可以优化为

var _this = this;
axios({
    method:"post",
    url:"http://localhost:8080/brand-case/brand/selectByPage?currentPage="+this.currentPage+"&pageSize=" + this.pageSize
}).then(resp =>{
    //设置表格数据
    _this.tableData = resp.data.rows; // {rows:[],totalCount:100}
    //设置总记录数
    _this.totalCount = resp.data.totalCount;
})
5.3.2 改变每页条目数

当我们改变每页显示的条目数后,需要重新发送异步请求。而下图是分页组件代码,@size-change 就是每页显示的条目数发生变化时会触发的事件

而该事件绑定了一个 handleSizeChange 函数,整个逻辑如下:

handleSizeChange(val) { //我们选择的是 ‘5条/页’ 此值就是 5.而我们选择了 `10条/页` 此值就是 10
    // 重新设置每页显示的条数
    this.pageSize  = val; 
    //调用 selectAll 函数重新分页查询数据
    this.selectAll();
}
5.3.3 改变当前页码

当我们改变页码时,需要重新发送异步请求。而下图是分页组件代码,@current-change 就是页码发生变化时会触发的事件

而该事件绑定了一个 handleSizeChange 函数,整个逻辑如下:

handleCurrentChange(val) { //val 就是改变后的页码
    // 重新设置当前页码
    this.currentPage  = val;
    //调用 selectAll 函数重新分页查询数据
    this.selectAll();
}

6,条件查询

上图就是用来输入条件查询的条件数据的。要做条件查询功能,先明确以下三个问题

  • 3个条件之间什么关系?

    同时满足,所用 SQL 中多个条件需要使用 and 关键字连接

  • 3个条件必须全部填写吗?

    不需要。想根据哪儿个条件查询就写那个,所以这里需要使用动态 sql 语句

  • 条件查询需要分页吗?

    需要

根据上面三个问题的明确,我们就可以确定sql语句了:

整个条件分页查询流程如下

(assets/image-20210828224859135.png)]

6.1 后端实现
6.1.1 dao实现

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

/**
     * 分页条件查询
     * @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 书写如下:

<!--查询满足条件的数据并进行分页-->
<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>
6.1.2 service实现

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

 /**
     * 分页条件查询
     * @param currentPage
     * @param pageSize
     * @param brand
     * @return
     */
PageBean<Brand>  selectByPageAndCondition(int currentPage,int pageSize,Brand brand);

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

 @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 属性值到时候需要进行模糊查询,所以前后需要拼接上 %

6.1.3 servlet实现

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

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

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

    // 获取查询条件对象
    BufferedReader br = request.getReader();
    String params = br.readLine();//json字符串
    
    //转为 Brand
    Brand brand = JSON.parseObject(params, Brand.class);
    
  • 调用 service 的 selectByPageAndCondition() 方法进行分页查询的业务逻辑处理

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

  • 响应 json 数据

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

/**
     * 分页条件查询
     * @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);
}
8.2 前端实现

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

  1. 查询表单绑定查询条件对象模型

    这一步在页面上已经实现了,页面代码如下:

  2. 点击查询按钮查询数据

    从上面页面可以看到给 查询 按钮绑定了 onSubmit() 函数,而在 onSubmit() 函数中只需要调用 selectAll() 函数进行条件分页查询。

  3. 改进 selectAll() 函数

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

    • 当前页码每页显示条数 这两个参数我们会拼接到 URL 的后面
    • 查询条件对象 这个参数需要以 json 格式提交给后端程序

    修改 selectAll() 函数逻辑为

    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;
    })
    

7,前端代码优化

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

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 中的新语法(箭头函数)来简化这部分代码,如上面的代码可以简化为:

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
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的 JavaWeb 项目完整案例项目名称:图书管理系统 技术栈:JavaServlet、JSP、MySQL、Tomcat 功能模块: 1. 用户登录:用户输入用户名和密码登录系统。 2. 图书查询:用户可以根据图书名称、作者、出版社等信息查询图书。 3. 图书借阅:用户可以选择图书进行借阅,并记录借阅时间和归还时间。 4. 图书归还:用户可以归还已借阅的图书,并更新归还时间。 5. 用户管理:管理员可以对用户进行添加、修改、删除等操作。 6. 图书管理:管理员可以对图书进行添加、修改、删除等操作。 7. 借阅记录查询:管理员可以查询借阅记录,包括借阅时间、归还时间等信息。 8. 统计报表:管理员可以生成图书借阅统计报表,包括借阅次数、借阅时间等信息。 实现步骤: 1. 数据库设计:设计数据库表结构,包括用户表、图书表、借阅记录表等。 2. 编写 JavaBean:编写 JavaBean 类,包括用户类、图书类、借阅记录类等。 3. 编写 DAO 层:编写数据访问层代码,实现对数据库的增删改查等操作。 4. 编写 Servlet:编写 Servlet 类,处理用户请求,调用 DAO 层代码进行数据操作。 5. 编写 JSP:编写 JSP 页面,展示数据和用户界面。 6. 部署项目:将项目部署到 Tomcat 服务器上,测试项目功能。 以上是一个简单的 JavaWeb 项目完整案例。当然,实际项目可能会更加复杂,需要考虑更多的业务需求和技术实现细节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值