SSM整合实例——快速开发CRUD
文章目录
前言
参考视频:链接: 尚硅谷SSM尚硅谷SSM实战演练丨ssm整合快速开发CRUD。
笔者编写此笔记目的主要为之后复习SSM整合框架时使用,并且开发其他SSM项目时也可以此为模板可供参考。同时也供大家学习,有不同见解的地方,欢迎交流!
本项目开发环境为IDEA,新建Maven工程步骤欢迎各位移步此文章:链接: idea创建maven项目.
1、创建maven工程
引入jar包:
ssm框架相关jar包:spring-webmVC、Spring-jdbc、spring-aspects、mybatis、mybatis-spring
数据库连接驱动相关jar包:c3p0、mysql
其他:jstl、servlet-api、junit
引入bootstrap前端框架
在webapp包下创建static包,在static包中导入下载好的bootstrap文件,并创建index.jsp界面,引入bootstrap的css样式文件和js文件。
在static包下创建js包,在js包中导入下载好的jquery文件,并在index.jsp页面中引入js包中jquery文件
编写ssm整合的关键配置文件
在web.xml文件中编写启动Spring容器的代码,加载Spring的配置文件为applicationContext.xml,在resources中创建applicationContext.xml文件(这里主要配置和业务逻辑有关的)。
<!-- 启动spring容器-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
在web.xml中配置springmvc的前端控制器,拦截所有请求。其中servlet-name的值为dispatcherServlet。在WEB-INF文件中创建dispatcherServlet-servlet.xml文件。
<!-- SpringMVC的前端控制器,拦截所有请求-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
在web.xml中配置字符编码过滤器。可以在idea搜索栏中搜索CharacterEncodingFilter类,以此设置类内成员属性encoding,forceRequestEncoding,forceResponseEncoding的值分别为utf-8,true,true
<!-- 字符编码过滤器,一定要放在所有过滤器之前-->
<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-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在web.xml中配置使用Rest风格的URI。
<!-- 使用Rest风格的URI,将页面普通的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>
2、配置SpringMVC
在dispatcherServlet-servlet文件中配置SpringMVC。SpringMVC的配置文件,包含网站跳转逻辑的控制、配置。
扫描控制器组件
<context:component-scan base-package="com.shenshang" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
注意:需要用到context:component-scan标签扫描包,在xml配置了这个标签后,spring可以自动去扫描base-package下面或者子包下面的java文件,如果扫描到有@Component @Controller@Service等这些注解的类,则把这些类注册为bean,context:component-scan有一个use-default-filters属性,该属性默认为true,这就意味着会扫描指定包下的全部的标有@Component的类,并注册成bean.也就是@Component的子注解@Service,@Reposity等。
如果只想扫描指定包下面的Controller,可以用子标签context:include-filter,如下代码块所示:
<context:component-scan base-package="tv.huan.weisp.web .controller">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
这样就会只扫描base-package指定下的有@Controller下的java类,并注册成bean
配置视图解析器
配置过扫描控制器后,继续配置视图解析器,方便页面返回。bean标签中class属性值为InternalResourceViewResolver的全类名。在bean标签中可以使用property标签,此标签中的name属性值为suffix时,可以通过value属性值设置视图后缀,此标签中的name属性值为prefix时,可以通过value属性值设置视图前缀。
<!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 视图后缀-->
<property name="suffix" value=".jsp"></property>
<!-- 试图前缀-->
<property name="prefix" value="/WEB-INF/views"></property>
</bean>
<mvc:default-servlet-handler></mvc:default-servlet-handler>
<!-- 告知Spring容器,启用注解驱动,支持@RequestMapping注解,这样就可以使用@RequestMapping来配置处理器-->
<mvc:annotation-driven></mvc:annotation-driven>
注意:配置视图解析器需要用到bean标签下的property标签用来设置视图前缀和视图后缀,视图后缀——controller中的方法返回的url字符串会添加该后缀,视图前缀——controller中的方法返回的url字符串会添加该前缀。
注意:mvc:default-servlet-hander标签的作用:在 WEB 容器启动的时候会在上下文中定义一个 DefaultServletHttpRequestHandler,它会对DispatcherServlet的请求进行处理,如果该请求已经作了映射,那么会接着交给后台对应的处理程序,如果没有作映射,就交给 WEB 应用服务器默认的 Servlet 处理,从而找到对应的静态资源,只有在找不到资源时才会报错。
注意:mvc:annotation-driven是告知Spring容器,启用注解驱动,支持@RequestMapping注解,这样就可以使用@RequestMapping来配置处理器。
3、配置Spring
在applicationContext.xml中配置Spring,这里主要配置和业务逻辑有关的。
配置数据源
使用bean标签,class属性值是c3p0叫做comboPooledDataSource的全类名。在此bean标签中需要使用到property标签设置数据源的基本信息(包括jdbcUrl,driverClass,user,password),在resources包下创建后缀为.properties的配置文件,在该文件中编写数据源的基本信息。同时还需要在xml文件中引入后缀为properties的文件,可以使用context:property-placeholder标签,其location属性值可以指定properties的文件路径。
<!-- 设置扫描包-->
<context:component-scan base-package="com.shenshang.crud" use-default-filters="false">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--配置数据源-->
<context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder>
<bean id="pooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/ssm_crud
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.user=root
jdbc.password=szx...2943
注意:在配置spring过程中,需要设置扫描包,需要用到context:conponent-scan标签,但是spring除了不扫描控制器,其他都要扫描,需要用到context:exclude-filter标签。Use-dafault-filters=”false”的情况下:context:exclude-filter指定的不扫描,context:include-filter指定的扫描
配置和MyBatis的整合
使用bean标签,类使用的是SqlSessionFactoryBean类,需要用到property标签指定MyBatis全局配置文件的位置(property标签中的name属性值设置为configLocation,value属性值设置为mybatis的配置文件路径,name属性值设置为dateSource,value属性值设置为刚刚设置的数据源名称),指定MyBatis的mapper文件的位置(在resources包中创建mapper包,同理以上)。
<!-- 配置和MyBatis的整合-->
<bean id="SqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="dataSource" ref="pooledDataSource"></property>
<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
</bean>
配置扫描器
使用bean标签,类使用的是MapperScannerConfigurer,使用property标签扫描所有dao接口的实现,加入到IOC容器中(property标签中name属性值为basePackage,value属性值为dao包的名称)
<!-- 配置扫描器-->
<bean id="scannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.shenshang.crud.dao"></property>
</bean>
配置事务管理器
使用bean标签,类使用的是DataSourceTransactionManager的全类名,使用property标签控制住数据源(property标签中name属性值为dateSource,ref属性值为数据源名称)。
<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="pooledDataSource"></property>
</bean>
开启基于注解的事务
<!-- 开启基于注解的事务-->
<aop:config>
<aop:pointcut id="txPoint" expression="execution(* com.shenshang.crud.service..*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"></aop:advisor>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 所有方法都是事务方法-->
<tx:method name="*"/>
<!-- 以get开头的所有方法-->
<tx:method name="get*" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
</beans>
开启基于注解的事务,使用xml配置形式的事务(必要主要的都是使用配置式),需要使用到aop:config标签,在该标签中使用aop:pointcut标签的expression属性编写切入点表达式,使用aop:advisor标签配置事务增强,aop:advisor标签的属性advice-ref表示事务如何切入,值和tx:advice标签的id属性值相对应,aop:advisor标签的属性poincut-ref表示切入哪些方法,其值和aop:pointcut标签的id值相对应。tx:advice标签中可以使用tx:attributes标签,用来配置方法,其中可以使用tx:method标签的name属性表示哪些方法是事务方法,可以讲read-only属性值设置为"true"表示此方法返回值为查询到的值,为只读形式。
4、配置MyBatis
复制相应配置模板
在官方文档中的Getting Started中找到相应的配置模板,复制以下内容到mybatis-config.xml中。
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
注意:还需要将原有的bean标签都删除掉,编写configuration标签
全局配置文件的编写
在官方文档中的Configuration XML中的setting中找到mapUnderscoreToCamelCase,新建settings标签(这个是mybatis中最为复杂的配置,这个标签中的设置能够深刻的影响到底层的运行,但是大部分情况下使用默认的配置就可以运行,不需要去配置这个属性,大多数情况下修改一些常用的规则就可以了,比如自动映射,驼峰命名映射,级联规则,是否开启缓存,执行器类型等等),将此名称作为setting标签中name的属性值,同时其value属性值设置为true。使用typeAliases标签为Java类型起别名,其中使用标签package,其属性值name设置为com.shenshang.crud.bean,使得自动扫描com.shenshang.crud.bean下的类型,使得在后续配置文件Category.xml中使用resultType的时候,可以直接使用Category,而不必写全com.shenshang.crud.bean.Category了。
<configuration>
<!-- 驼峰命名规则-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!-- 起别名-->
<typeAliases>
<package name="com.shenshang.crud.bean"/>
</typeAliases>
</configuration>
使用MyBatis逆向工程生成对应的bean和mapper
MyBatis Generator:简称MBG,是一个专门为MyBatis框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件,接口,以及bean类。支持基本的增删改查,以及QBC风格的条件查询。但是表连接、存储过程等这些复杂sql的定义需要我们手工编写。在maven官网搜索mybatis generator,将依赖复制到pom.xml中。
<!-- MyBatis逆向工程-->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.4.0</version>
</dependency>
在ssm_crud中创建员工表tbl_emp和部门表tbl_dept,并且在两张表之间创建外键联系,参照mybatis官网的Quick Start Guide怎样使用mybatis的逆向工程(也可以直接参考案例[在官网的XML Configuration Reference中,复制代码粘贴到新创建的主工程mbg.xml文件中])。开始修改mbg.xml文件,将默认信息修改成我们需要的信息,在mbg.xml中把classpathEntry标签删掉,配置数据库连接,指定javaBean生成的位置,指定sql映射文件生成的位置,指定dao接口生成的位置,table指定每个表的生成策略(table标签的tableName属性对应表名,domainObjectName对应自动生成的类名)。
<?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">
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/ssm_crud"
userId="root"
password="szx...2943">
</jdbcConnection>
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!--指定javaBean生成的位置-->
<javaModelGenerator targetPackage="com.shenshang.crud.bean" targetProject="path">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- 指定映射文件生成的位置-->
<sqlMapGenerator targetPackage="mapper" targetProject="path">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- 指定dao接口生成的位置-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.shenshang.crud.dao" targetProject="path">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- table指定每个表的生成策略-->
<table tableName="tbl_emp" domainObjectName="Employee"></table>
<table tableName="tbl_dept" domainObjectName="Department"></table>
</context>
</generatorConfiguration>
注 意 : 此 段 代 码 中 t a r g e t P a c k a g e 属 性 值 用 p a t h 代 替 了 , 要 注 意 换 成 相 应 的 文 件 路 径 \textcolor{red}{注意:此段代码中targetPackage属性值用path代替了,要注意换成相应的文件路径} 注意:此段代码中targetPackage属性值用path代替了,要注意换成相应的文件路径
在MyBatis官网找到Running MyBatis Generator中的Java Program,复制代码到新创建的com.shenshang.crud.test包下的MBGTest.java文件中(代码中需要修改的部分是File(“”),File文件中的值需要改成mbg.xml)。
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("generatorConfig.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);
注意:此时生成的bean类都是带有很多注释的,去掉注释的方法:在MyBatis官网中找到XML Configuration中的commentGenerator标签,在底部有示例,复制实例代码并将property标签的属性值name改为suppressAllComments粘贴到mbg.xml的context标签下(注意把之前生成过的bean文件删除)
<commentGenerator>
<property name="suppressAllComments" value="true" />
</commentGenerator>
注意:属性targetPackage的值为包名,且包名可以是不存在的,他会自动创建,属性targetProject的值为项目目录名,且必须是已存在的,否则会报错,targetProject的值的起始目录为当前子模块目录,在maven多模块项目中可以利用相对目录切换到同级子模块下,如…/shopping_bean/src/main/java
5、修改并测试mapper文件
查询时都带上部门信息
需要导入spring-test的单元测试依赖到pom.xml文件中:
<!-- Spring的单元测试依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.15</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.15</version>
<scope>compile</scope>
</dependency>
由于表tbl_emp和表tbl_dept之间由外键d_id相关联,希望查询员工信息时都带上部门信息,所以在EmployeeMapper.xml中编写新的查询语句。在这之前,需要在EmployeeMapper.java接口文件中增添两条查询语句,在Employee.java实体类文件中增添类属性deptment并生成getter和setter方法。
/*增添在EmployeeMapper.java接口文件中的查询语句*/
// 查询带有部门信息的员工信息
List<Employee> selectByExampleWithDept(EmployeeExample example);
Employee selectByPrimaryKeyWithDept(Integer empId);
/*增添在Employee.java实体类文件中的语句*/
private Department department;
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
在EmployeeMapper.xml文件中,编写新的select标签语句和sql标签语句,select标签语句复制过来已生成的查询不带部门信息的员工信息的select标签语句代码,仿照已生成的sql标签语句和以生成的select标签语句编写新的语句,编写新的sql标签语句中要带上dept_id和dept_name信息,在编写select标签语句时需要用到联表查询(注意left join on语句的使用:左连接:把左边的全部查出来,右边有的则匹配,没有则为null)。在返回值为集合形式的方法的select标签语句中要使用属性resultMap,另外创建resultMap标签,标签内部需要使用association标签(association的属性。javaType:完整Java类名或别名。property:映射数据库列的实体对象的属性。id。设置该项可以有效地提升MyBatis的性能。result。property:映射数据库列的实体类对象的属性。 column:数据库列名或别名),association标签中需要使用id标签对应deptId类属性,使用result标签对应查询返回属性deptName。
<!-- 查询带有部门信息的员工信息的resultMap-->
<resultMap id="WithDeptResultMap" type="com.shenshang.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.shenshang.crud.bean.Department">
<id column="dept_id" property="deptId"></id>
<result column="dept_name" property="deptName"></result>
</association>
</resultMap>
<!-- 查询带有部门信息的员工信息的查询列表项-->
<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" parameterType="com.shenshang.crud.bean.EmployeeExample" resultMap="WithDeptResultMap">
select
<if test="distinct">
distinct
</if>
<include refid="Base_Column_List" />
from tbl_emp e left join tbl_dept d on e.'d_id' = d.'dept_id'
<if test="_parameter != null">
<include refid="WithDept_Column_List" />
</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 tbl_emp e left join tbl_dept d on e.'d_id' = d.'dept_id'
where emp_id = #{empId,jdbcType=INTEGER}
</select>
测试mapper
在maven坐标官网查询出Spring test依赖并导入pom.xml文件中,在com.shenshang.crud.test包中创建MapperTest.java测试类,在类名前使用@ContextConfiguration注解指定Spring配置文件的位置。
关于ContextConfiguration注解:@ContextConfiguration(locations = {“classpath※:/※.xml”})。@ContextConfiguration括号里的locations = {“classpath:/※.xml”}就表示将class路径里的所有.xml文件都包括进来,那么刚刚创建的那么XML文件就会包括进来,那么里面自动扫描的bean就都可以拿到了,此时就可以在测试类中使用@Autowired注解来获取之前自动扫描包下的所有bean。
classpath和classpath*区别:
- classpath:只会到你的class路径中查找找文件。
- classpath*:不仅包含class路径,还包括jar文件中(class路径)进行查找。
在测试类中声明变量DeptmentMapper,并在此变量前加上@Autowired注解,然后可以通过此变量调用增删改查的方法,然后进行测试。(可以测试:insertSelective)
为了进行批量插入的测试,可以在application.xml中配置一个可以执行批量执行的sqlSession,在application.xml中创建bean标签,bean标签中的属性值class使用SqlSessionTemplate的全类名,id指定为SqlSessionTemplate,在bean标签内部使用constructor-arg标签,constructor-arg标签属性值name为sqlSessionFactory,ref使用之前指定的"sqlSessionFactory"。继续创建constructor-arg标签,constructor-arg标签属性值name为executorType,value值为BATCH。在测试类中创建返回值为SqlSession的变量,并在变量前添加@Autowired注解,然后进行测试。(使用UUID生成无实际意义的id号:UUID.randomUUID().toString().substring(0,5);)。
<!-- 批量添加-->
<bean class="org.mybatis.spring.SqlSessionTemplate" id="sqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"></constructor-arg>
<constructor-arg name="executorType" value="BATCH"></constructor-arg>
</bean>
@Autowired 对方法或构造函数进行标注,如果构造函数有两个入参,分别是 bean1 和bean2@Autowired 将分别寻找和它们类型匹配的 Bean,将它们作为 CountryService (Bean1 bean1 ,Bean2 bean2) 的入参来创建CountryService Bean。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath:applicationContext.xml"})
public class MapperTest {
@Autowired
DepartmentMapper departmentMapper;
@Autowired
EmployeeMapper employeeMapper;
@Autowired
SqlSession sqlSession;
@Test
public void CRUDTest(){
// departmentMapper.insertSelective(new Department(null,"computer"));
// employeeMapper.insertSelective(new Employee(null,"Jerry","女","[email protected]",1));
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
for (int i = 0; i < 10; i++) {
String uid = UUID.randomUUID().toString().substring(0, 5);
employeeMapper.insertSelective(new Employee(null,uid+"Mi","男",uid+"@shenshang"+i,1));
}
System.out.println("插入成功!");
System.out.println(departmentMapper);
// ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
// DepartmentMapper bean = ioc.getBean(DepartmentMapper.class);
// System.out.println(bean);
}
}
6、查询功能实现
- 访问index.jsp页面
- index.jsp页面发送出查询员工列表请求
- EmployeeeController来接受请求,查出员工数据
- 来到list.jsp页面进行展示
完成分页后台代码
在index.jsp中使用jsp:forward标签,标签内部的page属性值为"/emps",并在com.shenshang.crud.controller中创建EmployeeController.java文件。在EmployeeController.java文件中编写含分页插件的控制层代码(getEmps方法是返回页面数据展示,getPageInfoWithJson方法是以Json的数据形式返回页面):
@Controller
public class EmployeeController {
@Autowired
EmployeeService employeeService;
// @ResponseBody
// @RequestMapping("/emps")
public Msg getPageInfoWithJson(@RequestParam(value = "pn",defaultValue = "1") Integer pn){
//在查询之前只需要调用,传入页码,以及每页的大小
PageHelper.startPage(pn,5);
//startPage后面紧跟的这个查询就是一个分页查询
List<Employee> emps = employeeService.getAll();
//使用pageInfo包装查询后的结果,只需要将pageInfo交给页面就行
//封装了详细的分页信息,包括我们查询到的数据,传入连续显示的页数
PageInfo page = new PageInfo(emps,5);
return Msg.success().add("pageInfo",page);
}
@RequestMapping("/emps")
public String getEmps(@RequestParam(value = "pn",defaultValue = "1") Integer pn, Model model){
PageHelper.startPage(pn,5);
List<Employee> emps = employeeService.getAll();
PageInfo page = new PageInfo(emps,5);
model.addAttribute("pageInfo",page);
return "list";
}
}
注意:pn为pageNumber的缩写,表示的是当前页,但jsp页面并没有传参过来pn的值,此时可以使用defaultValue值赋给pn一个默认值,在使用PageHelper插件之前需要在maven官网搜索pageHelper,在pom.xml文件中引入pageHelper相关依赖。并且在mybatis中引入全局配置:
<!-- pageHelper分页插件-->
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
在com.shenshang.crud.service中创建EmployeeService.java文件,编写业务逻辑层代码:
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
// 查询所有员工
public List<Employee> getAll(){
return employeeMapper.selectByExampleWithDept(null);
}
}
注意:jsp:forward动作将请求转发到另一个jsp、Servlet或静态资源文件。请求被转向到的资源必须与发生请求的JSP位于相同的上下文环境,即同一个Web应用程序或同一个网站。
<%-- 分页--%>
<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}?pn=1">首页</a></li>
<c:if test="${pageInfo.pageNum!=1}">
<li>
<a href="${APP_PATH}?pn=${pageInfo.pageNum-1}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
</c:if>
<c:if test="${pageInfo.pageNum==1}">
<li class="disabled">
<a href="#" 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="${APP_PATH}?pn=${page_num}">${page_num}</a></li>
</c:if>
<c:if test="${page_num!=pageInfo.pageNum}">
<li><a href="${APP_PATH}?pn=${page_num}">${page_num}</a></li>
</c:if>
</c:forEach>
<c:if test="${pageInfo.pageNum==pageInfo.pages}">
<li class="disabled">
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</c:if>
<c:if test="${pageInfo.pageNum!=pageInfo.pages}">
<li>
<a href="${APP_PATH}?pn=${pageInfo.pageNum+1}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</c:if>
<li><a href="${APP_PATH}?pn=${pageInfo.pages}">末页</a></li>
</ul>
</nav>
</div>
</div>
查询-ajax:返回分页的json数据
- index.jsp页面直接发送ajax请求进行员工分页数据的查询。
- 服务器将查出的数据,以json字符串的形式返回给浏览器
- 浏览器收到json字符串,可以使用js对json进行解析,使用js通过dom增删改改变页面。
- 返回json。实现客户端的无关性。
返回json形式的数据前需要导入jackson依赖到pom.xml文件中:
<!-- 支持返回json字符串-->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.1</version>
</dependency>
在EmployeeController.java中创建新的方法返回值为PageInfo类型(使用@ResponseBody注解),为了@ResponseBody注解能正常工作需要导入jackson包的依赖。在bean包下创建Msg.java通用返回类。
注意:@ResponseBody作用:该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。使用时机:返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用。
//通用的返回类
public class Msg {
// 状态码 100-成功 200-失败
public int code;
// 提示信息
public String msg;
// 用户要返回浏览器的数据
public Map<String,Object> extend = new HashMap<String, Object>();
// 返回处理成功的Msg
public static Msg success(){
Msg result = new Msg(100,"处理成功");
return result;
}
// 返回处理失败的Msg
public static Msg fail(){
Msg result = new Msg(200,"处理失败");
return result;
}
// 添加信息到map集合中
public Msg add(String key , Object value){
this.getExtend().put(key, value);
return this;
}
public Msg(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Map<String, Object> getExtend() {
return extend;
}
public void setExtend(Map<String, Object> extend) {
this.extend = extend;
}
}
编写完以上的通用返回来后,将EmployController类中的getEmps方法注释掉,使用getPageInfoWithJson方法,此时访问/emps页面得到的将是json数据返回值。此时思路为:index页面使用js发送ajax请求,js要到分页的员工数据,js解析要到的员工数据,在页面进行显示。
创建新的index.jsp文件,复制list.jsp文件中的代码到新创建的index.jsp文件中,将列表信息和分页信息均革新为使用ajax技术进行查询显示。编写相关js代码获取json数据解析员工列表数据、解析分页条文字信息,解析分页条信息并均显示在jsp页面上。【先把列表和分页条以及分页信息显示出来,再实现细节部分js代码】:
<script type="text/javascript">
$(function () {
to_page(1);
});
function to_page(pn) {
// 页面加载完DOM以后发送ajax请求
$.ajax({
url:"${APP_PATH}/emps",
data:"pn="+pn,
type:"get",
success:function (result) {
// console.log(result);
// 解析并展示员工数据
build_emps_table(result);
// 解析展示分页文字信息
build_page_info(result);
// 解析展示分页条信息
build_nav_info(result);
}
});
}
// 解析展示员工数据
function build_emps_table(result) {
// 清空table表格
$("#emps_table tbody").empty();
let emps = result.extend.pageInfo.list;
$.each(emps,function (index,item) {
let empIdTd = $("<td></td>").append(item.empId);
let empNameTd = $("<td></td>").append(item.empName);
let gendTd = $("<td></td>").append(item.gender);
let emailTd = $("<td></td>").append(item.email);
let deptNameTd = $("<td></td>").append(item.department.deptName);
let editBtn = $("<button></button>").addClass("btn btn-success btn-sm glyphicon glyphicon-pencil").append("编辑");
let deltBtn = $("<button></button>").addClass("btn btn-warning btn-sm glyphicon glyphicon-trash").append("删除");
let btnTd = $("<td></td>").append(editBtn).append(" ").append(deltBtn);
$("<tr></tr>")
.append(empIdTd)
.append(empNameTd)
.append(gendTd)
.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_nav_info(result) {
$("#page_nav_area").empty();
let firstPage = $("<li></li>").append($("<a></a>").append("首页").attr("href","#"));
let previousPage = $("<li></li>").append($("<a></a>").append("«").attr("href","#"));
// 如果当前页是第一页
if (result.extend.pageInfo.pageNum == 1){
firstPage.addClass("disabled");
previousPage.addClass("disabled");
}
else {
firstPage.click(function () {
to_page(1);
})
previousPage.click(function () {
to_page(result.extend.pageInfo.pageNum - 1);
})
}
// 使用bootstrap内置类
let ul = $("<ul></ul>").addClass("pagination");
let nextPage = $("<li></li>").append($("<a></a>").append("»").attr("href","#"));
let lastPage = $("<li></li>").append($("<a></a>").append("末页").attr("href","#"));
// 如果当前页是末页
if (result.extend.pageInfo.pageNum == result.extend.pageInfo.pages){
nextPage.addClass("disabled");
lastPage.addClass("disabled");
}
else{
nextPage.click(function () {
to_page(result.extend.pageInfo.pageNum + 1);
})
lastPage.click(function () {
to_page(result.extend.pageInfo.pages);
})
}
// 向ul列表中加入首页和上一页
ul.append(firstPage).append(previousPage);
$.each(result.extend.pageInfo.navigatepageNums,function (index,item) {
let numLi = $("<li></li>").append($("<a></a>").append(item).attr("href","#"));
// 设置当前页分页条高亮显示
if (result.extend.pageInfo.pageNum == item){
numLi.addClass("active");
}
// 给分页条每个分页添加点击事件
numLi.click(function () {
to_page(item);
})
ul.append(numLi);
})
// 向ul列表中加入末页和下一页
ul.append(nextPage).append(lastPage);
let navEle = $("<nav></nav>").append(ul);
navEle.appendTo("#page_nav_area");
}
</script>
注意:$(function(){})这个函数在DOM加载完毕之后执行。DOM就是一个html页面的标签树。DOM加载完即是指页面的所有html标签(包括)图片都加载完了,即浏览器已经响应完了,加载完了,全部展现出来了。
7、新增功能实现
创建员工新增的模态框
- 在index.jsp页面点击“新增”
- 弹出新增对话框
- 去数据库查询部门列表,显示在对话框中
- 对用户输入的表单数据进行校验
- 用户输入数据完成保存
利用bootstrap样式库中的js插件编写新增的模态框,给index.jsp页面中的新增按钮绑定单击事件,使得单击新增按钮打开模态框(需要使用js代码,具体做法可参照bootstrap样式库的js插件中模态框的用法);利用bootstrap样式库编写模态框中的表单和提交按钮。
<%-- 按钮--%>
<div class="row">
<div class="col-md-4 col-md-offset-8">
<button class="btn btn-primary" id="emp_add_modal_btn">增加</button>
<button class="btn btn-danger">删除</button>
</div>
</div>
<%-- 模态框--%>
<!-- Modal -->
<div class="modal fade" id="emps_add_input" 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 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 class="col-sm-2 control-label">email:</label>
<div class="col-sm-10">
<input type="email" name="email" class="form-control" id="email_add_input" placeholder="email">
</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" checked="checked" value="M"> 男
</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-10">
<select class="form-control">
</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>
<script>
// 点击新增按钮弹出模态框
$("#emp_add_modal_btn").click(function () {
$("#emps_add_input").modal({
backdrop:"static"
})
});
</script>
Ajax显示下拉框的部门信息
弹出模态框前应该发送ajax请求,查出部门信息,显示在下拉列表中。在com.shenshang.crud.controller包中创建DepartmentController.java文件,在service包中创建DepartmentService业务逻辑类,在业务逻辑类中创建DepartmentMapper实体类属性,调用dao层函数,查询到部门信息的list列表,在模态框中的下拉列表中显示出来。
编写的DepartmentController控制类代码:
@Controller
public class DepartmentController {
@Autowired
private DepartmentService departmentService;
@ResponseBody
@RequestMapping("/depts")
public Msg getDeptWithJson(){
List<Department> list = departmentService.getDepts();
return Msg.success().add("depts",list);
}
}
编写的DepartmentService业务类代码:
@Service
public class DepartmentService {
@Autowired
private DepartmentMapper departmentMapper;
public List<Department> getDepts() {
List<Department> list = departmentMapper.selectByExample(null);
return list;
}
}
jsp页面利用ajax获取数据库数据,并和服务器进行交互:
// 点击新增按钮弹出模态框
$("#emp_add_modal_btn").click(function () {
// 发送ajax请求
$.ajax({
url: "${APP_PATH}/depts",
type: "GET",
success:function (result) {
// console.log(result);
// 显示部门信息在下拉列表中
// 每次添加数据库内容想下拉列表时都情况下拉列表中的内容
$("#dept_select_input").empty();
$.each(result.extend.depts,function (index,item) {
let optionEle = $("<option></option>").append(item.deptName).attr("value",item.deptId);
optionEle.appendTo("#dept_select_input");
})
}
})
// 弹出模态框
$("#emps_add_input").modal({
backdrop:"static"
})
});
保存按钮实现
URI:
- /emp/{id} GET 查询员工
- /emp POST 保存员工
- /emp/{id} PUT 修改员工
- /emp/{id} DELETE 删除员工
在EmployeeController控制类和EmployeeService业务类中编写saveEmp方法,在方法体内部使用service实体类调用EmployeeService类中的saveEmp方法,返回Msg实体类。
EmployeeController控制类中新增方法:
@ResponseBody
@RequestMapping(value = "/emp",method = RequestMethod.POST)
public Msg saveEmp(Employee employee){
employeeService.saveEmp(employee);
return Msg.success();
}
EmployeeService业务类中新增方法:
// 保存新增员工数据
public void saveEmp(Employee employee) {
employeeMapper.insertSelective(employee);
}
在index页面编写js代码,利用ajax在点击保存按钮后,将数据传输到数据库中保存,并退出模态框,以及直接显示到列表最后一页:
// 点击保存按钮保存新增员工数据
$("#save_emp_btn").click(function () {
// 模态框中填写的员工数据提交给服务器保存
// 发送ajax请求保存员工
$.ajax({
url: "${APP_PATH}/emp",
type: "POST",
data: $("#emps_add_input form").serialize(),
success: function () {
$("#emps_add_input").modal('hide');
to_page(pagesTotal);
}
})
})
注意:jquery中的serialize()方法:序列表表格内容为字符串,用于 Ajax 请求。
jquery前端校验实现
创建新的函数validate_add_form(),在点击保存按钮发送ajax请求之前,调用validate_add_form()函数对用户输入的表单数据进行校验,在函数体内部使用正则表达式对数据输入合法性进行判断:
// 校验用户输入的表单数据
function validate_add_form(){
let empName = $("#empName_add_input").val();
let regName = /(^[a-zA-Z0-9_-]{6,16}$)|(^[\u2E80-\u9FFF]{2,5})/;
if (!regName.test(empName)){
alert("用户名为6-16位英文(包含字符)或2-5个汉字");
return false;
}
let email = $("#email_add_input").val();
let regEmail = /^([a-z0-9\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;
if (!regEmail.test(email)){
alert("邮箱输入格式有误!");
return false;
}
return true;
}
使用bootstrap美化校验提示信息
可以利用bootstrap样式库中的校验样式对表单数据的校验进行美化,创建表单校验函数show_validate_msg(),在函数体内部首先使用removeClass()方法使得指定标签样式清空,然后对传入的参数进行判断,当校验信息正确时设置标签相应的样式,当校验信息有误时设置标签相应样式给出提示信息,js代码如下:
// 校验用户输入的表单数据
function validate_add_form(){
let empName = $("#empName_add_input").val();
let regName = /(^[a-zA-Z0-9_-]{6,16}$)|(^[\u2E80-\u9FFF]{2,5})/;
if (!regName.test(empName)){
// alert("用户名为6-16位英文(包含字符)或2-5个汉字");
show_validate_msg("#empName_add_input","error","用户名为6-16位英文(包含字符)或2-5个汉字");
return false;
}
show_validate_msg("#empName_add_input","success","格式正确");
let email = $("#email_add_input").val();
let regEmail = /^([a-z0-9\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;
if (!regEmail.test(email)){
// alert("邮箱输入格式有误!");
show_validate_msg("#email_add_input","error","邮箱输入格式有误!");
return false;
}
show_validate_msg("#email_add_input","success","格式正确");
return true;
}
// 校验表单数据函数
function show_validate_msg(ele,status,msg){
$(ele).parent().removeClass("has-success has-error");
$(ele).next("span").text("");
if (status == "success"){
$(ele).parent().addClass("has-success");
$(ele).next("span").text(msg);
}else if (status == "error"){
$(ele).parent().addClass("has-error");
$(ele).next("span").text(msg);
}
}
ajax检验用户名是否重复
如果用户名重复却依旧可以添加此用户进数据库,将会对依照用户名查询用户的功能造成影响,并且也不符合实际开发和上线,因此在新增用户时要检验表单用户名是否重复。为员工输入框增加绑定change事件,在EmployeeService类中创建新方法checkUser,在EmployeeController控制类中编写checkUser方法,在方法体内部使用employService实体类调用checkUser方法,根据checkUser方法的返回值判断用户名是否可用,返回通用信息类的成功或失败信息。在checkUser方法中,需要创建EmployeeExample的实体类example,通过实体类调用createCriteria方法创建出Criteria实体类,通过Criteria实体类调用andEmpNameEqualTo方法拼装动态sql语句,最后通过employeeMapper实体类调用countByExample方法,并返回boolean类型的方法返回值。
编写EmployeeController控制类中的checkUser方法:
@ResponseBody
@RequestMapping("/checkuser")
public Msg checkUser(@RequestParam(value = "empName") String empName){
if (employeeService.checkUser(empName)){
return Msg.success();
}
return Msg.fail();
}
编写EmployeeService业务类中的checkUser方法:
// 检验用户名是否有重复,是否可用
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中编写js代码,检验用户名是否重复而不可用
// 检验用户名是否重复而不可用
$("#empName_add_input").change(function () {
let empName = this.value;
$.ajax({
url:"${APP_PATH}/checkuser",
type: "POST",
data:"empName="+encodeURI(empName),
success:function (result) {
if (result.code == 100){
show_validate_msg("#empName_add_input","success","用户名可用");
// 给保存按钮增添属性,方便校验用户输入数据
$("#save_emp_btn").attr("ajax-va","success");
}else {
show_validate_msg("#empName_add_input","error","用户名重复不可用!");
$("#save_emp_btn").attr("ajax-va","error");
}
}
})
})
同时还要注意在保存按钮的方法中要增添对检验用户重复的代码块:
// 点击保存按钮保存新增员工数据
$("#save_emp_btn").click(function () {
// 模态框中填写的员工数据提交给服务器保存
if (!validate_add_form()){
return false;
}
// 校验用户名是否重复
if ($(this).attr("ajax-va") == "error"){
//清除之前校验数据是否合法时添加的样式
$("#empName_add_input").parent().removeClass("has-error has-success");
$("#empName_add_input").next("span").text("");
show_validate_msg("#empName_add_input","error","用户名重复不可用!");
return false;
}
// 发送ajax请求保存员工
$.ajax({
url: "${APP_PATH}/emp",
type: "POST",
data: $("#emps_add_input form").serialize(),
success: function () {
$("#emps_add_input").modal('hide');
to_page(pagesTotal + 1);
}
})
})
注意:保存按钮保存新增员工数据的代码实现中,成功发送ajax请求保存员工后,由于调用to_page函数立马传参pagesTotal,如果是新加员工正好另起新的一页,那么会由于pagesTotal不能及时更新而显示不到页尾(只能显示到页尾的前一页),所以将pageTotal+1确保传参的值是大于或等于总页数的。{其实为了图方便,完全可以直接传参一个超级大的数 eg:99999 就可以直接到页尾}
ajax校验用户名细节处理
目前,代码仍有不完善的地方,例如在用户输入222时,数据库中无此用户名,因此该用户名合法,但222并不满足用户名的命名规范,而提示用户命名不规范的提示信息只有在用户点击了保存按钮后才会显示出来;又例如在用户点击保存后模态框关闭,在模态框再次打开时,输入框的样式并没有清除;对这两点问题进行完善代码:
在后端也进行用户校验:完善EmployController控制类中的checkUser方法,在用户名不合法时首先返回Msg的错误信息,在前端index.jsp对错误提示信息进行接收。
@ResponseBody
@RequestMapping("/checkuser")
public Msg checkUser(@RequestParam(value = "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位中文!");
}
// 判断用户名是否重复,是否可用
if (employeeService.checkUser(empName)){
return Msg.success();
}
return Msg.fail().add("va_msg","用户名不可用!");
}
前端进行接收:
// 检验用户名是否重复而不可用
$("#empName_add_input").change(function () {
let empName = this.value;
$.ajax({
url:"${APP_PATH}/checkuser",
type: "POST",
data:"empName="+empName,
success:function (result) {
if (result.code == 100){
show_validate_msg("#empName_add_input","success","用户名可用");
// 给保存按钮增添属性,方便校验用户输入数据
$("#save_emp_btn").attr("ajax-va","success");
}else {
show_validate_msg("#empName_add_input","error",result.extend.va_msg);
$("#save_emp_btn").attr("ajax-va","error");
}
}
})
})
在index.jsp中创建reset_form方法,用于清楚表单数据和表单样式:
// 清楚表单内容
function reset_form(ele){
// 清楚表单输入框的内容
$(ele)[0].reset();
$(ele).find("*").removeClass("has-error has-success");
$(ele).find(".help-block").text("");
}
注意:jquery中是没有reset()方法的,是通过调用DOM中的reset方法来重置表单。
JSR303校验数据
为了避免用户通过F12修改前端代码以此达到越过前端校验而恶意存入数据的行为,需要加一层防护:JSR303后端校验。
在maven导入Hibernate-Vaildator依赖到pom.xml中:
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>7.0.4.Final</version>
</dependency>
在Employ类中的empName属性和email属性前加上@Pattern属性,并在属性内部添加正则表达式,和判定错误返回信息。需要注意的是在java文件中\是转义字符,所以正则表达式中的\在java文件中要改成\\就可以。在EmployController控制类中的saveEmp方法的参数列表中加入注解@Valid,并在参数列表中增添BindingResult类型的参数result,调用result参数的hasErrors()方法旁段是否校验成功,校验失败则使用result参数的getFieldErrors方法获取校验失败的错误信息集合,遍历获取到的错误信息集合,将每一条错误信息的字段名和信息(分别使用getFileld方法和getDefaultMessage方法获取)分别作为map集合的键和值放入map集合当中。最后将map放入Msg信息中。
@RequestMapping(value="/emp",method=RequestMethod.POST)
@ResponseBody
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();
}
}
在前端编写点击保存按钮后的js代码接收后端数据进行显示。
// 点击保存按钮保存新增员工数据
$("#save_emp_btn").click(function () {
// 模态框中填写的员工数据提交给服务器保存
if (!validate_add_form()){
return false;
}
// 校验用户名是否重复
if ($(this).attr("ajax-va") == "error"){
$("#empName_add_input").parent().removeClass("has-error has-success");
$("#empName_add_input").next("span").text("");
show_validate_msg("#empName_add_input","error","用户名重复不可用!");
return false;
}
// 发送ajax请求保存员工
$.ajax({
url: "${APP_PATH}/emp",
type: "POST",
data: $("#emps_add_input form").serialize(),
success: function (result) {
// alert(result.msg);
// 信息校验成功
$("#emps_add_input").modal('hide');
to_page(pagesTotal + 1);
}
})
8、修改功能实现
- 点击编辑
- 弹出用户修改的模态框(显示用户信息)
- 点击更新,完成用户修改
创建员工修改模态框
在前端index.jsp页面添加修改员工信息的模态框,并设置点击按钮弹出模态框。
function get_depts(ele){
$.ajax({
url: "${APP_PATH}/depts",
type: "GET",
success:function (result) {
// 显示部门信息在下拉列表中
// 每次添加数据库内容想下拉列表时都情况下拉列表中的内容
$("#dept_select_input2").empty();
$.each(result.extend.depts,function (index,item) {
let optionEle = $("<option></option>").append(item.deptName).attr("value",item.deptId);
// console.log(item.deptId);
optionEle.appendTo(ele);
})
}
})
}
// 员工信息修改的功能实现
$(document).on("click",".edit_btn",function () {
// 每次点击打开模态框前都清空模态框的数据
reset_form("#emps_update_input form");
// 发送ajax请求
get_depts("#dept_select_input2");
// 弹出模态框
$("#emps_update_input").modal({
backdrop:"static"
})
})
回显员工信息
在模态框中回显数据库中已有的员工信息。查看bootstrap中css全局样式中表单中的静态控件,将静态控件替换用户名后的输入框,在EmployeeController类中新增getEmp方法,在getEmp方法中实现查询业务逻辑,在EmployeeService业务类中创建getEmp方法,在方法体内部调用selectByPrimaryKey查询指定id的employ对象。在前端index.jsp页面中创建getEmp函数,发送ajax请求访问,将后端获取的emp显示在前端页面上:
EmployeeController控制类中新增getEmp方法:
@ResponseBody
@RequestMapping(value = "/emp/{id}",method = RequestMethod.GET)
public Msg getEmp(@PathVariable("id") Integer id){
Employee employee = employeeService.getEmp(id);
return Msg.success().add("emp",employee);
}
EmployeeService业务类中新增getEmp方法:
// 查询用户信息,用于修改业务
public Employee getEmp(Integer id) {
return employeeMapper.selectByPrimaryKey(id);
}
前端js代码回显数据:
function getEmp(id){
$.ajax({
url:"${APP_PATH}/emp/"+id,
type:"GET",
success:function (result) {
// console.log(result);
let empDate = result.extend.emp;
$("#empName_update_static").text(empDate.empName);
$("#email_update_input").val(empDate.email);
$("#emps_update_input input[name=gender]").val([empDate.gender]);
$("#dept_select_input2").val([empDate.dId]);
}
})
}
// 员工信息修改的功能实现
$(document).on("click",".edit_btn",function () {
// 每次点击打开模态框前都清空模态框的数据
reset_form("#emps_update_input form");
// 发送ajax请求
get_depts("#dept_select_input2");
getEmp($(this).attr("edit-id"));
// 弹出模态框
$("#emps_update_input").modal({
backdrop:"static"
})
})
注意:可以在展示员工数据列表增添editBtn时直接加入edit-id属性,并设置其属性值为员工id,以便使用attr("edit-id")方法传参员工id*
设计编辑更新成功按钮
在EmployeeController控制类中编写saveEmp方法,在方法体内部使用employeeService调用updateEmp方法,在EmployService业务类中编写updateEmp方法,在方法体内部实现查询出employee的dao操作。前端页面编写点击更新按钮保存emp_update_btn的js代码,在方法体内部校验过邮箱信息后,发送ajax请求保存更新员工的数据。
编写EmployeeController控制类中的saveEmp方法:
@ResponseBody
@RequestMapping(value = "/emp/{empId}",method = RequestMethod.PUT)
public Msg saveEmp(Employee employee){
employeeService.updateEmp(employee);
return Msg.success();
}
编写EmployeeService业务类中的updateEmp方法:
// 修改用户信息,并相应保存
public void updateEmp(Employee employee) {
employeeMapper.updateByPrimaryKeySelective(employee);
}
前端显示页面,发送ajax请求保存更新员工的数据:
$("#save_emp_btn2").click(function () {
let email = $("#email_update_input").val();
let 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","格式正确");
}
//发送ajax请求保存修改员工信息
$.ajax({
url:"${APP_PATH}/emp/"+ $(this).attr("edit-id"),
type:"POST",
data:$("#emps_update_input form").serialize()+"&_method=PUT",
success:function (result) {
alert(result.msg);
}
})
})
解决ajax直接发送PUT请求会报错的问题
当在前端发送ajax请求时,若发送请求类型是“PUT”类型,则服务器报错。报错原因是:Tomcat会将请求体中的数据,封装成map,后端可以通过request.getParameter(“”)方法从map中取值,当SpringMVC封装POJO对象时,会把POJO中每个属性的值,通过request.getParamter()方法取出。但ajax发送PUT请求时,请求体中的数据,后台通过request.getParameter()拿不到,当ajax发送的是PUT请求时,Tomcat不会封装请求体中的数据为map,只有ajax发送的是POST请求时才封装请求体为map。
解决方法是:要在web.xml文件中配置HttpPutFormContentFilter拦截器,作用是将请求体中的数据解析包装成一个map,request被重新包装,request.getParameter()被重写,就会从自己封装的map中取数据。
web.xml文件中配置HttpPutFormContentFilter拦截器:
<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>
将EmployeeController控制类中的saveEmp方法进行修改:
@ResponseBody
@RequestMapping(value = "/emp/{empId}",method = RequestMethod.PUT)
public Msg saveEmp(Employee employee,HttpServletRequest request){
System.out.println("请求体中的值:"+request.getParameter("gender"));
System.out.println("将要更新的员工数据:"+employee.toString());
employeeService.updateEmp(employee);
return Msg.success();
}
9、删除功能实现
单个员工的删除
在EmployController控制类中创建deleteEmpById方法,在方法体内部利用employService实体类调用deleteEmp方法,在EmployeeService业务类的deleteEmp方法中利用employeeMapper调用deleteByPrimaryKey方法删除指定员工。在前端页面设置点击删除按钮发出ajax请求,成功后跳转到当前页。
编写EmployeeController控制类中的deleteEmpById方法:
@ResponseBody
@RequestMapping(value = "emp/{id}",method = RequestMethod.DELETE)
public Msg deleteEmpById(@PathVariable("id") Integer id){
employeeService.deleteEmp(id);
return Msg.success();
}
编写EmployeeService业务类中的deleteEmp方法:
// 删除单个员工信息
public void deleteEmp(Integer id) {
employeeMapper.deleteByPrimaryKey(id);
}
前端实现删除按钮点击效果:
// 删除功能,点击删除按钮保存删除后的员工数据
$(document).on("click",".delete_btn",function () {
let empName = $(this).parents("tr").find("td:eq(1)").text();
let empId = $(this).attr("del-id");
if (confirm("你要删除【"+empName+"】吗?")){
$.ajax({
url:"${APP_PATH}/emp/"+ empId,
type:"DELETE",
success:function (result) {
alert(result.msg);
to_page(currentPage);
}
})
}
})
注意:td:eq(1)表示是选择改行中的第二个单元格。
设计复选框全选全不选
在表格表头处增添checkbox的单个复选框,并在表格体的每一行添加单个checkbox的复选框。点击表头处的复选框时编写相应的js代码使得下面每行的复选框都被选中:
// 点击复选框实现全选全不选功能
$("#check_all").click(function () {
$(".check_item").prop("checked",$(this).prop("checked"));
})
// 判断单行的复选框是否都选中,表头复选框要跟着变化
$(document).on("click",".check_item",function () {
let flag = $(".check_item:checked").length == $(".check_item").length;
$("#check_all").prop("checked",flag);
})
注意:prop方法修改和读取dom原生属性的值,attr获取自定义属性的值。
批量删除功能完成
改造EmployeeController控制类中的deleteEmpById方法,在方法体内部增加判断体,对传入的字符串是单个id还是id集合字符串做判断,id集合字符串则将此id集合字符串进行分割,将每个id放入list集合中,传参到EmployeeService业务类中,在业务类中创建deleteBatch方法,在方法体内部创建Criteria实体类实现动态sql的拼接,然后通过employeeMapper实体类调用deleteByExample实现批量删除员工:
改造EmployeeController控制类中的deleteEmpById方法:
@ResponseBody
@RequestMapping(value = "emp/{ids}",method = RequestMethod.DELETE)
public Msg deleteEmpById(@PathVariable("ids")String ids){
if (ids.contains("-")){
// 批量删除
List<Integer> idList = new ArrayList<>();
String[] ids_string = ids.split("-");
for (String idItem:
ids_string) {
int id = Integer.parseInt(idItem);
idList.add(id);
}
employeeService.deleteBatch(idList);
}else{
// 单个删除
int id = Integer.parseInt(ids);
employeeService.deleteEmp(id);
}
return Msg.success();
}
在EmployeeService业务类中创建deleteBatch方法:
// 批量删除员工
public void deleteBatch(List<Integer> ids){
EmployeeExample example = new EmployeeExample();
EmployeeExample.Criteria criteria = example.createCriteria();
criteria.andEmpIdIn(ids);
employeeMapper.deleteByExample(example);
}
完成前端发送ajax请求的js代码:
// 批量删除的功能完成
$("#emp_delete_all_btn").click(function () {
let empNames = "";
let 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 = empNames.substring(0,empNames.length-1);
// 去除empIds中末尾多余的逗号
empIds = empIds.substring(0,empIds.length-1);
if (confirm("确认删除【"+empNames+"】吗?")){
$.ajax({
url:"${APP_PATH}/emp/"+empIds,
type:"DELETE",
success:function (result) {
$("#check_all").prop("checked",false);
alert(result.msg);
to_page(currentPage);
}
})
}
})
总结
此项目到此完成,由于初学SSM框架,中间实现demo时遇到了各种各样的困难,感谢自己坚持下来,最终收获颇丰,以后常看常新。向尚硅谷雷神老师致以敬意!