SSM小项目-(11)-项目总结与正确的Maven项目架构

编码管理方便统一:

标签中的类名:取-
标签中的id:取_

一、项目总结

 

二、Maven项目打包

Eclipse 中 右键项目 run as ==>  maven install ==>可以打包成 war 项目包。
但是因为之前设置的目录有问题,所以打包时,说找不到 web.xml 配置文件。
正确的maven项目目录是:

三、发布项目到Tomcat

四、WAR包的目录结构

首先:maven的web工程中:src/main下面有三个文件夹

1、项目根地址/src/main/java   里面包含的就是一些类文件

2、项目根地址/src/main/resource  里面包含的就是一些资源文件

3、项目根地址/src/main/webapp 里面包含的就是一些前端代码和资源

其中1和2 最终会整合到webapp/WEB-INF/classes目录下。并且项目依赖的jar包,最终会整合到webapp/WEB-INF/lib目录下。至于webapp下面的META-INF目录,里面更新的文件不是很重要。原本就有一个MANIFEST.MF(清单文件,内容如下)

Manifest-Version: 1.0
Class-Path:

后来maven打包后,又添加了一个maven文件夹,里面是公司名文件夹–>项目名文件夹–>【pom.properties,pom.xml】

pom.properties里面的具体内容如下:

#Generated by Maven
#Wed Jul 5 13:12:07 CST 2018
version=0.0.1-SNAPSHOT
groupId=yyy(也就是公司名)
artifactId=xxx(也就是项目名)

 

SSM小项目-(10)-删除雇员模块及本项目总结

注意在目前了解到的所有的js操作中:
//只有下面的方法,查找有  点+类名 ,比如【.edit-btn】  这个算标签内的自定义属性
//其他地方,查找类名,都不需要添加 . 号 【原因就是  .  代表查找同类名的所有元素 】
$(document).on("click",".edit-btn",function(){      });

一、单个删除

1、前端代码

// delete-btn 这个是在一开始展示表格信息的时候,就已经添加了。
$(document).on("click",".delete-btn",function(){
			
	//弹出是否确认删除对话框
	//alert($(this).parents("tr").find("td:eq(1)").text());
         var empName = $(this).parents("tr").find("td:eq(1)").text()
	 var empId = $(this).attr("delete-id");
         if(confirm("确认删除【"+empName+"】")){
				
		$.ajax({
				url:"${APP_PATH}/emp/"+empId,
				type:"delete",
				success:function(result){
					alert(result.msg);
					to_page(g_currentPage);
				}
			});
	}
		    
		  
});

2、后端代码

EmployeeController.java

@ResponseBody
@RequestMapping(value="/emp/{id}",method=RequestMethod.DELETE)
public Msg deleteByEmpId(@PathVariable("id") int id){
	employeeService.deleteEmp(id);
	return Msg.success();
}

EmployeeService.java

/**
 * 根据id 删除用户
 * @param id
 */
public void deleteEmp(int id) {
	// TODO Auto-generated method stub
	employeeMapper.deleteByPrimaryKey(id);	
}

二、批量删除

1、前端代码

(1)添加checkbox  勾选功能

<!-- 显示表格数据 -->
<div class="row">
	<div class="col-md-12">
		<table class="table table-hover" id="emps_table">
			<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>
			<tbody>
				
			</tbody>
		</table>
	</div>
</div>
function build_emps_table(result) {
	//清空table表格,如果不请空,下一次请求时,数据会在原来的基础上,不断添加。
	$("#emps_table tbody").empty();
			
	var emps = result.extend.pageInfo.list;
	$.each(emps, function(index, item) {
		//alert(item.empName);
				
		//构建单元格
		var checkBoxTd = $("<td><input type='checkbox' class='check-item' /></td>");
		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">
			<i class="icon-edit icon-large"></i> 编辑
		</button>
		<button class="btn btn-danger btn-sm">
			<i class="icon-trash icon-large"></i> 删除
		</button>
		**/
	var editBtn = $("<button></button>").addClass("btn btn-primary btn-sm edit-btn")
			.append($("<i></i>").addClass("icon-edit icon-large")).append(" 编辑");
        //为编辑按钮添加一个自定义的属性,来表示当前员工id
        editBtn.attr("edit-id",item.empId);
                
        var delBtn =  $("<button></button>").addClass("btn btn-danger btn-sm delete-btn")
				.append($("<i></i>").addClass("icon-trash icon-large")).append(" 删除");
        //为删除按钮添加一个自定义的属性来表示当前删除的员工id
        delBtn.attr("delete-id",item.empId);
        var btnTd = $("<td></td>").append(editBtn).append(" ").append(delBtn);

                //append方法执行完成以后还是返回原来的元素
	$("<tr></tr>")
                .append(checkBoxTd)
                .append(empIdTd)
		.append(empNameTd)
		.append(genderTd)
		.append(emailTd)
		.append(deptNameTd)
		//.append(editBtn)
		//.append(delBtn)
		.append(btnTd)
		.appendTo("#emps_table tbody");
				
	});
}

(2)添加勾选逻辑

//完成全选、全不选功能
$("#check_all").click(function(){
			
	/***
	//获取id为p的标签的属性值
        $('#p').attr('class')
        //如果存在就是修改,如果不存在就是添加
        $('#p').attr('class','red')
        // 删除某个属性
        $('#p').removeAttr('name')
        //添加class名称
        $('#p').addClass('blue')
        //删除class名称
        $('#p').removeClass('yellow')
        //有class就删除 没有就添加
        $('#p').toggleClass('asdf')
        //val()获取输入框内容
        var s = $('input[name="user"]').val()
	***/
			
	// attr获取checked是undefined;
	// alert($(this).attr("checked"));  弹出的是 undefined
	// 用prop修改和读取dom原生属性的值;用attr获取自定义属性的值;这样就不容易出错			
	// alert($(this).prop("checked")); 
        // 可以正常弹出结果,也就是说:$(this).prop("checked") 返回是否选中的  true or false
	// 设置 是否 checked  $(this).prop("checked",true/false)
			
	//设置全选和全不选
	$(".check-item").prop("checked",$(this).prop("checked"));
});
		
//check_item 触发回调函数
$(document).on("click",".check-item",function(){
	//判断当前选择中的元素是否5个
	var flag = $(".check-item:checked").length==$(".check-item").length;
	$("#check_all").prop("checked",flag);
});
		
//点击全部删除,就是批量删除
$("#emp_delete_all_btn").click(function(){			
			
	var empNames ="";
	//组织员工id字符串
	var delete_ids_str="";
			
	$.each($(".check-item:checked"),function(){
		empNames = empNames+$(this).parents("tr").find("td:eq(2)").text() +",";
	        delete_ids_str += $(this).parents("tr").find("td:eq(1)").text() +"-";
	});
			
	//去除empNames多余的,
	empNames = empNames.substring(0,empNames.length-1);
	//去除删除的id的多余的-
	delete_ids_str = delete_ids_str.substring(0,delete_ids_str.length-1);
			
	if(confirm("确认删除【"+empNames+"】?")){
	//发送ajax请求
	 $.ajax({
			url:"${APP_PATH}/emp/"+delete_ids_str,
			type:"delete",
			success:function(result){
				alert(result.msg);
				to_page(g_currentPage);
			}
		});
	}
});

(3)后端处理逻辑

EmployeeController.java

 /**
  * 单个、批量 删除二合一,
  * 批量删除 1-2-3
  * 单个删除 1
  * 
  * @param ids
  * @return
  */
@ResponseBody
@RequestMapping(value="/emp/{ids}",method=RequestMethod.DELETE)
public Msg deleteByEmpId(@PathVariable("ids") String ids){
	//批量删除
	if(ids.contains("-")){
	       List<Integer> delete_id = new ArrayList<>() ;
               String[] str_ids = ids.split("-");
               for (String str :str_ids) {
            	   delete_id.add(Integer.parseInt(str));
		}
		employeeService.deleteBatch(delete_id);

	}else{
		int id = Integer.parseInt(ids);
		employeeService.deleteEmp(id);
	}
	return Msg.success();
}

EmployeeService.java

/**
 * 
 * @param ids
 */
public void deleteBatch(List<Integer> ids) {
	
        // TODO Auto-generated method stub
	EmployeeExample example = new EmployeeExample();
	Criteria criteria = example.createCriteria();
        //delete  from  xxx where emp_id in (1,2,3)
	criteria.andEmpIdIn(ids);
        employeeMapper.deleteByExample(example);
		
}

 

SSM小项目-(9)-修改雇员模块

注意在目前了解到的所有的js操作中:
//只有下面的方法,查找有  点+类名 ,比如【.edit-btn】   这个算标签内的自定义属性。
//其他地方,查找类名,都不需要添加 . 号  【原因就是  .  代表查找同类名的所有元素 】
$(document).on("click",".edit-btn",function(){      });

一、思路概述:

0、新建了一个  全局变量 当前页码信息,当保存提交时,显示的 还是当前页码。

1、get deps方法中   ajax 的异步和同步问题  获取部门请求,会影响更新操作前 获取员工信息,并赋值给 select 的dep 部门。两个都是 异步操作,容易出现提前赋值 select ,后在出现 select 部门选项

2、//2、发送ajax请求保存更新的员工数据   添加 _method
			$.ajax({
				url:"${APP_PATH}/emp/"+$(this).attr("edit-id"),
				type:"post",
				data:$("#empUpdateModal form").serialize()+"&_method=put",
				success:function(result){
				
这样做会来到,web.xml 中的过滤器

<!-- 使用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>



3、	@RequestMapping(value="/emp/{id}”,method=RequestMethod.PUT)
        改成
	@RequestMapping(value="/emp/{empId}",method=RequestMethod.PUT)

4、如果改成 ajax 的 put 方法,则不需要在data中添加 "&_method=put" ,但是  tomcat 无法封装 请求体,导致 报错。
* 原因:
	 * Tomcat:
	 * 		1、将请求体中的数据,封装一个map。
	 * 		2、request.getParameter("empName")就会从这个map中取值。
	 * 		3、SpringMVC封装POJO对象的时候。
	 * 				会把POJO中每个属性的值,request.getParamter("email");
	 * AJAX发送PUT请求引发的血案:
	 * 		PUT请求,请求体中的数据,request.getParameter("empName")拿不到
	 * 		Tomcat一看是PUT不会封装请求体中的数据为map,只有POST形式的请求才封装请求体为map
         *  查看下面的 tomcat 源码 解析  ,发现只有post 
	 * org.apache.catalina.connector.Request(类名)—parseParameters() (方法名)( 行号 3111);
	 * 
	 * protected String parseBodyMethods = "POST";
	 * if( !getConnector().isParseBodyMethod(getMethod()) ) {
                success = true;
                return;
            }
	 * 
	 * 
	 * 解决方案;
	 * 我们要能支持直接发送PUT之类的请求还要封装请求体中的数据
	 * 1、配置上HttpPutFormContentFilter;
	 * 2、他的作用;将请求体中的数据解析包装成一个map。
	 * 3、request被重新包装,request.getParameter()被重写,就会从自己封装的map中取数据
	 * 员工更新方法

二、添加模态框:

1、直接复制并修改之前的员工添加模态框:

		
<!-- 员工修改的模态框 -->
<div class="modal fade" id="empUpdateModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLongTitle">员工修改</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">

<form>
  <div class="form-group row">
    <label class="col-sm-3 col-form-label">empName</label>    
    <div class="col-sm-9">
      <input type="text" readonly class="form-control-plaintext" id="empName_update_input_disable" name="empName" value="none">
    </div>
    
  </div>
  
  <div class="form-group row">
    <label class="col-sm-3 col-form-label">email</label>
    <div class="col-sm-9">
      <input type="text"  class="form-control" id="email_update_input" name="email" placeholder="[email protected]">
          <div class="invalid-feedback"> 邮箱输入错误。</div>
    </div>

  </div>
  
  <div class="form-group row">
    <label  class="col-sm-3 col-form-label">gender</label>  
    <div class="col-sm-9">
<div class="form-check form-check-inline">
  <input class="form-check-input" type="radio" name="gender" id="gender1_update_input" checked value="M">
  <label class="form-check-label" >男</label>
</div>
<div class="form-check form-check-inline">
  <input class="form-check-input" type="radio" name="gender" id="gender2_update_input" value="F">
  <label class="form-check-label" >女</label>
</div>
    </div>  
  </div>
  
    <div class="form-group row">
    <label  class="col-sm-3 col-form-label">departName</label>  
    <div class="col-sm-6">
    <!-- 部门提交部门id即可 -->
    <select class="custom-select my-1 mr-sm-2" name="dId" id="dept_update_select">
    </select>
    </div>
    </div>
</form>

      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
        <button type="button" class="btn btn-primary" id="emp_update_done_btn">更新</button>
      </div>
    </div>
  </div>
</div>
		

2、添加点击出现模态框事件

注意绑定的时间,因为在生成button后,才能完成绑定。所以绑定的方法不是普通方法。[ edit-btn  这个伪类]

/*** 
1、我们是按钮创建之前就绑定了click,所以绑定不上。
$(".edit-btn").click(function(){
	alert("edit");
});
****/
//1)、可以在创建按钮的时候绑定。   
//2)、绑定点击  live() 方法,这个方法就算是后来添加的元素,也能绑定方法
/***
$(".edit-btn").live(function(){
	alert("edit");
});
***/
//但是新版jquery没有live方法,使用on方法进行替代
/*** 下面的写法 不对
$(".edit-btn").on("click",function(){
	alert("edit");
});
***/
// edit-btn 这个是在一开始展示表格信息的时候,就已经添加了。
//注意 这里是 .edit-btn   算标签内的自定义属性,只是这里使用时 有个点号
$(document).on("click",".edit-btn",function(){
			
	//0、清空表单样式和内容
	reset_form("#empUpdateModal form");
			
	//1、查出部门信息,并显示部门列表
	getDepts("#empUpdateModal select");
	//2、查询员工信息,并显示员工信息  // 坑爹 一开始写成了 attr(edit-id) 
	 getEmp($(this).attr("edit-id"));
			
	//3、把员工的id传递给模态框的更新按钮,为了发送ajax时传递id
	$("#emp_update_done_btn").attr("edit-id",$(this).attr("edit-id"));
			
	//alert("edit");
	$("#empUpdateModal").modal({
		backdrop:"static"
	});
			
			
});

3、将添加雇员信息用到的 getDepts() 方法,进行了抽取

async:false,   这里用到了同步请求方法,如果是异步方法,可能会出现第四步设置设置部门信息时,可能部门信息还没有获取到,就已经开始设置哪个部门是员工现在默认的了。

//1、查出部门信息,并显示部门列表
function getDepts(ele){
	$.ajax({
		url:"${APP_PATH}/depts",
		type:"GET",
		async:false, 
		success:function(result){
		     console.log(result);
	            //显示部门信息,在下拉列表中
	            // $("#dept_add_select") 换一种找法
	            //$("#empAddModal select")
	                
	            //清除之前留下的 option 标签
	             $(ele).empty();
	             $.each(result.extend.depts,function(){
	                     var optionEle = $("<option></option>").attr("value",this.deptId).append(this.deptName);
	                    optionEle.appendTo(ele);
	             });
		}
	});
}

4、显示员工信息方法

(1)前端请求代码:

function getEmp(id){
			
	$.ajax({
		url:"${APP_PATH}/emp/"+id,
		type:"get",
		success:function(result){
			console.log(result);
			var empData = result.extend.emp;
			$("#empName_update_input_disable").val(empData.empName);
			$("#email_update_input").val(empData.email);
			$("#empUpdateModal input[name=gender]").val([empData.gender]);
			$("#empUpdateModal select").val([empData.dId]);
		}
				
	})
}

(2)后端处理代码:

EmployeeController.java

/**
 * 根据id查询员工
 * @param id
 * @return
 */
@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.java

/**
 * 按照员工Id 查询员工
 * @param id
 * @return
 */
public Employee getEmp(Integer id) {
       // TODO Auto-generated method stub
       Employee employee = employeeMapper.selectByPrimaryKey(id);
       return employee;
}

三、保存员工修改信息

1、添加保存按钮点击事件

之前对前端框架不熟悉,忘记添加这句话了$("#empUpdateModal").modal("hide"); 
导致点击提交事件后,一直没有关闭模态框,我以为会自动关闭模态框,原因找了很久,后来才发现。

//点击更新,更新员工信息
$("#emp_update_done_btn").click(function(){
			
	console.log("提交更新方法---邮箱校验");		

	//验证邮箱是否合法
	if(!validate_form_ele("#email_update_input",g_email_reg,g_email_valid,g_email_invalid_format))
        return false;

			
	//2、发送ajax请求保存更新的员工数据
	$.ajax({
		url:"${APP_PATH}/emp/"+$(this).attr("edit-id"),
		type:"post",
		data:$("#empUpdateModal form").serialize()+"&_method=put",
		success:function(result){
			//alert(result);
			//console.log(result);
			if(result.code=100){
				//1、员工修改成功,需要关闭模态框。
				$("#empUpdateModal").modal("hide");
				//2、来到最后一页,显示刚才的数据;发送ajax请求,显示最后一页数据即可(用总记录数请求,保证是请求足够大,mybatis 分页插件 已经在mybatis-config.xml中配置了数据合法性校验,只会返回最后一页数据)
			        to_page(g_currentPage);
			}else{
				//后端校验  显示失败信息
				if(undefined!=result.extend.errorFileds.email){
		                 //显示邮箱错误信息
		    		show_validate_msg("#email_update_input","error",result.extend.errorFileds.email);
		                }
		        }
					
	      }//success 方法结束
							
			  
	});
});

 

特别注意:
1、to_page(g_currentPage); g_currentPage 是一个全局变量,我在显示分页信息时,将其赋了值。

2、保存时,ajax 发送的是post请求,然后用参数 data:$("#empUpdateModal form").serialize()+"&_method=put", 利用 web.xml的

<!-- 4、使用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、后端处理

EmployeeController.java

@ResponseBody
@RequestMapping(value="/emp/{empId}",method=RequestMethod.PUT)
public Msg saveEmp(Employee employee){
		
	System.out.println("将要更新的员工数据:"+employee);
	employeeService.updateEmp(employee);
	return Msg.success();
		
}

特别注意:value="/emp/{empId}"   ,不是 value="/emp/{id}"   因为empId要赋值给 employee 中的empId属性,前端的post 请求中,不包括empId。

前端put请求举例如下:
empName=adafaf&email=123123%4013.com&gender=F&dId=1&_method=put

EmployeeService.java

public void saveEmp(Employee employee) {
	// 下面这个方法 是全部插入 ,包括 连自增的id 都是
	// employeeMapper.insert(employee);
	// 下面这个方法是 有选择的插入,自增Id 被忽略 不会插入。
	employeeMapper.insertSelective(employee);
}

四、保存员工修改信息改良方法

1、前端直接用ajax的put请求:

$.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);
	}
});

