建站笔记(六)-分类管理

建站笔记(六)

分类管理

参考笔记原文链接

本文将从MVC架构,分类的新增、编辑修改、删除来讲述SpringBoot搭建个人博客的分类管理

1.持久层接口

正常情况下自己写代码的时候是一个一个功能来完成,为了方便书写,这里就将分类管理中所有功能一次列出来。

分析:

分析:

问:持久层需要哪些接口?

答:普通的增删改查,查询分类,删除分类,修改编辑分类,查询分类,因此需要有getAllType、saveType、updateType、deleteType接口

问:分类可是会涉及到多表查询的,光这些接口够了吗?编辑修改分类的时候如何判断是修改的哪个分类?跳转修改页面时如何将要修改的参数获取到前端页面?分类页面显示的时候需要显示博客信息,要如何将博客和分类一起显示出来?这些都是需要解决的问题,不过这里只解决分类管理的问题,多表问题后面遇到了再来解决。

答:根据功能来看,光以上接口肯定是不够的,还要有:

  • getTypeByName():新增或修改分类时根据分类名称来查询是否有同样的分类,因为不允许添加重复名字的分类
  • getType():跳转修改分类页面时根据id查询分类,方便将要修改的分类参数传递到前端页面

在dao包下创建TypeDao分类持久层接口,代码如下

package com.cbx.dao;

import com.cbx.entity.Type;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author cbx
 * @date 2022/3/4
 * @apiNote 分类持久层接口
 */
@Mapper
@Repository
public interface TypeDao {
    // 新增保存分类
    int saveType(Type type);

    // 根据id查询分类
    Type getType(Long id);

    // 查询所有分类
    List<Type> getAllType();

    // 根据名称查询分类
    Type getTypeByName(String name);

    // 编辑修改分类
    int updateType(Type type);

    // 删除分类
    void deleteType(Long id);

}

2.分类管理mapper

分析:

问:分类的mapper里面需要哪些SQL?

答:根据需求来:增、删、改、查,根据持久层的接口,查询需要有根据id查询分类、查询所有分类、根据分类名称查询分类

在mapper文件夹下创建TypeDao.xml文件,编写SQL,如下:

