基于SSM框架的CRUD小项目,外加JSR303,使用Maven搭建工程,前端使用Jquery对Ajax的封装进行异步请求

文章目录

  • 建议先学
  • 概要
  • 创建工程
  • 导入依赖
  • web.xml
  • dispatcherServlet-servlet.xml
  • applicationContext.xml
  • mybatis-config.xml
  • 创建表
  • 逆向工程创建ssm项目
  • 修改逆向工程
  • 测试逆向工程
  • 首页列表数据
    • 编写Controller
    • 编写jsp页面
  • 升级查询技术
  • 新增员工
  • 前端+后端校验
    • 前端
    • 后端
    • 例子
  • 编辑员工
  • 删除员工
    • 简单删除
    • 全选删除

建议先学

  1. 五万字的Spring5学习笔记,带你熟悉运用Spring5
  2. 四万多字的SpringMVC学习总结,带你领略不一样的SpringMVC
  3. 怎么请求数据库的数据?这套四万多字的Mybatis学习笔记给你答案,只做入门,不做深层次分析

概要

基于SSM框架的CRUD小项目,外加JSR303,使用Maven搭建工程,前端使用Jquery对Ajax的封装进行异步请求_第1张图片

  • 我们的目标是整个三大框架,来做一个简单的增删改查系统。
  • 该系统有以下几个要点
    • 该系统在网页中显示,数据要列出在网页上。
    • 系统提供增删改查的选项功能。
    • 系统提供分页的功能。
    • 系统提供数据检测的功能,检查增加的数据是否符合现实规则。

创建工程

  • 创建一个Maven工程,利用webapp模块。
  • 创建完毕后,里面是没有正常的Maven工程的包的,比如java test包之类,我们需要根据正常模板导入,但webapp模块不要删除。

导入依赖

  • 创建模块后,就要导入依赖,在pom.xml 中我们要配置基本的jar。

    • spring springMvc mybatis spring-mybatis
    • jstl servlet mysql-connnector durid junit
    
    
    
      4.0.0
    
      com.hyb
      SSM_CRUD
      1.0-SNAPSHOT
      war
    
    
      
        
        
          junit
          junit
          4.11
          test
        
    
    
        
        
          org.springframework
          spring-jdbc
          5.3.9
        
        
          org.springframework
          spring-aspects
          5.3.9
        
    
        
          org.springframework
          spring-webmvc
          5.3.9
        
    
        
          org.mybatis
          mybatis
          3.5.7
        
    
        
          org.mybatis
          mybatis-spring
          2.0.6
        
    
        
    
        
          com.alibaba
          druid
          1.1.22
        
    
    
        
          mysql
          mysql-connector-java
          8.0.26
        
    
        
          javax.servlet
          jstl
          1.1.2
        
    
    
        
          javax.servlet
          javax.servlet-api
          4.0.1
          provided
        
    
      
    
    
    

    注意,有时候你导入的依赖刷新Maven的时候可能还是会报红,这可能是镜像网站网络的问题,可以上网查阅更换aliyun的镜像网站。一般的解决办法还是在本地的镜像仓库的jar的垃圾文件删除,重新刷新一遍,若还是不行,就更换不同版本的jar。

  • 写完pom.xml文件后,我们要进行一些web的文件导入,这里我们使用前端框架Bootstrap 和JQuery 来进行。前面的框架主要用来解决css,后面的主要用来解决js。我们可以在index.jsp中看看如何导入

    <%--
      Created by IntelliJ IDEA.
      User: 黄渝斌
      Date: 2021/9/11
      Time: 10:36
      To change this template use File | Settings | File Templates.
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    
    
        index.jsp
        
        
        
        
        
        
        
    
    
        
    
    
    

    注意,这里的Bootstrap 框架由于支持cdn,所以不用下载包,而JQuery则需要下载。

web.xml

DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>



  <context-param>
    <param-name>contextConfigLocationparam-name>
    <param-value>classpath:applicationContext.xmlparam-value>
  context-param>


  <filter>
    <filter-name>CharacterEncodingFilterfilter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
    <init-param>
      <param-name>encodingparam-name>
      <param-value>utf-8param-value>
    init-param>
    <init-param>
      <param-name>forceRequestEncodingparam-name>
      <param-value>trueparam-value>
    init-param>
    <init-param>
      <param-name>forceResponseEncodingparam-name>
      <param-value>trueparam-value>
    init-param>
  filter>
  <filter>
    <filter-name>HiddenHttpMethodFilterfilter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilterfilter-class>
  filter>


  <filter-mapping>
    <filter-name>CharacterEncodingFilterfilter-name>
    <url-pattern>/*url-pattern>
  filter-mapping>

  <filter-mapping>
    <filter-name>HiddenHttpMethodFilterfilter-name>
    <url-pattern>/*url-pattern>
  filter-mapping>

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
  listener>

  <servlet>
    <servlet-name>dispatcherServletservlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
  servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServletservlet-name>
    <url-pattern>/url-pattern>
  servlet-mapping>

web-app>

注意,这个xml中,注意标签的优先级。

dispatcherServlet-servlet.xml

  • 该配置文件是SpringMVC的配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    
    <context:component-scan base-package="com.hyb.crud" use-default-filters="false">
        
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    context:component-scan>

    
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/view/">property>
        <property name="suffix" value=".jsp">property>
    bean>

    
        
    <mvc:default-servlet-handler/>
        
    <mvc:annotation-driven/>
beans>

applicationContext.xml

  • 该配置是Spring的配置文件和Spring和Mybatis整合的配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">

    <context:component-scan base-package="com.hyb.crud">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    context:component-scan>

    <context:property-placeholder location="classpath:jdbcSql.properties">context:property-placeholder>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}">property>
        <property name="url" value="${jdbc.url}">property>
        <property name="username" value="${jdbc.username}">property>
        <property name="password" value="${jdbc.password}">property>
    bean>

    
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource">property>
    bean>
    
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

    
    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        
        <property name="dataSource" ref="dataSource">property>
        
        <property name="configLocation" value="classpath:mybatis-config.xml">property>
        
        <property name="mapperLocations" value="classpath:mapper/*.xml">property>
        <property name="typeAliasesPackage" value="com.hyb.myBatis">property>
    bean>
    <mybatis-spring:scan base-package="com.hyb.crud.dao"/>
beans>

mybatis-config.xml

  • 该配置是mybatis配置文件

DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    settings>
    
    <typeAliases>
        <package name="com.hyb.crud.bean"/>
    typeAliases>

configuration>

创建表

  • 创建员工表和部门表,两个表都有各自的id,员工表的外键属于部门表的id

逆向工程创建ssm项目

  • 首先导入逆向工程的包。

  • 我们先创建应有的包名,然后可以用逆向工程创建ssm项目

  • 首先,我们得在工程目录下,创建一个xml文件,用来配置逆向工程的基本信息

    
    DOCTYPE generatorConfiguration
            PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
            "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    
    <generatorConfiguration>
    
    
        <context id="DB2Tables" targetRuntime="MyBatis3">
            
            <commentGenerator>
                <property name="suppressAllComments" value="true" />
            commentGenerator>
    
            <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                            connectionURL="jdbc:mysql://localhost:3306/hyb?serverTimezone=UTC"
                            userId="root"
                            password="15717747056HYB">
            jdbcConnection>
    
            <javaTypeResolver >
                <property name="forceBigDecimals" value="false" />
            javaTypeResolver>
    
            <javaModelGenerator targetPackage="com.hyb.crud.bean"
                                targetProject=".\src\main\java">
                <property name="enableSubPackages" value="true" />
                <property name="trimStrings" value="true" />
            javaModelGenerator>
    
            <sqlMapGenerator targetPackage="mapper"  targetProject=".\src\main\resources">
                <property name="enableSubPackages" value="true" />
            sqlMapGenerator>
    
            <javaClientGenerator type="XMLMAPPER" targetPackage="com.hyb.crud.dao"
                                 targetProject=".\src\main\java">
                <property name="enableSubPackages" value="true" />
            javaClientGenerator>
    
            <table tableName="employee" domainObjectName="Employee">table>
            <table tableName="department_1" domainObjectName="Department">table>
        context>
    generatorConfiguration>
    
  • 然后我们编写测试类,来测试,一次测试就可以创建出一个逆向工程

    @Test
    public void testReserval() throws Exception {
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        File configFile = new File("mbg.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
    }
    
  • 执行,逆向工程创建完毕,完成所有基本代码实现。

修改逆向工程

  • 在创建好的逆向工程中,我们会发现,在Employee的javabean中,是没有属性Department的,但是我们表里面是有这个属性的主键的,也就是Employee表里的外键。而且,我们在查询的时候,我们希望,通过员工表里的外键可以查出该部门信息。但是我们生成的sql映射文件里,是没有改sql语句执行,所以我们要修改。

  • 首先,我们要修改Employee 类的属性,为其加上get set方法。

    private Department department;
    
  • 之后,我们要在Employee 的dao的mapper接口,写出该带有部门信息的查询方法

    List<Employee> selectByExampleWithDept(EmployeeExample example);
    Employee selectByPrimaryKeyWithDept(Integer empId);
    
  • 写完后,要写其对应的sql语句,由于该sql映射文件是动态sql,所以我们可以仿照没有部分信息的查询方法来写。

    首先写我们resultMap

    <resultMap id="WithDeptResultMap" type="com.hyb.crud.bean.Employee">
      <id column="emp_id" jdbcType="INTEGER" property="empId" />
      <result column="emp_name" jdbcType="VARCHAR" property="empName" />
      <result column="gender" jdbcType="VARCHAR" property="gender" />
      <result column="email" jdbcType="VARCHAR" property="email" />
      <result column="dept_id" jdbcType="INTEGER" property="deptId" />
      <association property="department" javaType="com.hyb.crud.bean.Department">
        <id column="dept_id" property="deptId"/>
        <result column="dept_name" property="deptName"/>
      association>
    resultMap>
    

    注意,该resultMa不能在原来的resultMap中写,因为原来的resultMap是没有关联Department这个属性的,其sql语句也没有。我们写这个resultMap是为写我们带有部门信息的查询方法。

    下面,我们可以写这两个对应的动态sql语句

    <sql id="WithDept_Column_List">
      emp_id, emp_name, gender, email, employee.dept_id, department_1.dept_name
    sql>
    
    <select id="selectByExampleWithDept" parameterType="com.hyb.crud.bean.EmployeeExample" resultMap="WithDeptResultMap">
      select
      <if test="distinct">
        distinct
      if>
      <include refid="WithDept_Column_List" />
      from employee,deaprtment_1
      where employee.dept_id=department_1.dept_id
      <if test="_parameter != null">
        <include refid="Example_Where_Clause" />
      if>
      <if test="orderByClause != null">
        order by ${orderByClause}
      if>
    select>
    
    <select id="selectByPrimaryKeyWithDept" parameterType="java.lang.Integer" resultMap="WithDeptResultMap">
      select
      <include refid="WithDept_Column_List" />
      from employee,department_1
      where emp_id = #{empId,jdbcType=INTEGER}
    select>
    

测试逆向工程

  • 我们首先来测试DepartmentMapper ,而且,我们要用Spring-test的测试框架来测试

  • 首先,我们新建一个test测试类,然后新建一个带有Test注解的test测试方法,之后,我们在pom.xml中加入spring-test框架。请上镜像网站搜索。

  • 然后我们在测试类上,加上以下两个注解

    //使用Spring的单元测试模块
    @RunWith(SpringJUnit4ClassRunner.class)
    //解析的文件地址
    @ContextConfiguration(locations = {"classpath:applicationContext.xml"})
    

    注意,如果没有这两个注解,而你又在pom.xml文件中加入了jar,是因为你没有刷新Maven

    加入这两个注解后,我们可以自动注入DepartmentMapper了,然后尝试是否可以获取departmentMapper对象

    //    先测试DepartmentMapper
        @Autowired
        DepartmentMapper departmentMapper;
    
        @Test
        public void testReserval_1(){
            System.out.println(departmentMapper);
        }
    

    注意,这里的注入在IDEA中可能会标红,这不是错误的原因,不要管它。测试后,若是报错,会经常报spring-test junit 需要4.12版本以上。

  • 获取对象成功后,说明我们的配置文件基本完成和没有错误,可以进行真实测试,我们可以进行简单的增删改查测试

    //        departmentMapper.insertSelective(new Department(null,"开发部"));
    
    //        Department department = departmentMapper.selectByPrimaryKey(1);
    //        System.out.println(department);
    
    //        DepartmentExample e = new DepartmentExample();
    //        e.createCriteria().andDeptNameLike("%部");
    //        List departments = departmentMapper.selectByExample(e);
    //        System.out.println(departments);
    
    //        DepartmentExample e = new DepartmentExample();
    //        e.createCriteria().andDeptNameLike("%部");
    //        departmentMapper.updateByExample(new Department(1,"高级开发部"),e);
    //        System.out.println(departmentMapper.selectByPrimaryKey(1));
    
    //        DepartmentExample e = new DepartmentExample();
    //        e.createCriteria().andDeptNameLike("%高级%");
    //        departmentMapper.deleteByExample(e);
    

首页列表数据

编写Controller

  • 我们都知道,服务器启动的时候跳转的默认都是index.jsp,为了方便管理,我们可以在index.jsp中进行页面跳转。

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    
    

    注意,这个页面只保留这两项,其他都删除。

  • 可以看到,我们跳转到emps 页面,但这是ssm框架,我们需要处理数据,所以这里跳转的是Controller,我们可以创建一个Controller,然后编写一个方法,将里面数据写全。

    package com.hyb.crud.controller;
    
    import com.github.pagehelper.PageHelper;
    import com.github.pagehelper.PageInfo;
    import com.hyb.crud.bean.Employee;
    import com.hyb.crud.service.EmployeeService;
    import org.apache.ibatis.annotations.Param;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    import java.util.List;
    
    @Controller
    public class EmployeeController {
    
        @Autowired
        EmployeeService employeeService;
    
        @RequestMapping("/emps")
        public String getEmps(@RequestParam(value = "page",defaultValue = "1") Integer page, Model model){
    
    //        从第几页开始查,每页有几条数据
            PageHelper.startPage(page,5);
            List<Employee> emps = employeeService.getAll();
    //        交给PageInfo,连续显示5页
            PageInfo<Employee> employeePageInfo = new PageInfo<Employee>(emps,5);
            model.addAttribute("pageInfo",employeePageInfo);
            return "list";
        }
    }
    

    这里,只要我们将Service层写好就可以了。在利用分页框架的时候,记得先将PageHelper的jar导入。

  • 完成后,进行测试,这里的测试,我们还是用spring-test的测试方法。

    package com.hyb.crud.test;
    
    import com.github.pagehelper.PageInfo;
    import com.hyb.crud.bean.Employee;
    import com.sun.xml.internal.ws.api.client.WSPortInfo;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.mock.web.MockHttpServletRequest;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.springframework.test.context.web.WebAppConfiguration;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.MockMvcBuilder;
    import org.springframework.test.web.servlet.MvcResult;
    import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    import org.springframework.web.context.WebApplicationContext;
    
    import java.util.List;
    
    //使用Spring的单元测试模块
    @RunWith(SpringJUnit4ClassRunner.class)
    //装配SpringMVC的
    @WebAppConfiguration
    //解析的文件地址
    @ContextConfiguration(locations = {"classpath:applicationContext.xml","file:src/main/webapp/WEB-INF/dispatcherServlet-servlet.xml"})
    public class MVCTest {
    
    //    装配SpringMVC
        @Autowired
        WebApplicationContext context;
    //    虚拟mvc请求
        MockMvc mockMvc;
    
        @Before
        public void initMockMvc(){
            mockMvc= MockMvcBuilders.webAppContextSetup(context).build();
        }
    
        @Test
        public void testPage() throws Exception {
            MvcResult page = mockMvc.perform(MockMvcRequestBuilders.get("/emps").param("page", "1")).andReturn();
    //        取出pageINfo
            MockHttpServletRequest request = page.getRequest();
            PageInfo pageInfo = (PageInfo) request.getAttribute("pageInfo");
            System.out.println(pageInfo);
        }
    }
    

编写jsp页面

  • 因为这里我们是通过index请求到Controller然后到list页面,所以,我们接下来就利用BootStrap 框架来编写该jsp页面。然后读取出数据。

    <%--
      Created by IntelliJ IDEA.
      User: 黄渝斌
      Date: 2021/9/12
      Time: 12:31
      To change this template use File | Settings | File Templates.
    --%>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <%@ page isELIgnored="false" %>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@include file="/common/base.jsp"%>
    
    
        员工列表
        <%@include file="/common/link.jsp"%>
    
    
        

    SSM_CRUD

    编号 姓名 性别 邮箱 部门 操作
    ${emps.empId} ${emps.empName} ${emps.gender=="1"?"男":"女"} ${emps.email} ${emps.department.deptName}
    <%@include file="/common/page.jsp"%>
  • 从上面这个jsp页面可以知道,我们有几个jsp页面的抽取,首先是链接的抽取,我们可以将动态获取服务器地址的方法写在一个jsp页面中

    
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%
        /*改路径是以/开始没有以/结束*/
        pageContext.setAttribute("basePath",request.getContextPath());
    %>
    

    这个jsp页面是共用的。

  • 由于链接JQuery的文件和链接BootStrap框架的都需要,我们也可以抽取在一个jsp页面中

    <%--
      Created by IntelliJ IDEA.
      User: 黄渝斌
      Date: 2021/9/12
      Time: 12:29
      To change this template use File | Settings | File Templates.
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    
    
    
    
    
    
    
    
  • 然后还有分页功能,我们也可以抽取出在一个jsp页面中

    <%--
      Created by IntelliJ IDEA.
      User: 黄渝斌
      Date: 2021/9/12
      Time: 15:04
      To change this template use File | Settings | File Templates.
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    
    当前${pageInfo.pageNum}页,总${pageInfo.pages}页,总${pageInfo.total}记录
  • 注意,在这里我们需要导入Jsp的jar和JSl中taglib 两个重要jar包