2、后端解决方法

/**
 * 如果直接发送ajax=PUT形式的请求
 * 封装的数据
 * Employee 
 * [empId=1014, empName=null, gender=null, email=null, dId=null]
 * 
 * 问题:
 * 请求体中有数据;
 * 但是Employee对象封装不上;
 * update tbl_emp  where emp_id = 1014;
 * 
 * 原因:
 * Tomcat:
 * 		1、将请求体中的数据,封装一个map。
 * 		2、request.getParameter("empName")就会从这个map中取值。
 * 		3、SpringMVC封装POJO对象的时候。
 * 				会把POJO中每个属性的值,request.getParamter("email");
 * AJAX发送PUT请求引发的血案:
 * 		PUT请求,请求体中的数据,request.getParameter("empName")拿不到
 * 		Tomcat一看是PUT不会封装请求体中的数据为map,只有POST形式的请求才封装请求体为map
 * Tomcat 源码,查看 put 的处理逻辑
 * org.apache.catalina.connector.Request【类名】--parseParameters()【方法名】 (行号:3111);
 * 
 * protected String parseBodyMethods = "POST";
 * if( !getConnector().isParseBodyMethod(getMethod()) ) {
           success = true;
           return;
      }
 * 
 * 
 * 解决方案;
 * 我们要能支持直接发送PUT之类的请求还要封装请求体中的数据
 * 1、配置上HttpPutFormContentFilter;
 * 2、他的作用;将请求体中的数据解析包装成一个map。
 * 3、request被重新包装,request.getParameter()被重写,就会从自己封装的map中取数据
 * 员工更新方法
 * @param employee
 * @return
 */
	
@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);
	employeeService.updateEmp(employee);
	return Msg.success()	;
}

注意一下:需要在 web.xml中添加

<!-- 5、直接支持put请求的拦截器,即在put请求时将请求体数据封装成map,并让springmvc能够通过request.getParameter(属性名) 来获取数据 -->
<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>

 

 

 

 

 

SSM小项目-(8)-添加雇员模块之前端校验和后端校验

之前已经能实现添加雇员的功能了。但是我们还需要进行表单校验。
校验过程概述:

校验流程:
1、首先是前端校验,用户名和 邮箱  正则表达式规则
2、发送 ajax ,校验是否存在 用户名。
====
一、这个时候,出现一种情况:用户名 前端校验成功 满足大于6位,
但是数据库有重复。表单样式,显示错误了,但是还是能够保存。
提交保存按钮,提交前,必须要知道 当前 用户名是否 重名这个标记,
所以,就在每次检测用户名是否重名时,将结果作为保存按钮的属性。

二、如果第一次保存成功,第二次再打开时,信息没有变化,ajax没有去调用是否重复用户名,导致保存的按钮仍是激活的,这样仍能保存。所以,解决办法就是,每次打开清空表单内容。当然,第二次打开表单的时候,也残留了之前的样式,所以也需要清空表单样式。
====
三、 情况是,输入 aaa 发送ajax查询,数据库没有重复,就显示用户名可用。但是一提交,显示 用户名要大于6位 ,用户名提示信息由绿色变成了红色,导致用户无法保存。所以 ajax  请求不仅要查用户名重复,也要查名字 大于6位。 于是在ajax请求查用户名重复的服务器方法中,又添加了校验 用户名格式 的方法。 


四、上面这样做还会出现问题,如果输入aaabbb,告诉用户,用户名已存在,但用户仍点击提交按钮,还是会将表单红色提示的信息,替换成了绿色信息,虽然无法提交,因为保存按钮被冻结了。这种现象是因为,表单提交时,先前端进行了格式化校验,然后查看保存按钮的激活状态,最后发送保存请求。

其中,前端格式化校验,会发现用户名满足大于等于6位的要求,然后将表单信息由原来的用户名已存在红色信息改为了绿色,而格式化校验后,发现保存按钮被禁用,因为用户名重复。原因就在于,前端格式化校验不能同时进行用户名重复,导致红色错误提示被替换成了绿色信息。

解决办法心得:提交表单的时候,先校验 用户名是否是重复 的那个标记值,如果是false就直接返回不提交,这样就跳过了后面在对表单格式进行校验,这样做省的将错误信息:用户名已存在 ,通过表单格式校验 改成 用户名可用的状态。


五、 如果输入用户名张三,发现用户名不可用,就算现在保存按钮被冻结,但也可以禁用或修改js代码,或者修改表单属性,重新激活保存按钮,成功的将表单发送给服务器, 显然这样只能 防君子不防小人。

所以在错误信息发送到服务器时,我们仍要对错误信息进行校验,这就做后端校验。这样做用于校验表单格式和用户名重复两个功能。校验完后,用户要么保存要么不保存,总之都会发送信息给浏览器,告知用户是否保存成功。

校验心得总结:
在关键数据上,前端校验后,还需要后端校验,
jquery前端校验,ajax用户名重复校验,重要数据(后端校验(JSR303),唯一约束);前端校验 + 后端校验+ 数据库约束

jsr303校验规则,就是
1、添加jar包
2、变量中 添加定义注解,配置返回值
3、方法中形参 设置 注解和返回值

js表单重置学习:

//如果不请空,下一次请求时,数据会在原来的基础上,不断添加。

function reset_form(ele){
//重置表单内容
$(ele)[0].reset();
//清空表单样式
$(ele).find(“*”).removeClass(“hass-erroe has-success”);
$(eye).find(“.help-block”).text(“”);
}

一、前端校验[前端发起的校验]

大白话:

Bootstrap框架中, 表单对象的校验,首先只要给form对象 添加 wasvalidated 属性就行。此时表单会根据inputtype属性,自动校验,比如是否为空,又比如type=email,那么会检查 输入信息是否是email类型。这些是bootstrap,自己添加的。
一般我们不用,我们自己用代码校验并判断 input 是否合法,并给input标签 添加 is-invalidis-valid 属性,来表示是否合法。
表单中的:validfeedbackinvalidfeedback 是用来 展示校验结果信息的。

1、js校验表单格式:

遇到前端错误时,可以用 chrome 调试,记得打开开发者模式!

下面是插入 保存时添加校验 方法 ,
但是调用时 validate_add_form 方法忘记加括号了!要修改啊,坑爹。

//添加模态框 保存事件
$("#emp_add_save_btn").click(function(){
			
	if(!validate_add_form){
		//校验有误,失败
		return false;
	}
		
	//1.将模态框框中的数据提交给服务器进行保存
	//2.方式ajax请求,保存员工
	//alert($("#empAddModal form").serialize());
			
	 $.ajax({
		url:"${APP_PATH}/emp",
		type:"POST",
		data:$("#empAddModal form").serialize(), 
		success:function(result){
			//经过测试,服务器返回的是 json形式的 Msg对象, 到了浏览器中 变成了 result。所以: result.msg 相当于Msg对象里面的msg属性
			console.log(result);
			//alert(result.msg);
			//1、员工保存成功,需要关闭模态框。
			$("#empAddModal").modal("hide");
			//2、来到最后一页,显示刚才的数据;发送ajax请求,显示最后一页数据即可(用总记录数请求,保证是请求足够大,mybatis 分页插件 已经在mybatis-config.xml中配置了数据合法性校验,只会返回最后一页数据)
			to_page(totalRecords);
					
		}
	}); 
			
});

validate_add_form()方法实现:其中下面代码有错误:

var regemail = /^(a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/; if(!regEmail.test(emial)){...}
纠正:【regEmail 不是 regemailemail 不是 emial

//2、校验邮箱信息 var email = $("email_add_input").val(); 忘记添加#号了
正确的做法是:$("#email_add_input") 坑爹  !!!

邮箱的正则表达式也写错了,纠正一下:
      var regEmail = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;

//校验 添加员工信息 表单 合法性
function validate_add_form(){
	//拿到要校验的数据,使用正则表达式
			
	//1、校验用户信息
	//获取表单值
	var empName = $("#empName_add_input").val();  
	//编写正则表达式(英文字母 6到16个 或者 中文 2到5个)
	var regName = /(^[a-zA-Z0-9_-]{6,16}$)|(^[\u2E80-\u9FFF]{2,5})/;
	//校验正则表达式
	if(!regName.test(empName) ){
		// 弹窗校验 太丑 
		//alert("用户名可以是2-5位中文或者是6-16位英文或数字的组合");
		// $("#empName_add_input").addClass("is-invalid");
				
	        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(emial)){
		// 弹窗校验 太丑 
		//alert("邮箱格式不正确");
		//$("#email_add_input").addClass("is-invalid");
				
		show_validate_msg("#email_add_input","error","邮箱格式不正确");
		return false;
        }else{
		show_validate_msg("#email_add_input","success","");
	}
		
	return true;
}
		
		
//显示校验结果的提示信息
function show_validate_msg(ele,status,msg){
	//清除当前元素的校验状态
	$(ele).removeClass("is-invalid is-valid");
	$(ele).next("div").removeClass("valid-feedback  invalid-feedback").text("");
	if("success"==status){
		$(ele).addClass("is-valid");
		$(ele).next("div").addClass("valid-feedback").text(msg);
	}else if("error" == status){
		$(ele).addClass("is-invalid");
		$(ele).next("div").addClass("invalid-feedback").text(msg);
	}
}

2、js校验用户名是否重复

(1)添加表单变化监听事件:

index.jsp

//校验添加的用户名 是否可用。需要在 保存按钮提交前进行 判断,自定义属性留个记号,让保存按钮可用来判断 后端校验情况。
$("#empName_add_input").change(function(){
	//发送 ajax 请求,校验 用户名 是否可用。
	var 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","用户名可用");
				$("#emp_add_save_btn").attr("ajax-check-user","success");
			}else{
				show_validate_msg("#empName_add_input","error",result.extend.user_msg)
				$("#emp_add_save_btn").attr("ajax-check-user","error");	
			}
		}
				
	});
});

EmployeeController.java

/**
 * 检查用户名是否可用
 * 
 * @param empName
 * @return
 */
@ResponseBody
@RequestMapping("/checkUser")
public Msg checkUser(String empName) {
	
	boolean b = employeeService.checkUser(empName);
	if (b)
		return Msg.success().add("user_msg","用户名可用");
	else
		return Msg.fail().add("user_msg", "用户名不可用");
}

EmployeeService.java

/**
 * 检验用户名是否可用
 * 
 * @param empName
 * @return true:代表当前姓名可用 false:代表不可用
 */
public boolean checkUser(String empName) {

	EmployeeExample example = new EmployeeExample();
	Criteria criteria = example.createCriteria();
	criteria.andEmpNameEqualTo(empName);
	// 按照条件,统计符号条件的记录数
	long count = employeeMapper.countByExample(example);
	return count==0;
}

(2)添加模态框的重复性标记

