1、完善头像
1.1、新增头像属性
①domain
②页面
注意:这里显示的头像是头像路径地址,需要通过formatter方法进行格式化,解决显示格式不一致,同时在对应的路径准备头像图片
//头像格式化
function imageFormt(v, r, i) {
return `<img src="${v}" style="width: 50px;">`;
}
2、完善部门
- 员工与部门是多对一的关系
- 部门设置为懒加载的时候会出现no session问题,后面讲怎么解决
- 部门外键一定与数据库一致
- BaseQuery中创建查询规则需要注意属性问题【看代码】
- 部门页面展示同样需要formatter格式化
- 注意页面中部门name的属性问题
2.1、domain
①新建Department 类
@Entity
@Table(name = "department")
public class Department extends BaseDomain{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Department{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
}
②在Employee类中添加属性,并配置多对一关系
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="department_id")
private Department department;
2.2、其他层
通过Ctr+F查找替换,勾选mach case区分大小写,将Employee有的复制一份并修改
- query
- repository
- service
- controller
3、页面
①/department/index.jsp
复制employee/index.jsp,注意内容修改,属性不同
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@include file="/WEB-INF/views/head.jsp" %>
<html>
<head>
<title>Title</title>
<%--引入当前页面对应的js文件--%>
<script src="/js/model/department.js"></script>
</head>
<body>
<div id="toolbar">
<div style="margin-bottom:5px">
<a href="#" data-method="add" class="easyui-linkbutton" iconCls="icon-add" plain="true">添加</a>
<a href="#" data-method="edit" class="easyui-linkbutton" iconCls="icon-edit" plain="true">修改</a>
<a href="#" data-method="delete" class="easyui-linkbutton" iconCls="icon-remove" plain="true">删除</a>
</div>
<form id="searchForm" method="post">
部门: <input class="easyui-combobox" name="departmentid"
data-options="valueField:'id',textField:'name',url:'/department/list',panelHeight:'auto'"/>
<a href="#" data-method="search" class="easyui-linkbutton" iconCls="icon-search">查询</a>
</form>
</div>
<%--pagination:true分页条--%>
<table id="datagrid" class="easyui-datagrid"
data-options="url:'/department/page',fitColumns:true,singleSelect:true,fit:true,pagination:true,toolbar:'#toolbar',onRowContextMenu:showMenu">
<thead>
<tr>
<th data-options="field:'id',width:100">编码</th>
<th data-options="field:'name',width:100" sortable="true">名称</th>
</thead>
</table>
<%--添加或者修改的弹出框--%>
<div id="editDialog" class="easyui-dialog"
data-options="iconCls:'icon-save',resizable:true,modal:true,closed:true,buttons:'#btns'">
<form id="editForm" method="post">
<table cellpadding="1">
<tr>
<td> 部门</td>
<td>
<td><input class="easyui-textbox" type="text" name="name" data-options="required:true"/></td>
</td>
</tr>
</table>
</form>
<div id="btns">
<a href="javascript:;" data-method="save" class="easyui-linkbutton c3" data-options="iconCls:'icon-ok'">确定</a>
<a href="javascript:;" data-method="close" class="easyui-linkbutton c5"
data-options="iconCls:'icon-cancel'">取消</a>
</div>
</div>
<div id="gridMenu" class="easyui-menu" style="width:120px;">
<div data-options="iconCls:'icon-add'" data-method="add">添加</div>
<div data-options="iconCls:'icon-edit'" data-method="edit">修改</div>
<div data-options="iconCls:'icon-remove'" data-method="del">删除</div>
</div>
</body>
</html>
②department.js
$(function () {
//常用的元素都先在这里获取到
var datagrid = $("#datagrid");
var searchForm = $("#searchForm");
var editForm = $("#editForm");
var editDialog = $("#editDialog");
$("*[data-method]").on("click", function () {
//拿到点击触发的方法名
var methodName = $(this).data("method");
//动态调用方法
xuxusheng[methodName]();
})
xuxusheng = {
//方法的简写,需要javascript language version 6版本才行
add() {
//打开面板前把里面的数据清除
editForm.form("clear");
//修改添加框的标题
editDialog.dialog({title:'添加用户'});
//将密码框显示
$("*[data-hide]").show();
//禁用防止传参数
$("*[data-hide]>td>input").textbox("enable");
//把添加框(editDialog)打开
editDialog.dialog("center").dialog("open");
},
save(){
var url = "/department/save";
console.debug(editForm);
if ($("#departmentId").val()){
url = "/department/update?_cmd=update";
}
editForm.form('submit', {
//提交路径,变量名相同可以直接写
url:url,
onSubmit: function(){
// do some check
// return false to prevent submit;
//提交前验证
return $(this).form('validate');
},
//data是一个json字符串{"success":true}
success:function(data){
//把一个Json字符串转成JSON对象
//eval("("+data+")")
var result = JSON.parse(data);
if(result.success){
//成功就刷新当前页面
datagrid.datagrid("reload");
}else{
$.messager.alert('警告',`添加失败,原因是:${result.msg}`,"warning");
}
//关闭对话框
xuxusheng.close();
}
});
},
close(){
editDialog.dialog("close");
},
edit() {
//获取选择的行
var row = datagrid.datagrid("getSelected");
//判断是否选择了一行
//没有选择提示并结束
if (!row) {
$.messager.alert('提示', '请先选择一行进行修改', "info");
return;
}else {
//将密码框隐藏
$("*[data-hide]").hide();
//禁用防止传参数
$("*[data-hide]>td>input").textbox("disable");
//修改添加框的标题
editDialog.dialog({title:'修改用户'});
console.debug(editDialog);
//把添加框(editDialog)打开
editDialog.dialog("center").dialog("open");
//部门回显
if (row.department){
//有部门就回显部门
row["department.id"] = row.department.id;
}else {
//没有部门就回显空
row["department.id"]="";
}
//回显其他内容
editForm.form('load',row);
}
},
delete() {
//获取选择的行
var row = datagrid.datagrid("getSelected");
//判断是否选择了一行
if (!row) {
//没有选择提示并结束
$.messager.alert('提示', '请先选择一行进行删除', "info");
return;
}
//选择了提示是否确认删除
$.messager.confirm('确认', '您确认想要删除记录吗?', function (r) {
if (r) {
//通过ajax删除
$.get("/department/delete", {id: row.id}, function (result) {
//如果成功,重新加载页面
if (result.success) {
datagrid.datagrid("reload");
}else{
//失败给出提示消息,这里使用飘~,可以使用el表达式,不用拼接字符串了
$.messager.alert('提示',`删除失败,原因是:${result.msg}`,"error");
}
});
}
});
},
search() {
//1.拿到查询的值,
// var username = $("#username").val();
//var email = $("#email").val();
//下面这个方法需要引入jquery.jdirk.js
var params = searchForm.serializeObject();
//2.进行查询
datagrid.datagrid("load", params);
//datagrid.datagrid("load", {username: username, email: email});
}
};
})
//头像格式化
function imageFormt(v,r,i) {
return`<img src="${v}" style="width: 50px;">`;
}
//部门格式化
function deptFormt(v,r,i) {
return v?v.name:"";
}
function showMenu(e, rowIndex, rowData) {
//选中这个行
$("#departmentGrid").datagrid("selectRow",rowIndex);
//第0个位置的面板不支持相应功能
e.preventDefault();
$('#gridMenu').menu('show', {
left: e.pageX,
top: e.pageY
});
}
3、添加/修改方法
- 点击添加弹出对话框(设置对话框属性:居中,清除内容等)
- 添加内容之后保存,调用save方法
- 添加与修改通过是否有id进行判断
- 修改的时候密码隐藏与禁用
- 禁用密码造成数据丢失,通过@ModelAttribute(“xxUpdate”)解决数据丢失
3.1、employee.js代码
add方法
add() {
//打开面板前把里面的数据清除
editForm.form("clear");
//修改添加框的标题
editDialog.dialog({title: '添加用户'});
//将密码框显示
$("*[data-hide]").show();
//禁用防止传参数
$("*[data-hide]>td>input").textbox("enable");
//把添加框(editDialog)打开
editDialog.dialog("center").dialog("open");
},
edit方法
edit() {
//获取选择的行
var row = datagrid.datagrid("getSelected");
//判断是否选择了一行
//没有选择提示并结束
if (!row) {
$.messager.alert('提示', '请先选择一行进行修改', "info");
return;
} else {
//将密码框隐藏
$("*[data-hide]").hide();
//禁用防止传参数
$("*[data-hide]>td>input").textbox("disable");
//修改添加框的标题
editDialog.dialog({title: '修改用户'});
console.debug(editDialog);
//把添加框(editDialog)打开
editDialog.dialog("center").dialog("open");
//部门回显
if (row.department) {
//有部门就回显部门
row["department.id"] = row.department.id;
} else {
//没有部门就回显空
row["department.id"] = "";
}
//回显其他内容
editForm.form('load', row);
}
},
save方法
save() {
var url = "/employee/save";
if ($("#employeeId").val()) {
url = "/employee/update?_cmd=update";
}
editForm.form('submit', {
//提交路径,变量名相同可以直接写
url: url,
onSubmit: function () {
// do some check
// return false to prevent submit;
//提交前验证
return $(this).form('validate');
},
//data是一个json字符串{"success":true}
success: function (data) {
//把一个Json字符串转成JSON对象
//eval("("+data+")")
var result = JSON.parse(data);
if (result.success) {
//成功就刷新当前页面
datagrid.datagrid("reload");
} else {
$.messager.alert('警告', `添加失败,原因是:${result.msg}`, "warning");
}
//关闭对话框
vivi.close();
}
});
},
3.2、EmployeeController
//解决数据丢失
@ModelAttribute("xxUpdate")
public Employee xxUpdate(Long id, String _cmd) {
if (_cmd != null) {
Employee employee = employeeService.findOne(id);
return employee;
}
return null;
}
//保存方法,返回一个json :{success:true/false,msg:xxx}
@RequestMapping("/save")
@ResponseBody
public JsonResult save(Employee employee) {
return saveOrUpdate(employee);
}
//修改方法,虽然和保存一样,但是为了权限问题将两个路径分开
@RequestMapping("/update")
@ResponseBody
public JsonResult update(@ModelAttribute("xxUpdate")Employee employee) {
return saveOrUpdate(employee);
}
//将保存和修改两个方法合并
public JsonResult saveOrUpdate(Employee employee) {
try {
employeeService.save(employee);
return new JsonResult();
} catch (Exception e) {
e.printStackTrace();
return new JsonResult(false, e.getMessage());
}
}
}
4、解决no session问题
4.1、原因分析
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="department_id")
private Department department;
首先:这里的session是会话的意思,但是和之前的HttpSession是完全不同的东西,在hibernate框架框架中可以理解为sqlsession,与数据库交互的会话。
其次:懒加载的含义是,在hibernate执行查询操作时,并不会真正去数据库中查找出数据,而是要等到使用时才去数据库中查找数据。
原因 :Hibernate 允许对关联对象、属性进行延迟加载【department】,但是必须保证延迟加载的操作限于同一个 sqlseesion范围之内进行。如果在controller层调用service将查询的结果返回给web层,这时sqlsession已经关闭。我们查询的结果中如果有懒加载的属性,不是同一个session,将拿不到这个属性,导致了懒加载数据的访问异常。
4.2 、解决办法
在web.xml文件配置 OpenSessionInViewFilter过滤器,这种方法是延迟session关闭的时机,把一个Hibernate Session和一次完整的请求过程对应的线程相绑定。
<filter>
<filter-name>OpenSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate5.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>OpenSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
5、解决no serializer问题
5.1、原因分析
- jpa的懒加载对象自己为填加了一些属性,(“hibernateLazyInitializer”,“handler”,“fieldHandler”)
- 这些属性会影响到SpringMVC返回Json,而返回时有个内省机制
- 需要序列化对象有一个属性是一类类型,而你使用了Hibernate的延迟加载所以这里是个Hibernate的代理对象。该代理对象有些属性不能被序列化所以会报错。
5.2、解决办法,两种。
第一种:
在懒加载属性上面加注解。注意,在实体类上面加是无效的。
@JsonIgnoreProperties(value{“hibernateLazyInitializer”,“handler”,“fieldHandler”})
private Department department;
第二种:
第一步:创建一个新的类(重写com.fasterxml.jackson.databind.ObjectMapper)
package com.vivi.xxl.common;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
public class CustomMapper extends ObjectMapper {
public CustomMapper() {
this.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 设置 SerializationFeature.FAIL_ON_EMPTY_BEANS 为 false
this.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
}
}
第二步:在applicationContext-mvc.xml中添加配置
<!-- Spring MVC 配置 解决 no searilzer -->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json; charset=UTF-8</value>
<value>application/x-www-form-urlencoded; charset=UTF-8</value>
</list>
</property>
<!-- No serializer:配置 objectMapper 为我们自定义扩展后的 CustomMapper,解决了返回对象有关系对象的报错问题 -->
<property name="objectMapper">
<bean class="com.xuxusheng.aisell.common.CustomMapper"></bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
5、解决n-to-n问题
5.1、原因分析
一个持久化对象的OID被修改。
在本项目中,employee和department是属于多对一的关系,在修改员工中的部门时,出现了这个错误。
上图可以看到,在修改部门的时候,出现了department.id这个参数。
正常修改属性,是调用set()方法。
但是注意,这个 点 之前都会调用 get()方法,显示getDepartment,然后再set id,这里更改id的话就是修改的department的id。
而此时的department 是一个持久化对象,就会出现这个错误。
5.2、解决办法
当修改有关联对象的时候,将关联对象设置为空。
//解决数据丢失
@ModelAttribute("xxUpdate")
public Employee xxUpdate(Long id, String _cmd) {
if (_cmd != null) {
Employee employee = employeeService.findOne(id);
将修改的employee中的department对象设置为null
employee.setDepartment(null);
return employee;
}
return null;
}