升级查询技术

  • 上面的首页列表数据,我们是利用了index.jsp发送请求到Controller ,然后再请求到具体页面,但这只适合于浏览器和客户进行交互。

  • 要先做到手机端和电脑端都能和客户交互,可以使用Ajax请求的方式查询数据。

  • 首先,导入Json jar包。

    com.fasterxml.jackson.core jackson-databind 2.12.3
  • 然后在Controller中,我们可以直接返回一个PageInfo的数据,就会转换为JSON

        @RequestMapping("/emps")
        /*下面的注解代表返回一个JSON*/
        @ResponseBody
        public PageInfo<Employee> getEmpsByAjax(@RequestParam(value = "page",defaultValue = "1") Integer page){
    //        从第几页开始查,每页有几条数据
            PageHelper.startPage(page,5);
            List<Employee> emps = employeeService.getAll();
    //        交给PageInfo,连续显示5页
            return new PageInfo<Employee>(emps,5);
        }
    
  • 但是这个JSON不是通用的,因为我们返回这个是PageInfo,那若是别的请求返回的不是PageInfo呢?而且,java行为大致都是一样的,所以我们可以做一个通用的返回javabean

    package com.hyb.crud.bean;
    
    import sun.applet.resources.MsgAppletViewer;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class Msg {
    //    状态码,
        private String code;
        private String msg;
    
    //    用Map保存数据
        private Map<String,Object> map=new HashMap<String, Object>();
    
        /*
        * 报错PageInfo的数据,做链式方法
        * */
        public Msg add(String s,Object v){
            this.map.put(s,v);
            return this;
        }
    
        public static Msg fail(){
            Msg msg = new Msg();
            msg.setCode("100");
            msg.setMsg("处理失败!");
            return msg;
        }
    
        public static Msg success(){
            Msg msg = new Msg();
            msg.setCode("200");
            msg.setMsg("处理成功!");
            return msg;
        }
    
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    
        public Map<String, Object> getMap() {
            return map;
        }
    
        public void setMap(Map<String, Object> map) {
            this.map = map;
        }
    }
    

    这个javabean就具备了响应警告,和数据保存的功能,这两个功能,所有请求都需要用的。

  • 做出了公共返回类型,我们就得修改我们方法

        public Msg getEmpsByAjax(@RequestParam(value = "page",defaultValue = "1") Integer page){
    //        从第几页开始查,每页有几条数据
            PageHelper.startPage(page,5);
            List<Employee> emps = employeeService.getAll();
    //        交给PageInfo,连续显示5页
            return Msg.success().add("pageInfo",new PageInfo<Employee>(emps,5));
        }
    

    这里add方法不是static的,但是前面success方法是静态,调用该方法后就获取到一个Msg对应,所以可以调用该非静态方法。不仅如此,该add方法是链式方法,即add后面还可以调用add,若有其他数据,就可以添加新的数据。

  • 于是乎,我们就不需要list.jsp了,我们只在index.jsp做ajax请求,并将数据反馈在这个页面上。

    
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <%@ page isELIgnored="false" %>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@include file="/common/base.jsp"%>
    
    
        员工列表
        <%@include file="/common/link.jsp"%>
        
    
    
    

    SSM_CRUD

    编号 姓名 性别 邮箱 部门 操作

    注意,在这里,我们要配置插件分页合理化,不然虽然我们在分页栏到达极限的时候跳转不了,但页面记录数还是会跳转。

    在mybatis-config

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            
            <property name="reasonable" value="true"/>
        plugin>
    plugins>
    