//添加模态框 保存事件
$("#emp_add_save_btn").click(function(){
			
	//alert("校验测试");
			
	//1、判断之前的ajax用户名重复性校验是否成功。如果成功。
	if($(this).attr("ajax-check-user")=="error"){
		return false;
	}
.....

}

3、重新打开模态框bug

需要:

function reset_form(ele){
//重置表单内容
$(ele)[0].reset();
//清空表单样式
$(ele).find(“*”).removeClass(“hass-erroe has-success”);
$(eye).find(“.help-block”).text(“”);
}

4、在校验用户名重复中添加用户名格式校验

在EmployeeController.java中:

/**
 * 检查用户名是否可用
 * 
 * @param empName
 * @return
 */
@ResponseBody
@RequestMapping("/checkUser")
public Msg checkUser(String empName) {
	// 先判断用户名是否是合法的表达式;
	String regx = "(^[a-zA-Z0-9_-]{6,16}$)|(^[\u2E80-\u9FFF]{2,5})";
	if (!empName.matches(regx)) {
		return Msg.fail().add("user_msg", "用户名必须是6-16位数字和字母的组合或者2-5位中文");
	}

	boolean b = employeeService.checkUser(empName);
	if (b)
                return Msg.success().add("user_msg","用户名可用");
	else
		return Msg.fail().add("user_msg", "用户名不可用");
}

5、前端校验顺序调整

避免表单样式由红色错误信息变成绿色

//添加模态框 保存事件
$("#emp_add_save_btn").click(function(){
			
	//alert("校验测试");
			
	//1、判断之前的ajax用户名重复性校验是否成功。如果成功。
	if($(this).attr("ajax-check-user")=="error"){
		return false;
	}
			
	//2、前端校验表单格式
	if(!validate_add_form()){
		//校验有误,失败
		return false;
	}
			
		
	//发送ajax请求,如果前端校验被修改了,比如禁用js,修改js代码等
        //这时候需要后端校验,并将结果返回给浏览器	
	.....
			
});

二、对浏览器表单数据进行后端校验

1、导入jsr303校验规则的jar包,即在pom.xml中添加

<!--JSR303数据校验支持;tomcat7及以上的服务器, tomcat7以下的服务器:用的是旧的el.jar包,el表达式是旧标准的 ,需要额外给服务器的lib包中替换新的标准的el.jar包 -->
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-validator</artifactId>
	<version>5.4.1.Final</version>
</dependency>

2、对校验对象,添加注解

在Employee.java中:

package com.ssm.crud.bean;

import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.Email;

public class Employee {
    private Integer empId;
    /***
    //jsr303校验规则
    //@Pattern(regexp="(^[a-zA-Z0-9_-]{6,16}$)|(^[\u2E80-\u9FFF]{2,5})",message="")  
     * 这里的 \u3333 [3333是为了不报错才添加的]
     * java表达式是认识的 但是严格点还是要 \\u
    ***/
    @Pattern(regexp="(^[a-zA-Z0-9_-]{6,16}$)|(^[\\u2E80-\\u9FFF]{2,5})",message="用户名必须是6-16位数字和字母的组合或者2-5位中文")
    private String empName;

    private String gender;

    //jsr303校验规则
    //@Email 直接用这个注解也够了
    //@Pattern(regexp="^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$",message="")
    //在正则表达式中 \ 就是转义的,然后再java中 \ 也是转义的,所以需要java中的 \\ 来表示正则表达式中的一个 \
    @Pattern(regexp="^([a-z0-9_\\.-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$",message="邮箱格式不正确")
    private String email;

    private Integer dId;

    public Integer getEmpId() {
        return empId;
    }

    public void setEmpId(Integer empId) {
        this.empId = empId;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName == null ? null : empName.trim();
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender == null ? null : gender.trim();
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email == null ? null : email.trim();
    }

    public Integer getdId() {
        return dId;
    }

    public void setdId(Integer dId) {
        this.dId = dId;
    }
    
    //添加 Department 属性,用于关联查询
    private Department department;

	public Department getDepartment() {
		return department;
	}

	public void setDepartment(Department department) {
		this.department = department;
	}


	//生成自定义构造器 ,顺便 附带 无参构造器
	public Employee(Integer empId, String empName, String gender, String email, Integer dId) {
		super();
		this.empId = empId;
		this.empName = empName;
		this.gender = gender;
		this.email = email;
		this.dId = dId;
	}

	public Employee() {
		super();
	}

	@Override
	public String toString() {
		return "Employee [empId=" + empId + ", empName=" + empName + ", gender=" + gender + ", email=" + email
				+ ", dId=" + dId + ", department=" + department + "]";
	}
	
	
	
}

(3)方法形参中启用

在EmployeeController.java中:

/**
 * 员工保存
 * 1、支持JSR303校验
 * 2、导入Hibernate-Validator
 * 3、在bean的属性中 添加校验注解
 * 4、在调用方法参数中,写上 @Valid 启用校验功能,BindingResult 设置校验结果返回
 * @return
 */
@ResponseBody
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public Msg saveEmpWithJson(@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 {
		System.out.println(employee);
		employeeService.saveEmp(employee);
		return Msg.success();

	}
		
}

三、前端页面代码

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport"
	content="width=device-width, initial-scale=1, shrink-to-fit=no">

<title>员工列表</title>
<%
	pageContext.setAttribute("APP_PATH", request.getContextPath());
%>
<!-- web路径:
不以/开始的相对路径,找资源,以当前资源的路径为基准,经常容易出问题。
以/开始的相对路径,找资源,以服务器的路径为标准(http://localhost:8080);需要加上项目名
		http://localhost:8080/crud
 -->
<script type="text/javascript"
	src="${APP_PATH }/static/jquery-1.12.4/jquery.js"></script>
<link
	href="${APP_PATH }/static/bootstrap-4.1.3-dist/css/bootstrap.min.css"
	rel="stylesheet">
<script
	src="${APP_PATH }/static/bootstrap-4.1.3-dist/js/bootstrap.bundle.min.js"></script>

<link
	href="${APP_PATH }/static/font-awesome-3.2.1/css/font-awesome.min.css"
	rel="stylesheet">

</head>

<body>
	<div class="container">
		<!-- 标题 -->
		<div class="col-md-12">
			<h1>SSM-CRUD</h1>
		</div>

		<!-- 按钮 -->
		<div class="row">
			<div class="ml-auto mr-5">
				<button class="btn btn-primary btn-sm" id="emp_add_modal_btn">
					<i class="icon-file-alt icon-large"></i>  新增
				</button>
				<button class="btn btn-danger btn-sm">
					<i class="icon-trash icon-large"></i> 删除
				</button>
			</div>
		</div>
		
		<div class="text-center">
		<h5>测试列表</h5>
		</div>
		
		<!-- 员工添加的模态框 -->
<div class="modal fade" id="empAddModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLongTitle">员工添加</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">

<form>
  <div class="form-group row">
    <label class="col-sm-3 col-form-label">empName</label>
    <div class="col-sm-9">
      <input type="text"  class="form-control" id="empName_add_input"  name="empName"  placeholder="empName">
      <div class="invalid-feedback"> 用户名错误,请输入6-16个英文或2-5汉字。  </div>
    </div>
  </div>
  
  <div class="form-group row">
    <label class="col-sm-3 col-form-label">email</label>
    <div class="col-sm-9">
      <input type="text"  class="form-control" id="email_add_input" name="email" placeholder="[email protected]">
          <div class="invalid-feedback"> 邮箱输入错误。</div>
    </div>

  </div>
  
  <div class="form-group row">
    <label  class="col-sm-3 col-form-label">gender</label>  
    <div class="col-sm-9">
<div class="form-check form-check-inline">
  <input class="form-check-input" type="radio" name="gender" id="gender1_add_input" checked value="M">
  <label class="form-check-label" >男</label>
</div>
<div class="form-check form-check-inline">
  <input class="form-check-input" type="radio" name="gender" id="gender2_add_input" value="F">
  <label class="form-check-label" >女</label>
</div>
    </div>  
  </div>
  
    <div class="form-group row">
    <label  class="col-sm-3 col-form-label">departName</label>  
    <div class="col-sm-6">
    <!-- 部门提交部门id即可 -->
    <select class="custom-select my-1 mr-sm-2" name="dId" id="dept_add_select">
    </select>
    </div>
    </div>
</form>

      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
        <button type="button" class="btn btn-primary" id="emp_add_save_btn">保存</button>
      </div>
    </div>
  </div>
</div>
		
		
		
		

		<!-- 显示表格数据 -->
		<div class="row">
			<div class="col-md-12">
				<table class="table table-hover" id="emps_table">
					<thead>
						<tr>
							<th>#</th>
							<th>empName</th>
							<th>gender</th>
							<th>email</th>
							<th>deptName</th>
							<th>操作</th>
						</tr>
					</thead>
					<tbody>
					
					</tbody>
				</table>
			</div>
		</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">
		//定义 全局变量,总记录数
		var totalRecords ;
	
		//1、页面加载完成以后,直接去发送ajax请求,要到分页数据
		$(function(){
			//去首页
			to_page(1);
		});
		
		function to_page(pn){
			$.ajax({
				url:"${APP_PATH}/emps",
				data:"pn="+pn,
				type:"GET",
				success:function(result){
					//console.log(result);
					//1、解析并显示员工数据
					build_emps_table(result);
					//2、解析并显示分页信息
					build_page_info(result);
					//3、解析显示分页条数据
					build_page_nav(result);
				}
			});
		}
		
/* 		$(function() {
			$.ajax({
				url : "${APP_PATH}/emps",
				data : "pn=1",
				type : "GET",
				success : function(result) {
					//console.log(result) 
					//1、解析并显示员工信息
					build_emps_table(result);
					//2、解析并显示分页信息
					build_page_info(result);
					//3、解析显示分页条
					build_page_nav(result);

				}
			});
		}); */

		function build_emps_table(result) {
			//清空table表格,如果不请空,下一次请求时,数据会在原来的基础上,不断添加。
			$("#emps_table tbody").empty();
			
			var emps = result.extend.pageInfo.list;
			$.each(emps, function(index, item) {
				//alert(item.empName);
				
				//构建单元格
				var empIdTd = $("<td></td>").append(item.empId);
				var empNameTd = $("<td></td>").append(item.empName);
				var 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">
					<i class="icon-edit icon-large"></i> 编辑
				</button>
				<button class="btn btn-danger btn-sm">
					<i class="icon-trash icon-large"></i> 删除
				</button>
				**/
				var editBtn = $("<button></button>").addClass("btn btn-primary btn-sm edit-btn")
				.append($("<i></i>").addClass("icon-edit icon-large")).append(" 编辑");
                //为编辑按钮添加一个自定义的属性,来表示当前员工id
                editBtn.attr("edit-id",item.empId);
                var deleteBtn =  $("<button></button>").addClass("btn btn-danger btn-sm delete-btn")
				.append($("<i></i>").addClass("icon-trash icon-large")).append(" 删除");
                //为删除按钮添加一个自定义的属性来表示当前删除的员工id
                deleteBtn.attr("delete-id",item.empId);
                var btnTd = $("<td></td>").append(editBtn).append(" ").append(deleteBtn);

                //append方法执行完成以后还是返回原来的元素
				$("<tr></tr>")
                .append(empIdTd)
				.append(empNameTd)
				.append(genderTd)
				.append(emailTd)
				.append(deptNameTd)
				//.append(editBtn)
				//.append(deleteBtn)
				.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+"记录")
		     
					totalRecords = result.extend.pageInfo.total;
		}
		
		//解析显示分页条
		function build_page_nav(result) {
			//page_nav_area
			//请空之前的分页条,如果不请空,下一次请求时,数据会在原来的基础上,不断添加。
			$("#page_nav_area").empty();
			
			var ul = $("<ul></ul>").addClass("pagination");
			
			//构建元素
			var firstPageLi = $("<li></li>").addClass("page-item").append(  $("<a></a>").addClass("page-link").attr("href","#").append("首页")  );
			var prePageLi = $("<li></li>").addClass("page-item").append(  $("<a></a>").addClass("page-link").append("&laquo;")  );
		
			var lastPageLi = $("<li></li>").addClass("page-item").append(  $("<a></a>").addClass("page-link").attr("href","#").append("末页")   );
			var nextPageLi = $("<li></li>").addClass("page-item").append(  $("<a></a>").addClass("page-link").append("&raquo;")   );

			//添加首页和前一页 的提示
			ul.append(firstPageLi).append(prePageLi);
			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);
				});
			}
			
			//遍历页码号1,2,3等,给ul中添加页码提示
			$.each(result.extend.pageInfo.navigatepageNums,function(index,item){
				var numLi = $("<li></li>").addClass("page-item").append(  $("<a></a>").addClass("page-link").append(item)  );
				if(result.extend.pageInfo.pageNum == item){
					numLi.addClass("active");
				}
				
				numLi.click(function(){
					to_page(item);
				});
				
				ul.append(numLi);
			})
			
			//添加下一页和末页 的提示
			ul.append(nextPageLi).append(lastPageLi);
			if(result.extend.pageInfo.hasNextPage == false){
				nextPageLi.addClass("disabled");
				lastPageLi.addClass("disabled");
			}else{
				nextPageLi.click(function(){
					to_page(result.extend.pageInfo.pageNum +1);
				});
				lastPageLi.click(function(){
					to_page(result.extend.pageInfo.pages);
				});
			}
			
			//把ul加入到nav
			var navEle = $("<nav></nav>").append(ul);
			navEle.appendTo("#page_nav_area");
		}
		
		
		//情况表单信息 方法
		function reset_form(ele){
			
			//重置表单内容
			$(ele)[0].reset();
			
			//清空表单样式
			$(ele).find("*").removeClass("is-invalid is-valid");
			$(ele).find(".valid-feedback .invalid-feedback").text("");
			}
		
		//添加模态框的事件,一开始忘记添加#号了,emp_add_modal_btn
		$("#emp_add_modal_btn").click(function(){
			
			//清除表单数据(表单完整重置(表单的数据,表单的样式))
			reset_form("#empAddModal form");
			
			//发送ajax请求,弹出部门信息,显示在下拉列表中
			getDepts();
			
			//弹出模态框
			$("#empAddModal").modal({
				backdrop:"static"
			});
		});
		
		function getDepts(){
			$.ajax({
				url:"${APP_PATH}/depts",
				type:"GET",
				success:function(result){
					console.log(result);
	                //显示部门信息,在下拉列表中
	                // $("#dept_add_select") 换一种找法
	                //$("#empAddModal select")
	                
	                //清除之前留下的 option 标签
	                $("#empAddModal select").empty();
	                $.each(result.extend.depts,function(){
	                	var optionEle = $("<option></option>").attr("value",this.deptId).append(this.deptName);
	                    optionEle.appendTo("#empAddModal select");
	                });
				}
			});
		}
		
	
		
		//添加模态框 保存事件
		$("#emp_add_save_btn").click(function(){
			
			//alert("校验测试");
			
			//1、判断之前的ajax用户名重复性校验是否成功。如果成功。
			if($(this).attr("ajax-check-user")=="error"){
				return false;
			}
			
			//2、前端校验表单格式
			if(!validate_add_form()){
				//校验有误,失败
				return false;
			}
			
		
			
			//1.将模态框框中的数据提交给服务器进行保存
			//2.方式ajax请求,保存员工
			//alert($("#empAddModal form").serialize());
			
		  $.ajax({
				url:"${APP_PATH}/emp",
				type:"POST",
				data:$("#empAddModal form").serialize(), 
				success:function(result){
					//经过测试,服务器返回的是 json形式的 Msg对象, 到了浏览器中 变成了 result。所以: result.msg 相当于Msg对象里面的msg属性
					console.log(result);
					//alert(result.msg);
					if(result.code==100)
					{
						//1、员工保存成功,需要关闭模态框。
						$("#empAddModal").modal("hide");
						//2、来到最后一页,显示刚才的数据;发送ajax请求,显示最后一页数据即可(用总记录数请求,保证是请求足够大,mybatis 分页插件 已经在mybatis-config.xml中配置了数据合法性校验,只会返回最后一页数据)
						to_page(totalRecords);
					}else{
						//显示失败信息
						//console.log(result);
						//有哪个字段的错误信息,就显示哪个字段
						//alert(result.extend.errorFileds.email);
						//alert(result.extend.errorFileds.empName);
		                if(undefined!=result.extend.errorFileds.email){
		                	//显示邮箱错误信息
		    				show_validate_msg("#email_add_input","error",result.extend.errorFileds.email);
		                }
		                if(undefined!=result.extend.errorFileds.empName){
		                	//显示员工名字错误信息
		    				show_validate_msg("#emPName_add_input","error",result.extend.errorFileds.empName);
		                }
						
					}
					
					
				}
			}); 
			
		});
		
		
		//校验 添加员工信息 表单 合法性
		function validate_add_form(){
			//拿到要校验的数据,使用正则表达式
			
			//1、校验用户信息
			//获取表单值
			var empName = $("#empName_add_input").val();  
			//编写正则表达式(英文字母 6到16个 或者 中文 2到5个)
			//一开始正则表达式写错了,导致两个中文的名字无法保存 ,关键就是大括号的位置啊
			//var regName = /(^[a-zA-Z0-9_-]{6,16}$)|(^[\u2E80-\u9FFF]){2,5}/;
			var regName = /(^[a-zA-Z0-9_-]{6,16}$)|(^[\u2E80-\u9FFF]{2,5})/;

		    //校验正则表达式
			if(!regName.test(empName) ){
				// 弹窗校验 太丑 
				//alert("用户名可以是2-5位中文或者是6-16位英文或数字的组合");
			   // $("#empName_add_input").addClass("is-invalid");
				
			   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})$/;
		      var regEmail = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;

			if(!regEmail.test(email)){
				// 弹窗校验 太丑 
		    	//alert("邮箱格式不正确");
				//$("#email_add_input").addClass("is-invalid");
				
				show_validate_msg("#email_add_input","error","邮箱格式不正确");
		    	return false;
		    }else{
				show_validate_msg("#email_add_input","success","");
		    }
		
			return true;
		}
		
		
		//显示校验结果的提示信息
		function show_validate_msg(ele,status,msg){
			//清除当前元素的校验状态
			$(ele).removeClass("is-invalid is-valid");
			$(ele).next("div").removeClass("valid-feedback  invalid-feedback").text("");
			if("success"==status){
				$(ele).addClass("is-valid");
				$(ele).next("div").addClass("valid-feedback").text(msg);
			}else if("error" == status){
				$(ele).addClass("is-invalid");
				$(ele).next("div").addClass("invalid-feedback").text(msg);
			}
		}
		
		
		//校验添加的用户名 是否可用。需要在 保存按钮提交前进行 判断,自定义属性留个记号,让保存按钮可用来判断 后端校验情况。
		$("#empName_add_input").change(function(){
			//发送 ajax 请求,校验 用户名 是否可用。
			var 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","用户名可用");
					    $("#emp_add_save_btn").attr("ajax-check-user","success");
					}else{
						show_validate_msg("#empName_add_input","error",result.extend.user_msg)
					    $("#emp_add_save_btn").attr("ajax-check-user","error");	
					}
				}
				
			});
		});
		
	</script>

