SSM框架CRUD小案例
1.数据库准备
部门tbl_dept
员工tbl_emp
建立员工和部门的外键
2.在IDEA创建SSM项目环境
2.1配置Web模块
最上面的图是错误示范,注意!!! 在Tomcat配置了项目路径,就不需要再webapp这里配置项目路径,不然是找不到这里面的资源的!!!!!!!
2.2 引入Maven的SSM相关依赖
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- mvn mybatis-generator:generate -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
<!--这个是指代从哪里加载generatorConfig文件-->
<configurationFile>
src/main/resources/generator/generatorConfig.xml
</configurationFile>
</configuration>
<dependencies>
<!-- 数据库驱动 -->
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<properties>
<project.build.source>UTF-8</project.build.source>
<!--版本同一管理-->
<mybatis.version>3.2.8</mybatis.version>
<mybatis.spring.version>1.2.2</mybatis.spring.version>
<mysql.version>5.1.32</mysql.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.2.17.RELEASE</version>
</dependency>
<!--spring-tx-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.2.17.RELEASE</version>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>3.2.1.RELEASE</version>
</dependency>
<!--面向切面AOP依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency>
<!--连接池-->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--mybatis-->
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<!--日志用log4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--junit测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
</dependency>
</dependencies>
注意1:spring的相关依赖版本要一致,不然maven加载其他相关依赖的时候,会有不同版本的相同依赖也会加载进来,很可能会冲突。而且从maven仓库又下载就会很慢。
注意2: 当你在spring配置文件中死活都不出来某个标签的时候,很可能你在maven里导错包,首先需要重新导入正确的依赖。然后把这个xml上面错误的xsd约束删除掉,就可以了。
applicationContext-service.xml
<context:component-scan base-package="cn.zhanp.ssm_crud.service"></context:component-scan>
<!--配置事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* cn.zhanp.ssm_crud.service..*(..))"></aop:pointcut>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"></aop:advisor>
</aop:config>
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
注意点3:当你在IDEA中无法识别另外一个如applicationContext-dao中的数据源的引用bean时,应该在IDEA的spring Module中把spring配置文件都添加进去,同一个applicationContext就可以识别了
2.3 部署到Tomcat
2.4 配置MBG插件,逆向工程
<!-- mvn mybatis-generator:generate -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
<configurationFile>src/main/resources/generator/generatorConfig.xml</configurationFile>
</configuration>
<dependencies>
<!-- 数据库驱动 -->
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</plugin>
2.5 编写SSM各层的配置文件和数据库文件以及日志文件
springmvc.xml
<!--加载spring的配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:</param-value>
</context-param>
<!--监听器,加载context-param里的配置文件-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--加载springmvc的配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
总结:别忘了监听器,不配置监听器,根本就没人去帮你加载那几个配置文件
mybatis-config.xml
<configuration>
<settings>
<!--全局下划线转驼峰命名-->
<setting name="mapUnderscoreToCamelCase" value="true"></setting>
<!-- mybatis3的 SQL日志打印方式 -->
<!--<setting name="logImpl" value="log4j"/>-->
</settings>
<!--其余配置都在spring的xml中配置,通过属性注入到sqlSessionFactory的bean中,不在这里写-->
</configuration>
applicationContext-dao.xml
<!--配置数据源-->
<context:property-placeholder location="classpath:db/db.properties"></context:property-placeholder>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<!-- c3p0连接池的私有属性 -->
<property name="maxPoolSize" value="30" />
<property name="minPoolSize" value="10" />
<!-- 关闭连接后不自动commit -->
<property name="autoCommitOnClose" value="false" />
<!-- 获取连接超时时间 -->
<property name="checkoutTimeout" value="10000" />
<!-- 当获取连接失败重试次数 -->
<property name="acquireRetryAttempts" value="2" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="typeAliasesPackage" value="cn.zhanp.ssm_crud.model"></property>
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
</bean>
<!--注册一个特殊的sqlSession用于测试的时候支持批量插入-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory"></constructor-arg>
<constructor-arg value="BATCH"></constructor-arg>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
<property name="basePackage" value="cn.zhanp.ssm_crud.dao"></property>
</bean>
</beans>
总结:sqlSessionTemplate,用于注册一些特殊的sqlSession
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}
总结2: error-------通配符的匹配很全面, 但无法找到元素 'aop:config' 的声明。
解决:依旧是把xsd依赖重新写入(一定要注意mvc和aop这些的xsd约束引入是否正确)
总结3: 提示Error creating bean with name 'org.springframework.cache.interceptor.CacheInterceptor
又是xsd依赖引入错误
2.6 把依赖添加入到Artifact中
3.Spring单元测试模拟数据测试mapper
总结:
<!--在spring单元测试中,由于引入validator而导致的Tomcat7及以下的EL表达式版本不一致-->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-el-api</artifactId>
<version>8.5.24</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper-el</artifactId>
<version>8.5.24</version>
<scope>provided</scope>
</dependency>
4. 配置pageHelper,实现分页
参考官方文档:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md
mybatis-config.xml
<!--配置分页插件,也就是指定拦截器-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
5. 分页的后台代码
@RequestMapping(value = "/emps",method = {RequestMethod.GET,RequestMethod.POST})
public ModelAndView getEmps(@RequestParam(value = "pn",defaultValue = "1") Integer page_num){
// 实现分页,默认大小一页显示5条
PageHelper.startPage(page_num, 5);
List<EmployeeCustom> list = employeeService.getAllWithDept();
// 用pageInfo去包装查询后的结果,第二种构造器,第二参数为,连续显示的页数(以当前页为中心,1,2,3,4,5这样)
PageInfo<EmployeeCustom> pageInfo = new PageInfo<>(list,5);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("empList");
modelAndView.addObject("pageInfo",pageInfo);
return modelAndView;
}
总计:
在controller的方法里 要有一个@RequestParam(value="",defaultValue="") xxx
要求从前端传入第几页pageNumber, 又或者传两个,一个是页码,一个是每页的数目size,
因为我们很可能会首页就跳转到这里查看,所以很可能没携带页码参数,所以设置一个默认值
defaultValue,这样就可以实现了。
PageHelper.startPage(page_num, 5);
用静态方法去让mybatis设置的pageHelper的拦截器对紧跟着的第一个select()方法进行分页,并设置size
PageInfo pageInfo = new PageInfo<>(list,5);
用pageInfo 来包装查询后的结果,这样可以把结果包装进pageInfo对象中,
并且第二个参数是navigatepageNums: 是以当前分页为基数,计算导航页码的个数
举个例子:
比如当前页码为1,那么navigatepageNums五个,就是1,2,3,4,5。
当前页码为5,那么navigatepageNums五个,就是3,4,5,6,7 尽可能地以5为中心展开分页导航
并且能够获取相关分页的其他参数,比如total啊,判断是不是首页啊,有没有上一页,下一页啊这些方法。
//PageInfo包含了非常全面的分页属性
assertEquals(1, page.getPageNum());
assertEquals(10, page.getPageSize());
assertEquals(1, page.getStartRow());
assertEquals(10, page.getEndRow());
assertEquals(183, page.getTotal());
assertEquals(19, page.getPages());
assertEquals(1, page.getFirstPage());
assertEquals(8, page.getLastPage());
assertEquals(true, page.isFirstPage());
assertEquals(false, page.isLastPage());
assertEquals(false, page.isHasPreviousPage());
assertEquals(true, page.isHasNextPage());
6. bootstrap后台界面显示
总结的小问题:一开始不出现字体图标,为什么呢?
答:因为我只在项目中引入了bootstrap的css和js,而字体图标虽然是以class来标识,但是底层实现是要根据
fonts来匹配图标文件的,后缀为这种:
所以要引入bootstrap的fonts文件,并且和js文件夹在同等级目录结构
注意:SpringMVC静态文件的配置,一定要配置过滤器,不要拦截这个
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--加载springmvc的配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
<url-pattern>*.css</url-pattern>
<url-pattern>/assets/*"</url-pattern>
<url-pattern>/images/*</url-pattern>
<url-pattern>/static/*</url-pattern>
</servlet-mapping>
作用:在web.xml文件中经常看到这样的配置default,这个配置的作用是:对客户端请求的静态资源如图片、JS文件等的请求交由默认的servlet进行处理,也就是不经过DispatcherServlet的拦截
<div class="row">
<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 var="page" items="${pageInfo.list}">
<tr>
<td>${page.empId}</td>
<td>${page.empName}</td>
<td>${page.gender}</td>
<td>${page.email}</td>
<td>${page.department.deptName}</td>
<td>
<button type="button" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
修改
</button>
<button type="button" class="btn btn-sm btn-danger">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
删除
</button>
</td>
</tr>
</c:forEach>
</table>
</div>
<div class="row">
<div class="col-md-6">
当前总记录数:${pageInfo.total}
</div>
<div class="col-md-6" style="text-align: center">
<nav class="pagination">
<ul class="pagination">
<li>
<a href="${pageContext.request.contextPath}/emps?pn=1">首页</a>
</li>
<%--判断上一页是否可用--%>
<c:if test="${pageInfo.hasPreviousPage==true}">
<li>
<a href="${pageContext.request.contextPath}/emps?pn=${pageInfo.prePage}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
</c:if>
<c:forEach items="${pageInfo.navigatepageNums}" var="pageNum">
<li>
<a href="${pageContext.request.contextPath}/emps?pn=${pageNum}">${pageNum}</a>
</li>
</c:forEach>
<c:if test="${pageInfo.hasNextPage==true}">
<li>
<a href="${pageContext.request.contextPath}/emps?pn=${pageInfo.nextPage}" aria-label="Previous">
<span aria-hidden="true">»</span>
</a>
</li>
</c:if>
<li>
<a href="${pageContext.request.contextPath}/emps?pn=${pageInfo.pages}">末页</a>
</li>
</ul>
</nav>
</div>
</div>
7.统一JSON 响应类的编写
前提:
- 为什么要JSON?因为可以针对不同的前端,不管是浏览器,小程序,安卓移动端等等,都可以通过JSON
数据来达到数据交互的效果。而不用拘泥于以前的浏览器页面Html和jsp这些。
- 有了JSON,就可以在实际项目中快速分工。后端传JSON数据,前端通过Ajax向后端提供的接口获取JSON数据,解析JSON数据,渲染进页面中,即可完成交互。而不需要像以前那样,通过JSP等后端模板引擎,来在服务端渲染。造成前后端的开发耦合。
// 要导入jackson包,SpringMVC的ResponseBody才能起作用
@RequestMapping(value = "/emps",method = {RequestMethod.GET,RequestMethod.POST})
@ResponseBody
public Msg getEmpsWithJson(@RequestParam(value = "pn",defaultValue = "1") Integer page_num){
// 实现分页,默认大小一页显示5条
PageHelper.startPage(page_num, 5);
List<EmployeeCustom> list = employeeService.getAllWithDept();
// 用pageInfo去包装查询后的结果,第二种构造器,第二参数为,连续显示的页数(以当前页为中心,1,2,3,4,5这样)
PageInfo<EmployeeCustom> pageInfo = new PageInfo<>(list,5);
// 封装进JSON 响应类
Msg msg = Msg.success().add("pageInfo", pageInfo);
return msg;
}
public class Msg {
private int code; //代码
private String msg; //信息
//用户要返回给浏览器的数据,最好用一个map对象,前端可以直接取出这个map['key']这样的方式来取出需要的数据
//而且设置为Map的好处,通过返回Msg对象,可以进行链式添加,map中有多个键值对,可以传多个对象
private Map<String,Object> extend = new HashMap<>();
//返回Msg对象,进行链式操作
public static Msg success(){
Msg msg = new Msg();
msg.setCode(200);
msg.setMsg("处理成功");
return msg;
}
public static Msg fail(){
Msg msg = new Msg();
msg.setCode(500);
msg.setMsg("处理失败");
return msg;
}
//添加要返回的主体数据(先通过success和fail,拿到Msg,就可以进行链式操作了)
public Msg add(String key,Object value){
this.getExtend().put(key,value);
return this;
}
//此处省略getter,setter
8.用vue重构Jquery拼接的前端分离页面
window.baseURL = "http://localhost:8080/ssm_crud/"
$(document).ready(function () {
window.empList = new Vue({
el:"#empList",
data:{
emps:[]
},
method:{
},
created(){
window.getEmpsIndex();
// this.$forceUpdate() ;
}
})
})
function getEmpsIndex() {
console.log(baseURL+"emps");
$.get(baseURL+"emps",
{
pn:1
},
function (result) {
//一定要加上设置为window.xxx ,不然他就是一个新的引用,而不是上面的vue对象
window.empList.emps = result.extend.pageInfo.list;
});
}
总结:
为了让页面一开始就触发函数,获取首页数据,需要用到vue的生命周期函数
而且需要注意到JS的作用域,如果写成empList.emps = result.extend.pageInfo.list;
而不是window.empList.emps,那么JS就会新建一个empList对象,这样vue对象实际上没有获取到值。
这也是我以前用的方法,用window,保证操作的是同一个vue对象。
上面的方法的原因其实是:我忘记了ES5的特性,因为在里面的那个function函数中,this已经指向了全局变量window, 而不是当前的vue实例了,所以设置不了data值。
要用let self = this来保存this的引用,又或者用ES6的箭头函数
methods:{
getEmps:function (pn) {
$.get(baseURL+"emps",
{
pn:pn
},
function (result) {
console.log(this); //查看可知这个this又指向了window对象
self.emps = result.extend.pageInfo.list;
self.pageInfo = result.extend.pageInfo;
console.log("pages"+self.pageInfo.pages); //测试
console.log("pn"+pn);
});
}
}
<div id="empList" class="container">
<h1>SSM_CRUD</h1>
<div class="row" style="text-align: center">
<div class="col-md-offset-8">
<button class="btn btn-info">新增</button>
<button class="btn btn-danger">删除</button>
</div>
</div>
<div class="row">
<table class="table table-hover">
<tr>
<th>#</th>
<th>empName</th>
<th>gender</th>
<th>email</th>
<th>deptName</th>
<th>操作</th>
</tr>
<!--用vue重构列表渲染-->
<!--<c:forEach var="page" items="${pageInfo.list}">-->
<tr v-for="emp in emps">
<td>{{emp.empId}}</td>
<td>{{emp.empName}}</td>
<td>{{emp.gender}}</td>
<td>{{emp.email}}</td>
<td>{{emp.department.deptName}}</td>
<td>
<button type="button" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
修改
</button>
<button type="button" class="btn btn-sm btn-danger">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
删除
</button>
</td>
</tr>
<!--</c:forEach>-->
</table>
</div>
<!--分页界面代码-->
<div class="row">
<div class="col-md-6">
当前总记录数:{{pageInfo.total}}
</div>
<div class="col-md-6" style="text-align: center">
<nav class="pagination">
<ul class="pagination">
<li>
<span @click="getEmps(1)">首页</span>
</li>
<li v-if="pageInfo.hasPreviousPage">
<span aria-hidden="true" @click="getEmps(pageInfo.prePage)">«</span>
</li>
<li v-for="pageNum in pageInfo.navigatepageNums">
<span @click="getEmps(pageNum)">{{pageNum}}</span>
</li>
<li v-if="pageInfo.hasNextPage">
<span aria-hidden="true" @click="getEmps(pageInfo.nextPage)">»</span>
</li>
<li>
<span @click="getEmps(pageInfo.pages)">末页</span>
</li>
</ul>
</nav>
</div>
</div>
</div>
实现选中当前分页,当前分页高亮加深的效果。
<li v-for="pageNum in pageInfo.navigatepageNums" :class="{active:pageNum==pageInfo.pageNum}">
<span @click="getEmps(pageNum)">{{pageNum}}</span>
</li>
总结 : 动态class的语法一定要加{}
9. 添加员工
简介:通过以模态框的方式来编写 添加员工的界面。 在点击新增按钮后,让模态框显示。
怎么做出来的:通过bootstrap官方文档,拉取组件样式和 模态框model 的JS插件来完成这个界面。
细节:部门名称:是以下拉菜单的形式,所以要ajax获取部门数据。
总结:
因为有些我不用完全的form表单提交的方式,比如部门数据,是用的ul配li,所以在JS里面获取前端的所有表单数据,然后再手动submit上去校验。也不难,用下JQuery获取值和DOM结点即可
需要注意的技巧是:
获取单选按钮选中后的value值,用$('input[name="gender"]:checked').attr('value');
获取下拉菜单(但又不是select这种写法)选中的值,view层的绑定事件
<ul class="dropdown-menu" aria-labelledby="dLabel">
<!--要获取部门数据id,和name-->
<li v-for="dept in depts" class="pointer" @click="getSelectedDept(dept.deptId,dept.deptName)">
<span>{{dept.deptName}}</span>
</li>
</ul>
选择部门下拉菜单我是以按钮的形式
<button class="btn btn-sm btn-primary" id="dedt_btn"
type="button" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
选择部门
<span class="caret"></span>
</button>
配合JS
//获取选择的部门id
getSelectedDept:function (dept_id,dept_name) {
this.selected_dept_id = dept_id;
//按钮提示的回显
$("#dedt_btn").text(dept_name);
console.log(this.selected_dept_id)
},
```
界面代码:
<style type="text/css">
li span{
/*设置悬浮可点*/
cursor: pointer;
}
#add_emp li:hover{
display: block;
background: #8c8c8c;
padding: 10px;
/*设置悬浮可点*/
cursor: pointer;
}
</style>
<!-- Modal -->
<div class="modal fade" id="add_emp" tabindex="-1" role="dialog" aria-labelledby="add_empLabel">
<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="inputName" class="col-sm-2 control-label">名字</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputName" name="name" placeholder="name">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">性别</label>
<div class="col-sm-10">
<label class="radio-inline">
<input type="radio" name="gender" value="M"> 男
</label>
<label class="radio-inline">
<input type="radio" name="gender" value="F"> 女
</label>
</div>
</div>
<div class="form-group">
<label for="inputEmail" class="col-sm-2 control-label">Email</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="inputEmail" placeholder="Email">
</div>
</div>
<!--下拉菜单-->
<div class="form-group">
<label for="inputEmail" class="col-sm-2 control-label">选择部门</label>
<div class="dropdown col-sm-10">
<button class="btn btn-sm btn-primary" id="dedt_btn" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
选择部门
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dLabel">
<!--要获取部门数据id,和name-->
<li v-for="dept in depts" class="pointer" @click="getSelectedDept(dept.deptId,dept.deptName)">
<span>{{dept.deptName}}</span>
</li>
</ul>
</div>
</div>
</form>
</div>
<div class="form-group">
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" @click="add_emp_submit">提交</button>
</div>
</div>
</div>
</div>
</div>
//切换模态框的显示
add_emp_toggle:function () {
this.getDepts();
$("#add_emp").modal({
//设置 点击除模态框以外的其他地方,不会让模态框消失掉
backdrop:"static"
});
},
//获取部门信息
getDepts:function () {
let self = this;
$.get(baseURL+"depts",
function (result) {
self.depts = result.extend.depts;
})
},
//获取选择的部门id
getSelectedDept:function (dept_id,dept_name) {
this.selected_dept_id = dept_id;
//按钮提示的回显
$("#dedt_btn").text(dept_name);
console.log(this.selected_dept_id)
},
//增加员工 提交
add_emp_submit:function () {
let empName = $('#inputName').val();
let gender = $('input[name="gender"]:checked').attr('value');
let email = $('#inputEmail').val();
let dId = this.selected_dept_id;
console.log(empName)
console.log(gender)
console.log(email)
console.log(dId)
$.post(baseURL+"/emps/add",
{
empName:empName,
gender:gender,
email:email,
dId:dId
},
function (result) {
if(result.code==200){
//重新获取第一页数据,刷新页面
window.getEmpsIndex();
$("#add_emp").modal('hide');
}
})
}
添加员工 提交数据后 回显最后一页的实现:
//这个是添加员工的提交后的 回调函数
function (result) {
if(result.code==200){
//获取最后一页数据,刷新页面
let lastPage = result.extend.lastPage;
console.log("lastPage:"+lastPage)
self.getEmps(lastPage);
$("#add_emp").modal('hide');
}
})
//要拿到最后一页
@Override
public int getLastPage(int size) {
EmployeeExample employeeExample = new EmployeeExample();
EmployeeExample.Criteria criteria = employeeExample.createCriteria();
criteria.andEmpIdIsNotNull();
int num = employeeMapper.countByExample(employeeExample);
if(num%size==0){
return num/size;
}else{
return num/size+1;
}
}
@RequestMapping(value = "/emps/add",method = RequestMethod.POST)
@ResponseBody
public Msg addEmp(Employee employee){
employeeService.addEmployee(employee);
//要拿到最后一页
//总员工数
int lastPage = employeeService.getLastPage(5);
return Msg.success().add("lastPage",lastPage);
}
9.1 添加员工的校验状态
名字和邮箱的校验和前端显示
前端校验页面的用法
bootstrap摘录:
<div class="form-group has-success">
<label class="control-label" for="inputSuccess1">Input with success</label>
<input type="text" class="form-control" id="inputSuccess1" aria-describedby="helpBlock2">
<span id="helpBlock2" class="help-block">A block of help text that breaks onto a new line and may extend beyond one line.</span>
</div>
<div class="form-group has-warning">
<label class="control-label" for="inputWarning1">Input with warning</label>
<input type="text" class="form-control" id="inputWarning1">
</div>
<div class="form-group has-error">
<label class="control-label" for="inputError1">Input with error</label>
<input type="text" class="form-control" id="inputError1">
</div>
//模态框显示的事件中
add_emp_toggle:function () {
this.getDepts();
$("#add_emp").modal({
//点击背景不删除
backdrop:"static"
});
/**
* 前端JQuery校验
* 1.用户名交给后端检查校验正则表达式,再查看是否重名
* 2.其他的前端校验正则即可
*/
$("#inputName").focusout(function () {
let self = this;
let name = $(this).val();
$.post(baseURL+"namechecked",{
empName:name
},
function (result) {
//因为修改的时候,是添加样式,所以可能重复has-success和has-error一起,是不正确的
$(self).parent().removeClass("has-success has-error");
$(self).next().text("");
if(result.code==200){
window.empList.vali_name = true;
//说明成功,正则匹配,且用户名可用
$(self).parent().addClass("has-success");
let msg = result.extend.message;
$(self).next().text(msg);
}else{
window.empList.vali_name = false;
//说明不成功,正则不匹配或者用户名不可用,要拿出错误信息,添加相关样式,以及不许提交
console.log($(this));
$(self).parent().addClass("has-error");
let msg = result.extend.message;
$(self).next().text(msg);
}
});
});
//正则表达式判断其他表单参数
$("#inputEmail").focusout(function () {
$(this).parent().removeClass("has-success has-error");
$(this).next().text("");
let email = $(this).val();
let regx = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/
// 长度不限,可以使用英文(包括大小写)、数字、点号、下划线、减号,首字母必须是字母或数字;
console.log(regx.test(email));
if(regx.test(email)){
window.empList.vali_email = true;
//符合
//1. 给父元素添加bootstrap 校验成功样式
$(this).parent().addClass("has-success")
}else{
window.empList.vali_email = false;
//不符合
// 1. 给父元素添加bootstrap 校验错误样式
$(this).parent().addClass("has-error");
// 2. 在其下面添加span的提示内容
$(this).next().text("邮件格式错误!首字母必须是字母或数字");
}
});
},
@RequestMapping(value = "/namechecked",method = RequestMethod.POST)
@ResponseBody
public Msg nameChecked(@RequestParam(value = "empName",required = true) String name){
//1. 先正则 2. 后判断是否重复
String regx = "^[[[\u4e00-\u9fa5]a-zA-Z0-9]+[-|a-zA-Z0-9._]$]{2,7}";
if(name.matches(regx)){
boolean nameChecked = employeeService.getNameChecked(name);
if(nameChecked){
Msg msg = Msg.success().add("message", "用户名可用");
return msg;
}else{
Msg msg = Msg.fail().add("message", "用户名不可用");
return msg;
}
}else{
Msg msg = Msg.fail().add("message", "请填写2-7位中文或者数字或英文");
return msg;
}
}
选择部门和提交表单时候的校验
//获取选择的部门id
getSelectedDept:function (dept_id,dept_name) {
this.selected_dept_id = dept_id;
//按钮的回显
$("#dedt_btn").text(dept_name);
//删掉span提示信息
$("#dept_span").text('');
console.log(this.selected_dept_id)
},
//增加员工 提交
add_emp_submit:function () {
let self = this;
let empName = $('#inputName').val();
let gender = $('input[name="gender"]:checked').attr('value');
let email = $('#inputEmail').val();
let dId = this.selected_dept_id;
if(self.selected_dept_id==''){
$("#dedt_btn").next().css("color","red");
$("#dedt_btn").next().text("请选择部门");
self.vali_dept = false;
}else{
self.vali_dept = true;
//全都校验成功,才可以提交
if(self.vali_name&&self.vali_dept&&self.vali_email){
$.post(baseURL+"emps/add",
{
empName:empName,
gender:gender,
email:email,
dId:dId
},
function (result) {
if(result.code==200){
//获取最后一页数据,刷新页面
let lastPage = result.extend.lastPage;
console.log("lastPage:"+lastPage)
self.getEmps(lastPage);
$("#add_emp").modal('hide');
//清空表单数据
$("#add_emp_form")[0].reset();
$("#add_emp_form").find("*").removeClass("has-error has-success");
$("#add_emp_form").find(".help-block").text('');
//清空校验状态位
self.vali_name = false;
self.vali_dept = false;
self.vali_email = false;
//清空选中的部门
self.selected_dept_id = "";
$("#dedt_btn").text("选择部门");
}
})
}else{
alert("请正确填写表单中的信息,再提交");
}
//提交表单
}
// console.log(empName)
// console.log(gender)
// console.log(email)
// console.log(dId)
}
总结:校验页面的编写
用正则(前后端都可以,用后端进行重名校验
通过后端或者前端校验结束后拿到提示信息,根据匹配情况给前端页面设置样式(比如success,error)以及
span里的提示信息
然后还要注意当重新聚焦到(通过聚焦focusOut()判断正则和重名)的时候,要注意先清空success,error的class样式,还有清空span里的提示信息
我是通过标志位判断每个表单参数是否匹配,都匹配了才可以提交Ajax请求提交表单。
记住:提交完表单后,要在回调函数里清空表单的数据,以及前端界面的一些校验提示的样式和提示信息。
还要清空我设置的校验标志位,以及这里的下拉菜单选中的部门id
9.2JSR303后端校验
防君子,不防小人的前端JS校验,要通过后端的校验来保证表单参数的正确性。这个时候JSR303应运而生!
<!--JSR303校验-->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>
这里要注意点:如果使用Tomcat7及以下的版本,那么validator和Tomcat lib中的EL表达式语法是不匹配的。会提示找不到Class EL....之类的异常
解决方法:下载
往tomcat的lib中添加了el-api-3.0.0的jar包
public class Employee {
private Integer empId;
@Pattern(regexp = "^[[[\\u4e00-\\u9fa5]a-zA-Z0-9]+[-|a-zA-Z0-9._]$]{2,7}",message = "请填写2-7位中文或者数字或英文")
private String empName;
private String gender;
@Pattern(regexp = "^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*\\.[a-zA-Z0-9]{2,6}$",message = "邮件格式错误!首字母必须是字母或数字")
private String email;
@RequestMapping(value = "/emps/add",method = RequestMethod.POST)
@ResponseBody
public Msg addEmp(@Valid Employee employee, BindingResult result) {
HashMap<String, Object> map = new HashMap<>();
//正则校验有误
if (result.hasErrors()) {
List<FieldError> errors = result.getFieldErrors();
for (FieldError error : errors) {
map.put(error.getField(), error.getDefaultMessage());
}
return Msg.fail().add("errorFields", map);
}
//重名校验有误
String empName = employee.getEmpName();
boolean nameChecked = employeeService.getNameChecked(empName);
if (!nameChecked) {
map.put("empNameDuplicated", "已存在该用户名");
return Msg.fail().add("errorFields", map);
} else {
//校验正确
employeeService.addEmployee(employee);
//要拿到最后一页
//总员工数
int lastPage = employeeService.getLastPage(5);
return Msg.success().add("lastPage", lastPage);
}
}
如何拿到返回的后端校验返回的信息,去做其他的处理
//增加员工 提交
add_emp_submit:function () {
let self = this;
let empName = $('#inputName').val();
let gender = $('input[name="gender"]:checked').attr('value');
let email = $('#inputEmail').val();
let dId = this.selected_dept_id;
if(self.selected_dept_id==''){
$("#dedt_btn").next().css("color","red");
$("#dedt_btn").next().text("请选择部门");
self.vali_dept = false;
}else{
//全都校验成功,才可以提交
if(self.vali_name&&self.vali_dept&&self.vali_email){
$.post(baseURL+"emps/add",
{
empName:empName,
gender:gender,
email:email,
dId:dId
},
function (result) {
//先进入后端校验,防君子,不防小人,所以需要后端JSR303校验
if(result.code==500){
//后端校验,有误
let error_emp_name = result.extend.errorFields.empName;
let error_emp_email = result.extend.errorFields.email;
let error_emp_name_duplicated = result.extend.errorFields.empNameDuplicated;
//说明这个数据校验有误
if(error_emp_name!=undefined){
$("#inputName").parent().addClass("has-error");
$("#inputName").next().text(error_emp_name);
}
if(error_emp_email!=undefined){
$("#inputEmail").parent().addClass("has-error");
$("#inputEmail").next().text(error_emp_email);
}
if(error_emp_name_duplicated!=undefined){
$("#inputName").parent().addClass("has-error");
$("#inputName").next().text(error_emp_name_duplicated);
}
} else if(result.code==200){
//获取最后一页数据,刷新页面
let lastPage = result.extend.lastPage;
console.log("lastPage:"+lastPage)
self.getEmps(lastPage);
$("#add_emp").modal('hide');
//清空表单数据
$("#add_emp_form")[0].reset();
$("#add_emp_form").find("*").removeClass("has-error has-success");
$("#add_emp_form").find(".help-block").text('');
//清空校验状态位
self.vali_name = false;
self.vali_dept = false;
self.vali_email = false;
//清空选中的部门
self.selected_dept_id = "";
$("#dedt_btn").text("选择部门");
}
})
}else{
alert("请正确填写表单中的信息,再提交");
}
10. 修改员工
10.1 模态框
总结:我上面的添加员工 那个下拉菜单有问题,,,是自己写的,而不是用select组件,所以浪费了很多时间获取数据和绑定事件。下面对修改框做了调整
<!-- 修改员工Modal -->
<div class="modal fade" id="update_emp_modal" tabindex="-1" role="dialog" aria-labelledby="update_empLabel">
<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" id="update_emp_form">
<div class="form-group">
<label class="col-sm-2 control-label">名字</label>
<div class="col-sm-10">
<p class="form-control-static" id="update_Name">{{emp.empName}}</p>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">性别</label>
<div class="col-sm-10">
<label class="radio-inline">
<input type="radio" name="gender" value="M" :checked="'M'==emp.gender?'checked':''"> 男
</label>
<label class="radio-inline">
<input type="radio" name="gender" value="F" :checked="'F'==emp.gender?'checked':''"> 女
</label>
</div>
</div>
<div class="form-group">
<label for="update_Email" class="col-sm-2 control-label">Email</label>
<div class="col-sm-10">
<input type="email" class="form-control" name="email" id="update_Email" v-bind:value="emp.email">
<span class="help-block"></span>
</div>
</div>
<!--下拉菜单-->
<div class="form-group">
<label for="inputEmail" class="col-sm-2 control-label">选择部门</label>
<div class="col-sm-10">
<select class="form-control" name="dId">
<option v-for="dept in depts" v-bind:value="dept.deptId">{{dept.deptName}}</option>
</select>
</div>
</div>
</form>
</div>
<div class="form-group">
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="update_emp_button" @click="update_emp_submit(pageInfo.pageNum)">提交</button>
</div>
</div>
</div>
</div>
</div>
10.2 修改员工的JS
把校验函数封装一下,代码复用。增加,修改都可以用。通过传入相应的选择器即可。
//封装前端校验邮件函数,通过传入相应的(输入框的)选择器
validate_email:function(ele){
//正则表达式判断其他表单参数
$(ele).focusout(function () {
$(ele).parent().removeClass("has-success has-error");
$(ele).next().text("");
let email = $(ele).val();
let regx = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/
// 长度不限,可以使用英文(包括大小写)、数字、点号、下划线、减号,首字母必须是字母或数字;
console.log(regx.test(email));
if(regx.test(email)){
window.empList.vali_email = true;
//符合
//1. 给父元素添加bootstrap 校验成功样式
$(ele).parent().addClass("has-success")
}else{
window.empList.vali_email = false;
//不符合
// 1. 给父元素添加bootstrap 校验错误样式
$(ele).parent().addClass("has-error");
// 2. 在其下面添加span的提示内容
$(ele).next().text("邮件格式错误!首字母必须是字母或数字");
}
});
},
模态框的显示:包含了修改员工的信息的回显,以及表单校验,和模态框显示
//修改模态框的显示
updateEmp:function(empId){
let self = this;
//获取该员工的信息
$.get(baseURL+"emps/"+empId,
function (result) {
self.emp = result.extend.emp;
let gender = result.extend.emp.gender;
let dId = result.extend.emp.dId;
console.log("gender:"+gender)
//给下拉菜单赋值
$("#update_emp select").val(dId);
//要把empId传递到修改模态框的提交
$("#update_emp_button").attr("edit-id",empId);
});
//获取部门数据
this.getDepts();
//显示模态框
$("#update_emp_modal").modal({
backdrop:"static"
});
//表单校验
this.validate_email("#update_Email");
},
提交修改员工表单: 前后端遵循Restful API风格,修改用put请求
update_emp_submit:function(pageNum){
let empId = $("#update_emp_button").attr("edit-id");
let self = this;
//邮件格式符合
if(self.vali_email){
$.ajax({
url:baseURL+"emps/"+empId,
type:"PUT",
data:$("#update_emp_form").serialize(),
success:function (result) {
if(result.code==200){
//修改成功
//跳转到修改的员工的本页
self.getEmps(pageNum);
//清掉表单数据以及状态位
self.vali_email = false;
$("#update_emp_modal").find('*').removeClass("has-success has-error");
$("#update_emp_modal .help-block").text("");
$("#update_emp_modal").modal('hide');
}else{
//后端校验邮件。。。没必要啊
}
}
});
}else{
//邮件有误,不得更新
alert("请正确填写邮件!")
}
},
10.3 Restful API对put请求的使用
首先引入一些能支持Restful API的过滤器,比如通过携前端提交表单时带_method参数可以把表面上为POST请求转换处理成Rest风格的put或delete
<!-- 浏览器不支持put,delete等method,由该filter将/xxx?_method=delete转换为标准的http delete方法 -->
<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>
因为Tomcat对于前端提交的表单,内部的实现是:将POST请求的报文,把请求体里的参数封装成一个map。
然后request.getParam("")就是从map里获取键值对。而通过查看Tomcat的源码可知:
/**
* Comma-separated list of HTTP methods that will be parsed according
* to POST-style rules for application/x-www-form-urlencoded request bodies.
*/
protected String parseBodyMethods = "POST";
//当Tomcat判断不是POST请求,直接返回,不会对请求体进行处理。
//所以request.getParam()根本获取不到参数
if( !getConnector().isParseBodyMethod(getMethod()) ) {
success = true;
return;
}
所以需要一个转换器,以过滤器的方式来增强request对象,让这个转换器替Tomcat识别put请求,并完成put请求报文的封装。让增强后的request调用getParameter()后能获取表单数据。
<filter>
<filter-name>HttpMethodPutFilter</filter-name>
<filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HttpMethodPutFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
控制器:
//获取某一位员工的信息
@RequestMapping(value = "/emps/{empId}",method = RequestMethod.GET)
@ResponseBody
public Msg getEmp(@PathVariable(value = "empId") int empId){
Employee emp = employeeService.getEmp(empId);
return Msg.success().add("emp",emp);
}
//修改一位员工的信息
@RequestMapping(value = "/emps/{empId}",method = RequestMethod.PUT)
@ResponseBody
public Msg updateEmp(@PathVariable(value = "empId") int empId, Employee employee){
employeeService.updateEmp(empId,employee);
return Msg.success();
}
11. 删除员工
// 删除一位员工
@RequestMapping(value = "/emps/{empId}",method = RequestMethod.DELETE)
@ResponseBody
public Msg deleteEmp(@PathVariable(value = "empId") int empId){
employeeService.deleteEmp(empId);
return Msg.success();
}
<button type="button" class="btn btn-sm btn-danger" @click="del_emp(emp.empId,emp.empName)">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
删除
</button>
//删除单个员工
del_emp:function (empId,empName) {
let self = this;
if(confirm("你确认删除【"+empName+"】吗?"))
$.ajax({
url:baseURL+"emps/"+empId,
type:"DELETE",
success:function (result) {
if(result.code==200){
//删除成功
self.getEmps(self.pageInfo.pageNum);
}
}
});
}
12.批量删除
JS批量删除
//批量删除
del_emp_batch:function () {
let self = this;
let checkAll = $("#selectAll").prop("checked");
//可以删除二合一。 多个用-相连接,后端再分离出每个要删除的员工Id
let empIds = new Array();
if(checkAll){
for(let i=0;i<self.emps.length;i++){
console.log(empIds)
empIds.push(self.emps[i].empId);
}
console.log("empIds:"+empIds)
let old_empIds = empIds.join(',');
//用-连起来
console.log(typeof old_empIds)
console.log(typeof empIds)
empIds = empIds.join('-');
if(confirm("你确认删除【"+old_empIds+"】吗")){
$.ajax({
url:baseURL+"emps/batch/"+empIds,
type:"DELETE",
success:function (result) {
if(result.code=200){
self.getEmps(self.pageInfo.pageNum+1);
//把选中框的状态关掉
$("#selectAll").prop("checked",false);
$("input[name=del_emp]:checked").prop("checked",false);
}
}
});
}
}else{
//拿到所有选中的节点
let del_emps = $("input[name=del_emp]:checked");
$.each(del_emps,function (index,del_emp) {
empIds.push($(del_emp).attr("del-id"));
})
let old_empIds = empIds.join(',')
empIds = empIds.join('-');
if(confirm("你确认删除【"+old_empIds+"】吗")){
$.ajax({
url:baseURL+"emps/batch/"+empIds,
type:"DELETE",
success:function (result) {
if(result.code=200){
self.getEmps(self.pageInfo.pageNum);
//把选中框的状态关掉
$("#selectAll").prop("checked",false);
$("input[name=del_emp]").prop("checked",false);
}
}
});
}
}
}
全选,非全选:
//全选和全不选
selectAll:function () {
let checkAll = $("#selectAll").prop("checked");
if(checkAll){
//为该页多选框也选中
$("input[name='del_emp']").prop("checked",true);
}else{
$("input[name='del_emp']").prop("checked",false);
}
},
//选中某一个,当该页所有的都被选中,则全选框自动变checked
selectOne:function (empName,empId) {
let checkAll = $("input[name=del_emp]:checked").length == $("input[name=del_emp]").length;
if(checkAll){
$("#selectAll").prop("checked",true);
}else{
$("#selectAll").prop("checked",false);
}
},
JQuery相关语法
<script type="text/javascript">
var arr = new Array(3)
arr[0] = "George"
arr[1] = "John"
arr[2] = "Thomas"
document.write(arr.join())
</script>
<head>
<title></title>
<script src="jquery-1.9.0.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
$('input:hidden').each(function (index, obj) {
alert(obj.name + "..." + obj.value);
});
});
</script>
</head>
<body>
<input type="hidden" value="1" name="a"/>
<input type="hidden" value="2" name="b"/>
<input type="hidden" value="3" name="c"/>
</body>
上面这段代码用到了input集合的索引,有用到了input集合的dom对象,可以通过该对象,拿到其对应的属性如:name,value等;
$.each()方法
- 该方法处理一维数组,代码如下:
$.each(["aaa","bbb","ccc"],function(index,value){
alert(i+"..."+value);
});
控制器:
//批量删除按钮触发的
@RequestMapping(value = "/emps/{empIds}",method = RequestMethod.DELETE)
@ResponseBody
public Msg deleteEmp(@PathVariable(value = "empIds") String empIds){
if(empIds.contains("-")){
ArrayList<Integer> ids = new ArrayList<>();
String[] split = empIds.split("-");
for(String empId:split){
int id = Integer.parseInt(empId);
ids.add(id);
employeeService.deleteEmpList(ids);
}
}else{
employeeService.deleteEmp(Integer.parseInt(empIds));
}
return Msg.success();
}
总结:多个id用-连接起来,而不是封装成数组直接传过去,让Springmvc做参数绑定。记住这种技巧。
13.文件上传
总结:
要在spring配置多媒体解析器
因为MultipartResolver依赖于Apache的这个jar包
<!--文件上传的依赖-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>spring注册Bean
<!--配置多媒体解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property>
<property name="maxInMemorySize" value="#{1024*1024*1024}"></property>
<property name="maxUploadSize" value="#{1024*1024*5}"></property>
</bean>
@Controller
public class FileController {
@RequestMapping(value = "/file",method = RequestMethod.POST)
public String uploadFile(@RequestParam(value = "file") MultipartFile multipartFile, Model model, HttpServletRequest request){
String basePath = "E:/";
if(multipartFile!=null&&!multipartFile.isEmpty()){
//1.获取原始文件名
String originalFilename = multipartFile.getOriginalFilename();
//2.获取前缀
String originalFilenamePrefix = originalFilename.substring(0, originalFilename.lastIndexOf('.'));
//3.封装新的文件名 名字+时间戳
String newFileName = originalFilenamePrefix + new Date().getTime()
+ originalFilename.substring(originalFilename.lastIndexOf('.'));
//4. 打开文件流
File file = new File(basePath+newFileName);
//5. 保存文件
try {
multipartFile.transferTo(file);
model.addAttribute("fileName",originalFilename);
} catch (IOException e) {
e.printStackTrace();
System.out.println("上传失败");
}
}
return "upload/uploadSuc";
}
14. 拦截器(登陆认证)
拦截器实现的原理:
- 实现HandlerInterceptor接口。编写拦截器
- 配置拦截器和HandlerMapping的映射关系
- 编写控制器
<!--拦截器 和HandlerMapping之间的配置 在HandlerMapping找到相应的Handler之前,指定某个拦截器进行拦截-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="cn.zhanp.ssm_crud.intercepetor.LoginInteceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
public class LoginInteceptor implements HandlerInterceptor {
/**
* 这个方法 是在 进入Handler方法之前执行的
* 应用:在这里进行身份认证,身份权限认证等等 不过要放行(登陆页面的url)
*/
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
//1. 如果是登陆页面,放行
String requestURI = httpServletRequest.getRequestURI();
if(requestURI.contains("login") || requestURI.contains("toLogin")){
return true;
}else{
HttpSession session = httpServletRequest.getSession();
String username = (String)session.getAttribute("username");
if(username!=null){
return true;
}else{
// 回到登陆页面
httpServletResponse.sendRedirect(httpServletRequest.getContextPath()+"/toLogin");
return false;
}
}
}
/**
* 这个方法 是在Handler方法之后,返回ModelAndView之前执行的
* 应用: 公用的一些数据模型(ModelAndView)可以在这里设置,比如导航菜单
*/
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
/**
* 这个方法是在 Handler执行完成后执行
* 应用: 捕捉异常把
*/
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
SSM框架CRUD小案例的更多相关文章
- JavaWeb_(Struts2框架)Ognl小案例查询帖子
此系列博文基于同一个项目已上传至github 传送门 JavaWeb_(Struts2框架)Struts创建Action的三种方式 传送门 JavaWeb_(Struts2框架)struts.xml核 ...
- ssm框架的小总结
一.mybatis框架 mybatis框架主要就是完成持久层的实现,简化了持久层的开发, 1.首先是配置文件的编写,我们这里就命名为mybatis-config.xml,先配置文件头,然后加载连接数据 ...
- angular前端框架简单小案例
一.angular表达式 <head> <meta charset="UTF-8"> <title>Title</title> &l ...
- 一个DRF框架的小案例
第一步:安装DRF DRF需要以下依赖: Python (2.7, 3.2, 3.3, 3.4, 3.5, 3.6) Django (1.10, 1.11, 2.0) DRF是以Django扩展应用的 ...
- 一个ssm综合小案例-商品订单管理----写在前面
学习了这么久,一直都是零零散散的,没有把知识串联起来综合运用一番 比如拦截器,全局异常处理,json 交互,RESTful 等,这些常见技术必须要掌握 接下来呢,我就打算通过这么一个综合案例把这段时间 ...
- ssm框架(Spring Springmvc Mybatis框架)整合及案例增删改查
三大框架介绍 ssm框架是由Spring springmvc和Mybatis共同组成的框架.Spring和Springmvc都是spring公司开发的,因此他们之间不需要整合.也可以说是无缝整合.my ...
- ssm框架的搭建实现CRUD的操作
最近在开发公司的一个系统,系统的框架是用ssm的框架搭建的,当然和这次写博客的不一样,它拥有很多的配置文件,企业级的开发所需要的配置文件是非常繁琐的,今天记录一下一个简单的SSM框架的搭建和实现一个C ...
- react框架实现点击事件计数小案例
下面将以一个小案例来讲解react的框架的一般应用,重点内容在代码段都有详细的解释,希望对大家有帮助 代码块: 代码块: import React from 'react'; import React ...
- springmvc文件上传下载简单实现案例(ssm框架使用)
springmvc文件上传下载实现起来非常简单,此springmvc上传下载案例适合已经搭建好的ssm框架(spring+springmvc+mybatis)使用,ssm框架项目的搭建我相信你们已经搭 ...
随机推荐
- python文件打包为exe可执行文件的方法
我自己常用Pyinstaller库打包 第一步: 安装pyinstaller库 pip install pyinstaller 第二步: 在py文件所在目录输入 mydemo.py是自己写的py文 ...
- python模块之datetime
相比于time模块,datetime模块的接口则更直观.更容易调用 datetime模块定义了下面这几个类: datetime.date:表示日期的类.常用的属性有year, month, day: ...
- hdu 5441
Travel Time Limit: 1500/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)Total Su ...
- 开源OA系统启动:基础数据,工作流设计
原文:http://www.cnblogs.com/kwklover/archive/2007/01/13/bpoweroa_03_baseandworkflowdesign.html自从开源OA系统 ...
- C语言高效编程的几招,你会了几招了?
编写高效简洁的C 语言代码,是许多软件工程师追求的目标.本文就工作中的一些体会和经验做相关的阐述,不对的地方请各位指教. 第1 招:以空间换时间 计算机程序中最大的矛盾是空间和时间的矛盾,那么,从这个 ...
- bzoj3039 joyoi1939 玉蟾宫 悬线法
悬线法 #include <iostream> #include <cstring> #include <cstdio> using namespace std; ...
- 面试中注意3个javascript的问题
JavaScript 是所有现代浏览器的官方语言.因此,各种语言的开发者面试中都会遇到 JavaScript 问题. 本文不讲最新的 JavaScript 库,通用开发实践,或任何新的 ES6 函数. ...
- Tensorflow 笔记 -- tensorboard 的使用
Tensorflow 笔记 -- tensorboard 的使用 TensorFlow提供非常方便的可视化命令Tensorboard,先上代码 import tensorflow as tf a = ...
- 用meta标签让网页用360打开时默认为极速模式
最近做项目,用360浏览器访问自己的本地网页,发现都是默认在兼容模式下打开,做的淡入淡出轮播效果在兼容模式下看时,感觉切换很生硬.百度,发现360官网帮助里有说明用meta标签控制浏览器内核,网址为h ...
- HDU——1005Number Sequence(模版题 二维矩阵快速幂+操作符重载)
Number Sequence Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)T ...