一、创建一个Maven工程
1、引入项目依赖的jar包
srping、springMVC、mybatis、数据库连接池、数据库驱动、jstl、servlet-api、junit
2、引入bootstarp前端框架
官网查看,大致在index.jsp中配置如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>Bootstrap 101 Template</title>
<!-- Bootstrap -->
<link rel="stylesheet" href="css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<!-- HTML5 shim 和 Respond.js 是为了让 IE8 支持 HTML5 元素和媒体查询(media queries)功能 -->
<!-- 警告:通过 file:// 协议(就是直接将 html 页面拖拽到浏览器中)访问页面时 Respond.js 不起作用 -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
</head>
<body>
<!-- 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="js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
<button class="btn btn-success">按钮</button>
</body>
</html>
3、编写ssm整合相关的配置文件
web.xml、applicationContext.xml、spring_mvc.xml、jdbc.properties、XxxMapper.xml、sqlMapConfig.xml等,使用mybatis的逆向工程生成对应的bean以及mapper
1) 首先配置web.xml文件:
① 启动spring容器,配置spring监听器,作用是项目已启动,就加载初始化参数中的配置文件;
② springMVC的前端控制器,拦截匹配的请求,Servlet拦截匹配规则要自己定义,把拦截下来的请求,依据相应的规则分发到目标Controller来处理,是配置spring MVC的第一步;
③ 字符编码过滤器,一定要放在所有过滤器之前。作用是解决乱码问题;
④ 使用Rest风格的URI(原本是发不出delete、put方式的请求),把页面的post请求转为delete、put的请求
web.xml如下:
<?xml version="1.0" encoding="UTF-8" ?>
<web-app
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>
<display-name>Archetype Created Web Application</display-name>
<!--1、启动spring的容器-->
<!--配置spring中的监听器,
项目一启动,就加载参数中的配置文件-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--配置spring中的全局初始化参数-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--2、springMVC的前端控制器,拦截所有请求-->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param> <!--全局初始化参数,加载配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring_mvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--3、字符编码过滤器,一定要放在所有过滤器之前-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param> <!--初始化参数,指定初始UTF-8编码-->
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--4、使用Rest风格的URI(原本是发不出delete、put方式的请求)
把页面的post请求转为delete、put的请求-->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
2) 接着配置springMVC的配置文件spring-mvc.xml:
作用: SpringMVC的配置文件,包含网站跳转、逻辑的控制配置。
① 组件扫描(此配置文件中,只扫描控制器controller);
② 配置内部资源视图解析器,方便页面返回(jsp文件都放入WEB-INF/views中);
③ 两个标准配置:
开放静态资源的访问:先找匹配地址,如果找不到就交给原始容器(Tomcat)找对应的静态资源;
mvc的注解驱动:默认底层就会继承jackson进行对象或集合的json格式字符串的转换,能支持springMVC更高级的一些功能,JSR303校验,快捷的ajax...映射动态请求。
spring-mvc.xml如下:
<?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 http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--SpringMVC的配置文件,包含网站跳转逻辑的控制,配置-->
<!--组件扫描-->
<context:component-scan base-package="com.chu" use-default-filters="false">
<!--只扫描控制器controller-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--配置内部资源视图解析器,方便页面返回-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--拿到前缀,视图的名称,jsp文件都放入WEB-INF/views中-->
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!--两个标准配置-->
<!--① 开放静态资源的访问,先找匹配地址,如果找不到就交给原始容器(Tomcat)找对应的静态资源-->
<mvc:default-servlet-handler/>
<!--② mvc的注解驱动,默认底层就会继承jackson进行对象或集合的json格式字符串的转换
能支持springMVC更高级的一些功能,JSR303校验,
快捷的ajax...映射动态请求-->
<mvc:annotation-driven/>
</beans>
3) 接着配置spring的配置文件applicationContext.xml:
作用: spring配置文件,主要配置和业务逻辑相关的,核心点:数据源、与mybatis的整合、事务控制。
① 组件扫描(此配置文件中,不扫控制器,其他都扫);
② 配置数据源(使用加载外部properties文件的方式)
③ 配置和MyBatis的整合(指定核心配置文件[sqlMapConfig.xml]的位置、mapper映射文件[mapper/*.xml]的位置)、(配置扫描器,扫描mybatis所有dao接口的实现,加入到ioc容器中)
④ 事务控制的配置(主要使用xml配置形式的事务方式)
jdbc.properties如下:
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/ssm_crud?serverTimezone=GMT
jdbc.user=root
jdbc.password=4680
applicationContext.xml如下:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--spring配置文件,主要配置和业务逻辑相关的-->
<!--Spring配置文件的核心点:数据源、与mybatis的整合、事务控制-->
<!--扫描组件-->
<context:component-scan base-package="com.chu">
<!--不扫控制器,其他都扫-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--加载外部的properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置数据源-->
<bean id="pooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--===========配置和MyBatis的整合===========-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--指定mybatis核心(全局)配置文件的位置-->
<property name="configLocation" value="classpath:sqlMapConfig.xml"></property>
<property name="dataSource" ref="pooledDataSource"></property>
<!--指定mybatis的mapper文件的位置(映射文件)-->
<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
</bean>
<!--配置扫描器,将mybatis接口的实现加入到ioc容器中-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--扫描所有dao接口的实现,加入到ioc容器中-->
<property name="basePackage" value="com.chu.crud.dao"></property>
</bean>
<!--========================================-->
<!--事务控制的配置-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--控制数据源-->
<property name="dataSource" ref="pooledDataSource"></property>
</bean>
<!--开启基于注解的事务/使用xml配置形式的事务
(主要的都是使用配置式)-->
<aop:config>
<!--切入点表达式-->
<aop:pointcut id="txPoint" expression="execution(* com.chu.crud.service..*(..))"/>
<!--配置事务增强-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/>
</aop:config>
<!--配置事务增强,事务如何切入-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--所有方法都是事务方法-->
<tx:method name="*"/>
<!--以get开始的所有方法-->
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
</beans>
4) 接着配置mybatis的核心配置文件sqlMapConfig.xml(也可以在applicationContext.xml中写入,但一般单独写出):
① 驼峰命名规则
② 自定义别名,顺序要在properties之后,mapping之前
<?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>
<!--自定义别名,顺序要在properties之后,mapping之前-->
<typeAliases>
<typeAlias type="com.chu.crud.bean"/>
</typeAliases>
</configuration>
注:spring的在applicationContext.xml中整合mybatis时已经指定了映射文件的位置,故不需要在核心配置文件中使用映射器加载映射文件。
例如,不需要如下配置:
<!--映射器 加载映射文件-->
<mappers>
<mapper resource="com/chu/mapper/UserMapper.xml"></mapper>
</mappers>
5)创建出mapper文件中XxxMapper.xml对应的dao接口的表:
tbl_emp表:
tbl_dept表:
建立外键(两表联系):
6)创建出tbl_emp表、tbl_dept表对应的dao、bean类、mapper文件中XxxMapper.xml配置文件:
注意: 使用mybatis的逆向工程 生成对应的bean以及mapper。
使用说明网址如下:
http://mybatis.org/generator/index.html
① 导入mybatis-generator-core的坐标
<!--MBG-->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.7</version>
</dependency>
② 在当前工程下创建一个普通xml文件:mbg.xml
示例:网页中左侧XML Configuration Reference
先删除<classPathEntry location=…>,生成自己的。
① 生成不带注释的
② 配置数据库连接
③ 指定javaBean生成的位置
④ 指定sql映射文件生成的位置
⑤ 指定dao接口的生成位置
⑥ table标签指定每个表的生成策略
<?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>
<context id="DB2Tables" targetRuntime="MyBatis3">
<!--生成不带注释的-->
<commentGenerator>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--配置数据库连接-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/ssm_crud?serverTimezone=GMT"
userId="root"
password="4680">
</jdbcConnection>
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!--指定javaBean生成的位置-->
<javaModelGenerator
targetPackage="com.chu.crud.bean"
targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!--指定sql映射文件生成的位置-->
<sqlMapGenerator
targetPackage="mapper"
targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!--指定dao接口生成的位置-->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.chu.crud.dao"
targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!--table指定每个表的生成策略-->
<table tableName="tbl_emp" domainObjectName="Employee"></table>
<table tableName="tbl_dept" domainObjectName="Department"></table>
</context>
</generatorConfiguration>
③ 生成对应的bean、dao以及mapper
网站左侧:Running MyBatis Generator→From another Java program with an XML configuration中点击Java program获取示例。
MGBTest生成: 注意代码中修改配置文件名为mbg.xml,提示File错误,要手动导包即可。
public class MGBTest {
@Test
public void test1() 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);
}
}
7)在生成的XxxMapper.xml映射文件中增加表之间的联查语句:
需求: 查询员工的同时部门信息也得到查询(一对一:一个员工对应一个部门)
① Employee类中加入Department部门类的属性:
public class Employee {
private Integer empId;
private String empName;
private String gender;
private String email;
private Integer dId;
//希望查询员工的同时部门信息也得到查询
// (一对一:一个员工对应一个部门)
private Department department;
get、set、toString...
}
② EmployeeMapper接口中加入selectByExampleWithDept、selectByPrimaryKeyWithDept抽象方法:
public interface EmployeeMapper {
...
...
//查询员工同时带部门信息
List<Employee> selectByExampleWithDept(EmployeeExample example);
Employee selectByPrimaryKeyWithDept(Integer empId);
...
...
}
③ EmployeeMapper.xml中的sql语句:
新增的resultMap(查询之后的返回集形式):
<resultMap id="WithDeptResultMap" type="com.chu.crud.bean.Employee">
<id column="emp_id" jdbcType="INTEGER" property="empId" />
<result column="emp_name" jdbcType="VARCHAR" property="empName" />
<result column="gender" jdbcType="CHAR" property="gender" />
<result column="email" jdbcType="VARCHAR" property="email" />
<result column="d_id" jdbcType="INTEGER" property="dId" />
<!--指定联合查询出的部门字段的封装-->
<association property="department" javaType="com.chu.crud.bean.Department">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
</association>
</resultMap>
新增的公共sql语句:
<sql id="WithDept_Column_List">
e.emp_id, e.emp_name, e.gender, e.email, e.d_id, d.dept_id, d.dept_name
</sql>
新增的查询方法:
<!--查询员工同时带部门信息-->
<select id="selectByExampleWithDept" resultMap="WithDeptResultMap">
select
<if test="distinct">
distinct
</if>
<include refid="WithDept_Column_List" />
from tbl_emp e
left join tbl_dept d on e.`d_id`=d.`dept_id`
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
</select>
<select id="selectByPrimaryKeyWithDept" resultMap="WithDeptResultMap">
select
<include refid="WithDept_Column_List" />
from tbl_emp e
left join tbl_dept d on e.`d_id`=e.`dept_id`
where emp_id = #{empId,jdbcType=INTEGER}
</select>
4、测试mapper
由于XxxMapper在spring配置文件(applicationContext.xml)中把其加入到容器中:
方式一:可以通过如下获取mapper对象:
//1、创建SoringIOC容器
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
//2、从容器中获取mapper
DepartmentMapper departmentMapper = ioc.getBean(DepartmentMapper.class);
方式二:注解方式
步骤如下:
1、导入Spring-test模块
2、@ContextConfiguration指定spring配置文件的位置
3、直接@Autowired要使用的组件即可
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class MapperTest {
@Autowired
DepartmentMapper departmentMapper;
@Autowired
EmployeeMapper employeeMapper;
@Autowired
SqlSession sqlSession;
//测试DepartmentMapper
@Test
public void testCRUD(){
//1、插入几个部门
/*
departmentMapper.insertSelective(new Department(null,"开发部"));
departmentMapper.insertSelective(new Department(null,"测试部"));
*/
//2、生成员工数据,测试员工插入
//employeeMapper.insertSelective(new Employee(null,"Jerry","M","Jerry@qq.com",1));
//3、批量插入多个员工:使用可以执行批量操作的sqlSession
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
for(int i=0;i<=100;i++){
String uid = UUID.randomUUID().toString().substring(0, 5)+i;
mapper.insertSelective(new Employee(null,uid,"M",uid+"@qq.com",1));
}
System.out.println("批量完成");
}
}
二、查询展示
步骤:
① 访问index.jsp页面
② index.jsp页面发送出查询员工列表请求
③ EmploeeController来接受请求,查出员工数据
④ 来到list.jsp页面进行展示
①-②:请求转发到当前项目下的emps请求
<%--请求转发到当前项目下的emps请求--%>
<jsp:forward page="/emps"></jsp:forward>
③ EmploeeController来接受请求,查出员工数据:(查出的数据要分页,mybatis笔记中也有提到)
pop.xml中引入分页坐标:
<!--引入PageHelper分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>4.0</version>
</dependency>
接受请求,查询数据:
@Controller
public class EmployeeController {
@Autowired
EmployeeService employeeService;
/*查询员工该数据(分页查询)*/
@RequestMapping("/emps") //value传入的当前页,model用来保存分页数据
public String getEmps(@RequestParam(value = "pn",defaultValue = "1")Integer pn, Model model){
//这不是一个分页查询
//引入PageHelper分页插件
//设置分页相关参数 当前页+每页显示的条数
PageHelper.startPage(pn,5);
//startPage后面紧跟的这个查询就是分页查询
List<Employee> emps=employeeService.getAll();
//使用pageInfo包装查询后的结果,只需要pageInfo交给页面就行了
// 封装了详细的分页信息,包括查询出的数据,和连续传入的页数
PageInfo page = new PageInfo<>(emps,5);
model.addAttribute("pageInfo",page);
return "list"; //因为有视图解析器,地址就是/WEB-INF/views/list.jsp
}
}
④ list页面展示:
利用遍历展示查询到的数据,同时用到判断控制页面展示
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>员工列表</title>
<%
pageContext.setAttribute("App_PATH",request.getContextPath());
%>
<%--web路径:
不以/开始的相对路径,找资源,以当前资源的路径为基准,经常容易出问题
以/开始的相对路径,找资源,以服务器的路径为标准(http://localhost:3306),需要加上项目名
http://localhost:3306/crud
--%>
<script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
<link rel="stylesheet" href="${App_PATH}/static/bootstrap-3.4.1-dist/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<script src="${App_PATH}/static/bootstrap-3.4.1-dist/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
</head>
<body>
<%--搭建显示页面--%>
<div class="container">
<%--标题--%>
<div class="row"></div>
<div class="col-md-12">
<h1>SSM-SRUD</h1>
</div>
<%--按钮--%>
<div class="row"></div>
<div class="col-md-4 col-md-offset-8">
<button class="btn btn-primary">新增</button>
<button class="btn btn-danger">删除</button>
</div>
<%--显示表格数据--%>
<div class="row"></div>
<div class="col-md-12">
<table class="table table-hover">
<tr>
<th>#</th>
<th>empName</th>
<th>gender</th>
<th>email</th>
<th>deptName</th>
<th>操作</th>
</tr>
<c:forEach items="${pageInfo.list}" var="emp">
<tr>
<th>${emp.empId}</th>
<th>${emp.empName}</th>
<th>${emp.gender=="M"?"男":"女"}</th>
<th>${emp.email}</th>
<th>${emp.department.deptName}</th>
<th>
<button class="btn btn-primary btn-sm">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
编辑
</button>
<button class="btn btn-danger btn-sm">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
删除
</button>
</th>
</tr>
</c:forEach>
</table>
</div>
<%--显示分页信息--%>
<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">
<li><a href="${App_PATH}/emps?pn=1">首页</a> </li>
<c:if test="${pageInfo.hasPreviousPage}">
<li>
<a href="${App_PATH}/emps?pn=${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 href="#">${page_Num}</a> </li>
</c:if>
<c:if test="${page_Num!=pageInfo.pageNum}">
<li><a href="${App_PATH}/emps?pn=${page_Num}">${page_Num}</a> </li>
</c:if>
</c:forEach>
<c:if test="${pageInfo.hasNextPage}">
<li>
<a href="${App_PATH}/emps?pn=${pageInfo.pageNum+1}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</c:if>
<li><a href="${App_PATH}/emps?pn=${pageInfo.pages}">末页</a> </li>
</ul>
</nav>
</div>
</div>
</div>
</body>
</html>
缺点:现阶段只支持浏览器端到服务端,安卓端和IOS不能满足。
解决方案:使用json格式数据。提升了系统的拓展性。
三、优化查询-ajax
步骤:
① index.jsp页面直接发送ajax请求进行员工分页数据的查询
② 服务器将查出的数据,以json字符串的形式返回给浏览器
③ 浏览器收到json字符串。可以使用js对json进行解析,使用js通过dom增删改查改变页面。
④ 返回json。实现客户端的无关性。
①
<%--请求转发到当前项目下的emps请求--%>
<jsp:forward page="/emps"></jsp:forward>
② 借助Msg类返回状态码和提示信息,以及用链式返回的方式返回给浏览器数据(EmployeeController调用Msg类中的的函数,然后此函数的返回值,再调用其内部的函数)
Msg类:
public class Msg {
// 状态码 100-成功 200-失败
private int code;
//提示信息
private String msg;
//用户要返回给浏览器的数据
private Map<String,Object> extend =new HashMap<>();
public static Msg success(){
Msg result = new Msg();
result.setCode(100);
result.setMsg("处理成功!");
return result;
}
public static Msg fail(){
Msg result = new Msg();
result.setCode(200);
result.setMsg("处理失败!");
return result;
}
public Msg add(String key,Object value){
this.getExtend().put(key,value);
return this;
get、set、toString...
处理请求方式二: - - - 使用 @ResponseBody 将返回对象自动转为json字符串
@Controller
public class EmployeeController {
@Autowired
EmployeeService employeeService;
/*方式二:导入jackson包,直接将返回的对象转为json字符串*/
@RequestMapping("/emps")
@ResponseBody //将返回的对象自动转为json字符串
public Msg getEmpWithJson(@RequestParam(value = "pn",defaultValue = "1")Integer pn){
//这不是一个分页查询
//引入PageHelper分页插件
//设置分页相关参数 当前页+每页显示的条数
PageHelper.startPage(pn,5);
//startPage后面紧跟的这个查询就是分页查询
List<Employee> emps=employeeService.getAll();
//使用pageInfo包装查询后的结果,只需要pageInfo交给页面就行了
// 封装了详细的分页信息,包括查询出的数据,和连续传入的页数
PageInfo page = new PageInfo<>(emps,5);
//链式返回,有返回的处理的状态和数据
return Msg.success().add("pageInfo",page);
}
③ ajax请求得到分页数据,解析处理数据
ajxa请求中调用解析显示员工数据、解析显示分页信息、解析显示分页条的函数:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>员工列表</title>
<%
pageContext.setAttribute("App_PATH",request.getContextPath());
%>
<%--web路径:
不以/开始的相对路径,找资源,以当前资源的路径为基准,经常容易出问题
以/开始的相对路径,找资源,以服务器的路径为标准(http://localhost:3306),需要加上项目名
http://localhost:3306/crud
--%>
<script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
<link rel="stylesheet" href="${App_PATH}/static/bootstrap-3.4.1-dist/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<script src="${App_PATH}/static/bootstrap-3.4.1-dist/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
</head>
<body>
<%--搭建显示页面--%>
<div class="container">
<%--标题--%>
<div class="row"></div>
<div class="col-md-12">
<h1>SSM-SRUD</h1>
</div>
<%--按钮--%>
<div class="row"></div>
<div class="col-md-4 col-md-offset-8">
<button class="btn btn-primary">新增</button>
<button class="btn btn-danger">删除</button>
</div>
<%--显示表格数据--%>
<div class="row"></div>
<div class="col-md-12">
<table class="table table-hover" id="emps_table">
<%--表头放在thead中--%>
<thead>
<tr>
<th>#</th>
<th>empName</th>
<th>gender</th>
<th>email</th>
<th>deptName</th>
<th>操作</th>
</tr>
</thead>
<%--单元格数据放tbody中--%>
<tbody>
</tbody>
</table>
</div>
<%--显示分页信息--%>
<div class="row">
<%--分页文字信息--%>
<div class="col-md-6" id="page_info_area"></div>
<%--分页条信息--%>
<div class="col-md-6" id="page_nav_area"></div>
</div>
</div>
<script type="text/javascript">
//1、页面加载完成以后,直接去发送一个ajax请求,要到分页数据。
$(function (){
//去首页
to_page(1);
});
//跳转到对应页
function to_page(pn){
$.ajax({
url:"${App_PATH}/emps", //一个用来包含发送请求的URL字符串
data:"pn="+pn,
type:"GET",
success:function (result){
//console.log(result); 输出获取的json字符串,下面是处理
//1、调用解析并显示员工数据的函数
build_emps_table(result);
//2、调用解析并显示分页信息的函数
build_page_info(result);
//3、调用解析显示分页条的函数
build_page_nav(result);
}
});
}
//解析显示员工数据,返回数据中extend中pageInfo中的list属性
function build_emps_table(result){
//每次调用清空table表格,不然会累加
$("#emps_table tbody").empty();
var emps = result.extend.pageInfo.list;
$.each(emps,function(index,item){
//以下操作是把遍历的数据加入到单元格td中,然后将数据再加入到每行tr中
// 最后添加到id为emps_table表格的tbody中
var empIdTd = $("<td></td>").append(item.empId);
var empNameTd = $("<td></td>").append(item.empName);
var genderTd = $("<td></td>").append(item.gender=='M'?"男":"女");
var emailTd = $("<td></td>").append(item.email);
var deptNameTd = $("<td></td>").append(item.department.deptName);
//编辑和删除按钮
/*
* <button class="btn btn-primary btn-sm">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
编辑
</button>
<button class="btn btn-danger btn-sm">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
删除
</button>
* */
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方法执行完成以后还是返回原来的元素
$("<tr></tr>").append(empIdTd)
.append(empNameTd)
.append(genderTd)
.append(emailTd)
.append(deptNameTd)
.append(btnTd)
.appendTo("#emps_table tbody");
});
}
//解析显示分页信息
function build_page_info(result){
//清空,避免累加
$("#page_info_area").empty();
$("#page_info_area").append("当前"+result.extend.pageInfo.pageNum
+"页,总"+result.extend.pageInfo.pages+ "页,总"
+result.extend.pageInfo.total+"条记录")
}
//解析显示分页条
function build_page_nav(result){
//清空,避免累加
$("#page_nav_area").empty();
var ul=$("<ul></ul>").addClass("pagination");
//构建元素
var firstPageLi =$("<li></li>").append($("<a></a>").append("首页").attr("href","#"));
var prePageLi =$("<li></li>").append($("<a></a>").append("«"));
//判断如果是否有前一页
if (result.extend.pageInfo.hasPreviousPage==false){
firstPageLi.addClass("disabled")
prePageLi.addClass("disabled")
}else {
//为元素添加点击翻页的事件
firstPageLi.click(function (){
to_page(1);
});
prePageLi.click(function (){
to_page(result.extend.pageInfo.pageNum-1);
});
}
var nextPageLi =$("<li></li>").append($("<a></a>").append("»"));
var lastPageLi =$("<li></li>").append($("<a></a>").append("末页").attr("href","#"));
//判断如果是否有后一页,是的话则禁用
if (result.extend.pageInfo.hasNextPage==false){
lastPageLi.addClass("disabled")
nextPageLi.addClass("disabled")
}else {
//为元素添加点击翻页的事件
lastPageLi.click(function (){
to_page(result.extend.pageInfo.pages);
});
nextPageLi.click(function (){
to_page(result.extend.pageInfo.pageNum+1);
});
}
//添加首页和前一页的提示
ul.append(firstPageLi).append(prePageLi);
// 页码号遍历给ul
$.each(result.extend.pageInfo.navigatepageNums,function (index,item){
var numLi =$("<li></li>").append($("<a></a>").append(item));
if(result.extend.pageInfo.pageNum==item){
numLi.addClass("active");
}
//给遍历得到的每个页码绑定单击事件,点击时发送ajax请求
numLi.click(function (){
to_page(item)
});
ul.append(numLi);
});
//添加末页和下一页的提示
ul.append(nextPageLi).append(lastPageLi);
//把ul加入到nav
var navEle=$("<nav></nav>").append(ul);
navEle.appendTo("#page_nav_area");
}
</script>
</body>
</html>
四、增加
步骤:
1、 再index.jsp页面点击“新增”
2、 弹出新增对话框
3、 去数据库查询部门列表,显示在对话框中
4、 用户输入数据,并进行校验
jQuery前端合法性校验,ajax用户名重复校验,重要数据[后端校验(JSR303),唯一约束]
5、 完成保存
URI:
/emp/{id} GET查询员工
/emp POST保存员工
/emp/{id} PUT修改员工
/emp/{id} DELETE删除员工
1-2、 弹出新增对话框 - - -员工添加模态框
<!-- 员工添加的模态框 -->
<div class="modal fade" id="empAddModal" 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_add_input" class="col-sm-2 control-label">empName</label>
<div class="col-sm-10">
<input type="text" name="empName" class="form-control" id="empName_add_input" placeholder="empName">
</div>
</div>
<div class="form-group">
<label for="email_add_input" class="col-sm-2 control-label">email</label>
<div class="col-sm-10">
<input type="text" name="email" class="form-control" id="email_add_input" placeholder="email@chu.com">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">gender</label>
<div class="col-sm-10">
<%--多选--%>
<label class="radio-inline">
<input type="radio" name="gender" id="gender1_add_input" value="M" checked="checked"> 男
</label>
<label class="radio-inline">
<input type="radio" name="gender" id="gender2_add_input" value="F"> 女
</label>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">deptName</label>
<div class="col-sm-4">
<%--下拉列表 变化的,部门提交部门id即可--%>
<select class="form-control" name="dId">
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox"> Remember me
</label>
</div>
</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>
给页面 “新增”按钮中绑定单击事件 ,并使用javascript代码,通过id元素调用上述模态框:
//绑定新增按钮的事件
$("#emp_add_modal_btn").click(function (){
$("#empAddModal").modal({
backdrop:"static"
});
});
3、 弹出模态框之前,给服务器发送一个ajax请求,获取数据库中的部门信息,显示在对话框中:
在每一次打开新增的之前都要 清空表单的样式和内容:
//清空表单样式及内容
function reset_form(ele){
$(ele)[0].reset();
//清空表单样式
$(ele).removeClass("has-error has-success");
$(ele).find(".help-block").text("")
}
//绑定新增按钮的事件
//点击新增按钮弹出模态框
$("#emp_add_modal_btn").click(function (){
//清除表单数据(表单重置-表单的数据,表单的样式) 调用函数
reset_form("#empAddModal form");
//发送ajax请求,查出部门信息,显示在下拉列表中,调用下面的函数
getDepts();
//弹出模态框
$("#empAddModal").modal({
backdrop:"static"
});
});
//查出所以的部门信息并显示在下拉列表中
function getDepts(){
//清空,避免累加
$("#dept_add_select").empty();
//ajax请求去查询获取部门信息
$.ajax({
url:"${App_PATH}/depts",
type:"GET",
success:function (result){
//{"code":100,"msg":"处理成功!","extend":{"depts":[{"deptId":1,"deptName":"开发部"},{"deptId":2,"deptName":"测试部"}]}}
//显示部门信息在下拉列表中
//$("#dept_add_select").append(""),下面遍历后添加
$.each(result.extend.depts,function (){
var optionEle=$("<option></option>").append(this.deptName).attr("value",this.deptId);
//把添加部门信息后的变量添加到id为dept_add_select的下拉列表中
optionEle.appendTo("#dept_add_select");
});
}
});
}
处理请求的控制器DepartmentController:
@Controller
public class DepartmentController {
@Autowired
private DepartmentService departmentService;
//返回所有部门的信息
@RequestMapping("/depts")
@ResponseBody
public Msg getDepts(){
//查处的所有部门信息
List<Department> list=departmentService.getDepts();
return Msg.success().add("depts",list);
}
}
4、 输入数据并校验 - 校验姓名和邮箱是否符合格式
1)jQuery前端合法性校验:
//校验表单数据
function validate_add_form(){
//1、拿到要校验的名字数据,使用正则表达式
var empName=$("#empName_add_input").val();
var regName=/(^[a-zA-Z0-9_-]{6,16}$)|(^[\u2e80-\u9FFF]{2,5}$)/;
if (!regName.test(empName)){
alert("用户名可以是2-5位中文或者6-16位英文和数字的组合")
return false;
};
//2、校验邮箱信息
var email=$("#email_add_input").val();
var regEmail=/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;
if (!regEmail.test(email)){
alert("邮箱格式不正确")
return false;
};
return true;
}
弹窗提示不够美观,使用表单的校验状态来美化校验:
要在表单中加入框架中的格式来美化校验
<%--美化校验--%>
<span class="help-block"></span>
//校验表单数据
function validate_add_form(){
//1、拿到要校验的名字数据,使用正则表达式
var empName=$("#empName_add_input").val();
var regName=/(^[a-zA-Z0-9_-]{6,16}$)|(^[\u2e80-\u9FFF]{2,5}$)/;
if (!regName.test(empName)){
//alert("用户名可以是2-5位中文或者6-16位英文和数字的组合")
//应该清空这个元素之前的样式
show_validate_msg("#empName_add_input","error","用户名可以是2-5位中文或者6-16位英文和数字的组合");
return false;
}else {
show_validate_msg("#empName_add_input","success","");
};
//2、校验邮箱信息
var email=$("#email_add_input").val();
var regEmail=/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;
if (!regEmail.test(email)){
//alert("邮箱格式不正确")
// 使用框架对其进行美化,给当前控件的父元素添加需要的类
show_validate_msg("#email_add_input","error","邮箱格式不正确");
return false;
}else {
show_validate_msg("#email_add_input","success","");
};
return true;
}
/*
* 抽取出的校验函数,ele表示当前元素,status表示状态,msg表示提示信息
* */
function show_validate_msg(ele,status,msg){
//清楚当前元素的校验状态
$(ele).parent().removeClass("has-success has-error");
$(ele).next("span").text("");
if ("success"==status){
$(ele).parent().addClass("has-success");
//如果span中有内容也清空
$(ele).next("span").text(msg);
}else if ("error"==status){
// 使用框架对其进行美化,给当前控件的父元素添加需要的类
$(ele).parent().addClass("has-error");
//紧邻的span元素中的内容就是要提示的信息
$(ele).next("span").text(msg)
}
}
补:
2)ajax用户名重复校验
ajax校验员工是否重复:
当员工姓名输入之后,发送ajxa请求给服务器,服务器告知用户名是否可用,不可用需要提示
EmployeeController中处理ajax请求:
@Controller
public class EmployeeController {
@Autowired
EmployeeService employeeService;
//校验员工名是否可用
@RequestMapping("/checkuser")
@ResponseBody
public Msg checkuser(@RequestParam("empName") String empName){
//先判断用户名是否合法
String regx="(^[a-zA-Z0-9_-]{6,16}$)|(^[\u2e80-\u9FFF]{2,5}$)";
if (!empName.matches(regx)){
return Msg.fail().add("va_msg","用户名必须是6-16位英文和数字的组合或者2-5位中文");
}
//判断用户名是否存在
boolean b=employeeService.checkUser(empName);
if(b){
return Msg.success();
}else {
return Msg.fail().add("va_msg","用户名不可用");;
}
}
}
EmployeeService类中添加校验方法:
//校验用户名是否可用 true代表当前姓名可用,fasle表示不可用
public boolean checkUser(String empName) {
EmployeeExample example = new EmployeeExample();
EmployeeExample.Criteria criteria = example.createCriteria();
criteria.andEmpNameEqualTo(empName);
long count = employeeMapper.countByExample(example);
return count == 0;
}
index.jsp页面添加校验发送ajax校验请求:
给按钮添加attr自定义属性来赋值请求得到的状态,为后面保存前通过此属性的值进行判断。
//员工姓名输入框绑定事件,用于校验用户名是否可用
$("#empName_add_input").change(function (){
//发送ajax求情校验用户名是否可用
var empName =this.value;
$.ajax({
url:"${App_PATH}/checkuser",
data:"empName="+empName,
type:"POST",
success:function (result){
if (result.code==100){
show_validate_msg("#empName_add_input","sucess","用户名可用")
//给按钮添加attr自定义属性来赋值请求得到的状态
$("#emp_save_btn").attr("ajax-va","success");
}else {
show_validate_msg("#empName_add_input","error",result.extend.va_msg)
$("#emp_save_btn").attr("ajax-va","error");
}
}
});
});
3)重要数据[后端校验(JSR303)]
① 导入JSR303数据校验的包
<!--JSR303数据校验支持,tomacat7及以上的服务器,
tomacat7一下的服务器:el表达式,额外给服务器的lib包中替换新的标准的el
-->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.5.Final</version>
</dependency>
② 给要校验的属性自定义校验规则 Employee的属性:empName、email…
public class Employee {
private Integer empId;
// JSR303,给要校验的属性自定义校验规则
@Pattern(regexp = "(^[a-zA-Z0-9_-]{6,16}$)|(^[\\u2e80-\\u9FFF]{2,5}$)"
,message = "用户名必须是6-16位英文和数字的组合或者2-5位中文")
private String empName;
private String gender;
//@Email 可用用本身存在的注解,为了保持和前端校验的一致,使用自定义
@Pattern(regexp = "^([a-z0-9_\\.-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$"
,message = "邮箱格式不正确")
private String email;
......
}
③ EmployeeController处理请求时,对传入的对象属性进行校验,result封装校验结果:
@Controller
public class EmployeeController {
@Autowired
EmployeeService employeeService;
//员工数据保存
/*
* 1、要支持JSR303校验,需要导入Hibernate-Validator包
* */
@RequestMapping(value = "/emp",method = RequestMethod.POST)
@ResponseBody
// Valid代表此属性要校验,result封装校验的结果
public Msg saveEmp(@Valid Employee employee, BindingResult result){
if (result.hasErrors()){
//校验失败,返回失败,在模态框中显示校验失败的错误信息
Map<String,Object> map =new HashMap<>();
List<FieldError> errors = result.getFieldErrors();
for (FieldError fieldError:errors){
System.out.println("错误的字段名:"+fieldError.getField());
System.out.println("错误信息:"+fieldError.getDefaultMessage());
map.put(fieldError.getField(),fieldError.getDefaultMessage());
}
return Msg.fail().add("errorFields",map);
}else {
employeeService.saveEmp(employee);
return Msg.success();
}
}
...
}
④ 在发送ajax请求保存员工时,进行后端JSR303校验(下面代码的第3步)
//给保存输入数据的按钮绑定单击事件
$("#emp_save_btn").click(function (){
// 模态框中填写的表单数据提交给服务器进行保存
//1、先对要提交给服务器的数据进行前端校验
if (!validate_add_form()){
return false;
};
//2、再判断之前的ajax用户名校验是否成功,利用请求后attr自定义属性添加的状态进行判断
if ($(this).attr("ajax-va")=="error"){
return false;
}
//3、发送ajax请求保存员工 表单中只有一个form表单,序列化得到输入的数据
$.ajax({
url:"${App_PATH}/emp",
type:"POST",
data: $("#empAddModal form").serialize(),
success:function (result){
// 根据JSR303校验封装的校验结果中的状态码,判断是否合法
if (result.code==100){
//员工保存成功
//1、关闭模态框
$("#empAddModal").modal('hide');
//2、来到最后一页,显示刚才保存的数据
// 发送ajax请求显示最后一页数据即可,总页数+1
to_page(totalPages+1);
}else {
//显示失败信息
//console.log(result);
//有哪个字段的错误信息就显示哪个字段的
if (undefined != result.extend.errorFields.email){
//显示邮箱错误信息 调用校验函数
show_validate_msg("#email_add_input","error",result.extend.errorFields.email)
}
if (undefined != result.extend.errorFields.empName){
//显示员工名字的错误信息
show_validate_msg("#empName_add_input","error",result.extend.errorFields.empName)
}
}
}
});
});
5、 保存数据
利用按钮id绑定单击事件: 要定义一个全局变量保存总页数,调用两步校验:先对提交给服务器的数据进行合法性的校验,再发送ajax请求校验用户名是否存在。
//给保存输入数据的按钮绑定单击事件
$("#emp_save_btn").click(function (){
//1、模态框中填写的表单数据提交给服务器进行保存
//1、先对要提交给服务器的数据进行校验
if (!validate_add_form()){
return false;
};
//2、再判断之前的ajax用户名校验是否成功,利用请求后attr自定义属性添加的状态进行判断
if ($(this).attr("ajax-va")=="error"){
return false;
}
//3、发送ajax请求保存员工 表单中只有一个form表单,序列化得到输入的数据
$.ajax({
url:"${App_PATH}/emp",
type:"POST",
data: $("#empAddModal form").serialize(),
success:function (result){
//员工保存成功
//1、关闭模态框
$("#empAddModal").modal('hide');
//2、来到最后一页,显示刚才保存的数据
// 发送ajax请求显示最后一页数据即可,总页数+1
to_page(totalPages+1);
}
});
});
EmployeeController处理请求: 请求方法要是POST,表示保存数据
@Controller
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@RequestMapping(value = "/emp",method = RequestMethod.POST)
@ResponseBody
public Msg saveEmp(Employee employee){
employeeService.saveEmp(employee);
return Msg.success();
}
}
五、修改
步骤:
1、点击编辑
2、弹出用户修改的模态框(显示用户信息)
3、点击更新,完成用户修改
1-2、点击编辑,弹出用户修改的模态框
1)员工修改的模态框代码: 员工名使用静态控件,不作修改
<!-- 员工修改的模态框 -->
<div class="modal fade" id="empUpdateModal" 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" >员工修改</h4>
</div>
<div class="modal-body">
<%--水平表单--%>
<form class="form-horizontal">
<div class="form-group">
<label for="empName_add_input" class="col-sm-2 control-label">empName</label>
<div class="col-sm-10">
<p class="form-control-static" id="empName_update_static"></p>
<%--美化校验--%>
<span class="help-block"></span>
</div>
</div>
<div class="form-group">
<label for="email_add_input" class="col-sm-2 control-label">email</label>
<div class="col-sm-10">
<input type="text" name="email" class="form-control" id="email_update_input" placeholder="email@chu.com">
<%--美化校验--%>
<span class="help-block"></span>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">gender</label>
<div class="col-sm-10">
<%--多选--%>
<label class="radio-inline">
<input type="radio" name="gender" id="gender1_update_input" value="M" checked="checked"> 男
</label>
<label class="radio-inline">
<input type="radio" name="gender" id="gender2_update_input" value="F"> 女
</label>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">deptName</label>
<div class="col-sm-4">
<%--下拉列表 变化的,部门提交部门id即可--%>
<select class="form-control" name="dId" id="dept_add_select">
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox"> Remember me
</label>
</div>
</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" id="emp_update_btn">更新</button>
</div>
</div>
</div>
</div>
2)给每行数据(解析显示员工数据时)的编辑和删除按钮添加标识(用于后面的事件绑定):
...
// 给编辑按钮添加一个edit_btn标识,给删除按钮添加一个delete_btn标识,用于下面绑定单击事件
var editBtn=$("<button></button>").addClass("btn btn-primary btn-sm edit_btn")
.append($("<span></span>")).addClass("glyphicon glyphicon-pencil").append("编辑");
var delBtn=$("<button></button>").addClass("btn btn-danger btn-sm delete_btn")
.append($("<span></span>")).addClass("glyphicon glyphicon-trash").append("删除");
...
3)普通的绑定方式是绑定不上的,因为按钮创建之前就绑定了click,所以绑定不上,采用下面方式② on
解决方式一:可用在创建按钮的时候绑定
解决方式二:绑定单击事件.live(), 但jQuery新版没有live,使用on进行替代,选文档中,给其后代绑定
编辑按钮绑定后,执行数据清除、①、②以及模态框的弹出
$(document).on("click",".edit_btn",function (){
//① 查出部门信息,并显示部门列表,上面新增时的ajax请求函数
getDepts("#empUpdateModal select")
//② 查出员工信息,显示员工信息,传入编辑按钮的自定义属性(其值为员工id)
getEmp($(this).attr("edit-id"));
//弹出模态框
$("#empUpdateModal").modal({
backdrop: "static"
});
});
① 查询显示部门信息,调用函数getDepts
//查出所有的部门信息并显示在下拉列表中
function getDepts(ele){
//清空对应下拉列表,避免累加
$(ele).empty();
$.ajax({
url:"${App_PATH}/depts",
type:"GET",
success:function (result){
//{"code":100,"msg":"处理成功!","extend":{"depts":[{"deptId":1,"deptName":"开发部"},{"deptId":2,"deptName":"测试部"}]}}
//显示部门信息在下拉列表中
//$("#dept_add_select").append(""),下面遍历后添加
$.each(result.extend.depts,function (){
var optionEle=$("<option></option>").append(this.deptName).attr("value",this.deptId);
//把添加部门信息后的变量添加到id为dept_add_select的下拉列表中
optionEle.appendTo(ele);
});
}
});
}
② 显示员工信息的发出ajax请求函数getEmp: 根据员工id查询获取数据,
编辑按钮的自定义属性edit-id(其值为员工id):
...
// 给编辑按钮添加一个edit_btn标识,给删除按钮添加一个delete_btn标识,用于下面绑定单击事件
var editBtn=$("<button></button>").addClass("btn btn-primary btn-sm edit_btn")
.append($("<span></span>")).addClass("glyphicon glyphicon-pencil").append("编辑");
//为编辑按钮添加一个自定义的属性,来表示当前员工id
editBtn.attr("edit-id",item.empId);
var delBtn=$("<button></button>").addClass("btn btn-danger btn-sm delete_btn")
.append($("<span></span>")).addClass("glyphicon glyphicon-trash").append("删除");
...
发出ajax请求获取对应id的数据:
//获取员工信息的ajax请求
function getEmp(id){
$.ajax({
url:"${App_PATH}/emp/"+id,
type:"GET",
success:function (result){
//console.log(result);
//拿到员工数据,给对应的表单赋值
var empData = result.extend.emp;
$("#empName_update_static").text(empData.empName);
$("#email_update_input").val(empData.email);
$("#empUpdateModal input[name=gender]").val([empData.gender]);
$("#empUpdateModal select").val([empData.dId]);
}
});
}
EmployeeController处理ajxa请求获取员工数据并返回:
@Controller
public class EmployeeController {
@Autowired
EmployeeService employeeService;
//处理ajax请求以获取员工数据
@RequestMapping(value = "/emp/{id}",method = RequestMethod.GET)
@ResponseBody
public Msg getEmp(@PathVariable("id") Integer id){
Employee employee=employeeService.getEmp(id);
return Msg.success().add("emp",employee);
}
......
}
EmployeeService类中使用employeeMapper执行sql语句根据id获取员工数据:
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
//按照员工id查询员工信息
public Employee getEmp(Integer id) {
Employee employee = employeeMapper.selectByPrimaryKey(id);
return employee;
}
......
}
3、点击更新,完成用户修改
1)给模态框的==“更新”按钮绑定单击事件==(验证邮箱合法后发送ajxa请求):
要先把员工的id传递给模态框的更新按钮:
$("#emp_update_btn").attr("edit-id",$(this).attr("edit-id"));
绑定单击事件,验证邮箱合法性,发送ajax请求
ajax请求方式一: POST请求转为PUT请求
//点击更新,更新员工信息
$("#emp_update_btn").click(function (){
//验证邮箱是否合法
//1、校验邮箱信息
var email=$("#email_update_input").val();
var regEmail=/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;
if (!regEmail.test(email)){
//alert("邮箱格式不正确")
// 使用框架对其进行美化,给当前控件的父元素添加需要的类
show_validate_msg("#email_update_input","error","邮箱格式不正确");
return false;
}else {
show_validate_msg("#email_update_input","success","");
};
//2、校验通过之后,发送ajax请求保存更新的员工数据
$.ajax({
url:"${App_PATH}/emp/"+$(this).attr("edit-id"),
type:"POST",
data:$("#empUpdateModal form").serialize()+"&_method=PUT",
success:function (result){
//alert(result.msg);
//1、关闭模态框
$("#empUpdateModal").modal("hide");
//2、回到本页面,使用到全局变量
to_page(currentPage);
}
});
});
上述的ajax请求是将POST请求转为PUT请求来进行的(利用到了web.xml配置文件中的Rest风格的URI):
<!--4、使用Rest风格的URI(原本是发不出delete、put方式的请求)
把页面的post请求转为delete、put的请求-->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
ajax请求方式二: 直接使用PUT请求
//点击更新,更新员工信息
$("#emp_update_btn").click(function (){
//验证邮箱是否合法
//1、校验邮箱信息
var email=$("#email_update_input").val();
var regEmail=/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;
if (!regEmail.test(email)){
//alert("邮箱格式不正确")
// 使用框架对其进行美化,给当前控件的父元素添加需要的类
show_validate_msg("#email_update_input","error","邮箱格式不正确");
return false;
}else {
show_validate_msg("#email_update_input","success","");
};
//2、校验通过之后,发送ajax请求保存更新的员工数据
$.ajax({
url:"${App_PATH}/emp/"+$(this).attr("edit-id"),
type:"PUT",
data:$("#empUpdateModal form").serialize(),
success:function (result){
//alert(result.msg);
//1、关闭模态框
$("#empUpdateModal").modal("hide");
//2、回到本页面,使用到全局变量
to_page(currentPage);
}
});
});
直接使用PUT发送请求,tomcat不会资助的封装PUT请求的参数,需要在web.xml中配置HttpPutFormContentFilter过滤器,这样SpringMVC才能得到前台发送来的请求。
具体解析:
配置上HttpPutFormContentFilter,它的作用是将请求体中的数据解析包装成一个map,request被重新包装,request。getParameter()被重写,就会从自己封装的map中取数据。
<filter>
<filter-name>HttpPutFormContentFilter</filter-name>
<filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HttpPutFormContentFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2)EmployeeController处理ajxa请求:
要是PUT请求,且参数名为empId与表名的一致,不然获取不到。
@RequestMapping(value = "/emp/{empId}",method = RequestMethod.PUT)
@Controller
public class EmployeeController {
@Autowired
EmployeeService employeeService;
//保存更新的员工数据
@RequestMapping(value = "/emp/{empId}",method = RequestMethod.PUT)
@ResponseBody
public Msg updateEmp(Employee employee){
employeeService.updateEmp(employee);
return Msg.success();
}
......
}
六、删除
步骤:
1、单个删除
URI:/emp/{id} 请求方式DELETE
1、单个删除
1)给每个员工数据的删除按钮绑定单击事件:
① 首先要获取此删除按钮对应的员工姓名
② 弹出是否删除的对话框
③ 判断是否确认删除,删除则发送ajax请求根据id(要给删除按钮自定义属性del-id获取要被删除的员工id)进行删除,删除后回到本页。
//给每个员工数据的删除按钮绑定单击事件
$(document).on("click",".delete_btn",function (){
//1、弹出确认删除对话框
// 找到要被删除的员工名,this标识当前点击的删除按钮的祖先tr下的第二个td里的内容
// alert($(this).parents("tr").find("td:eq(1)").text());
var empName=$(this).parents("tr").find("td:eq(1)").text();
//拿到要被删除的员工id,(是删除按钮自定义属性 del-id)
var empId=$(this).attr("del-id")
if (confirm("确认删除【"+empName+"】吗?")){
//确认,则发送ajax请求根据员工id删除
$.ajax({
url:"${App_PATH}/emp/"+empId,
type:"DELETE",
success:function (result){
alert(result.msg);
//回到本页
to_page(currentPage);
}
});
}
});
2)EmployeeController处理ajax请求,根据id删除员工数据:
public class EmployeeController {
@Autowired
EmployeeService employeeService;
// 处理ajax请求,按照id去删除员工数据
@RequestMapping(value = "/emp/{id}",method = RequestMethod.DELETE)
@ResponseBody
public Msg deleteEmpById(@PathVariable("id") Integer id){
employeeService.deleteEmp(id);
return Msg.success();
}
......
}
2、批量删除
1)给表头添加复选框 id=“check_all”
<%--表头放在thead中--%>
<thead>
<tr>
<th>
<input type="checkbox" id="check_all"/>
</th>
<th>#</th>
<th>empName</th>
<th>gender</th>
<th>email</th>
<th>deptName</th>
<th>操作</th>
</tr>
</thead>
2)每行数据添加复选框class=‘check_item’,后添加到tr标签中
var checkBoxTd = $("<td><input type='checkbox' class='check_item'/></td>")
var xxx
...
...
...
$("<tr></tr>").append(checkBoxTd)
.append(xxx)
...
3)完成全选/全不选功能
① 给表头的复选框绑定单击事件,使员工的复选框与表头一致:
$("#check_all").click(function (){
//当前复选框的是否选中的状态,attr获取checked是undefined;
// dom原生的属性使用prop来修改和获取,attr获取自定义属性的值
//alert($(this).prop("checked")); 选中是true,不选中是flase
// 设置每个员工的复选框的值与表头的一致
$(".check_item").prop("checked",$(this).prop("checked"));
});
② 当全选中员工复选框时,表头的复选框也要保存一致:
绑定单击事件,因为是后面创建的,需要使用on进行绑定
//当每页的员工都被选中,表头的复选框也要被选中
$(document).on("click",".check_item",function(){
//判断当前选中的元素是否是此页的全部员工个数
var flag=$(".check_item:checked").length == $(".check_item").length;
$("#check_all").prop("checked",flag);
});
4)给批量删除的按钮绑定单击事件:
注:
① 找到每个被选中的复选框,this代表当前遍历的被选中的复选框
② 在遍历过程中组装员工名,用于弹出提示删除窗口;组装员工id用于根据id批量删除
③ 确认删除则发送ajax请求
//点击全部删除,就批量删除,绑定单击事件
$("#emp_delete_all_btn").click(function (){
var empNames="";
var del_idstr="";
//找到每个被选中的复选框,this代表当前遍历的被选中的复选框
$.each($(".check_item:checked"),function(){
// 当前复选框的祖先tr的第三个td里的内容就是当前行的员工姓名
empNames += $(this).parents("tr").find("td:eq(2)").text()+",";
//组装员工id字符串
del_idstr+=$(this).parents("tr").find("td:eq(1)").text()+"-";
});
//取出 empNames多余的 , id字符串多余的 -
empNames=empNames.substring(0,empNames.length-1);
del_idstr=del_idstr.substring(0,del_idstr.length-1);
if(confirm("确认删除【"+empNames+"】吗?")){
// 发送ajax请求删除
$.ajax({
url:"${App_PATH}/emp/"+del_idstr,
type:"DELETE",
success:function (result){
alert(result.msg);
//回到本页
to_page(currentPage);
}
});
}
});
5)EmployeeController中修改上面的单个删除,使其也可以批量删除:
@Controller
public class EmployeeController {
@Autowired
EmployeeService employeeService;
// 处理ajax请求,按照单个/批量删除员工数据
/* 批量删除:1-2-3
单个删除:1
* */
@RequestMapping(value = "/emp/{ids}",method = RequestMethod.DELETE)
@ResponseBody
public Msg deleteEmpById(@PathVariable("ids") String ids){
//判断是否是批量删除
if(ids.contains("-")){
//批量删除
List<Integer> del_ids = new ArrayList<>();
String[] str_ids = ids.split("-");
//组装id的集合,遍历转换成Integer类型后添加到list集合中
for (String string:str_ids){
del_ids.add(Integer.parseInt(string));
}
employeeService.deleteBatch(del_ids);
}else {
//单个删除
Integer id=Integer.parseInt(ids);
employeeService.deleteEmp(id);
}
return Msg.success();
}
......
}
Service层的EmployeeService类中的单个删除、批量删除的方法:
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
......
//员工单个删除
public void deleteEmp(Integer id) {
employeeMapper.deleteByPrimaryKey(id);
}
//员工批量删除
public void deleteBatch(List<Integer> ids) {
EmployeeExample example = new EmployeeExample();
EmployeeExample.Criteria criteria = example.createCriteria();
//delete from xxx where emp_id in(1,2,3)
criteria.andEmpIdIn(ids);
employeeMapper.deleteByExample(example);
}
}