</body>
</html>

三、校验模块改进

(1)基本想法:

1、先前端校验-再后端校验

2、每个校验模块中都有校验优先顺序。
【根据具体情况判断,比如校验单个表单元素时,先本地后服务器,减少服务器压力,提交表单时,先判断之前服务器传回来的校验结果,然后再进行前端验证。】

2、表单变化时,应该用前端校验,表单提交时用后端校验。

(2)具体方法:

1、抽取了单个表单对象的校验方法,定义了全局变量【校验正则表达式,校验展示信息】

2、对每个表单对象,一旦变化就进行单独校验。

3、提交表单时,先判断之前服务器传回来的校验结果,然后再进行前端校验。

4、表单提交后,在进行服务端校验。

(3)修改后的网页代码:

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport"
	content="width=device-width, initial-scale=1, shrink-to-fit=no">

<title>员工列表</title>
<%
	pageContext.setAttribute("APP_PATH", request.getContextPath());
%>
<!-- web路径:
不以/开始的相对路径,找资源,以当前资源的路径为基准,经常容易出问题。
以/开始的相对路径,找资源,以服务器的路径为标准(http://localhost:8080);需要加上项目名
		http://localhost:8080/crud
 -->
<script type="text/javascript"
	src="${APP_PATH }/static/jquery-1.12.4/jquery.js"></script>
<link
	href="${APP_PATH }/static/bootstrap-4.1.3-dist/css/bootstrap.min.css"
	rel="stylesheet">
<script
	src="${APP_PATH }/static/bootstrap-4.1.3-dist/js/bootstrap.bundle.min.js"></script>

<link
	href="${APP_PATH }/static/font-awesome-3.2.1/css/font-awesome.min.css"
	rel="stylesheet">

</head>

<body>
	<div class="container">
		<!-- 标题 -->
		<div class="col-md-12">
			<h1>SSM-CRUD</h1>
		</div>

		<!-- 按钮 -->
		<div class="row">
			<div class="ml-auto mr-5">
				<button class="btn btn-primary btn-sm" id="emp_add_modal_btn">
					<i class="icon-file-alt icon-large"></i>  新增
				</button>
				<button class="btn btn-danger btn-sm">
					<i class="icon-trash icon-large"></i> 删除
				</button>
			</div>
		</div>
		
		<div class="text-center">
		<h5>测试列表</h5>
		</div>
		
		<!-- 员工添加的模态框 -->
<div class="modal fade" id="empAddModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLongTitle">员工添加</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">

<form>
  <div class="form-group row">
    <label class="col-sm-3 col-form-label">empName</label>
    <div class="col-sm-9">
      <input type="text"  class="form-control" id="empName_add_input"  name="empName"  placeholder="empName">
      <div class="invalid-feedback"> 用户名错误,请输入6-16个英文或2-5汉字。  </div>
    </div>
  </div>
  
  <div class="form-group row">
    <label class="col-sm-3 col-form-label">email</label>
    <div class="col-sm-9">
      <input type="text"  class="form-control" id="email_add_input" name="email" placeholder="[email protected]">
          <div class="invalid-feedback"> 邮箱输入错误。</div>
    </div>

  </div>
  
  <div class="form-group row">
    <label  class="col-sm-3 col-form-label">gender</label>  
    <div class="col-sm-9">
<div class="form-check form-check-inline">
  <input class="form-check-input" type="radio" name="gender" id="gender1_add_input" checked value="M">
  <label class="form-check-label" >男</label>
</div>
<div class="form-check form-check-inline">
  <input class="form-check-input" type="radio" name="gender" id="gender2_add_input" value="F">
  <label class="form-check-label" >女</label>
</div>
    </div>  
  </div>
  
    <div class="form-group row">
    <label  class="col-sm-3 col-form-label">departName</label>  
    <div class="col-sm-6">
    <!-- 部门提交部门id即可 -->
    <select class="custom-select my-1 mr-sm-2" name="dId" id="dept_add_select">
    </select>
    </div>
    </div>
</form>

      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
        <button type="button" class="btn btn-primary" id="emp_add_save_btn">保存</button>
      </div>
    </div>
  </div>
</div>
		
		
		
		

		<!-- 显示表格数据 -->
		<div class="row">
			<div class="col-md-12">
				<table class="table table-hover" id="emps_table">
					<thead>
						<tr>
							<th>#</th>
							<th>empName</th>
							<th>gender</th>
							<th>email</th>
							<th>deptName</th>
							<th>操作</th>
						</tr>
					</thead>
					<tbody>
					
					</tbody>
				</table>
			</div>
		</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">
		//定义 全局变量,总记录数
		var g_totalRecords ;
		var g_empName_reg =  /(^[a-zA-Z0-9_-]{6,16}$)|(^[\u2E80-\u9FFF]{2,5})/;
	    var g_empName_valid ="用户名可用";
	    var g_empName_invalid_format = "用户名可以是2-5位中文或者6-16位英文和数字的组合";
	    var g_empName_invalid_exist = "用户名已存在";
	    
		var g_email_reg =  /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;
	    var g_email_valid = "";
	    var g_email_invalid_format="邮箱格式不正确";
	    
	    
	    //获取表单值
		//var empName = $("#empName_add_input").val();  
		//编写正则表达式(英文字母 6到16个 或者 中文 2到5个)
		       //一开始正则表达式写错了,导致两个中文的名字无法保存 ,关键就是大括号的位置啊
		       //var regName = /(^[a-zA-Z0-9_-]{6,16}$)|(^[\u2E80-\u9FFF]){2,5}/;
		//var regName = /(^[a-zA-Z0-9_-]{6,16}$)|(^[\u2E80-\u9FFF]{2,5})/;
	    //show_validate_msg("#empName_add_input","error","用户名可以是2-5位中文或者6-16位英文和数字的组合");

		
		//校验邮箱信息
		//var email = $("#email_add_input").val();
		      // 正则表达式 看花眼了啊 少了 一个 [
		      //var regEmail = /^(a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;
	    //  var regEmail = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;  
	    // show_validate_msg("#email_add_input","error","邮箱格式不正确");
	    
		
		//表单内容和样式重置方法
		function reset_form(ele){
			//重置表单内容
			$(ele)[0].reset();
			
			//清空表单样式
			$(ele).find("*").removeClass("is-invalid is-valid");
			$(ele).find(".valid-feedback .invalid-feedback").text("");
			}
		
		
		//校验表单元素  参数:元素 和 正则表达式 ,提示信息
		function validate_form_ele(ele,reg,tip_success,tip_error){
			//拿到要校验的数据,使用正则表达式
			var ele_value = $(ele).val();
	       if( reg.test(ele_value)){
	    	   show_validate_msg(ele,"success",tip_success);
	    	   return true;
	       }else{
	    	   show_validate_msg(ele,"error",tip_error);
	    	   return false;
	       }
		}
		
		//显示校验结果的提示信息
		function show_validate_msg(ele,status,msg){
			//清除当前元素的校验状态
			$(ele).removeClass("is-invalid is-valid");
			$(ele).next("div").removeClass("valid-feedback  invalid-feedback").text("");
			if("success"==status){
				$(ele).addClass("is-valid");
				$(ele).next("div").addClass("valid-feedback").text(msg);
			}else if("error" == status){
				$(ele).addClass("is-invalid");
				$(ele).next("div").addClass("invalid-feedback").text(msg);
			}
		}
		
		
		//1、页面加载完成以后,直接去发送ajax请求,要到分页数据
		$(function(){
			//去首页
			to_page(1);
		});
		
		
		
		/********************公共方法上面*********************/
		
		function to_page(pn){
			$.ajax({
				url:"${APP_PATH}/emps",
				data:"pn="+pn,
				type:"GET",
				success:function(result){
					//console.log(result);
					//1、解析并显示员工数据
					build_emps_table(result);
					//2、解析并显示分页信息
					build_page_info(result);
					//3、解析显示分页条数据
					build_page_nav(result);
				}
			});
		}
		

		function build_emps_table(result) {
			//清空table表格,如果不请空,下一次请求时,数据会在原来的基础上,不断添加。
			$("#emps_table tbody").empty();
			
			var emps = result.extend.pageInfo.list;
			$.each(emps, function(index, item) {
				//alert(item.empName);
				
				//构建单元格
				var empIdTd = $("<td></td>").append(item.empId);
				var empNameTd = $("<td></td>").append(item.empName);
				var 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">
					<i class="icon-edit icon-large"></i> 编辑
				</button>
				<button class="btn btn-danger btn-sm">
					<i class="icon-trash icon-large"></i> 删除
				</button>
				**/
				var editBtn = $("<button></button>").addClass("btn btn-primary btn-sm edit_btn")
				.append($("<i></i>").addClass("icon-edit icon-large")).append(" 编辑");
                //为编辑按钮添加一个自定义的属性,来表示当前员工id
                editBtn.attr("edit-id",item.empId);
                var delBtn =  $("<button></button>").addClass("btn btn-danger btn-sm delete_btn")
				.append($("<i></i>").addClass("icon-trash icon-large")).append(" 删除");
                //为删除按钮添加一个自定义的属性来表示当前删除的员工id
                delBtn.attr("del-id",item.empId);
                var btnTd = $("<td></td>").append(editBtn).append(" ").append(delBtn);

                //append方法执行完成以后还是返回原来的元素
				$("<tr></tr>")
                .append(empIdTd)
				.append(empNameTd)
				.append(genderTd)
				.append(emailTd)
				.append(deptNameTd)
				//.append(editBtn)
				//.append(delBtn)
				.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+"记录")
		     
					g_totalRecords = result.extend.pageInfo.total;
		}
		
		//解析显示分页条
		function build_page_nav(result) {
			//page_nav_area
			//请空之前的分页条,如果不请空,下一次请求时,数据会在原来的基础上,不断添加。
			$("#page_nav_area").empty();
			
			var ul = $("<ul></ul>").addClass("pagination");
			
			//构建元素
			var firstPageLi = $("<li></li>").addClass("page-item").append(  $("<a></a>").addClass("page-link").attr("href","#").append("首页")  );
			var prePageLi = $("<li></li>").addClass("page-item").append(  $("<a></a>").addClass("page-link").append("&laquo;")  );
		
			var lastPageLi = $("<li></li>").addClass("page-item").append(  $("<a></a>").addClass("page-link").attr("href","#").append("末页")   );
			var nextPageLi = $("<li></li>").addClass("page-item").append(  $("<a></a>").addClass("page-link").append("&raquo;")   );

			//添加首页和前一页 的提示
			ul.append(firstPageLi).append(prePageLi);
			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);
				});
			}
			
			//遍历页码号1,2,3等,给ul中添加页码提示
			$.each(result.extend.pageInfo.navigatepageNums,function(index,item){
				var numLi = $("<li></li>").addClass("page-item").append(  $("<a></a>").addClass("page-link").append(item)  );
				if(result.extend.pageInfo.pageNum == item){
					numLi.addClass("active");
				}
				
				numLi.click(function(){
					to_page(item);
				});
				
				ul.append(numLi);
			})
			
			//添加下一页和末页 的提示
			ul.append(nextPageLi).append(lastPageLi);
			if(result.extend.pageInfo.hasNextPage == false){
				nextPageLi.addClass("disabled");
				lastPageLi.addClass("disabled");
			}else{
				nextPageLi.click(function(){
					to_page(result.extend.pageInfo.pageNum +1);
				});
				lastPageLi.click(function(){
					to_page(result.extend.pageInfo.pages);
				});
			}
			
			//把ul加入到nav
			var navEle = $("<nav></nav>").append(ul);
			navEle.appendTo("#page_nav_area");
		}
		
		/********************加载网页模块结束*********************/

		
		
        /********************添加用户信息模块*********************/
        
		//添加模态框的事件,一开始忘记添加#号了,emp_add_modal_btn
		$("#emp_add_modal_btn").click(function(){
			
			//清除表单数据(表单完整重置(表单的数据,表单的样式))
			reset_form("#empAddModal form");
			
			//发送ajax请求,弹出部门信息,显示在下拉列表中
			getDepts();
			
			//弹出模态框
			$("#empAddModal").modal({
				backdrop:"static"
			});
		});
		
		function getDepts(){
			$.ajax({
				url:"${APP_PATH}/depts",
				type:"GET",
				success:function(result){
					console.log(result);
	                //显示部门信息,在下拉列表中
	                // $("#dept_add_select") 换一种找法
	                //$("#empAddModal select")
	                
	                //清除之前留下的 option 标签
	                $("#empAddModal select").empty();
	                $.each(result.extend.depts,function(){
	                	var optionEle = $("<option></option>").attr("value",this.deptId).append(this.deptName);
	                    optionEle.appendTo("#empAddModal select");
	                });
				}
			});
		}
		
	
		
		//添加模态框 保存事件
		$("#emp_add_save_btn").click(function(){
			
			//alert("校验测试");
			
			//1、判断之前的ajax用户名重复性校验是否成功。如果成功。
			if($(this).attr("ajax-check-user")=="error"){
				return false;
			}
			
	
			if(! validate_form_ele("#empName_add_input",g_empName_reg,
					g_empName_valid,g_empName_invalid_format))	
				return false;
		
			if(! validate_form_ele("#email_add_input",g_email_reg,
					g_email_valid,g_email_invalid_format))	
				return false;
			
			//1.将模态框框中的数据提交给服务器进行保存
			//2.方式ajax请求,保存员工
			//alert($("#empAddModal form").serialize());
			
		  $.ajax({
				url:"${APP_PATH}/emp",
				type:"POST",
				data:$("#empAddModal form").serialize(), 
				success:function(result){
					//经过测试,服务器返回的是 json形式的 Msg对象, 到了浏览器中 变成了 result。所以: result.msg 相当于Msg对象里面的msg属性
					console.log(result);
					//alert(result.msg);
					if(result.code==100)
					{
						//1、员工保存成功,需要关闭模态框。
						$("#empAddModal").modal("hide");
						//2、来到最后一页,显示刚才的数据;发送ajax请求,显示最后一页数据即可(用总记录数请求,保证是请求足够大,mybatis 分页插件 已经在mybatis-config.xml中配置了数据合法性校验,只会返回最后一页数据)
						to_page(g_totalRecords);
					}else{
						//显示失败信息
						//console.log(result);
						//有哪个字段的错误信息,就显示哪个字段
						//alert(result.extend.errorFileds.email);
						//alert(result.extend.errorFileds.empName);
		                if(undefined!=result.extend.errorFileds.email){
		                	//显示邮箱错误信息
		    				show_validate_msg("#email_add_input","error",result.extend.errorFileds.email);
		                }
		                if(undefined!=result.extend.errorFileds.empName){
		                	//显示员工名字错误信息
		    				show_validate_msg("#emPName_add_input","error",result.extend.errorFileds.empName);
		                }
						
					}
					
					
				}
			}); 
			
		});
		
		
		//校验添加的用户名 是否可用。需要在 保存按钮提交前进行 判断,自定义属性留个记号,让保存按钮可用来判断 后端校验情况。
		$("#empName_add_input").change(function(){
			
			if(! validate_form_ele("#empName_add_input",g_empName_reg,
					g_empName_valid,g_empName_invalid_format))	
				return false;
		
			
			//发送 ajax 请求,校验 用户名 是否可用。
			var 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",g_empName_valid);
					    $("#emp_add_save_btn").attr("ajax-check-user","success");
					}else{
						show_validate_msg("#empName_add_input","error",result.extend.user_msg)
					    $("#emp_add_save_btn").attr("ajax-check-user","error");	
					}
				}
				
			});
		});
		
		//校验 email框
		$("#email_add_input").change(function(){
			//校验表单
			validate_form_ele("#email_add_input",g_email_reg,g_email_valid,g_email_invalid_format)
					
			});
		
		
	</script>