新增员工

  • 点击新增员工,探出BootStrap 模态框,我们可以将这模态框框架修改,变成属于自己的模态框。

    
    

    下面按钮对应。

  • 写好,可以发送ajax请求,请求数据库中部门的名字。

    //    对列表框发送ajax请求,将部门的数据显示在下拉列表框中
        $(function () {
            $("#add_bnt").click(function () {
                getDepts();
                $("#myModal").modal({
                    //防止点击背景就消失
                    backdrop:"static"
                });
            });
        })
    
        function getDepts() {
            $.ajax({
                url:"${basePath}/depts",
                type:"GET",
                success:function (data) {
                    console.log(data)
                    $.each(data.map.depts,function () {
                        var select=$("").append(this.deptName).attr("value",this.deptId);
                        select.appendTo("#deptId");
                    })
                }
            })
        }
    

前端+后端校验

前端

  • 前端校验于程序猿来说不合理,在浏览器的开发者工具中,可以修改前端代码,如果只是用前端的知识来进行表单的验证,数据会极度的不安全,所以有必要采用后端校验。
  • 说明:其实前端校验可以不要,只用后端+前端知识结合校验就可以了。

后端

  • 后端校验的好处是,我们从前端发送ajax请求,然后在后端去请求数据库,例如:出现相同或者为空的时候,可以将警告保存,然后交给浏览器去处理。