<?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.cbx.dao.TypeDao">


    <!--新增保存分类-->
    <insert id="saveType" parameterType="com.cbx.entity.Type">
        insert into myblog.t_type values (#{id},#{name});
    </insert>

    <!--编辑更新分类-->
    <update id="updateType" parameterType="com.cbx.entity.Type">
        update myblog.t_type set name = #{name} where id = #{id};
    </update>

    <!-- 删除分类-->
    <delete id="deleteType">
        delete from myblog.t_type where id = #{id};
    </delete>


    <!--根据id查询分类-->
    <select id="getType" resultType="com.cbx.entity.Type">
        select * from myblog.t_type where id = #{id};
    </select>


    <!--查询所有分类-->
    <select id="getAllType" resultType="com.cbx.entity.Type">
        select * from myblog.t_type;
    </select>

    <!--根据名称查询分类-->
    <select id="getTypeByName" resultType="com.cbx.entity.Type">
        select * from myblog.t_type where name = #{name};
    </select>

</mapper>

讲解:

  • parameterType属性:用于指定传入参数的类型,传入的是一个类的对象,所以写全类名
  • resultType属性:用于指定结果集的类型
  • #{ } 字符:代表占位符,类似 jdbc 中的 ?,用于执行语句时替换实际的数据

3.分类管理业务层

分类业务层接口

这里和持久层接口是一样的,就不做分析,在service包下创建TypeService接口,代码如下:

package com.cbx.service;

import com.cbx.entity.Type;

import java.util.List;

/**
 * @author cbx
 * @date 2022/3/4
 * @apiNote
 */
public interface TypeService {
    // 新增保存分类
    int saveType(Type type);

    // 根据id查询分类
    Type getType(Long id);

    // 查询所有分类
    List<Type> getAllType();

    // 根据名称查询分类
    Type getTypeByName(String name);

    // 编辑修改分类
    int updateType(Type type);

    // 删除分类
    void deleteType(Long id);

}

接口实现类

没啥好分析的,直接调用持久层接口,在Impl包下创建TypeServiceImpl类实现TypeService接口,代码如下:

package com.cbx.service.impl;

import com.cbx.dao.TypeDao;
import com.cbx.entity.Type;
import com.cbx.service.TypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * @author cbx
 * @date 2022/3/4
 * @apiNote 分类业务层接口实现类
 * @Service注解:用于标注业务层组件
 *
 * @Autowired注解:@Autowired表示被修饰的类需要注入对象,spring会扫描所有被@Autowired标注的类,然后根据类型在ioc容器中找到匹配的类注入
 *
 * @Transactional注解:实现事务操作
 */
@Service
public class TypeServiceImpl implements TypeService {

    @Autowired
    private TypeDao typeDao;

    @Override
    @Transactional
    public int saveType(Type type) {
        int i = typeDao.saveType(type);
        return i;
    }

    @Override
    @Transactional
    public Type getType(Long id) {
        Type type = typeDao.getType(id);
        return type;
    }

    @Override
    @Transactional
    public List<Type> getAllType() {
        List<Type> allType = typeDao.getAllType();
        return allType;
    }

    @Override
    public Type getTypeByName(String name) {
        Type type = typeDao.getTypeByName(name);
        return type;
    }

    @Override
    @Transactional
    public int updateType(Type type) {
        int i = typeDao.updateType(type);
        return i;
    }

    @Override
    @Transactional
    public void deleteType(Long id) {
        typeDao.deleteType(id);
    }

}

讲解:

  • @Service注解:用于标注业务层组件
  • @Autowired注解:@Autowired表示被修饰的类需要注入对象,spring会扫描所有被@Autowired标注的类,然后根据类型在ioc容器中找到匹配的类注入
  • @Transactional注解:实现事务操作

4.分类管理控制器

分析:

问:分类管理控制器需要考虑哪些功能?

答:基本的增、删、改、查功能

问:增删改查够了吗?为了有良好的体验,前端页面显示的时候需要能够分页显示,操作成功后在前端有信息提示,并需要做重复判断,这些要如何实现呢?

答:分页显示使用PageHelper插件具体使用可以参考:SpringBoot引入Pagehelper分页插件 或者Springboot Mybatis使用pageHelper实现分页查询, 前端信息提示可以用model.addAttribute,重复添加使用@Valid注解:请求数据校验,再做一个逻辑判断就可以了

添加PageHelper分页插件,在pom.xml中添加:

<!--引入分页插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.12</version>
        </dependency>

注意在application-xxx.yml的配置文件中进行分页插件的配置

pagehelper:
  auto-dialect: mysql
  params: count=countSql
  reasonable: true
  support-methods-arguments: true

配置参数的说明如下:

·helper-dialect:

配置使用哪种数据库语言,不配置的话pageHelper也会自动检测

·reasonable:

配置分页参数合理化功能,默认是false。 #启用合理化时,如果pageNum<1会查询第一页,如果pageNum>总页数会查询最后一页; #禁用合理化时,如果pageNum<1或pageNum>总页数会返回空数据。

·params:

为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值; 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值, 默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。

·support-methods-arguments:

支持通过Mapper接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。

在admin包下创建TypeController控制器类,代码如下:

package com.cbx.controller.admin;

import com.cbx.entity.Type;
import com.cbx.service.TypeService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.validation.Valid;
import java.util.List;

/**
 * @author cbx
 * @date 2022/3/4
 * @apiNote
 * @Controller注解:用于标注控制层组件
 * @RequestMapping("/admin"):建立请求URL和处理方法之间的对应关系
 * @GetMapping注解:一个组合注解,是@RequestMapping(method = RequestMethod.GET)的缩写,用于将HTTP get请求映射到特定处理程序的方法注解
 * @PostMapping注解:一个组合注解,是@RequestMapping(method = RequestMethod.POST)的缩写,用于将HTTP post请求映射到特定处理程序的方法注解
 * @Valid注解:请求数据校验,用来判断是否有重复的分类
 * @PathVariable注解:获取URL中的数据
 * attributes.addFlashAttribute:相当于重定向后,在URL后面拼接了参数,这样在重定向之后的页面后者控制器再去获取URL后面的参数就可以了
 */
@Controller
@RequestMapping("/admin")
public class TypeController {

    @Autowired
    private TypeService typeService;

    // 分页查询分类列表
    @GetMapping("types")
    public String list(Model model, @RequestParam(defaultValue = "1",value = "pageNum")Integer pageNum){
        // 按照排序字段 倒序 排序
        String orderBy = "id desc";
        PageHelper.startPage(pageNum,10,orderBy);
        List<Type> list = typeService.getAllType();
        PageInfo<Type> pageInfo = new PageInfo<>(list);
        model.addAttribute("pageInfo",pageInfo);
        return "admin/types";
    }

    // 返回新增分类页面
    @GetMapping("/types/input")
    public String input(Model model){
        model.addAttribute("type",new Type());
        return "admin/types-input";
    }

    // 新增分类
    @PostMapping("/types")
    public String post(@Valid Type type, RedirectAttributes attributes){
        Type type1 = typeService.getTypeByName(type.getName());
        if (type1 != null){
            attributes.addFlashAttribute("message","不能添加重复的分类");
            return "redirect:/admin/types/input";
        }
        int t = typeService.saveType(type);
        if (t == 0){
            attributes.addFlashAttribute("message","新增失败");
        }else {
            attributes.addFlashAttribute("message","新增成功");
        }
        return "redirect:/admin/types";
    }



    // 跳转修改分类页面
    @GetMapping("/types/{id}/input")
    public String editInput(@PathVariable Long id,Model model){
        model.addAttribute("type",typeService.getType(id));
        return "admin/types-input";
    }

    // 编辑修改分类
    @PostMapping("/types{id}")
    public String editPost(@Valid Type type, RedirectAttributes attributes){
        Type type1 = typeService.getTypeByName(type.getName());
        if (type1 != null){
            attributes.addFlashAttribute("message","不能添加重复的分类");
            return "redirect:/admin/types/input";
        }
        int t = typeService.updateType(type);
        if (t == 0){
            attributes.addFlashAttribute("message","编辑失败");
        }else {
            attributes.addFlashAttribute("message","编辑成功");
        }
        return "redirect:/admin/types";
    }

    // 删除分类
    @GetMapping("/types/{id}/delete")
    public String delete(@PathVariable Long id,RedirectAttributes attributes){
        typeService.deleteType(id);
        attributes.addFlashAttribute("message","删除成功");
        return "redirect:/admin/types";
    }

}

讲解:

  • @Controller注解:用于标注控制层组件
  • @RequestMapping("/admin"):建立请求URL和处理方法之间的对应关系
  • @GetMapping注解:一个组合注解,是@RequestMapping(method = RequestMethod.GET)的缩写,用于将HTTP get请求映射到特定处理程序的方法注解
  • @PostMapping注解:一个组合注解,是@RequestMapping(method = RequestMethod.POST)的缩写,用于将HTTP post请求映射到特定处理程序的方法注解
  • @Valid注解:请求数据校验,用来判断是否有重复的分类
  • @PathVariable注解:获取URL中的数据
  • attributes.addFlashAttribute:相当于重定向后,在URL后面拼接了参数,这样在重定向之后的页面后者控制器再去获取URL后年的参数就可以了

下面是关于RedirectAttributes的使用

因为使用重定向的跳转方式的情况下,跳转到的地址无法获取 request 中的值。RedirecAtrributes 很好的解决了这个问题。

  1. redirectAttributes.addAttributie("param", value);

这种方法相当于在重定向链接地址追加传递的参数。以上重定向的方法等同于 return "redirect:/hello?param=value" ,注意这种方法直接将传递的参数暴露在链接地址上,非常的不安全,慎用。

  1. redirectAttributes.addFlashAttributie("param", value);

这种方法是隐藏了参数,链接地址上不直接暴露,但是能且只能在重定向的 “页面” 获取 param 参数值。其原理就是将设置的属性放到 session 中,session 中的属性在跳到页面后马上销毁

注意:这种方式在页面中可以正常获取,但是跳转目标是控制器方法的情况下,需要使用 @ModelAttribute 注解绑定参数后才能获取。

5.前后端交互

1.新增:

<a href="#" th:href="@{/admin/types/input}">
        <button type="button" class="ui teal button m-mobile-wide m-margin-top"><i class="pencil icon"></i>新增</button>
</a>

2.编辑删除:

<a href="#" th:href="@{/admin/types/{id}/input(id=${type.id})}" class="ui mini teal basic button">编辑</a>
<a href="#" th:href="@{/admin/types/{id}/delete(id=${type.id})}"  onclick="return confirm('确定要删除该分类吗?三思啊! 删了可就没了!')" class="ui mini red basic button">删除</a>

3.查询分类列表:

<a href="#" th:href="@{/admin/types}" class="teal active item">列表</a>

4.分页:

<div class="ui inverted divided stackable grid">
    <div class="three wide column" align="center">
      <a class="item" th:href="@{/admin/types(pageNum=${pageInfo.hasPreviousPage}?${pageInfo.prePage}:1)}" th:unless="${pageInfo.isFirstPage}">上一页</a>
    </div>
    
    <div class="ten wide column" align="center">
      <p>第 <span th:text="${pageInfo.pageNum}"></span> 页,共 <span th:text="${pageInfo.pages}"></span> 页,有 <span th:text="${pageInfo.total}"></span> 个分类</p>
    </div>
    
    <div class="three wide column" align="center">
      <a class="item" th:href="@{/admin/types(pageNum=${pageInfo.hasNextPage}?${pageInfo.nextPage}:${pageInfo.pages})}" th:unless="${pageInfo.isLastPage}">下一页</a>
    </div>
</div>

说明:

(pageNum=${pageInfo.hasPreviousPage}?${pageInfo.prePage}:1):参数pageNum表示当前为第几页,如果${pageInfo.hasPreviousPage}不为空,说明有上一页,那就将${pageInfo.prePage}赋值给pageNum,如果为空,说明没有上一页了,就是第一页,就赋值1给pageNum,th:unless="${pageInfo.isFirstPage}"表示条件为此时不是第一页才显示“上一页”这个超链接。${pageInfo.pages}表示一共多少页,${pageInfo.total}表示分页信息里有多少个分类,即多少个实体。

运行代码,访问:http://localhost:8080/admin ,登录后,点击分类管理,进入分类管理页面,可以对分类进行增、删、改、查

至此,分类管理实现完成。

end
  • 作者:AWhiteElephant(联系作者)
  • 发表时间:2022-03-14 22:39
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 转载声明:如果是转载栈主转载的文章,请附上原文链接
  • 评论