</body>
</html>

 

 

 

 

 

 

SSM小项目-(7)-添加雇员模块功能实现

一、明确需求

保存 Employee对象 :
需要 empName email gender dId   Id(因为是自增的所以不需要)

明确使用 rest 风格的操作方式 :
/depts  请求url
post 请求方式
数据存放在表单中,然后给springmvc  pojo 赋值到bean中。

二、实现添加用户模态框

1、在原来的添加用户按钮上绑定事件

原来的按钮:

<!-- 按钮 -->
<div class="row">
	<div class="ml-auto mr-5">
		<button class="btn btn-primary btn-sm" id="emp_add_modal_btn">
			<i class="icon-file-alt icon-large"></i>  新增
		</button>
		<button class="btn btn-danger btn-sm">
			<i class="icon-trash icon-large"></i> 删除
		</button>
	</div>
</div>

js绑定事件:

//添加模态框的事件,一开始忘记添加#号了,emp_add_modal_btn
$("#emp_add_modal_btn").click(function(){
	//发送ajax请求,弹出部门信息,显示在下拉列表中
	getDepts();
			
	//弹出模态框,static 代表 点击模态框 外面也不能关闭 模态框
	$("#empAddModal").modal({
		backdrop:"static"
	});
});

getDepts()方法是为了从服务器端获取,部门以便在下拉框中填充。

function getDepts(){
	$.ajax({
		url:"${APP_PATH}/depts",
		type:"GET",
		success:function(result){
			console.log(result);
	        //显示部门信息,在下拉列表中
	        // $("#dept_add_select") 换一种找法
	        //$("#empAddModal select")
                
                 //清除之前留下的 option 标签
                  $("#empAddModal select").empty();

	          $.each(result.extend.depts,function(){
	               var optionEle = $("<option></option>").attr("value",this.deptId).append(this.deptName);
	               optionEle.appendTo("#empAddModal select");
	           });
	       }
	});
}

2、服务器返回部门信息

新建 DepartmentController.java

package com.ssm.crud.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.ssm.crud.bean.Department;
import com.ssm.crud.service.DepartmentService;
import com.ssm.crud.util.Msg;

@Controller
public class DepartmentController {

	@Autowired
    DepartmentService departmentService;
	
	@ResponseBody
	@RequestMapping("/depts")
	public Msg getDepts(){
	    return Msg.success().add("depts", departmentService.getAll()) ;
	}
	
}

新建DepartmentService.java

package com.ssm.crud.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.ssm.crud.bean.Department;
import com.ssm.crud.mapper.DepartmentMapper;

@Service
public class DepartmentService {

	@Autowired
	DepartmentMapper departmentMapper;
	
	public List<Department> getAll(){
		
		return departmentMapper.selectByExample(null);
	}
	
}

Msg.java 之前已经提到过了

package com.ssm.crud.util;

import java.util.HashMap;
import java.util.Map;

/**
 * 通用的返回的类
 * 
 * @author lfy
 * 
 */
public class Msg {
	//状态码   100-成功    200-失败
	private int code;
	//提示信息
	private String msg;
	
	//返回给浏览器的数据
	private Map<String, Object> extend = new HashMap<String, Object>();

	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;
	}
	
	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;
	}
	
	
}

3、新建模态框并整合部门信息

<!-- 员工添加的模态框 -->
<div class="modal fade" id="empAddModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLongTitle">员工添加</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">

<form>
  <div class="form-group row">
    <label class="col-sm-3 col-form-label">empName</label>
    <div class="col-sm-9">
      <input type="text"  class="form-control" id="empName_add_input"  name="empName"  value="empName">
    </div>
  </div>
  
  <div class="form-group row">
    <label class="col-sm-3 col-form-label">email</label>
    <div class="col-sm-9">
      <input type="text"  class="form-control" id="email_add_input" name="email" value="[email protected]">
    </div>
  </div>
  
  <div class="form-group row">
    <label  class="col-sm-3 col-form-label">gender</label>  
    <div class="col-sm-9">
<div class="form-check form-check-inline">
  <input class="form-check-input" type="radio" name="gender" id="gender1_add_input" checked value="M">
  <label class="form-check-label" >男</label>
</div>
<div class="form-check form-check-inline">
  <input class="form-check-input" type="radio" name="gender" id="gender2_add_input" value="F">
  <label class="form-check-label" >女</label>
</div>
    </div>  
  </div>
  
    <div class="form-group row">
    <label  class="col-sm-3 col-form-label">departName</label>  
    <div class="col-sm-6">
    <!-- 部门提交部门id即可 -->
    <select class="custom-select my-1 mr-sm-2" name="dId" id="dept_add_select">
    </select>
    </div>
    </div>
</form>

      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
        <button type="button" class="btn btn-primary" id="emp_add_save_btn">保存</button>
      </div>
    </div>
  </div>
</div>

前面已经在js中 ,将ajax返回的结果填入到了select标签中了:如下

$.each(result.extend.depts,function(){
	var optionEle = $("<option></option>").attr("value",this.deptId).append(this.deptName);
	 optionEle.appendTo("#empAddModal select");
  });

三、实现保存员工功能

1、前端部分:用js给保存按钮添加ajax事件:

//添加模态框 保存事件
$("#emp_add_save_btn").click(function(){
			
	
			
	//1.将模态框框中的数据提交给服务器进行保存
	//方式ajax请求,保存员工
	//alert($("#empAddModal form").serialize());
			
	$.ajax({
		url:"${APP_PATH}/emp",
		type:"POST",
		data:$("#empAddModal form").serialize(), 
		success:function(result){
			//经过测试,服务器返回的是 json形式的 Msg对象, 到了浏览器中 变成了 result。所以: result.msg 相当于Msg对象里面的msg属性
			console.log(result);
			//alert(result.msg);
			//1、员工保存成功,需要关闭模态框。
			$("#empAddModal").modal("hide");
			//2、来到最后一页,显示刚才的数据;发送ajax请求,显示最后一页数据即可(用总记录数请求,保证是请求足够大,mybatis 分页插件 已经在mybatis-config.xml中配置了数据合法性校验,只会返回最后一页数据)
			to_page(totalRecords);
					
		}
	}); 
			
});

特别提醒:js中  totalRecords 变量,是在 js代码一开始就有的

<script type="text/javascript">
	//定义 全局变量,总记录数
	var totalRecords ;
	
	//1、页面加载完成以后,直接去发送ajax请求,要到分页数据
	$(function(){
		//去首页
		to_page(1);
	});
  。。。

然后在 显示页面信息时,被赋值:

$("#page_info_area").append("当前第"+result.extend.pageInfo.pageNum+
					"页,总共"+result.extend.pageInfo.pages+"页,总"+result.extend.pageInfo.total+"记录")
		     
					totalRecords = result.extend.pageInfo.total;

最后,在因为保存员工信息,跳转时,跳转到 最大记录数页:PageHelp 自动过滤最大页数

//添加模态框 保存事件
$("#emp_add_save_btn").click(function(){
			
			
	//1.将模态框框中的数据提交给服务器进行保存
	//方式ajax请求,保存员工
	//alert($("#empAddModal form").serialize());
			
	$.ajax({
		url:"${APP_PATH}/emp",
		type:"POST",
		data:$("#empAddModal form").serialize(), 
		success:function(result){
			//经过测试,服务器返回的是 json形式的 Msg对象, 到了浏览器中 变成了 result。所以: result.msg 相当于Msg对象里面的msg属性
			console.log(result);
			//alert(result.msg);
			//1、员工保存成功,需要关闭模态框。
			$("#empAddModal").modal("hide");
			//2、来到最后一页,显示刚才的数据;发送ajax请求,显示最后一页数据即可(用总记录数请求,保证是请求足够大,mybatis 分页插件 已经在mybatis-config.xml中配置了数据合法性校验,只会返回最后一页数据)
			to_page(totalRecords);
					
		}
	}); 
			
});

2、后端部分:

EmployeeController.java  【核心代码】

@Autowired
EmployeeService employeeService;

/**
 * 员工保存
 * 
 * @return
 */
@ResponseBody
@RequestMapping(value="/emp",method=RequestMethod.POST)
public  Msg saveEmpWithJson(Employee employee){
	System.out.println(employee);
	employeeService.saveEmp(employee);
	return Msg.success();
}

EmployeeService.java【核心代码】

@Autowired
EmployeeMapper employeeMapper;

public void saveEmp(Employee employee) {
        //下面这个方法 是全部插入 ,包括 连自增的id 都是
	//employeeMapper.insert(employee);
	//下面这个方法是 有选择的插入,自增Id 被忽略 不会插入。
	employeeMapper.insertSelective(employee);
}

 

SSM小项目-(6)-前端交互基于ajax请求方案

一、数据交互用的是json

所以springmvc 需要导入Jackson.jar 包,这样才能返回json数据。
Maven构建的项目,操作如下:在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.8.8</version>
</dependency>

实际加载的jar包如下:
jackson-annotations.jar
jackson-core.jar
jackson-databind.jar

二、编辑后端

消息对象:Msg.java

package com.ssm.crud.util;

import java.util.HashMap;
import java.util.Map;

/**
 * 通用的返回的类
 * 
 * @author lfy
 * 
 */
public class Msg {
	//状态码   100-成功    200-失败
	private int code;
	//提示信息
	private String msg;
	
	//返回给浏览器的数据
	private Map<String, Object> extend = new HashMap<String, Object>();

	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;
	}
	
	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;
	}
	
	
}

EmployeeController.java

package com.ssm.crud.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ssm.crud.bean.Employee;
import com.ssm.crud.mapper.EmployeeMapper;
import com.ssm.crud.service.employeeService;
import com.ssm.crud.util.Msg;

@Controller
public class EmployeeController {

	@Autowired
	employeeService employeeService;

	@Autowired
	EmployeeMapper employeeMapper;

	@RequestMapping("/emps")
	@ResponseBody
	public Msg getEmpsWithJson(@RequestParam(value = "pn", defaultValue = "1") Integer pn, Model model) {
		
		// 引入PageHelper分页插件
		//设置返回查询结果的排序规律(字段名 排序规律)
		PageHelper.orderBy("emp_id asc");
		// 在查询之前只需要调用,传入页码,以及每页的大小
		PageHelper.startPage(pn, 5);
		// startPage后面紧跟着的这个查询操作就是一个分页查询操作
		List<Employee> emps = employeeService.getAll();

		// 使用pageInfo包装查询后的结果,只需要将pageInfo交给页面就行了。
		// 封装了详细的分页信息,包括有我们查询出来的数据,传入连续显示的页数
		PageInfo<Employee> page = new PageInfo<Employee>(emps, 5);
		
        return Msg.success().add( "pageInfo" , page);		
	}
}

三、编辑前端

index.jsp

前端首先是 加载html 标签,然后加载 JavaScript 语句
js执行过程分三步:1、加载 table 数据 2、加载分页信息  3、加载分页栏。

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport"
	content="width=device-width, initial-scale=1, shrink-to-fit=no">

<title>员工列表</title>
<%
	pageContext.setAttribute("APP_PATH", request.getContextPath());
%>
<!-- web路径:
不以/开始的相对路径,找资源,以当前资源的路径为基准,经常容易出问题。
以/开始的相对路径,找资源,以服务器的路径为标准(http://localhost:3306);需要加上项目名
		http://localhost:3306/crud
 -->
<script type="text/javascript"
	src="${APP_PATH }/static/jquery-1.12.4/jquery.js"></script>
<link
	href="${APP_PATH }/static/bootstrap-4.1.3-dist/css/bootstrap.min.css"
	rel="stylesheet">
<script
	src="${APP_PATH }/static/bootstrap-4.1.3-dist/js/bootstrap.bundle.min.js"></script>

<link
	href="${APP_PATH }/static/font-awesome-3.2.1/css/font-awesome.min.css"
	rel="stylesheet">

</head>

<body>
	<div class="container">
		<!-- 标题 -->
		<div class="col-md-12">
			<h1>SSM-CRUD</h1>
		</div>

		<!-- 按钮 -->
		<div class="row">
			<div class="col-md-4 offset-md-8">
				<button class="btn btn-primary btn-sm">
					<i class="icon-edit icon-large"></i> 编辑
				</button>
				<button class="btn btn-danger btn-sm">
					<i class="icon-trash icon-large"></i> 删除
				</button>
			</div>
		</div>

		<!-- 显示表格数据 -->
		<div class="row">
			<div class="col-md-12">
				<table class="table table-hover" id="emps_table">
					<thead>
						<tr>
							<th>#</th>
							<th>empName</th>
							<th>gender</th>
							<th>email</th>
							<th>deptName</th>
							<th>操作</th>
						</tr>
					</thead>
					<tbody>
					
					</tbody>
				</table>
			</div>
		</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">
		//页面加载完成以后,直接发送ajax请求,拿到分页数据
	
		//1、页面加载完成以后,直接去发送ajax请求,要到分页数据
		$(function(){
			//去首页
			to_page(1);
		});
		
		function to_page(pn){
			$.ajax({
				url:"${APP_PATH}/emps",
				data:"pn="+pn,
				type:"GET",
				success:function(result){
					//console.log(result);
					//1、解析并显示员工数据
					build_emps_table(result);
					//2、解析并显示分页信息
					build_page_info(result);
					//3、解析显示分页条数据
					build_page_nav(result);
				}
			});
		}
		
/* 		$(function() {
			$.ajax({
				url : "${APP_PATH}/emps",
				data : "pn=1",
				type : "GET",
				success : function(result) {
					//console.log(result) 
					//1、解析并显示员工信息
					build_emps_table(result);
					//2、解析并显示分页信息
					build_page_info(result);
					//3、解析显示分页条
					build_page_nav(result);

				}
			});
		}); */

		function build_emps_table(result) {
			//清空table表格,如果不请空,下一次请求时,数据会在原来的基础上,不断添加。
			$("#emps_table tbody").empty();
			
			var emps = result.extend.pageInfo.list;
			$.each(emps, function(index, item) {
				//alert(item.empName);
				
				//构建单元格
				var empIdTd = $("<td></td>").append(item.empId);
				var empNameTd = $("<td></td>").append(item.empName);
				var 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">
					<i class="icon-edit icon-large"></i> 编辑
				</button>
				<button class="btn btn-danger btn-sm">
					<i class="icon-trash icon-large"></i> 删除
				</button>
				**/
				var editBtn = $("<button></button>").addClass("btn btn-primary btn-sm edit-btn")
				.append($("<i></i>").addClass("icon-edit icon-large")).append(" 编辑");
                //为编辑按钮添加一个自定义的属性,来表示当前员工id
                editBtn.attr("edit-id",item.empId);
                var deleteBtn =  $("<button></button>").addClass("btn btn-danger btn-sm delete-btn")
				.append($("<i></i>").addClass("icon-trash icon-large")).append(" 删除");
                //为删除按钮添加一个自定义的属性来表示当前删除的员工id
                deleteBtn.attr("delete-id",item.empId);
                var btnTd = $("<td></td>").append(editBtn).append(" ").append(deleteBtn);

                //append方法执行完成以后还是返回原来的元素
				$("<tr></tr>")
                .append(empIdTd)
				.append(empNameTd)
				.append(genderTd)
				.append(emailTd)
				.append(deptNameTd)
				//.append(editBtn)
				//.append(deleteBtn)
				.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
			//请空之前的分页条,如果不请空,下一次请求时,数据会在原来的基础上,不断添加。
			$("#page_nav_area").empty();
			
			var ul = $("<ul></ul>").addClass("pagination");
			
			//构建元素
			var firstPageLi = $("<li></li>").addClass("page-item").append(  $("<a></a>").addClass("page-link").attr("href","#").append("首页")  );
			var prePageLi = $("<li></li>").addClass("page-item").append(  $("<a></a>").addClass("page-link").append("&laquo;")  );
		
			var lastPageLi = $("<li></li>").addClass("page-item").append(  $("<a></a>").addClass("page-link").attr("href","#").append("末页")   );
			var nextPageLi = $("<li></li>").addClass("page-item").append(  $("<a></a>").addClass("page-link").append("&raquo;")   );

			//添加首页和前一页 的提示
			ul.append(firstPageLi).append(prePageLi);
			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);
				});
			}
			
			//遍历页码号1,2,3等,给ul中添加页码提示
			$.each(result.extend.pageInfo.navigatepageNums,function(index,item){
				var numLi = $("<li></li>").addClass("page-item").append(  $("<a></a>").addClass("page-link").append(item)  );
				if(result.extend.pageInfo.pageNum == item){
					numLi.addClass("active");
				}
				
				numLi.click(function(){
					to_page(item);
				});
				
				ul.append(numLi);
			})
			
			//添加下一页和末页 的提示
			ul.append(nextPageLi).append(lastPageLi);
			if(result.extend.pageInfo.hasNextPage == false){
				nextPageLi.addClass("disabled");
				lastPageLi.addClass("disabled");
			}else{
				nextPageLi.click(function(){
					to_page(result.extend.pageInfo.pageNum +1);
				});
				lastPageLi.click(function(){
					to_page(result.extend.pageInfo.pages);
				});
			}
			
			//把ul加入到nav
			var navEle = $("<nav></nav>").append(ul);
			navEle.appendTo("#page_nav_area");
		}
	</script>