例子

  • 首先,我们得导入JSR303校验 的包,博主用

    <dependency>
      <groupId>org.hibernategroupId>
      <artifactId>hibernate-validatorartifactId>
      <version>6.1.0.Finalversion>
    dependency>
    

    这个版本相对比较稳定,而且import class的时候一定不要搞错,这个版本是javax的。

  • 这个jar提供了相当多的注解,方便我们可以直接在javabean的属性上加入校验注解,然后jar会给我自动解析这个注解,实现错误信息的记录。

    //    自定义校验模式,表示识别一个正则表达式,后面是错误信息
        @Pattern(regexp = "^[a-zA-Z][a-zA-Z0-9_]{2,15}$",
                message = "字母开头,允许3-16位节,允许字母数字下划线")
        private String empName;
    
    //    官方的正则表达式
    //    @Email(message = "邮箱格式不正确")
    //    这里注意,在java / 代表转义字符,所以要改成//
        @Pattern(regexp = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$",message = "邮箱格式不正确")
        private String email;
    
  • 这时,当我们增加员工的时候,该注解会自动保存信息。当然,我们要在Controller里面获取

        @RequestMapping(value = {"/emp"},method = RequestMethod.POST)
        @ResponseBody
    //    下面的参数注解代表返回的校验
    //    第二份参数代表返回的错误信息结果集
        public Msg addEmp(@Valid Employee e, BindingResult result){
            Map<String,Object> resultMap=new HashMap<String, Object>();
            System.out.println(result.hasErrors());
            if (result.hasErrors()){
    //        返回错误信息
                List<FieldError> fieldErrors = result.getFieldErrors();
                for (FieldError error:fieldErrors
                ) {
    //         将数据放进map里,
                    resultMap.put(error.getField(),error.getDefaultMessage());
                }
                return Msg.fail().add("resultMap",resultMap);
            }
            employeeService.add(e);
            return Msg.success();
        }
    

    这里面是在原来的基础上改进的。

  • 然后就是前端的一些代码,这里为了方便,我将前段校验和后端校验都发上来。

    <%--
      Created by IntelliJ IDEA.
      User: 黄渝斌
      Date: 2021/9/12
      Time: 12:31
      To change this template use File | Settings | File Templates.
    --%>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <%@ page isELIgnored="false" %>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@include file="/common/base.jsp"%>
    
    
        员工列表
    
        <%@include file="/common/link.jsp"%>
        
    
    
    

    SSM_CRUD

    <%--弹出框新增--%>
    编号 姓名 性别 邮箱 部门 操作

    里面还设计一个后端设计

        @RequestMapping(value={"/change"},method = RequestMethod.GET)
        @ResponseBody
        public Msg queryIsEmpty(@RequestParam("empName") String empName){
            boolean isEmpty = employeeService.queryIsEmptyByName(empName);
    //        在前端校验中的测试中,我们会发现,当我们输入了不可用的名字会显示可用之后才执行不可用的判断,
    //        这是因为在判断不可用的同时,加入了一层用户名是否存在的校验,所以我们要用户名是否存在的后端中加一层校验
            String s="^[a-zA-Z][a-zA-Z0-9_]{2,15}$";
    //        这是String里一种支持校验正则表达式的方法
            boolean matches = empName.matches(s);
            if (!matches){
                return Msg.fail().add("success_msg","字母开头,允许3-16位,允许字母数字下划线");
            }
            if (isEmpty){
                return Msg.success();
            }else {
                return Msg.fail().add("success_msg","用户名已存在");
            }
        }
    

    上面这个设计,是纯手写的后端校验。JSR303 是一个后端校验的框架,这两个是区分开的。

编辑员工

  • 编辑员工我们要弹出模态框,然后将数据回显在表单上,最后做数据验证。

  • 首先是探出模态框,和添加员工的jsp代码一样,但是属性名字不能一样。

    
    
  • 之后,便是进行数据回显,进行数据回显,我们要知道一点,这个编辑的按钮是随着数据的加载而产生的,也就是动态产生的,所以我们要为其绑定单击事件,一定要在有了该按钮的时候才可以绑定。

    为了方便绑定,我们可以在产生数据的时候,为这个编辑的按钮增加一些属性。

    var editBtn = $("").addClass("btn btn-primary btn-sm edit_emp")
        .append($("").addClass("glyphicon glyphicon-pencil")).append("编辑");
    
    /*
    * 这里一定得这样赋值,因为这个按钮是动态增加的
    * 所以,可以每一次动态增加的时候添加一个属性
    * */
    editBtn.attr("this_id",item.empId);
    /*
    * $("#add_emp_update").attr("empId",item.empId) 不能将这个设置在这里,
    * 因为下面的按钮在页面加载完成后已经结束了,然后根据循环,只会将最后一次的数据赋值
    * */
    // $("#add_emp_update").attr("empId",item.empId)
    
    //下面这个设置是为传递一个部门id,可以根据这个部门id更改选中的属性
    editBtn.attr("this_deptId",item.deptId);
    
  • 可以看到,上面我们将编辑的按钮动态的增加了两个属性,然后我们为编辑这个按钮增加一个单击事件,但这个按钮的单击事件要注意,因为这个是根据数据显示才出现的按钮,所以要在数据加载时绑定。绑定方法有些不同。

            /*
            * 下面是对修改用户功能
            * */
    
            //当我们窗体加载完成,事件绑定完成,而数据还需要发送ajax请求才能出来(修改的按钮),所以,我们要在全局document中绑定
    
            $(document).on("click",".edit_emp",function () {
                //我们要将部门信息显示出来,可以调用增加员工那里的getDept的方法,
                //但是员工那里写死了,所以我们可以将那个方法稍作修改
                //将要植入数据的标签通过参数传入就可以了
                getDepts("#_deptId_update")
    
                //发送ajax请求,请求当前修改的数据到表中
                //下列不能用val()方法
                var id=$(this).attr("this_id");
    
                /*
                * 下面的按钮一定要这样赋值,因为这是动态的,每次点击编辑,将当前的id获取,赋值给更新按钮的属性
                * 就不会出现只能取到最后一个值的情况
                * */
                $("#add_emp_update").attr("empId",id)
                var deptId=$(this).attr("this_deptId");
                // $("#_deptId_update option").val([deptName]);
                // $("#_deptId_update option").append(deptName).attr("value",this_data.deptId)
                this_list(id,deptId);
                $("#myModal_update").modal({
                    //防止点击背景就消失
                    backdrop:"static"
                });
    
                /*
                * 下面一定不能这样调用,代码虽然看清来很美,但是按钮是动态生成的。
                * 第一次你修改的时候没有错,
                * */
                // edit(id);
                /*function edit(id){
                    $.ajax({
                        <%--url:"${basePath}/emp/"+id,--%>
                        type:"PUT",
                        data:$("#myModal_update form").serialize(),
                        success:function (data) {
                            //    关闭模态框
                            $("#myModal_update").modal('hide');
                            clear("#myModal_update form");
                            to_page(presentPage);
                        }
                    })
                }*/
    
            })
    
    
    
    
            function this_list(id,deptId) {
                $.ajax({
                    url:"${basePath}/emp",
                    type:"GET",
                    data:"empId="+id,
                    success:function (data) {
                        var this_data=data.map.thisEmp;
                        $("#empName_update").attr("value",this_data.empName).text(this_data.empName);
                        var gender=this_data.gender;
                        if (gender==="1"){
                            $("#gender_1_update").val([gender]);
                        }else{
                            $("#gender_0_update").val([gender]);
                        }
                        $("#email_update").attr("value",this_data.email).text(this_data.email);
                        $("#_deptId_update option[value="+deptId+"]").prop("selected", true);
                    }
                })
            }
    
    /*
    * 根据id搜索数据
    * */
    @RequestMapping(value = {"/emp"},method = RequestMethod.GET)
    @ResponseBody
    public Msg queryEmpById(@RequestParam("empId")Integer empId){
        Employee employee = employeeService.queryEmpById(empId);
        return Msg.success().add("thisEmp",employee);
    }
    

    注意:在ajax中,除了get和post以外的请求是不支持的,除非你的加了个参数_method=put…,若你想在type中直接输入请求方式而不加参数,要在web.xml中设置过滤器参数

    
    <filter>
      <filter-name>FormContentFilterfilter-name>
      <filter-class>org.springframework.web.filter.FormContentFilterfilter-class>
    filter>
    
    <filter-mapping>
      <filter-name>HiddenHttpMethodFilterfilter-name>
      <url-pattern>/*url-pattern>
    filter-mapping>
    <filter-mapping>
      <filter-name>FormContentFilterfilter-name>
      <url-pattern>/*url-pattern>
    filter-mapping>
    
  • 搜索了数据之后,就可以根据表单进行回显数据,回显成功后,就可以和前面的一样,进行前端和后端的验证

    //用户名是否存在校验
    $(function () {
        //注意,这里一定要有这个失去焦点事件,或者其他事件,不然你直接让$(function(){})先加载,弹出框都没出来便发送ajax请求,对后来的验证就没有了
        $("#empName").blur(function () {
            ifUser();
        });
    
    //    前端email校验,会有bug
        $("#email").blur(function () {
            emailJudge("#email","#inputErrorEmail");
        });
    
        $("#email_update").blur(function () {
            emailJudge("#email_update","#inputErrorEmail_update");
        });
    
        $("#empName_update").blur(function () {
            userJudge("#empName_update","#inputErrorName_update");
        });
    
    });
    

    前端验证完毕后,就可以进行修改的单击事件的绑定

    $(document).on("click","#add_emp_update",function () {
        /*
        * 下面注释的一定不能这样获取id
        * 因为这个类是动态的,也就是说,我们将所有编辑的按钮都加上这个类的时候
        * 当这个页面加载完成,我们获取的值就是第一个类的按钮的值,
        * */
        // var id=$(".edit_emp").attr("this_id");
    
        /*
        * 而通过下面去获取id的属性,就是每一次点击编辑按钮赋值给修改按钮的值,这个值是动态的,只有在点击编辑按钮的时候才会被赋值
        * */
        var id=$(this).attr("empId");
        $.ajax({
    
            /*
            * 这里一定要传入一个id值,因为我们下面的表单序列化,得到的对象是没法将当前的id属性放进去的
            * 如果单单传入这个对象,系统无法根据id属性修改
            * 而如果在表单序列化后面加入连接符,连接一个id属性上去也是不行的,
            * 这样的意思只是将id属性传了过去,我们也没有用到,如果是在传入地址到时候传进去,会自动解析成对象里的id属性
            * */
            url:"${basePath}/emp/"+id,
            type:"PUT",
            data:$("#myModal_update form").serialize(),
            success:function (data) {
                if (data.code==="200"){
                    $("#add_emp_update").attr("ajax_alert","success")
                    //    关闭模态框
                    $("#myModal_update").modal('hide');
                    clear("#myModal_update form");
                    to_page(presentPage);
                }else {
    
                    $("#add_emp_update").attr("ajax_alert","error")
                    if (!data.map.isNoEmpty){
                        $("#empName_update").parent().addClass("has-error");
                        $("#inputErrorName_update").text(data.map.isNoEmptyText);
    
                    }
                    if (undefined!==data.map.resultMapPlus.email){
                        // 显示员工名错误信息
                        $("#email_update").parent().addClass("has-error");
                        $("#inputErrorEmail_update").text(data.map.resultMapPlus.email);
                    }
                    if (undefined!==data.map.resultMapPlus.empName){
                        //    显示邮箱错误信息
                        $("#empName_update").parent().addClass("has-error");
                        $("#inputErrorName_update").text(data.map.resultMapPlus.empName);
    
                    }
    
    
                }
    
            }
        })
    });
    

    我们可以看到,单击事件成功后,在后端会回传数据,下面是后端的代码

       /*
        * 修改员工信息
        * 这里必须穿一个id值过来,因为你在前端的表单序列化里,是没有id属性的
        * 但是在sql语句里,是通过id给你更新一个值的
        * */
        @RequestMapping(value = {"/emp/{empId}"},method = RequestMethod.PUT)
        @ResponseBody
        public Msg updateEmp(@Valid Employee e,BindingResult result){
            Map<String,Object> resultMap=new HashMap<String, Object>();
            if (result.hasErrors()){
    //        返回错误信息
                List<FieldError> fieldErrors = result.getFieldErrors();
                for (FieldError error:fieldErrors
                ) {
    //         将数据放进map里,
                    resultMap.put(error.getField(),error.getDefaultMessage());
                }
                return Msg.fail().add("resultMapPlus",resultMap);
            }
    //        如果数据没有错误,就要判断,重新改变的用户名是否已经存在
    //        这里的判断要注意的一点是,我们的判断是要排除自己本身的
            Boolean isEmpty= employeeService.queryIsEmptyOtherName(e.getEmpId(),e.getEmpName());
            if (isEmpty) {
                return Msg.fail().add("isNoEmpty",false).add("isNoEmptyText","员工已存在");
            }
            employeeService.updateEmp(e);
            return Msg.success().add("isNoEmpty",true).add("isNoEmptyText","");
        }
    

    这个后端便同时进行了数据的查询和验证。

  • 注意:在做编辑员工的时候,原来一些方法是写死的,所以这里我做了一些简单的抽取。

