组合关系:就是强聚合,双向的多对一、一对多,他们之间是不能分割的。
在JPA中组合关系的配置:级联,级联要么不配,要么就配置最强的:all+孤儿删除
在单据的地方可配置级联+孤儿删除
UML,如果是实心的菱形,就是组合关系,如果是空心的菱形就是聚合关系。
要想整体和部分不可分割,代码就要配置级联。
一对多,性能超级差
双向一对多的话,就要让一方放弃关系维护,才能提高性能。
组合关系-采购订单模型
分析表字段:
1、每个字段的意思
2、这个字段的类型(为什么要用这个类型)
3、字段是否可以为空
4、这个字段的值应该从哪里来
配置采购单关系
private Date vdate;
private BigDecimal totalamount;
private BigDecimal totalnum;
private Date inputtime=new Date();
private Date auditortime;
// 状态
private Integer status=0;
// 与 供应商的表关系
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "supplier_id")
private Supplier supplier;
// 与审核人的关系
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "auditor_id")
private Employee auditor;
// 与录入员的关系
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "inputUser_id")
private Employee inputuser;
// 与采购员的关系
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "buyer_id")
private Employee buyer;
// 一般组合关系使用list
@OneToMany(cascade = CascadeType.ALL,mappedBy = "bill",fetch = FetchType.LAZY,orphanRemoval = true)
private List<Purchasebillitem> items=new ArrayList<>();
配置采购详单关系
private BigDecimal price;
private BigDecimal num;
private BigDecimal amount;
private String descs;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id")
private Product product;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "bill_id")
// 表示不用理他
@JsonIgnore
private Purchasebill bill;
purchasebill.jsp
表格部分
<tr>
<th data-options="field:'vdate',width:100">交易时间</th>
<th data-options="field:'totalamount',width:100">总金额</th>
<th data-options="field:'totalnum',width:100">总数量</th>
<th data-options="field:'inputtime',width:100">录入时间</th>
<th data-options="field:'supplier',width:100,formatter:formatName">供应商</th>
<th data-options="field:'auditor',width:100,formatter:formatName">审核人</th>
<th data-options="field:'inputuser',width:100,formatter:formatName">录入人</th>
<th data-options="field:'buyer',width:100,formatter:formatName">采购员</th>
<th data-options="field:'status',width:100,formatter:formatStatus">状态</th>
</tr>
高级查询部分
<div>
<form id="searchForm">
交易时间:
<input name="beginDate" class="easyui-datebox" style="width:120px"/>--
<input name="endDate" class="easyui-datebox" style="width:120px"/>
状态:
<select class="easyui-combobox" panelHeight="auto" name="status" style="width:120px;">
<option value="">--请选择--</option>
<option value="0">待审核</option>
<option value="1">已审核</option>
<option value="-1">已废除</option>
</select>
<a href="javascript:;" data-method="search" class="easyui-linkbutton" iconCls="icon-search">查询</a>
</form>
</div>
使前台显示的交易时间为我们定义的格式
我们用一个easyUI的日历插件来获取时间,设置日期格式,加上东八区
//添加下面的注解,进行时间配置,使前台显示的交易时间为我们定义的格式
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
public Date getVdate() {
return vdate;
}
提醒自己:jsp表格里面的field必须和domain里面配置的字段一样
做高级查询
后台接受前台的数据
PurchasebillQuery,get/set,
private String name;
// 查询的开始时间
private Date beginDate;
// 查询的结束时间
private Date endDate;
// 状态
private Integer status;
//这里帮我们直接返回Specification这个对象
//StringUtils.isNotBlank(stru):不为空且不是空字符串
// ge:大于等于,le:小于等于,lt:小于
@Override
public Specification createSpec(){
// 想在采购单管理表里面的高级查询里面,选择查询一天之内任何时间的数据,lang3给我们提供了这个方法
// 先在结束时间上加一天
Date addDays=null;
if(endDate!=null){
addDays = DateUtils.addDays(endDate, 1);
}
Specification<Purchasebill> spec = Specifications.<Purchasebill>and()
.like(StringUtils.isNotBlank(name),"name", "%"+name+"%")
.ge(beginDate!=null,"vdate" ,beginDate )
// 为了能够实现可以查询一天内的所有数据,下面还要将上面加1的字段写入
.lt(endDate!=null,"vdate" ,addDays )
.eq(status!=null,"status",status)
.build();
return spec;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBeginDate() {
return beginDate;
}
// 在SpringMVC里面,要想接收一个日期参数,需要在set上面加上一个下面的注解,并配置格式
@DateTimeFormat(pattern = "yyyy-MM-dd")
public void setBeginDate(Date beginDate) {
this.beginDate = beginDate;
}
public Date getEndDate() {
return endDate;
}
// 在SpringMVC里面,要想接收一个日期参数,需要在set上面加上一个下面的注解,并配置格式
@DateTimeFormat(pattern = "yyyy-MM-dd")
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}
小知识:400错误,表示参数类型出错。
SpringMVC接受日期参数
在SpringMVC里面,如果想要接受一个日期参数,要set上面加一个下面的注解@DateTimeFormat
后台的数据传到前台
在做高级查询的时候,时间选择我们使用日历,直接将class改成下图所示就可以了
设计高级查询
在设计高级查询的时候,做一个下拉框,在后台写死
<div>
<form id="searchForm">
交易时间:
<input name="beginDate" class="easyui-datebox" style="width:120px"/>--
<input name="endDate" class="easyui-datebox" style="width:120px"/>
状态:
<select class="easyui-combobox" panelHeight="auto" name="status" style="width:120px;">
<option value="">--请选择--</option>
<option value="0">待审核</option>
<option value="1">已审核</option>
<option value="-1">已废除</option>
</select>
<a href="javascript:;" data-method="search" class="easyui-linkbutton" iconCls="icon-search">查询</a>
</form>
</div>
调节下拉框的长度(panelHeight)
在高级查询里面查询一天内所有的数据
想在高级查询里面,查一天之中的所有数据怎么办,结束时间加一天,然后下面写小于
@Override
public Specification createSpec(){
// 想在采购单管理表里面的高级查询里面,选择查询一天之内任何时间的数据,lang3给我们提供了这个方法
// 先在结束时间上加一天
Date addDays=null;
if(endDate!=null){
addDays = DateUtils.addDays(endDate, 1);
}
Specification<Purchasebill> spec = Specifications.<Purchasebill>and()
.like(StringUtils.isNotBlank(name),"name", "%"+name+"%")
.ge(beginDate!=null,"vdate" ,beginDate )
// 为了能够实现可以查询一天内的所有数据,下面还要将上面加1的字段写入
.lt(endDate!=null,"vdate" ,addDays )
.eq(status!=null,"status",status)
.build();
return spec;
}
增删改查
弹出的操作数据表单,交易时间、供应商、采购员,只需要这三个是我们需要进行选择的。
供应商和采购员的下拉框,从数据库中读出来
<input id="purchasebillId" type="hidden" name="id"/>
<table cellpadding="5">
<tr>
<td>交易时间:</td>
<td><input name="vdate" type="text" class="easyui-datebox" data-options="required:true"/>
</td>
</tr>
<tr>
<td>供应商:</td>
<td><input name="supplier.id" class="easyui-combobox" panelHeight="auto" style="width:120px"
data-options="valueField:'id',textField:'name',url:'/util/supplierList'"/>
</td>
</tr>
<tr>
<td>采购员:</td>
<td><input name="buyer.id" class="easyui-combobox" style="width:120px"
data-options="valueField:'id',textField:'username',url:'/util/buyerList'"/>
</td>
</tr>
</table>
通过部门筛选拿到采购部所有员工
我忘记的小知识:所有的下拉框数据获取都写在UtilController里面,拿到所有采购员的方法,是下面写的通过部门筛选拿到的采购部的所有员工的方法。
@RequestMapping("/buyerList")
@ResponseBody
public List<Employee> buyerList(){
return employeeService.findBuyer();
}
JPQL
在EmployeeRepository里面字儿JPQL查询语句,当需要进行选择查询的时候
//查询出部门
@Query("select o from Employee o where o.department.name=?1")
List<Employee> findByDeptName(String deptName);
IEmployeeService
List<Employee> findBuyer();
EmployeeServiceImpl
@Override
public List<Employee> findBuyer() {
return employeeRepository.findByDeptName("采购部");
}
小知识:时间格式里面的东八区只有获取的时候才需要。
继续上面的操作:
添加保存功能:
在采购单controller里面,记得在save方法里面获取当前登录用户,不然无法保存数据,会报错。
@RequestMapping("/save")
@ResponseBody
public JsonResult save(Purchasebill purchasebill){
// 拿到当前的登录对象
Employee loginUser = UserContext.getUser();
purchasebill.setInputuser(loginUser);
return saveOrUpdate(purchasebill);
}
明细数据保存
效果图:
需要引入的js
<!-- 编辑框支持 -->
<script src="/easyui/plugin/cellEdit/jeasyui.extensions.datagrid.getColumnInfo.js"></script>
<script src="/easyui/plugin/cellEdit/jeasyui.extensions.datagrid.editors.js"></script>
<script src="/easyui/plugin/cellEdit/jeasyui.extensions.datagrid.edit.cellEdit.js"></script>
解决可编辑grid的问题,easyUI一定要版本1.6.3,不然兼容器有问题
js有顺序问题,
将上面选中部分的内容拷贝到采购单管理的js文件中,要记得放到function最后面
//dg代表要操作的表格
//defaultRow:默认行,表格里面的列属性
//insertPosition:数据插入的位置,top,表示从上面插入,bottom,表示从下面插入
var dg = $("#dg1"),
defaultRow = { product: "", productcolor: "", productImage: "", num: "", price: "", amount: "", descs: "" },
insertPosition = "bottom";
//表格的初始化操作
var dgInit = function () {
//表格里面的列里面的数据
var getColumns = function () {
var result = [];
var normal = [
{
field: 'product', title: '产品', width: 180,
editor: {
type: "combobox",//编辑器类型,这里我们写入产品通过下拉列表,不用验证
//编辑器的属性
options: {
valueField:'id',
textField:'name',
url:'/util/productList',
panelHeight:"auto",
required: true
}
},
formatter:function(value){
return value?value.name:"";
}
},
{
field: 'productcolor', title: '颜色', width: 180,
formatter:function(v,r,i){
if(r && r.product){
//我也不知道为什么,这里要想显示颜色,就必须要写显示的范围
return `<div style='width: 20px;height: 20px;background-color: ${r.product.color}'></div>`
}
}
},
{
field: 'productImage', title: '图片', width: 100,
formatter:function(v,r,i){
if(r && r.product){
return `<img style='height: 50px;' alt="" src='${r.product.smallpic}'>`;
}
}
},
{
field: 'num', title: '数量', width: 100,
editor: {
type: "numberbox",
options: {
required: false,
precision:2 //保留两位小数
}
}
},
{
field: 'price', title: '价格', width: 100,
editor: {
type: "numberbox",
options: {
required: false,
precision:2 //保留两位小数
}
}
},
{
field: 'amount', title: '小计', width: 100,
formatter:function (v,r,i){
if (r.num && r.price){
//toFixed,表示将结果保留两位小数
return (r.num*r.price).toFixed(2);
}
}
},
{
field: 'descs', title: '描述', width: 100,
editor: {
type: "text",
}
}
];
result.push(normal);
return result;
};
//这里的option是grid的所有属性
var options = {
idField: "ID",
rownumbers: true,//行号
fitColumns: true,//自适应列
fit: true,//自适应父容器
border: true,//border为true,表示给grid添加边框
toolbar:"#editGridTools",//这里写的是按钮的id
singleSelect: true,//单选,只选中一行
columns: getColumns(),//获取到grid里面的所有的列
enableCellEdit: true //表示开启单元格编辑功能
};
dg.datagrid(options);
};
//下面的这个方法是为了得到每次插入行的索引
//dg.datagrid("getRows").length,拿到最后一行的索引
var getInsertRowIndex = function () {
//返回的类型是number
return insertPosition == "top" ? 0 : dg.datagrid("getRows").length;
}
//这里是添加一行,删除一行按钮的绑定事件
var buttonBindEvent = function () {
//表示注册一个添加事件
$("#btnInsert").click(function () {
//这里拿到插入的数据的索引位置
var targetIndex = getInsertRowIndex(), targetRow = $.extend({}, defaultRow, { ID: $.util.guid() });
dg.datagrid("insertRow", { index: targetIndex, row: targetRow });
//index表示第几行,field表示要操作那个字段
dg.datagrid("editCell", { index: targetIndex, field: "product" });
});
//删除行,不用连接到数据库,只是在添加,修改的时候一次性删除多余不要的行
$("#btnRemove").click(function () {
//拿到选中的行
var row = dg.datagrid("getSelected");
//如果不为空
if(row){
//拿到当前选中行的索引
var index=dg.datagrid("getRowIndex",row)
// 通过索引将选中行直接从grid里面删除
dg.datagrid("deleteRow",index);
}
});
};
//执行上面的两个方法
dgInit(); buttonBindEvent();
可以在option里面添加边框属性,
border: true,//border为true,表示给grid添加边框
配置两个按钮,在purchasebill.jsp里面
<table id="dg1" style="width: 680px;height: 260px"></table>
<div id="editGridTools" class="datagrid-toolbar">
<a id="btnInsert" class="easyui-linkbutton" data-options="iconCls:'icon-add',plain:true">新增一行</a>
<a id="btnRemove" class="easyui-linkbutton" data-options="iconCls:'icon-remove',plain:true">删除一行</a>
</div>
按钮效果:
dg:代表要操作的表格
为grid添加上对应的字段:
var dg = $("#dg1"),
defaultRow = { product: "", productcolor: "", productImage: "", num: "", price: "", amount: "", descs: "" },
insertPosition = "bottom";
需要编辑的表格,要有edit
//表格的初始化操作
var dgInit = function () {
//表格里面的列里面的数据
var getColumns = function () {
var result = [];
var normal = [
{
field: 'product', title: '产品', width: 180,
editor: {
type: "combobox",//编辑器类型,这里我们写入产品通过下拉列表,不用验证
//编辑器的属性
options: {
valueField:'id',
textField:'name',
url:'/util/productList',
panelHeight:"auto",
required: true
}
},
formatter:function(value){
return value?value.name:"";
}
},
{
field: 'productcolor', title: '颜色', width: 180,
formatter:function(v,r,i){
if(r && r.product){
//我也不知道为什么,这里要想显示颜色,就必须要写显示的范围
return `<div style='width: 20px;height: 20px;background-color: ${r.product.color}'></div>`
}
}
},
{
field: 'productImage', title: '图片', width: 100,
formatter:function(v,r,i){
if(r && r.product){
return `<img style='height: 50px;' alt="" src='${r.product.smallpic}'>`;
}
}
},
{
field: 'num', title: '数量', width: 100,
editor: {
type: "numberbox",
options: {
required: false,
precision:2 //保留两位小数
}
}
},
{
field: 'price', title: '价格', width: 100,
editor: {
type: "numberbox",
options: {
required: false,
precision:2 //保留两位小数
}
}
},
{
field: 'amount', title: '小计', width: 100,
formatter:function (v,r,i){
if (r.num && r.price){
//toFixed,表示将结果保留两位小数
return (r.num*r.price).toFixed(2);
}
}
},
{
field: 'descs', title: '描述', width: 100,
editor: {
type: "text",
}
}
];
result.push(normal);
return result;
};
点击添加按钮,绑定一个事件,添加一行
//下面的这个方法是为了得到每次插入行的索引
//dg.datagrid("getRows").length,拿到最后一行的索引
var getInsertRowIndex = function () {
//返回的类型是number
return insertPosition == "top" ? 0 : dg.datagrid("getRows").length;
}
//这里是添加一行,删除一行按钮的绑定事件
var buttonBindEvent = function () {
//表示注册一个添加事件
$("#btnInsert").click(function () {
//这里拿到插入的数据的索引位置
var targetIndex = getInsertRowIndex(), targetRow = $.extend({}, defaultRow, { ID: $.util.guid() });
dg.datagrid("insertRow", { index: targetIndex, row: targetRow });
//index表示第几行,field表示要操作那个字段
dg.datagrid("editCell", { index: targetIndex, field: "product" });
});
//删除行,不用连接到数据库,只是在添加,修改的时候一次性删除多余不要的行
$("#btnRemove").click(function () {
//拿到选中的行
var row = dg.datagrid("getSelected");
//如果不为空
if(row){
//拿到当前选中行的索引
var index=dg.datagrid("getRowIndex",row)
// 通过索引将选中行直接从grid里面删除
dg.datagrid("deleteRow",index);
}
});
};
//执行上面的两个方法
dgInit(); buttonBindEvent();
当点击保存的时候,前台页面被保存的数据中没有总金额和总数量,数据库里面,只有采购单里面添加了数据,但是还是没有总金额和总数量,采购单明细里面没有添加任何数据。
当点击保存两条数据的时候,前台的显示只有一条数据,而且没有数量和价格和小计,因为他只提交了添加表单的上半部分。
上面的错误可能是保存的时候一方和多方不能相互找到。,测试结果,多方找一方找不到。
尝试解决这个错误
双方都能找到对方,但是还是出错,因为产品没有传id
下面前台代码传一个product.id
双方添加成功,但是没有计算小计和总数,我们需要在java代码中进行计算。
直接相乘的话,会报错,因为BigDecimal不支持。应该写下面的格式。
关于添加表单的清除
add(){
//将被隐藏的密码框和确认密码框显示出来
$("*[data-show]").show();
$("*[data-show] input").validatebox("enable");
//打开对话框
purchasebillDialog.dialog("center").dialog("open");
// 清空上次添加的数据
purchasebillForm.form("clear");
// 清空额外的数据
dg.datagrid("loadData",[]);
},
修改表单额外数据的回显
edit(){
//获取选中的行
var row = purchasebillDataGrid.datagrid("getSelected");
//判断是否选中
if(!row){
$.messager.alert("提示","亲,您还没有选中哦","info");
return;
}
//将被显示的密码框和确认密码框隐藏起来出来
$("*[data-show]").hide();
$("*[data-show] input").validatebox("disable");
//打开对话框
purchasebillDialog.dialog("center").dialog("open");
//把form表单里面的数据清空
purchasebillForm.form("clear");
//回显数据
//回显供应商
if(row.supplier){
row["supplier.id"]=row.supplier.id;
}
if(row.buyer){
row["buyer.id"]=row.buyer.id;
}
//回显数据
purchasebillForm.form("load",row);
// 回显额外的数据
// 要先复制一条出来,因为如果不先复制一条的话,到时候在前台点击修改,
// 修改了里面的数据之后,又点击取消,再次打开,数据就会发生变化
var copyItems=[...row.items];
dg.datagrid("loadData",copyItems);
},
修改数据只加不减,不然就报n to n错误
解决
@ModelAttribute("editPurchasebill")
public Purchasebill beforeEdit(Long id,String cmd){
//只有修改才做查询
if(id!=null && "update".equals(cmd)){
Purchasebill dbPurchasebill = purchasebillService.findOne(id);
//解决n-to-n的问题(凡是要传过来的关联对象,都把它清空)
dbPurchasebill.setSupplier(null);
dbPurchasebill.setBuyer(null);
dbPurchasebill.getItems().clear();
return dbPurchasebill;
}
return null;
}