</body>
</html>

 

 

SSM小项目-(5)-前端交互基于http请求方案

一、后端数据接口

EmployeeController.java

package com.ssm.crud.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ssm.crud.bean.Employee;
import com.ssm.crud.mapper.EmployeeMapper;
import com.ssm.crud.service.employeeService;

@Controller
public class EmployeeController {

	@Autowired 
	employeeService employeeService;
	
	@Autowired
	EmployeeMapper employeeMapper;
	
	/**
	 * 查询员工数据(分页查询)
	 * 
	 * @return
	 */
	@RequestMapping("/emps")
	public String getEmps( @RequestParam(value = "pn", defaultValue = "1") Integer pn, Model model) {
		
                // 引入PageHelper分页插件
		//设置返回查询结果的排序规律(字段名 排序规律)
		PageHelper.orderBy("emp_id asc");
		// 在查询之前只需要调用,传入页码,以及每页的大小
		PageHelper.startPage(pn, 5);
		// startPage后面紧跟着的这个查询操作就是一个分页查询操作
		List<Employee> emps = employeeService.getAll();
		
		
		// 使用pageInfo包装查询后的结果,只需要将pageInfo交给页面就行了。
		// 封装了详细的分页信息,包括有我们查询出来的数据,传入连续显示的页数
		PageInfo page = new PageInfo(emps, 5);
                //设置返回查询结果的排序规律(字段名 排序规律)
		PageHelper.orderBy("emp_id asc");

		model.addAttribute("pageInfo", page);

		/*****  一开始 EmployeeService 方法中 居然 返回的 null,忘记修改了 坑爹的教训啊 
		System.out.println("当前页码:"+page.getPageNum());
		System.out.println("总记录数:"+page.getTotal());
		System.out.println("每页的记录数:"+page.getPageSize());
		System.out.println("总页码:"+page.getPages());
		System.out.println("是否第一页:"+page.isIsFirstPage());
		System.out.println("连续显示的页码:");
		int[] nums = page.getNavigatepageNums();
		for (int i = 0; i < nums.length; i++) {
			System.out.println(nums[i]);
		}	
		******/
		return "list";
	}
	
}

从上面的数据接口我们可以看出,当http请求比如   /emp?pn=2    就能返回需要的信息了。  

二、编写结果返回页

请求地址:http://localhost:8080/ssm.crud/emps

结果返回:list.jsp  【因为是转发,不会显示结果页的url路径】

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport"
	content="width=device-width, initial-scale=1, shrink-to-fit=no">

<title>员工列表</title>
<%
	pageContext.setAttribute("APP_PATH", request.getContextPath());