删除员工

简单删除

  • 在每一列中,我们可以点击删除按钮,提示是否删除

  • 其原理和编辑员工几乎一样,首先得获取该tr里的id,用于后端删除,还有tr里的name,用于前端警告

    var delBtn = $("").addClass("btn btn-danger btn-sm del_emp")
        .append($("").addClass("glyphicon glyphicon-trash del_emp")).append("删除");
    
    delBtn.attr("this_id",item.empId);
    
  • 然后发送ajax请求,这里开始验证

    $(document).on("click",".del_emp",function () {
        var id=$(this).attr("this_id");
        var name=$(this).parents("tr").find("td:eq(1)").text();
        if (confirm("你确定要删除"+name+"吗?")){
            $.ajax({
                url:"${basePath}/emp",
                type:"delete",
                data:"empId="+id,
                success:function (data) {
                    to_page(presentPage);
                }
            })
        }
    
    })
    
  • 后端代码比较简单,这里省略。

全选删除

  • 前面我们做的列表是没有全选的,所以这里我们要为其添加上全选按钮,但是要注意的一个细节是,前面的简单删除,我们获取员工时用的eq,所以现在得改变

    
        
    
    

    然后我们要为每个tr都添加这个input

    var empChecked=$("").append("")
    
    var tr=$("").append(empChecked).append(empIdTd).append(empNameTd)
        .append(genderTd).append(emailTd).append(deptNameTd)
        .append(btnTd)
    
  • 然后我们为全选input添加click属性,也要为单个input添加click属性

    //    为全选绑定单击事件,因为这是已经加载出来的,所以不用动态绑定
        $(function () {
            $("#checkAll").click(function () {
            //    下面我们要做的事情就是选中所有,下面所有的都跟着选中,就要改变input标签里的checked属性
            //    在input标签中,我们没有显示写checked属性,但其是默认有的,
            //    对于这种原生的属性的,我们用attr就不那么好看,所以可以用prop
            //    attr可以获取咱们自定义的属性
    
            //    1.我们先拿到所有的checked
                let allChecked = $(this).prop("checked");
            //    2.上面返回true或者false,然后我们用这个修改单个选择,这里最好用类选择器,因为我们本身就要全部选中
                $(".check_item").prop("checked",allChecked);
            });
    
        //    为单个选中绑定单击事件,这里主要是为了我们全部单个选中的时候,为了美化,全选也要进行选择
        //    单个选中是动态生成的
            $(document).on("click",".check_item",function () {
            //    获取单个全部选中时的长度
                let totalLength=$(".check_item").length;
            //    单个选中的总个数
                let itemLength=$(".check_item:checked").length;
    
                if (totalLength===itemLength) $("#checkAll").prop("checked",true);
    
                if (totalLength!==itemLength) $("#checkAll").prop("checked",false);
            })
        })
    
  • 之后就是点击总删除按钮,提示信息,发送请求

    $(function () {
        $("#del_bnt").click(function () {
            // $(".check_item").prop("checked",$("#checkAll").prop("checked",true));
            var empNames="";
            var empIds="";
            $.each($(".check_item:checked"),function () {
                empNames+=$(this).parents("tr").find("td:eq(2)").text()+",";
                empIds+=$(this).parents("tr").find("td:eq(1)").text()+"-";
            })
            //去掉后缀多余符号
            empNames=empNames.substring(0,empNames.length-1);
            empIds=empIds.substring(0,empIds.length-1);
            let confirmAll;
            if (totalPage===0){
                confirm("该表没有员工数据!")
            } else if (empNames.length===0){
                confirm("你还未选中,请选择!")
            } else if ($("#checkAll").prop("checked")){
                confirmAll=confirm("你确定全部删除吗?");
            } else {
                confirmAll=confirm("确定要删除"+"["+empNames+"]"+"吗?")
            }
            if (confirmAll){
                $.ajax({
                    url:"${basePath}/emp",
                    type:"delete",
                    data:"empIds="+empIds,
                    success:function (data) {
                        to_page(presentPage);
                        $("#checkAll").prop("checked",false);
                    }
                })
            }else {
                $("#checkAll").prop("checked",false);
                $.each($(".check_item:checked"),function () {
                   $(this).prop("checked",false);
                })
            }
        });
    })
    
  • 发送请求后,我们去后端写代码,注意这里的Controller代码可以在单个删除的原基础上改变成批量删除

    @RequestMapping(value = {"/emp"},method = RequestMethod.DELETE)
    @ResponseBody
    public Msg delEmp(@RequestParam("empIds")String empIds){
        if (empIds.length()==1) {
            employeeService.delEmp(Integer.parseInt(empIds));
        }else {
            List<Integer> list=new ArrayList<Integer>();
            String[] empIdsString = empIds.split("-");
            for (String s:empIdsString
                 ) {
                list.add(Integer.parseInt(s));
            }
            employeeService.delEmpByBath(list);
        }
        return Msg.success();
    }
    
    public void delEmpByBath(List<Integer> list){
        EmployeeExample example = new EmployeeExample();
        example.createCriteria().andEmpIdIn(list);
        employeeMapper.deleteByExample(example);
    }
    
    public void delEmp(Integer empId) {
        employeeMapper.deleteByPrimaryKey(empId);
    }
    

你可能感兴趣的:(SSM,SSM,Spring,SpringMVC,Mybatis,Maven)