文章目录
建议先学
- 五万字的Spring5学习笔记,带你熟悉运用Spring5
- 四万多字的SpringMVC学习总结,带你领略不一样的SpringMVC
- 怎么请求数据库的数据?这套四万多字的Mybatis学习笔记给你答案,只做入门,不做深层次分析
概要
- 我们的目标是整个三大框架,来做一个简单的增删改查系统。
- 该系统有以下几个要点
- 该系统在网页中显示,数据要列出在网页上。
- 系统提供增删改查的选项功能。
- 系统提供分页的功能。
- 系统提供数据检测的功能,检查增加的数据是否符合现实规则。
创建工程
- 创建一个Maven工程,利用webapp模块。
- 创建完毕后,里面是没有正常的Maven工程的包的,比如java test包之类,我们需要根据正常模板导入,但webapp模块不要删除。
导入依赖
-
创建模块后,就要导入依赖,在pom.xml 中我们要配置基本的jar。
- spring springMvc mybatis spring-mybatis
- jstl servlet mysql-connnector durid junit
<?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>com.hyb</groupId> <artifactId>SSM_CRUD</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <!--测试包--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!--配置ssm框架jar--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.9</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.9</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.9</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency> <!--数据库连接池和驱动--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.22</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.26</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> </dependencies> </project>
注意,有时候你导入的依赖刷新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" %> <html> <head> <title>index.jsp</title> <!-- Bootstrap --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous"> <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) --> <script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script> <!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 --> <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script> <script type="text/javascript" href="js/jquery-3.5.1.js"></script> </head> <body> <button type="button" class="btn btn-default">这是一个按钮</button> </body> </html>
注意,这里的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>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
注意,这个xml中,注意标签的优先级。
dispatcherServlet-servlet.xml
- 该配置文件是SpringMVC的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!--配置SpringMVC-->
<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>
<!--两个标准配置-->
<!--将SpringMVC不能处理的请求交给Tomcat-->
<mvc:default-servlet-handler/>
<!--加载注解驱动,支持JSR303校验,快捷ajax请求-->
<mvc:annotation-driven/>
</beans>
applicationContext.xml
- 该配置是Spring的配置文件和Spring和Mybatis整合的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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"/>
<!--整合mybatis
-->
<!--创建SqlSessionFactory 对象-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 指定数据源 -->
<property name="dataSource" ref="dataSource"></property>
<!-- MyBatis的配置文件 -->
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<!-- MyBatis的SQL映射文件 -->
<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配置文件
<?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>
<!--驼峰命名规则-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!--类型别名-->
<typeAliases>
<package name="com.hyb.crud.bean"/>
</typeAliases>
</configuration>
创建表
- 创建员工表和部门表,两个表都有各自的id,员工表的外键属于部门表的id
逆向工程创建ssm项目
-
首先导入逆向工程的包。
-
我们先创建应有的包名,然后可以用逆向工程创建ssm项目
-
首先,我们得在工程目录下,创建一个xml文件,用来配置逆向工程的基本信息
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!-- <classPathEntry location="/Program Files/IBM/SQLLIB/java/db2java.zip" />--> <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<Department> 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" %> <jsp:forward page="/emps"></jsp:forward>
注意,这个页面只保留这两项,其他都删除。
-
可以看到,我们跳转到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"%> <html> <head> <title>员工列表</title> <%@include file="/common/link.jsp"%> </head> <body> <div class="container"> <div class="row"> <div class="col-md-12"> <h1>SSM_CRUD</h1> </div> </div> <div class="row"> <div class="col-md-4 col-md-offset-8"> <button class="btn btn-success"> <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>新增 </button> <button class="btn btn-warning"> <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>删除 </button> </div> </div> <div class="row"> <div class="col-md-12"> <table class="table table-hover"> <tr> <th>编号</th> <th>姓名</th> <th>性别</th> <th>邮箱</th> <th>部门</th> <th >操作</th> </tr> <c:forEach items="${pageInfo.list}" var="emps"> <tr> <th>${emps.empId}</th> <th>${emps.empName}</th> <th>${emps.gender=="1"?"男":"女"}</th> <th>${emps.email}</th> <th>${emps.department.deptName}</th> <th> <button class="btn btn-success"> <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> 修改 </button> <button class="btn btn-warning"> <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> 删除 </button> </th> </tr> </c:forEach> </table> </div> </div> <%@include file="/common/page.jsp"%> </div> </body> </html>
-
从上面这个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" %> <script type="text/javascript" href="${basePath}/js/jquery-3.5.1.js"></script> <!-- Bootstrap --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous"> <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) --> <script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script> <!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 --> <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
-
然后还有分页功能,我们也可以抽取出在一个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" %> <div class="row"> <div class="col-md-6"> 当前${pageInfo.pageNum}页,总${pageInfo.pages}页,总${pageInfo.total}记录 </div> <div class="col-md-6"> <nav aria-label="Page navigation"> <ul class="pagination"> <c:if test="${!pageInfo.isFirstPage}"> <li> <a href="${basePath}/emps?page=1">首页</a> </li> </c:if> <c:if test="${pageInfo.hasPreviousPage}"> <li> <a href="${basePath}/emps?page=${pageInfo.pageNum-1}" aria-label="Previous"> <span aria-hidden="true">«</span> </a> </li> </c:if> <c:forEach items="${pageInfo.navigatepageNums}" var="page_num"> <c:if test="${page_num==pageInfo.pageNum}"> <li class="active"><a>${page_num}</a></li> </c:if> <c:if test="${page_num!=pageInfo.pageNum}"> <li><a href="${basePath}/emps?page=${page_num}">${page_num}</a></li> </c:if> </c:forEach> <c:if test="${pageInfo.hasNextPage}"> <li> <a href="${basePath}/emps?page=${pageInfo.pageNum+1}" aria-label="Next"> <span aria-hidden="true">»</span> </a> </li> </c:if> <c:if test="${!pageInfo.isLastPage}"> <li> <a href="${basePath}/emps?page=${pageInfo.pages}">末页</a> </li> </c:if> </ul> </nav> </div> </div>
-
注意,在这里我们需要导入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"%> <html> <head> <title>员工列表</title> <%@include file="/common/link.jsp"%> <script type="text/javascript" > $(function () { to_page(1); }) function to_page(pn) { $.ajax({ url:"${basePath}/emps", data:"page="+pn, type:"GET", success:function (data) { build_list(data); build_page_count(data); build_page(data); } }) } function build_list(data) { //清空表格里的数据 $("#emps_table tbody").empty(); var emps=data.map.pageInfo.list; $.each(emps,function (index,item) { // alert(item.empName); var empIdTd=$("<td></td>").append(item.empId); var empNameTd=$("<td></td>").append(item.empName); var empGenderTd=$("<td></td>").append(item.gender=="1"?"男":"女"); var empEmailTd=$("<td></td>").append(item.email); var empDeptTd=$("<td></td>").append(item.department.deptName); var button_1=$("<button></button>").addClass("btn btn-success") .append($("<span></span>")).addClass("glyphicon glyphicon-pencil") .append("编辑"); var button_2=$("<button></button>").addClass("btn btn-warning") .append($("<span></span>")).addClass("glyphicon glyphicon-trash") .append("删除") var button_1_2=$("<td></td>").append(button_1).append(" ").append(button_2); $("<tr></tr>").append(empIdTd).append(empNameTd) .append(empGenderTd).append(empEmailTd) .append(empDeptTd).append(button_1_2) .appendTo("#emps_table tbody"); }) } function build_page_count(data) { $("#page_count").empty(); var page=data.map.pageInfo; $("#page_count").append("当前"+page.pageNum+"页,总"+page.pages+"页,总"+ page.total+"记录"); } function build_page(data) { $("#page").empty(); var ul=$("<ul></ul>").addClass("pagination"); //首页 var firstPage=$("<li></li>").append($("<a></a>").append("首页").attr("href","#")); firstPage.click(function () { to_page(1); }); //上一页 var upPage=$("<li></li>").append($("<a></a>").append("<span></span>").append("«").attr("href","#")); upPage.click(function () { to_page(data.map.pageInfo.pageNum-1); }); if(!data.map.pageInfo.hasPreviousPage){ firstPage.addClass("disabled"); upPage.addClass("disabled"); } //首页,前一页 ul.append(firstPage).append(upPage); $.each(data.map.pageInfo.navigatepageNums,function (index,itme) { var numi=$("<li></li>").append($("<a></a>").append(itme).attr("href","#")); numi.click(function () { to_page(itme); }); if (data.map.pageInfo.pageNum===itme){ numi.addClass("active"); } // 当前页 ul.append(numi); }) //下一页 var downPage=$("<li></li>").append($("<a></a>").append("<span></span>").append("»").attr("href","#")); downPage.click(function () { to_page(data.map.pageInfo.pageNum+1); }); //末页 var endPage=$("<li></li>").append($("<a></a>").append("末页").attr("href","#")); endPage.click(function () { to_page(data.map.pageInfo.pages); }); if(!data.map.pageInfo.hasNextPage){ endPage.addClass("disabled"); downPage.addClass("disabled"); } ul.append(downPage).append(endPage); var nav=$("<nav></nav>").append(ul); $("#page").append(nav); } </script> </head> <body> <div class="container"> <div class="row"> <div class="col-md-12"> <h1>SSM_CRUD</h1> </div> </div> <div class="row"> <div class="col-md-4 col-md-offset-8"> <button class="btn btn-success"> <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>新增 </button> <button class="btn btn-warning"> <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>删除 </button> </div> </div> <div class="row"> <div class="col-md-12"> <table class="table table-hover" id="emps_table"> <thead> <tr> <th>编号</th> <th>姓名</th> <th>性别</th> <th>邮箱</th> <th>部门</th> <th >操作</th> </tr> </thead> <tbody> </tbody> </table> </div> </div> <div class="row"> <div class="col-md-6" id="page_count"> </div> <div class="col-md-6" id="page"> </div> </div> </div> </body> </html>
注意,在这里,我们要配置插件分页合理化,不然虽然我们在分页栏到达极限的时候跳转不了,但页面记录数还是会跳转。
在mybatis-config
<plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <!-- 配置分页合理化 --> <property name="reasonable" value="true"/> </plugin> </plugins>
新增员工
-
点击新增员工,探出BootStrap 模态框,我们可以将这模态框框架修改,变成属于自己的模态框。
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="myModalLabel">新增员工</h4> </div> <div class="modal-body"> <form class="form-horizontal"> <div class="form-group"> <label for="empName" class="col-sm-2 control-label">姓名</label> <div class="col-sm-10"> <input type="email" class="form-control" id="empName" placeholder="Name"> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">性别</label> <div class="col-sm-10"> <label class="radio-inline"> <input type="radio" name="inlineRadioOptions" id="gender_1" value="1" checked="checked">男 </label> <label class="radio-inline"> <input type="radio" name="inlineRadioOptions" id="gender_0" value="0">女 </label> </div> </div> <div class="form-group"> <label for="email" class="col-sm-2 control-label">邮箱</label> <div class="col-sm-10"> <input type="password" class="form-control" id="email" placeholder="Email"> </div> </div> <div class="form-group"> <label for="deptId" class="col-sm-2 control-label">部门</label> <div class="col-sm-10"> <select class="form-control" id="deptId"> </select> </div> </div> </form> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button> <button type="button" class="btn btn-primary">保存</button> </div> </div> </div> </div>
<div class="row"> <div class="col-md-4 col-md-offset-8"> <button id="add_bnt" class="btn btn-success" data-keyboard="true" > <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>新增 </button> <button id="del_bnt" class="btn btn-warning"> <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>删除 </button> </div> </div>
下面按钮对应。
-
写好,可以发送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=$("<option></option>").append(this.deptName).attr("value",this.deptId); select.appendTo("#deptId"); }) } }) }
前端+后端校验
前端
- 前端校验于程序猿来说不合理,在浏览器的开发者工具中,可以修改前端代码,如果只是用前端的知识来进行表单的验证,数据会极度的不安全,所以有必要采用后端校验。
- 说明:其实前端校验可以不要,只用后端+前端知识结合校验就可以了。
后端
- 后端校验的好处是,我们从前端发送ajax请求,然后在后端去请求数据库,例如:出现相同或者为空的时候,可以将警告保存,然后交给浏览器去处理。
例子
-
首先,我们得导入JSR303校验 的包,博主用
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>6.1.0.Final</version> </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"%> <html> <head> <title>员工列表</title> <%@include file="/common/link.jsp"%> <script type="text/javascript" > var totalPage; $(function () { to_page(1); }) function to_page(pn) { $.ajax({ url:"${basePath}/emps", data:"page="+pn, type:"GET", success:function (data) { totalData=data; build_list(data); build_page_count(data); build_page(data); } }) } function build_list(data) { //清空表格里的数据 $("#emps_table tbody").empty(); var emps=data.map.pageInfo.list; $.each(emps,function (index,item) { var empIdTd = $("<td></td>").append(item.empId); var empNameTd = $("<td></td>").append(item.empName); var genderTd = $("<td></td>").append(item.gender==='1'?"男":"女"); var emailTd = $("<td></td>").append(item.email); var deptNameTd = $("<td></td>").append(item.department.deptName); var editBtn = $("<button></button>").addClass("btn btn-primary btn-sm") .append($("<span></span>").addClass("glyphicon glyphicon-pencil")).append("编辑"); var delBtn = $("<button></button>").addClass("btn btn-danger btn-sm") .append($("<span></span>").addClass("glyphicon glyphicon-trash")).append("删除"); var btnTd = $("<td></td>").append(editBtn).append(" ").append(delBtn); //append方法执行完成以后还是返回原来的元素,这里每次append都会产生新的<tr></tr> var tr=$("<tr></tr>").append(empIdTd).append(empNameTd) .append(genderTd).append(emailTd).append(deptNameTd) .append(btnTd) $("#emps_table tbody").append(tr) }) } function build_page_count(data) { $("#page_count").empty(); var page=data.map.pageInfo; $("#page_count").append("当前"+page.pageNum+"页,总"+page.pages+"页,总"+ page.total+"记录"); totalPage=page.total; } function build_page(data) { $("#page").empty(); var ul=$("<ul></ul>").addClass("pagination"); //首页 var firstPage=$("<li></li>").append($("<a></a>").append("首页").attr("href","#")); firstPage.click(function () { to_page(1); }); //上一页 var upPage=$("<li></li>").append($("<a></a>").append("<span></span>").append("«").attr("href","#")); upPage.click(function () { to_page(data.map.pageInfo.pageNum-1); }); if(!data.map.pageInfo.hasPreviousPage){ firstPage.addClass("disabled"); upPage.addClass("disabled"); } //首页,前一页 ul.append(firstPage).append(upPage); $.each(data.map.pageInfo.navigatepageNums,function (index,itme) { var numi=$("<li></li>").append($("<a></a>").append(itme).attr("href","#")); numi.click(function () { to_page(itme); }); if (data.map.pageInfo.pageNum===itme){ numi.addClass("active"); } // 当前页 ul.append(numi); }) //下一页 var downPage=$("<li></li>").append($("<a></a>").append("<span></span>").append("»").attr("href","#")); downPage.click(function () { to_page(data.map.pageInfo.pageNum+1); }); //末页 var endPage=$("<li></li>").append($("<a></a>").append("末页").attr("href","#")); endPage.click(function () { to_page(data.map.pageInfo.pages); }); if(!data.map.pageInfo.hasNextPage){ endPage.addClass("disabled"); downPage.addClass("disabled"); } ul.append(downPage).append(endPage); var nav=$("<nav></nav>").append(ul); $("#page").append(nav); } // 对列表框发送ajax请求,将部门的数据显示在下拉列表框中 $(function () { $("#add_bnt").click(function () { getDepts(); $("#myModal").modal({ //防止点击背景就消失 backdrop:"static" }); }); }) function getDepts() { $("#_deptId").empty(); $.ajax({ url:"${basePath}/depts", type:"GET", success:function (data) { $.each(data.map.depts,function () { var select=$("<option></option>").append(this.deptName).attr("value",this.deptId).attr("name","deptId"); select.appendTo("#_deptId"); }) } }) } //点击保存按钮 $(function () { $("#add_emp").click(function () { //若用户不合法,阻止提交ajax请求 if (!userJudge()){ return false; } //若用户名已存在,阻止其提交ajax请求 if ($(this).attr("ajax_alert")==="error"){ return false; } $.ajax({ url:"${basePath}/emp", type:"POST", //表单序列化,变成json data:$("#myModal form").serialize(), success:function (data) { /* * 因为我们在后端进行校验,所以我们要在关闭模态框之前进行判断 * */ if (data.code==="200"){ // 关闭模态框 $("#myModal").modal('hide'); //清空表单项 clear(); // 跳转到最后一页 to_page(totalPage); }else { if (undefined!==data.map.resultMap.email){ // 显示员工名错误信息 $("#email").parent().addClass("has-error"); $("#inputErrorEmail").text(data.map.resultMap.email); } if (undefined!==data.map.resultMap.empName){ // 显示邮箱错误信息 $("#empName").parent().addClass("has-error"); $("#inputErrorName").text(data.map.resultMap.empName); } } } }) }); }) //表单的清空 $(function () { $("#_reset").click(function () { clear(); }); }) function clear() { //表单重置 $("#myModal form")[0].reset(); // $("#empName").val(""); // $("#email").val(""); } //用户名是否存在校验 $(function () { //注意,这里一定要有这个失去焦点事件,或者其他事件,不然你直接让$(function(){})先加载,弹出框都没出来便发送ajax请求,对后来的验证就没有了 $("#empName").blur(function () { ifUser(); }); // 前端email校验,会有bug $("#email").blur(function () { emailJudge(); }); }); function ifUser() { var empName=$("#empName").val(); $.ajax({ url:"${basePath}/change", type:"GET", data:"empName="+empName, success:function (data) { if (data.code==="200"){ $("#empName").parent().removeClass("has-error"); $("#empName").parent().addClass("has-success"); $("#inputErrorName").text(""); //设置状态属性 $("#add_emp").attr("ajax_alert","success") }else { $("#empName").parent().removeClass("has-success"); $("#empName").parent().addClass("has-error"); $("#inputErrorName").text(data.map.success_msg); $("#add_emp").attr("ajax_alert","error") } } }) } //前端email校验 function emailJudge() { var email=$("#email").val(); var jemail=/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/; if (!jemail.test(email)) { $("#email").parent().addClass("has-error"); $("#inputErrorEmail").text("请输入正确的邮箱格式"); return false; } if (jemail.test(email)) { $("#email").parent().removeClass("has-error"); $("#email").parent().addClass("has-success"); $("#inputErrorEmail").text(""); } } //表单校验,前端校验 function userJudge() { var username= $("#empName").val(); //帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线) var juser=/^[a-zA-Z][a-zA-Z0-9_]{2,15}$/; if (!juser.test(username)) { $("#empName").parent().addClass("has-error"); $("#inputErrorName").text("字母开头,允许3-16位,允许字母数字下划线"); return false; } if (juser.test(username)) { $("#empName").parent().removeClass("has-error"); $("#empName").parent().addClass("has-success"); // $("#inputErrorName").text("") ifUser(); return true; } return false; } </script> </head> <body> <div class="container"> <div class="row"> <div class="col-md-12"> <h1>SSM_CRUD</h1> </div> </div> <%--弹出框新增--%> <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="myModalLabel">新增员工</h4> </div> <div class="modal-body"> <form class="form-horizontal" id="_form"> <div class="form-group"> <label for="empName" class="col-sm-2 control-label">姓名</label> <div class="col-sm-10"> <input type="email" class="form-control" name="empName" id="empName" placeholder="Name"> <span id="inputErrorName" class="help-block"></span> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">性别</label> <div class="col-sm-10"> <label class="radio-inline"> <input type="radio" name="gender" id="gender_1" value="1" checked="checked">男 </label> <label class="radio-inline"> <input type="radio" name="gender" id="gender_0" value="0">女 </label> </div> </div> <div class="form-group"> <label for="email" class="col-sm-2 control-label">邮箱</label> <div class="col-sm-10"> <input type="email" class="form-control" name="email" id="email" placeholder="Email"> <span id="inputErrorEmail" class="help-block"></span> </div> </div> <div class="form-group"> <label for="_deptId" class="col-sm-2 control-label" >部门</label> <div class="col-sm-10"> <select class="form-control" name="deptId" id="_deptId"> </select> </div> </div> </form> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" name="_reset" id="_reset"> 清空 </button> <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button> <button type="button" class="btn btn-primary" id="add_emp">保存</button> </div> </div> </div> </div> <div class="row"> <div class="col-md-4 col-md-offset-8"> <button id="add_bnt" class="btn btn-success" data-keyboard="true" > <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>新增 </button> <button id="del_bnt" class="btn btn-warning"> <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>删除 </button> </div> </div> <div class="row"> <div class="col-md-12"> <table class="table table-hover" id="emps_table"> <thead> <tr> <th>编号</th> <th>姓名</th> <th>性别</th> <th>邮箱</th> <th>部门</th> <th >操作</th> </tr> </thead> <tbody> </tbody> </table> </div> </div> <div class="row"> <div class="col-md-6" id="page_count"> </div> <div class="col-md-6" id="page"> </div> </div> </div> </body> </html>
里面还设计一个后端设计
@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代码一样,但是属性名字不能一样。
<div class="modal fade" id="myModal_update" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="myModalLabel_update">修改员工</h4> </div> <div class="modal-body"> <form class="form-horizontal" id="_form_update"> <div class="form-group"> <label for="empName_update" class="col-sm-2 control-label">姓名</label> <div class="col-sm-10"> <input type="email" class="form-control" name="empName" id="empName_update" placeholder="Name"> <span id="inputErrorName_update" class="help-block"></span> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">性别</label> <div class="col-sm-10"> <label class="radio-inline"> <input type="radio" name="gender" id="gender_1_update" value="1" checked="checked">男 </label> <label class="radio-inline"> <input type="radio" name="gender" id="gender_0_update" value="0">女 </label> </div> </div> <div class="form-group"> <label for="email_update" class="col-sm-2 control-label">邮箱</label> <div class="col-sm-10"> <input type="email" class="form-control" name="email" id="email_update" placeholder="Email"> <span id="inputErrorEmail_update" class="help-block"></span> </div> </div> <div class="form-group"> <label for="_deptId_update" class="col-sm-2 control-label" >部门</label> <div class="col-sm-10"> <select class="form-control" name="deptId" id="_deptId_update"> </select> </div> </div> </form> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" name="_reset_update" id="_reset_update"> 清空 </button> <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button> <button type="button" class="btn btn-primary" id="add_emp_update">修改</button> </div> </div> </div> </div>
-
之后,便是进行数据回显,进行数据回显,我们要知道一点,这个编辑的按钮是随着数据的加载而产生的,也就是动态产生的,所以我们要为其绑定单击事件,一定要在有了该按钮的时候才可以绑定。
为了方便绑定,我们可以在产生数据的时候,为这个编辑的按钮增加一些属性。
var editBtn = $("<button></button>").addClass("btn btn-primary btn-sm edit_emp") .append($("<span></span>").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中设置过滤器参数
<!--此过滤器FormContentFilter为了让SpringMVC使得Ajax支持put请求--> <filter> <filter-name>FormContentFilter</filter-name> <filter-class>org.springframework.web.filter.FormContentFilter</filter-class> </filter>
<filter-mapping> <filter-name>HiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>FormContentFilter</filter-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 = $("<button></button>").addClass("btn btn-danger btn-sm del_emp") .append($("<span></span>").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,所以现在得改变
<th> <label for="checkAll" > <input type="checkbox" name="checkAll" id="checkAll"> </label> </th
然后我们要为每个tr都添加这个input
var empChecked=$("<td></td>").append("<input type='checkbox' class='check_item'/>")
var tr=$("<tr></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); }