%>
<!-- web路径:
不以/开始的相对路径,找资源,以当前资源的路径为基准,经常容易出问题。
以/开始的相对路径,找资源,以服务器的路径为标准(http://localhost:8080);需要加上项目名
		http://localhost:8080/ssm.crud
 -->
<script type="text/javascript"
	src="${APP_PATH }/static/jquery-1.12.4/jquery.js"></script>
<link
	href="${APP_PATH }/static/bootstrap-4.1.3-dist/css/bootstrap.min.css"
	rel="stylesheet">
<script
	src="${APP_PATH }/static/bootstrap-4.1.3-dist/js/bootstrap.bundle.min.js"></script>

<link
	href="${APP_PATH }/static/font-awesome-3.2.1/css/font-awesome.min.css"
	rel="stylesheet">

</head>

<body>
	<div class="container">
		<!-- 标题 -->
		<div class="col-md-12">
			<h1>SSM-CRUD</h1>
		</div>

		<!-- 按钮 -->
		<div class="row">
			<div class="col-md-4 offset-md-8">
				<button class="btn btn-primary btn-sm">
					<i class="icon-edit icon-large"></i> 编辑
				</button>
				<button class="btn btn-danger btn-sm">
					<i class="icon-trash icon-large"></i> 删除
				</button>
			</div>
		</div>

		<!-- 显示表格数据 -->
		<div class="row">
			<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">
									<i class="icon-edit icon-large"></i> 编辑
								</button>
								<button class="btn btn-danger btn-sm">
									<i class="icon-trash icon-large"></i> 删除
								</button>
							</th>
						</tr>
					</c:forEach>
				</table>
			</div>
		</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 example">
				<ul class="pagination">
					<li class="page-item"><a class="page-link"
						href="${APP_PATH}/emps?pn=1">首页</a></li>

					<c:if test="${pageInfo.hasPreviousPage}">
						<li class="page-item"><a class="page-link"
							href="${APP_PATH}/emps?pn=${pageInfo.pageNum-1}"
							aria-label="Previous"> <span aria-hidden="true">&laquo;</span><span
								class="sr-only">Previous</span></a></li>
					</c:if>
					<!-- jstl 标签 获取的都是用 .属性  不能用 get属性方法,否则会报错-->
					<c:forEach items="${pageInfo.navigatepageNums}" var="page_Num">
						<c:choose>
							<c:when test="${pageInfo.pageNum == page_Num }">
								<li class="page-item active"><a class="page-link" href="#">${page_Num}</a></li>
							</c:when>
							<c:otherwise>
								<li class="page-item"><a class="page-link"
									href="${APP_PATH}/emps?pn=${page_Num}">${page_Num}</a></li>
							</c:otherwise>
						</c:choose>
					</c:forEach>

					<c:if test="${pageInfo.hasNextPage}">
						<li class="page-item"><a class="page-link"
							href="${APP_PATH}/emps?pn=${pageInfo.pageNum+1}"
							aria-label="Next"> <span aria-hidden="true">&raquo;</span> <span
								class="sr-only">Next</span>
						</a></li>
					</c:if>
					<li class="page-item"><a class="page-link"
						href="${APP_PATH}/emps?pn=${pageInfo.pages }">末页</a></li>
				</ul>
				</nav>
			</div>
		</div>
	</div>


</body>
</html>

 

SSM小项目-(4)-搭建Bootstrap前端框架

前端框架搭建

一、下载bootsrap框架

本项目采用的是V4版本,V4版本和V3有点不同,V4版本移除了图库。所以我们需要自己导入其他图库。

Bootstrap框架需要 jQuery.js,同时V4版本还需要 Popper.js。Our bootstrap.bundle.js and bootstrap.bundle.min.js include Popper, but not jQuery.

下载 Bootstrap 后,我们可以看到只有两个文件夹,一个是 js文件夹,一个是css文件夹。使用时只需要引入:JQuery.js , bootstrap.bundle.min.js,bootstrap.min.css就可以了。

二、下载图标

Bootstrap doesn’t include an icon library by default, but we have a handful of recommendations for you to choose from. While most icon sets include multiple file formats, we prefer SVG implementations for their improved accessibility and vector support.

Preferred

We’ve tested and used these icon sets ourselves.

More options

While we haven’t tried these out, they do look promising and provide multiple formats—including SVG.

这里以  Font Awesome 举例,此处代码下载 ,此处使用说明 。
Font Awesome 总结来说:就是将下载好的 font 文件夹 拿出来,并从 css 文件夹中拿出 font-awesome.min.css 将这两个拿到 web项目中,并且在  font-awesome.min.css中编辑 font文件夹 所在路径。使用时,只需要引入 font-awesome.min.css 就行了。

我用的是 3.0版本的 Font Awesome,此处下载代码 ,框架引用方式和最新版的相同。只是在代码使用上有所不同:
3.0版本  <i class="icon-camera-retro"></i> 调用照相机图标。
5.4版本 <i class="fas fa-ghost"></i> 调用幽灵图标。

The fa prefix has been deprecated in version 5. The new default is the fas solid style and the fabstyle for brands.

三、前端框架整合概览

四、编写测试网页

index.jsp

[请求地址:http://localhost:8080/ssm.crud/index.jsp]

<%@ page language="java" import="java.util.*"
	contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Bootstrap 概述</title>
<%
	pageContext.setAttribute("APP_PATH", request.getContextPath());
%>
<!-- web路径:
不以/开始的相对路径,找资源,以当前资源的路径为基准,经常容易出问题。
以/开始的相对路径,找资源,以服务器的路径为标准(http://localhost:8080);需要加上项目名
		http://localhost:8080/crud
 -->
<script type="text/javascript"
	src="${APP_PATH }/static/jquery-1.12.4/jquery.js"></script>
<link
	href="${APP_PATH }/static/bootstrap-4.1.3-dist/css/bootstrap.min.css"
	rel="stylesheet">
<script
	src="${APP_PATH }/static/bootstrap-4.1.3-dist/js/bootstrap.bundle.min.js"></script>

<link
	href="${APP_PATH }/static/font-awesome-3.2.1/css/font-awesome.min.css"
	rel="stylesheet">

</head>
<body>
	<%
		/***  一开始,不写注释,想靠 <!-- -->将jsp跳转标签注释掉,发现没有效果啊,坑爹
		<!-- 
		<jsp:forward page="/emps"></jsp:forward>   
		--> ***/
	%>

	<div class="container">

		<!-- 标题 -->
		<div class="col-md-12">
			<h1>Bootstrap 是行列式布局 ,一行有12列。</h1>
		</div>

		<!-- 按钮 -->
		<div class="row">
			<!-- 布局占了4列,偏移8列 -->
			<div class="col-md-4 offset-md-8 ">
				<div class="bg-info">
					<h5>测试偏移</h5>
				</div>
			</div>
		</div>

		<hr />

		<nav class="navbar navbar-expand-lg navbar-light bg-light"> <a
			class="navbar-brand" href="#">Navbar</a>
		<button class="navbar-toggler" type="button" data-toggle="collapse"
			data-target="#navbarSupportedContent"
			aria-controls="navbarSupportedContent" aria-expanded="false"
			aria-label="Toggle navigation">
			<span class="navbar-toggler-icon"></span>
		</button>

		<div class="collapse navbar-collapse" id="navbarSupportedContent">
			<ul class="navbar-nav mr-auto">
				<li class="nav-item active"><a class="nav-link" href="#">Home
						<span class="sr-only">(current)</span>
				</a></li>
				<li class="nav-item"><a class="nav-link" href="#">Link</a></li>
				<li class="nav-item dropdown"><a
					class="nav-link dropdown-toggle" href="#" id="navbarDropdown"
					role="button" data-toggle="dropdown" aria-haspopup="true"
					aria-expanded="false"> Dropdown </a>
					<div class="dropdown-menu" aria-labelledby="navbarDropdown">
						<a class="dropdown-item" href="#">Action</a> <a
							class="dropdown-item" href="#">Another action</a>
						<div class="dropdown-divider"></div>
						<a class="dropdown-item" href="#">Something else here</a>
					</div></li>
				<li class="nav-item"><a class="nav-link disabled" href="#">Disabled</a>
				</li>
			</ul>
			<form class="form-inline my-2 my-lg-0">
				<input class="form-control mr-sm-2" type="search"
					placeholder="Search" aria-label="Search">
				<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
			</form>
		</div>
		</nav>

		<hr />

		<div>
			<h6>右对齐的关键属性是==> margin-left:auto 这样子浏览器会尽可能增加右边距离</h6>
		</div>

		<hr />

		<div class="row">
			<!-- 右对齐的方式,利用了 ml-auto ==> margin-left:auto ,
		     这样子浏览器解析的时候,会尽可能的让布局多占空间的,所以变成右对齐了
		     mr-3 ==> margin-right:3*bootstrap的基本单位。
		     mx-auto ==>代表这个单位对左右两边的距离都是自动的,这个单位在父类看来是居中的。
		      -->
			<div class="ml-auto mr-3">
				<!-- 按钮样式,直接加类就行 -->
				<button class="btn btn-primary btn-sm ">
					<!-- 图标是加载了 font-awesome 的 -->
					<i class="icon-edit icon-large"></i> 编辑
				</button>
				<button class="btn btn-danger btn-sm ">
					<i class="icon-trash icon-large"></i> 删除
				</button>
			</div>


		</div>
		<hr />

		<div class="row">
            <!-- text-center 代表在本对象看来, 自己内部对象是居中的 -->
			<div class="col-md-12  text-center">
				<h5>
					所有的布局组件:如按钮,表单,表格,分页等组件,都有demo,可以直接复制粘贴拿来 <span
						class="badge badge-secondary">修改使用</span>
				</h5>
			</div>

		</div>


		<table class="table table-striped  table-hover">
			<thead>
				<tr>
					<th scope="col">#</th>
					<th scope="col">First</th>
					<th scope="col">Last</th>
					<th scope="col">Handle</th>
				</tr>
			</thead>
			<tbody>
				<tr>
					<th scope="row">1</th>
					<td>Mark</td>
					<td>Otto</td>
					<td>@mdo</td>
				</tr>
				<tr>
					<th scope="row">2</th>
					<td>Jacob</td>
					<td>Thornton</td>
					<td>@fat</td>
				</tr>
				<tr>
					<th scope="row">3</th>
					<td>Mark</td>
					<td>Otto</td>
					<td>@mdo</td>
				</tr>
			</tbody>
		</table>


		<nav aria-label="Page navigation example">
		<ul class="pagination justify-content-end">
			<li class="page-item disabled"><a class="page-link" href="#"
				tabindex="-1">Previous</a></li>
			<li class="page-item"><a class="page-link" href="#">1</a></li>
			<li class="page-item"><a class="page-link" href="#">2</a></li>
			<li class="page-item"><a class="page-link" href="#">3</a></li>
			<li class="page-item"><a class="page-link" href="#">Next</a></li>
		</ul>
	</div>

	<hr />


	<div class="row">

		<div class="col-md-8 mx-auto">
			<div class="bg-info text-center">
				<p >表单对象的校验,首先只要给form对象 添加 was-validated 属性就行,
				此时表单会根据 input的type属性,自动校验,比如是否为空,比如type=email,那么会检查是否是email。
				这些是bootstrap,自己添加的。一般我们不用,我们自己校验,判断 input 合法吧,
				并给input标签 添加 is-invalid 或 is-valid 属性,来表示是否合法。
				表单中的:valid-feedback 和 invalid-feedback 是用来 展示校验结果信息的
				</p>
			</div>
			
			
			<div>
			<form class="needs-validation" novalidate>
			<div class="form-row">
				<div class="col-md-4 mb-3">
					<label for="validationCustom01">First name</label> <input
						type="text" class="form-control" id="validationCustom01"
						placeholder="First name" value="Mark" required>
					<div class="valid-feedback">Looks good!</div>
				</div>
				<div class="col-md-4 mb-3">
					<label for="validationCustom02">Last name</label> <input
						type="text" class="form-control" id="validationCustom02"
						placeholder="Last name" value="Otto" required>
					<div class="valid-feedback">Looks good!</div>
				</div>
				<div class="col-md-4 mb-3">
					<label for="validationCustomUsername">Username</label>
					<div class="input-group">
						<div class="input-group-prepend">
							<span class="input-group-text" id="inputGroupPrepend">@</span>
						</div>
						<input type="text" class="form-control"
							id="validationCustomUsername" placeholder="Username"
							aria-describedby="inputGroupPrepend" required>
						<div class="invalid-feedback">Please choose a username.</div>
					</div>
				</div>
			</div>
			<div class="form-row">
				<div class="col-md-6 mb-3">
					<label for="validationCustom03">City</label> <input type="text"
						class="form-control" id="validationCustom03" placeholder="City"
						required>
					<div class="invalid-feedback">Please provide a valid city.</div>
				</div>
				<div class="col-md-3 mb-3">
					<label for="validationCustom04">State</label> <input type="text"
						class="form-control" id="validationCustom04" placeholder="State"
						required>
					<div class="invalid-feedback">Please provide a valid state.</div>
				</div>
				<div class="col-md-3 mb-3">
					<label for="validationCustom05">Zip</label> <input type="text"
						class="form-control" id="validationCustom05" placeholder="Zip"
						required>
					<div class="invalid-feedback">Please provide a valid zip.</div>
				</div>
			</div>
			<div class="form-group">
				<div class="form-check">
					<input class="form-check-input" type="checkbox" value=""
						id="invalidCheck" required> <label
						class="form-check-label" for="invalidCheck"> Agree to
						terms and conditions </label>
					<div class="invalid-feedback">You must agree before
						submitting.</div>
				</div>
			</div>
			<button class="btn btn-primary" type="submit">Submit form</button>
		</form>
			
			</div>
		</div>
	
	</div>
	
	
	<script>
		// Example starter JavaScript for disabling form submissions if there are invalid fields
		(function() {
			'use strict';
			window.addEventListener('load',
					function() {
						// Fetch all the forms we want to apply custom Bootstrap validation styles to
						var forms = document
								.getElementsByClassName('needs-validation');
						// Loop over them and prevent submission
						var validation = Array.prototype.filter.call(forms,
								function(form) {
									form.addEventListener('submit', function(
											event) {
										if (form.checkValidity() === false) {
											event.preventDefault();
											event.stopPropagation();
										}
										form.classList.add('was-validated');
									}, false);
								});
					}, false);
		})();
	</script>

</body>
</html>

 

 

SSM小项目-(3)-模拟网页请求进行后端单元测试

一、加载log4j框架,方便调试

前面的文章已经将SSM环境搭建好了,我们现在开始写过网页,测试一下。
为了测试方便:我们加载log4j框架,用于查看错误日志。【框架需要jar包和配置文件】

pom.xml中配置:

<!-- 这个是 最新版2.x 和 1.x 版本 的配置文件 不兼容 https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core 
	<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> 
	<version>2.11.1</version> </dependency> -->
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
	<groupId>log4j</groupId>
	<artifactId>log4j</artifactId>
	<version>1.2.17</version>
</dependency>

log4j.xml 配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
 
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
 
 <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
   <param name="Encoding" value="UTF-8" />
   <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m  (%F:%L) \n" />
   </layout>
 </appender>
 <logger name="java.sql">
   <level value="debug" />
 </logger>
 <logger name="org.apache.ibatis">
   <level value="info" />
 </logger>
 <root>
   <level value="debug" />
   <appender-ref ref="STDOUT" />
 </root>
</log4j:configuration>

web工程,只要放在项目根目录就行了,目的是打包后在class文件的根目录中,这样就能自动加载了。如果不想自动加载,则可以将配置文件改个名或者移动到其他目录,并在代码中指明 log4j配置文件 的路径。参看java工具-log4j框架-基本介绍和文件配置及框架加载调用

二、编写测试请求页

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<jsp:forward page="/emps"></jsp:forward>

一开始写错成了:jsp:fowward 导致报错Invalid standard action 要注意了啊。

三、编写请求处理

EmployeeController.java

package com.ssm.crud.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ssm.crud.bean.Employee;
import com.ssm.crud.mapper.EmployeeMapper;
import com.ssm.crud.service.employeeService;

@Controller
public class EmployeeController {

	@Autowired 
	employeeService employeeService;
	
	@Autowired
	EmployeeMapper employeeMapper;
	
	/**
	 * 查询员工数据(分页查询)
	 * 
	 * @return
	 */
	@RequestMapping("/emps")
	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);

		/*****  一开始 EmployeeService 方法中 居然 返回的 null,忘记修改了 坑爹的教训啊 
		System.out.println("当前页码:"+page.getPageNum());
		System.out.println("总记录数:"+page.getTotal());
		System.out.println("每页的记录数:"+page.getPageSize());
		System.out.println("总页码:"+page.getPages());
		System.out.println("是否第一页:"+page.isIsFirstPage());
		System.out.println("连续显示的页码:");
		int[] nums = page.getNavigatepageNums();
		for (int i = 0; i < nums.length; i++) {
			System.out.println(nums[i]);
		}	
		******/
		return "list";
	}
	
}

EmployeeService.java

package com.ssm.crud.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.ssm.crud.bean.Employee;
import com.ssm.crud.mapper.EmployeeMapper;

@Service
public class employeeService {

	@Autowired
	EmployeeMapper employeeMapper;
	
	public  List<Employee> getAll() {
                // 这个地方坑爹啊,一开始 返回了 null,忘记把 结果返回了
		return employeeMapper.selectByExampleWithDept(null);
		
	}

}

四、编写结果返回页

list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>员工列表</title>
</head>
<body>

</body>
</html>

五、模拟网页请求的单元测试

在测试当中,会遇到 校验错误:

INFO  10-13 15:56:27,762 HV000001: Hibernate Validator 5.4.1.Final  (Version.java:30) 
DEBUG 10-13 15:56:27,785 Cannot find javax.persistence.Persistence on classpath. Assuming non JPA 2 environment. All properties will per default be traversable.  (DefaultTraversableResolver.java:70) 
DEBUG 10-13 15:56:27,796 Failed to set up a Bean Validation provider  (OptionalValidatorFactoryBean.java:43) 
javax.validation.ValidationException: HV000183: Unable to initialize 'javax.el.ExpressionFactory'. Check that you have the EL dependencies on the classpath, or use ParameterMessageInterpolator instead
	at 
。。。。。。
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: java.lang.NoClassDefFoundError: javax/el/ExpressionFactory
	at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.buildExpressionFactory(ResourceBundleMessageInterpolator.java:98)
	... 46 more

看log日志,是缺少 el jar包,于是在pom中添加

<dependency>
	<groupId>javax.el</groupId>
	<artifactId>el-api</artifactId>
	<version>2.2</version>
	<scope>provided</scope>
</dependency>

<dependency>
	<groupId>org.glassfish.web</groupId>
	<artifactId>el-impl</artifactId>
	<version>2.2</version>
	<scope>test</scope>
</dependency>

Juint单元测试:【勘误,现在回来重新说明一下:maven的web工程的网页和资源应该放在webapp目录下,所以下面的file:WebContent/WEB-INF/springMVC-servlet.xml 应该替换成 file:src/main/webapp/WEB-INF/springMVC-servlet.xml

package com.ssm.crud.test;

import static org.junit.Assert.*;

import java.util.List;

import org.apache.log4j.xml.DOMConfigurator;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import com.github.pagehelper.PageInfo;
import com.ssm.crud.bean.Employee;

/**
 * 使用Spring测试模块提供的测试请求功能,测试curd请求的正确性 Spring4测试的时候,需要servlet3.0的支持
 * 
 * @author lfy
 *    当我构建好Maven的web项目时,我发现 有两个 放 web 文件的 地方,我选择将网页文件放在 WebContent 文件夹中
 *    file:WebContent/WEB-INF/springMVC-servlet.xml 【WebContent下面有网页文件】
 *    file:src/main/webapp/WEB-INF/springMVC-servlet.xml 【src/main/webapp下面什么文件都没有】
 */

//@RunWith这个是 junit-4.jar包 提供的注解, 而 SpringJUnit4ClassRunner.class 是spring-test 提供的类
@RunWith(SpringJUnit4ClassRunner.class)
//@WebAppConfiguration  这个是 spring-test.jar包 提供的注解, 用于根据 web.xml的配置,注入 SpringMVC 容器,使SpringMVC容器不需要创建 ,直接获取就行。
@WebAppConfiguration
//@ContextConfiguration 这个是 spring-test.jar包 提供的注解 , 用于启动 spring容器,方便直接获取spring容器中的bean
@ContextConfiguration(locations = { "classpath:springIOC.xml", "file:WebContent/WEB-INF/springMVC-servlet.xml" })
public class SpringMVCTest {

	
	@Autowired// 传入Springmvc的ioc,因为容器内部的bean可以自动注入,但SpringMVC这个容器不是springIOC中的bean,无法自动注入,所以需要在类前面添加 @WebAppConfiguration ,根据web.xml配置 来注入SpringMVC容器
	WebApplicationContext context;
	
	// 虚拟mvc请求,获取到处理结果。
	MockMvc mockMvc;

	@Before // 表示 每次调用 http请求 都要初始化一下
	public void initMokcMvc() {
		mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
	}

	@Test
	public void testPage() throws Exception {
		
 
		// 模拟请求拿到返回值
		MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/emps").param("pn", "3")).andReturn();

		// 请求成功以后,请求域中会有pageInfo;我们可以取出pageInfo进行验证

		MockHttpServletRequest request = result.getRequest();
		PageInfo pi = (PageInfo) request.getAttribute("pageInfo");
		System.out.println("当前页码:" + pi.getPageNum());
		System.out.println("总页码:" + pi.getPages());
		System.out.println("总记录数:" + pi.getTotal());
		System.out.println("在页面需要连续显示的页码");
		int[] nums = pi.getNavigatepageNums();
		for (int i : nums) {
			System.out.println(" " + i);
		}

		// 获取员工数据
		List<Employee> list = pi.getList();
		for (Employee employee : list) {
			System.out.println("ID:" + employee.getEmpId() + "==>Name:" + employee.getEmpName());
		}

	}

}

六、当前项目概览

经过后面的测试验证,网页文件放在WebContent目录下是对的,已在服务器上运行成功。(勘误,上面的蓝色字是之前学习时记录的,现在回来勘误,其实不应该放在WebContent目录中,项目第一章配置环境时已经重新说明过了,maven的web工程的网页和资源应该放在webapp目录下)

SSM小项目-(2)-配置整合SSM

有时配置文件会报错,主要是网络读取不到文件的原因。可以先clean project ,然后再重新update project。报错举例:

- schema_reference.4: Failed to read schema document 'http://www.springframework.org/schema/tx/spring- tx-4.3.xsd', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.

一、配置Web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">
	
	<!--1、启动Spring的容器  -->
	<!-- needed for ContextLoaderListener -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:springIOC.xml</param-value>
	</context-param>

	<!-- Bootstraps the root web application context before servlet initialization -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	
	<!--2、springmvc的前端控制器,拦截所有请求  -->
	<!-- The front controller of this Spring Web application, responsible for handling all application requests -->
	<servlet>
		<servlet-name>springMVC</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- Map all requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>springMVC</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>
			<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>
	
	<!-- 4、使用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>
<!-- 5、直接支持put请求的拦截器,即在put请求时将请求体数据封装成map,并让springmvc能够通过request.getParameter(属性名) 来获取数据 -->
	<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>
	
</web-app>

二、配置springmvc

与web.xml目录同级下创建,springMVC-servlet.xml ,这是根据web.xml中配置的springMVC 得来的。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

	<!--SpringMVC的配置文件,包含网站跳转逻辑的控制,配置  -->
	<context:component-scan base-package="com.ssm.crud" use-default-filters="false">
		<!--只扫描控制器。  -->
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	</context:component-scan>
	
	<!--配置视图解析器,方便页面返回  -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/views/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>
	
	<!--两个标准配置  -->
	<!-- 将springmvc不能处理的请求交给tomcat -->
	<mvc:default-servlet-handler/>
	<!-- 能支持springmvc更高级的一些功能,JSR303校验,快捷的ajax...映射动态请求 -->
	<mvc:annotation-driven/>

</beans>

三、配置spring容器

因为web.xml已经定义了:spring的容器名是 springIOC,路径是类的根目录。
所以开发的时候,就在src/main/resources  目录下创建 springIOC.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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

	<context:component-scan base-package="com.ssm.crud">
		<context:exclude-filter type="annotation"
			expression="org.springframework.stereotype.Controller" />
	</context:component-scan>

	<!-- Spring的配置文件,这里主要配置和业务逻辑有关的 -->
	<!--=================== 数据源,事务控制,xxx ================-->
	<context:property-placeholder location="classpath:dbconfig.properties" />
	<bean id="pooledDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="url" value="${jdbc.url}" />
		<property name="driverClassName" value="${jdbc.driver}" />
		<property name="username" value="${jdbc.user}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

	<!--================== 配置和MyBatis的整合=============== -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<!-- 指定mybatis全局配置文件的位置 -->
		<property name="configLocation" value="classpath:mybatis-config.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.ssm.crud.mapper"></property>
	</bean>
	
	<!-- 配置一个可以执行批量的sqlSession -->
	<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>
		<constructor-arg name="executorType" value="BATCH"></constructor-arg>
	</bean>
	<!--=============================================  -->

	<!-- ===============事务控制的配置 ================-->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!--控制住数据源  -->
		<property name="dataSource" ref="pooledDataSource"></property>
	</bean>
	<!--开启基于注解的事务,使用xml配置形式的事务(必要主要的都是使用配置式)  -->
	<aop:config>
		<!-- 切入点表达式  固定为:execution()  
		内部参数【 *(代表返回值类型所以)  com.ssm.crud.service..(代表这个包及下面的子包)  *(紧跟前面的这个星号,代表类中的所有方法)  (..)(紧跟前面的这个标记,代表方法里面的参数 任意多)  】-->
		<aop:pointcut expression="execution(* com.ssm.crud.service..*(..))" id="txPoint"/>
		<!-- 配置事务增强  txAdvice:代表事务切入规则  txPoint:代表切入哪些方法 -->
		<aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/>
	</aop:config>
	
	<!--配置事务增强,也就是 事务如何切入  -->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<!-- 所有方法都是事务方法 -->
			<tx:method name="*"/>
			<!--以get开始的所有方法 ,认为都是查询方法,可以调优,写成 read only = true -->
			<tx:method name="get*" read-only="true"/>
		</tx:attributes>
	</tx:advice>
	
	<!-- Spring配置文件的核心点(数据源、与mybatis的整合,事务控制) -->
	
</beans>

四、配置mybatis.xml和数据库属性文件

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<settings>
		<setting name="mapUnderscoreToCamelCase" value="true"/>
	</settings>
	
	<typeAliases>
		<package name="com.ssm.crud.bean"/>
	</typeAliases>
	

</configuration>

dbconfig.properties

##### 	<!-- http://jingyan.baidu.com/article/b7001fe197a3f60e7282dd8d.html 中文数据库存取乱码问题 ,在 xml文件中 & 要转义成 &amp; 而到了properties中就直接用&就行了 -->
jdbc.url=jdbc:mysql://localhost:3306/ssm_crud?useUnicode=true&characterEncoding=UTF-8
jdbc.driver=com.mysql.jdbc.Driver
jdbc.user=root
jdbc.password=Cool123!

五、搭建数据库

tbl_emp

emp_id     emp_name   gender   email     d_id

tbl_dept

dept_id      dept_name

六、mybatis自动生成代码(MBG)

1、先导入mybatis-generator-core.jar 包。
在pom.xml中,添加依赖【前面已经添加好了】

<!-- MBG -->
<!-- https://mvnrepository.com/artifact/org.mybatis.generator/mybatis-generator-core -->
<dependency>
	<groupId>org.mybatis.generator</groupId>
	<artifactId>mybatis-generator-core</artifactId>
	<version>1.3.5</version>
</dependency>

2、添加 mbg.xml 配置文件

特别提醒:下面的路径写错的话,MBG 就会无法生成,虽然 运行没有报错。

linux系统下:  targetProject=”./src/main/java”
Windows系统下:targetProject=”.\src\main\java”

<?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" 
			userId="root"
			password="Cool123!">
		</jdbcConnection>

		<javaTypeResolver>
			<property name="forceBigDecimals" value="false" />
		</javaTypeResolver>

		<!-- 指定javaBean生成的位置 -->
		<javaModelGenerator 
		    targetPackage="com.ssm.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接口生成的位置,mapper接口 -->
		<javaClientGenerator 
		    type="XMLMAPPER"
			targetPackage="com.ssm.crud.mapper" 
			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>

3、执行生成代码

package com.ssm.crud.test;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;

public class MBGTest {

	public static void main(String[] args) 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);
		System.out.println("hhh");
	}
}

