创建模块后,就要导入依赖,在pom.xml 中我们要配置基本的jar。
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则需要下载。
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中,注意标签的优先级。
<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>
<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>
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>
首先导入逆向工程的包。
我们先创建应有的包名,然后可以用逆向工程创建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);
我们都知道,服务器启动的时候跳转的默认都是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);
}
}
因为这里我们是通过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");
})
}
})
}
首先,我们得导入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);
}