七、修改mybatis的生成文件实现关联查询

目的是使:查询employee时,能够顺便获取到 department。

1、修改bean

Employee.java

package com.ssm.crud.bean;

public class Employee {
    private Integer empId;

    private String empName;

    private String gender;

    private String email;

    private Integer dId;

    public Integer getEmpId() {
        return empId;
    }

    public void setEmpId(Integer empId) {
        this.empId = empId;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName == null ? null : empName.trim();
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender == null ? null : gender.trim();
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email == null ? null : email.trim();
    }

    public Integer getdId() {
        return dId;
    }

    public void setdId(Integer dId) {
        this.dId = dId;
    }
    
    //添加 Department 属性,用于关联查询
    private Department department;

	public Department getDepartment() {
		return department;
	}

	public void setDepartment(Department department) {
		this.department = department;
	}


	//生成自定义构造器 ,顺便 附带 无参构造器
	public Employee(Integer empId, String empName, String gender, String email, Integer dId) {
		super();
		this.empId = empId;
		this.empName = empName;
		this.gender = gender;
		this.email = email;
		this.dId = dId;
	}

	public Employee() {
		super();
	}
	
	
}

Department.java

package com.ssm.crud.bean;

public class Department {
    private Integer deptId;

    private String deptName;

    public Integer getDeptId() {
        return deptId;
    }

    public void setDeptId(Integer deptId) {
        this.deptId = deptId;
    }

    public String getDeptName() {
        return deptName;
    }

    public void setDeptName(String deptName) {
        this.deptName = deptName == null ? null : deptName.trim();
    }

    
    //创建构造器时,一定要带上 无参构造器,出于mybatis的 赋值原因
	public Department(Integer deptId, String deptName) {
		super();
		this.deptId = deptId;
		this.deptName = deptName;
	}
    
    public Department(){
    	
    }
}

2、修改Dao接口文件

package com.ssm.crud.mapper;

import com.ssm.crud.bean.Employee;
import com.ssm.crud.bean.EmployeeExample;
import java.util.List;
import org.apache.ibatis.annotations.Param;

public interface EmployeeMapper {
    long countByExample(EmployeeExample example);

    int deleteByExample(EmployeeExample example);

    int deleteByPrimaryKey(Integer empId);

    int insert(Employee record);

    int insertSelective(Employee record);

    List<Employee> selectByExample(EmployeeExample example);

    Employee selectByPrimaryKey(Integer empId);

    int updateByExampleSelective(@Param("record") Employee record, @Param("example") EmployeeExample example);

    int updateByExample(@Param("record") Employee record, @Param("example") EmployeeExample example);

    int updateByPrimaryKeySelective(Employee record);

    int updateByPrimaryKey(Employee record);
    
    
    // 为了 提供 关联查询,自己添加的 两个接口 方法
    List<Employee> selectByExampleWithDept(EmployeeExample example);
    Employee selectByPrimaryKeyWithDept(Integer empId);
}

3、修改sql映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ssm.crud.mapper.EmployeeMapper">
  <resultMap id="BaseResultMap" type="com.ssm.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" />
  </resultMap>
  <sql id="Example_Where_Clause">
    <where>
      <foreach collection="oredCriteria" item="criteria" separator="or">
        <if test="criteria.valid">
          <trim prefix="(" prefixOverrides="and" suffix=")">
            <foreach collection="criteria.criteria" item="criterion">
              <choose>
                <when test="criterion.noValue">
                  and ${criterion.condition}
                </when>
                <when test="criterion.singleValue">
                  and ${criterion.condition} #{criterion.value}
                </when>
                <when test="criterion.betweenValue">
                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
                </when>
                <when test="criterion.listValue">
                  and ${criterion.condition}
                  <foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
                    #{listItem}
                  </foreach>
                </when>
              </choose>
            </foreach>
          </trim>
        </if>
      </foreach>
    </where>
  </sql>
  <sql id="Update_By_Example_Where_Clause">
    <where>
      <foreach collection="example.oredCriteria" item="criteria" separator="or">
        <if test="criteria.valid">
          <trim prefix="(" prefixOverrides="and" suffix=")">
            <foreach collection="criteria.criteria" item="criterion">
              <choose>
                <when test="criterion.noValue">
                  and ${criterion.condition}
                </when>
                <when test="criterion.singleValue">
                  and ${criterion.condition} #{criterion.value}
                </when>
                <when test="criterion.betweenValue">
                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
                </when>
                <when test="criterion.listValue">
                  and ${criterion.condition}
                  <foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
                    #{listItem}
                  </foreach>
                </when>
              </choose>
            </foreach>
          </trim>
        </if>
      </foreach>
    </where>
  </sql>
  <sql id="Base_Column_List">
    emp_id, emp_name, gender, email, d_id
  </sql>
  
  <!-- 自动创建的是 查询员工不带部门信息的 -->
  <select id="selectByExample" parameterType="com.ssm.crud.bean.EmployeeExample" resultMap="BaseResultMap">
    select
    <if test="distinct">
      distinct
    </if>
    <include refid="Base_Column_List" />
    from tbl_emp
    <if test="_parameter != null">
      <include refid="Example_Where_Clause" />
    </if>
    <if test="orderByClause != null">
      order by ${orderByClause}
    </if>
  </select>
  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from tbl_emp
    where emp_id = #{empId,jdbcType=INTEGER}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
    delete from tbl_emp
    where emp_id = #{empId,jdbcType=INTEGER}
  </delete>
  <delete id="deleteByExample" parameterType="com.ssm.crud.bean.EmployeeExample">
    delete from tbl_emp
    <if test="_parameter != null">
      <include refid="Example_Where_Clause" />
    </if>
  </delete>
  <insert id="insert" parameterType="com.ssm.crud.bean.Employee">
    insert into tbl_emp (emp_id, emp_name, gender, 
      email, d_id)
    values (#{empId,jdbcType=INTEGER}, #{empName,jdbcType=VARCHAR}, #{gender,jdbcType=CHAR}, 
      #{email,jdbcType=VARCHAR}, #{dId,jdbcType=INTEGER})
  </insert>
  <insert id="insertSelective" parameterType="com.ssm.crud.bean.Employee">
    insert into tbl_emp
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="empId != null">
        emp_id,
      </if>
      <if test="empName != null">
        emp_name,
      </if>
      <if test="gender != null">
        gender,
      </if>
      <if test="email != null">
        email,
      </if>
      <if test="dId != null">
        d_id,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="empId != null">
        #{empId,jdbcType=INTEGER},
      </if>
      <if test="empName != null">
        #{empName,jdbcType=VARCHAR},
      </if>
      <if test="gender != null">
        #{gender,jdbcType=CHAR},
      </if>
      <if test="email != null">
        #{email,jdbcType=VARCHAR},
      </if>
      <if test="dId != null">
        #{dId,jdbcType=INTEGER},
      </if>
    </trim>
  </insert>
  <select id="countByExample" parameterType="com.ssm.crud.bean.EmployeeExample" resultType="java.lang.Long">
    select count(*) from tbl_emp
    <if test="_parameter != null">
      <include refid="Example_Where_Clause" />
    </if>
  </select>
  <update id="updateByExampleSelective" parameterType="map">
    update tbl_emp
    <set>
      <if test="record.empId != null">
        emp_id = #{record.empId,jdbcType=INTEGER},
      </if>
      <if test="record.empName != null">
        emp_name = #{record.empName,jdbcType=VARCHAR},
      </if>
      <if test="record.gender != null">
        gender = #{record.gender,jdbcType=CHAR},
      </if>
      <if test="record.email != null">
        email = #{record.email,jdbcType=VARCHAR},
      </if>
      <if test="record.dId != null">
        d_id = #{record.dId,jdbcType=INTEGER},
      </if>
    </set>
    <if test="_parameter != null">
      <include refid="Update_By_Example_Where_Clause" />
    </if>
  </update>
  <update id="updateByExample" parameterType="map">
    update tbl_emp
    set emp_id = #{record.empId,jdbcType=INTEGER},
      emp_name = #{record.empName,jdbcType=VARCHAR},
      gender = #{record.gender,jdbcType=CHAR},
      email = #{record.email,jdbcType=VARCHAR},
      d_id = #{record.dId,jdbcType=INTEGER}
    <if test="_parameter != null">
      <include refid="Update_By_Example_Where_Clause" />
    </if>
  </update>
  <update id="updateByPrimaryKeySelective" parameterType="com.ssm.crud.bean.Employee">
    update tbl_emp
    <set>
      <if test="empName != null">
        emp_name = #{empName,jdbcType=VARCHAR},
      </if>
      <if test="gender != null">
        gender = #{gender,jdbcType=CHAR},
      </if>
      <if test="email != null">
        email = #{email,jdbcType=VARCHAR},
      </if>
      <if test="dId != null">
        d_id = #{dId,jdbcType=INTEGER},
      </if>
    </set>
    where emp_id = #{empId,jdbcType=INTEGER}
  </update>
  <update id="updateByPrimaryKey" parameterType="com.ssm.crud.bean.Employee">
    update tbl_emp
    set emp_name = #{empName,jdbcType=VARCHAR},
      gender = #{gender,jdbcType=CHAR},
      email = #{email,jdbcType=VARCHAR},
      d_id = #{dId,jdbcType=INTEGER}
    where emp_id = #{empId,jdbcType=INTEGER}
  </update>
  
  
   <!--  ****** 自定义的 带部门信息 的两种查询  ******  -->
   <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>
   <!--   List<Employee> selectByExampleWithDept(EmployeeExample example);
    Employee selectByPrimaryKeyWithDept(Integer empId); 
    -->
 <resultMap type="com.ssm.crud.bean.Employee" id="WithDeptResultMap">
  	<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.ssm.crud.bean.Department">
    	<id column="dept_id" property="deptId"/>
    	<result column="dept_name" property="deptName"/>
    </association>
  </resultMap>
   <!-- 查询员工同时带部门信息 -->
  <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`=d.`dept_id`
    where emp_id = #{empId,jdbcType=INTEGER}
  </select>
  
  
</mapper>

八、Junit单元测试

需要在Maven中的pom.xml中,导入 junit.jar 包。

  <!--  一开始这里初始化 Junit 版本为3 ,
        当 eclipse 创建测试用例时,我选4版本的用例,发现又添加了Junit4 依赖包
        为了统一,我就在这里 改成 Juin4 依赖包,这样 eclipse创建 单元测试时就不会 重新添加依赖包了 -->
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

方法一:代码加载spring容器

package com.ssm.crud.test;

import java.io.IOException;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.ssm.crud.mapper.DepartmentMapper;
import com.ssm.crud.mapper.EmployeeMapper;


public class CodeTest {
	
	DepartmentMapper departmentMapper;

	@Test
	public void testCRUD() throws IOException{

	    //1、创建SpringIOC容器  ,这个地方 却可以 正确加载 路径 xxx/target/classes/springIOC.xml
		
		//1、创建SpringIOC容器
		ApplicationContext ioc = new ClassPathXmlApplicationContext("springIOC.xml");
		//2、从容器中获取mapper
		EmployeeMapper bean = ioc.getBean(EmployeeMapper.class);
		
		System.out.println(bean.selectByPrimaryKey(1));
       		
	}

}

方法二:自动加载spring容器

需要在Maven的pom.xml中导入 spring-test.jar 包。

<!--Spring-test 单元测试 适配框架,用于在单元测试时,无需初始化就可以使用Spring 容器,及Spring注解 -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-test</artifactId>
	<version>4.3.7.RELEASE</version>
</dependency>
package com.ssm.crud.test;

import java.io.IOException;

import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.ssm.crud.mapper.DepartmentMapper;
import com.ssm.crud.mapper.EmployeeMapper;


/**
 * 测试dao层的工作
 * @author lfy
 *推荐Spring的项目就可以使用Spring的单元测试,可以自动注入我们需要的组件
 *1、导入SpringTest模块
 *2、@ContextConfiguration指定Spring配置文件的位置
 *3、直接autowired要使用的组件即可
 */

//@RunWith这个是 junit-4.jar包 提供的注解, 而 SpringJUnit4ClassRunner.class 是spring-test 提供的类
@RunWith(SpringJUnit4ClassRunner.class)

//@ContextConfiguration 这个是 spring-test.jar包 提供的注解 , 用于启动 spring容器,方便直接获取spring容器中的bean
@ContextConfiguration(locations={"classpath:springIOC.xml"})
public class AnnotationTest {
	
	
	@Autowired
	DepartmentMapper departmentMapper;
	
	@Autowired
	EmployeeMapper employeeMapper;
	
	@Autowired
	SqlSession sqlSession;
	

	/**
	 * 测试DepartmentMapper
	 */

	@Test
	public void testCRUD() throws IOException{
	    

          System.out.println(employeeMapper.selectByPrimaryKey(1));
       
	 
		
		
		//1、插入几个部门
		departmentMapper.insertSelective(new Department(null, "开发部"));
		departmentMapper.insertSelective(new Department(null, "测试部"));
		
		//2、生成员工数据,测试员工插入
		employeeMapper.insertSelective(new Employee(null, "Jerry", "M", "[email protected]", 1));
		
		//3、批量插入多个员工;批量,使用可以执行批量操作的sqlSession。
		
//		for(){
//			employeeMapper.insertSelective(new Employee(null, , "M", "[email protected]", 1));
//		}
		EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
		for(int i = 0;i<1000;i++){
			String uid = UUID.randomUUID().toString().substring(0,5)+i;
			mapper.insertSelective(new Employee(null,uid, "M", uid+"@atguigu.com", 1));
		}
		System.out.println("批量完成");
		

		
	}

}

ps:关于加载log4j,也可以采用如下方法

public class JUnit4ClassRunner extends SpringJUnit4ClassRunner {
    static {
      try {
        // classpath 是 二进制 class文件 的执行根路径
        Log4jConfigurer.initLogging("classpath:log4j.xml");
      } catch (FileNotFoundException ex) {
        System.err.println("Cannot Initialize log4j");
      }
    }
    public JUnit4ClassRunner(Class<?> clazz) throws InitializationError {
      super(clazz);
    }
  }
@RunWith(JUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:springIOC.xml")

public class AnnotationTest {
    ...
}

Junit加载spring的runner(SpringJUnit4ClassRunner)要优先于spring加载log4j【log4j的自动默认加载功能】,因此采用普通方法,无法实现spring先加载log4j后被Junit加载。所以我们需要新建JUnit4ClassRunner类,修改SpringJUnit4ClassRunner加载log4j的策略。这样加载log4j就会优先于加载spring了。

数据库单元测试通过以后,就可以开始写web页面了。