一步一步学grails:4 关系映射

资源在此处下载。

GORM-groovy 对象关系映射中,最常见的不是1:1关系,而是1M关系,也不是MM关系(因为MM关系比较麻烦,一般不能直接定义,通常都要拆分成两个1M关系来处理,并且要引入第3个类,否则会出现问题)。在本项目中,最利于理解1M的地方,就是“生产计划”planning域。

这不是一个单独的域,他分别与其他4个域类都发生了111M关系。这些域都不能单独看待,而要整体看待。

1、  下面来讨论这5个域类:

1)        生产计划

实际上就是根据订单而制订出的产品的具体生产过程。它包含了以下几个重要的内容:

首先,既然是根据订单进行生产,那么生产计划中必须包含有订单对象(即产品编号-前面已定义了这个域)。

其次,产品生产是要消耗一定原材料的,而且原材料可能不只一种,因此在生产计划中应当有物料表,以列出生产中可能用到的各种原材料。

最后,一个产品可能需要使用到一个或一系列的生产工艺才能制造出来,因此还应当在生产计划中列出工艺表,以列出可能用到的生产工艺。

生产计划中产品编号是一个相对简单属性,一个计划只针对一个产品,因此只会有一个产品编号,这只是1:1关系而已,前面已经定义了产品编号域,这里不再讨论。

属性物料表和工艺表则不同,它们包含的都是集合,由多个对象构成列表,因此这是1:m1对多)关系,而集合里面的每个对象都是复杂对象,应当定义为域类。下面来一一讨论。当然,除了这些复杂对象外,生产计划还有一些简单的属性,比如简单的string int。该域类代码如下:

class Planning {

   

    static constraints = {

    productNo()

    productName()

    spec(nullable:true)

    pieces()

    inspection(nullable:true)

    warehousing(nullable:true)

    shipment(nullable:true)

    factMaterial(nullable:true)

    consumeMaterial(nullable:true)

    accountsActual()

    accountsDue()

    rejection(nullable:true)

    linkman(nullable:true)

    remark(nullable:true)

    }

    static optionals=['spec','inspection','warehousing','shipment','factMaterial','consumeMaterial','accountsActual','accountsDue','rejection','linkman','remark']

    ProductionNo productNo   //产品编号

    String productName       //产品名

    String spec              //规格型号

   

    static hasMany=[materials:Material,craftList:Crafts]//两个1对多关系:与物料表表项,与工艺表表项

    int pieces            //件数

    String inspection     //检验

    String warehousing       //入库

    String shipment          //出库,领用

    String factMaterial      //实际用料

    String consumeMaterial   //材料消耗

    float accountsActual  //实收金额

    float accountsDue     //应收金额

    String rejection      //退货

    String linkman        //联系人

    String remark         //备注

   

}

注意其中的2个地方。一个是

ProductionNo productNo   //产品编号

这表明了一个1:1关系,因此我们还需要修改ProductionNo域类,加上这句:

static belongTo=[Planning]//定义11关系,即1个生产编号对应1个生产计划

另一个地方是

static hasMany=[materials:Material,craftList:Crafts]//两个1对多关系:与物料表表项,与工艺表表项

这定义了两个1:m关系。

2)        物料表

物料表实际上是1个以上的物料表表项组成的列表。物料表中的表项我们可以单独定义为一个域:

class Material {//Material域:定义生产计划中某个产品的一种原材料-物料表表项

    static constraints = {

    }

    MaterialCost fee                       //定义一个材料的价格11关系   

    float amount=0                         //该材料所需数量

}

而一个MaterialCost定义了一个材料的价格:

class MaterialCost {

    static constraints = {

    code(blank:false,nullable:false)//必填

    name(blank:false,nullable:false)//必填

    price(min:0.0f,nullable:false)//必填,值不能为负

    unit()

    remark()

    }

    static optionals=["unit","remark"]

    String code              //代码

    String name              //物料类型

    float price              //单价()

    String unit='/u516C/u65A4'      //单位,默认"公斤",native2acii转码

    String remark          //备注

    String toString(){

       String.format('%1$s%2$s',code,name)

       //"${coo.shortName}${prefix}-${sno}${suffix}"

    }   

}

 

3)        工艺表

工艺表表项也可以定义为一个单独的域:

class Crafts {//Crafts域:定义生产计划中某个产品的一种工艺 - 工艺表表项

 

    static constraints = {

    }

    ProcessingFee fee //定义一个加工费,11关系

    float hours       //定义工时

    int sequence   //定义序号

}

其中引用了ProcessiongFee加工费。加工费定义了一种加工工艺的费用,域定义如下:

class ProcessingFee {//域:加工费

    static constraints = {

       code()

       workType(nullale:true)

       processFee(nullale:true)

       labourFee(nullale:true)

       remark(nullale:true)

    }

    String code          //代码

    String workType      //工种

    float processFee  //加工费

    float labourFee      //人工费

    String remark     //备注

    String toString(){

       "${code} - ${workType}"

    }

    static optionals=["workType","processFee","labourFee","remark"]

}

2、   生成脚手架

定义完上述5个域类,再生成它们的脚手架代码。

3、  修改“生产计划”控制器中的action代码

由于我们希望在每编制一个新的“生产计划”都需要先编一个“产品编号”而不是直接就从编制“生产计划”开始,所以我们需要在PlanningController控制器修改action

def create = {

            redirect(controller: 'productionNo', action: 'create')

}

这样当url指向planningControllercreate动作时,实际上是调用productionNoControllercreate动作。

4、  修改“产品编号”控制器中的action

在产品编号create页面中,create按钮将调用productionNoControllersave动作。我们也需要修改它,使得“产品编号”一编好,就显示“编制生产计划”页面:

def save = {

            def productionNoInstance = new ProductionNo(params)

            if(!productionNoInstance.hasErrors()) {

              if(productionNoInstance.save()){

                    flash.message = "ProductionNo ${productionNoInstance.id} created"

                    redirect(action:'create_2',controller:'planning',params:['productNo.id':productionNoInstance.id])

              }

            }

            else {

                render(view:'create',model:[productionNoInstance:productionNoInstance])

            }

        }

注意划线部分的代码,redirect方法的前两个参数指定将跳转到PlanningControllercreate_2动作。别着急,这个create_2我们将在后面定义它。

值得注意的是第三个参数。我们本来是想把新建的“产品编号”对象实例传递给create_2的,然而最终我们只需要把对象实例的id传给create_2就可以了。Create_2会把这个id值代表的对象传递到页面去。

5、  编辑“生产计划”控制器中的action

在其中新增一个action

def create_2 = {

        def planningInstance = new Planning()

        planningInstance.properties = params

        render(view:'create',model:[planningInstance:planningInstance])

        //return ['planningInstance':planningInstance]

}

这个action 渲染页面create.gsp

6、  修改create.gsp

“生产计划”的属性有很多,我们在新建一个“生产计划”时,并不需要一次性把所有的属性都进行赋值——有的属性需要在新建时指定,而有的属性,很可能需要在生产完成之后再来填写它,因此需要修改create.gsp页面,使得在新建“生产计划”时不需要填写全部那么多的属性就可以新建一个生产计划。同时,需要注意的是产品编号这个字段,需要把它从下拉列表改为hidden控件。因为我们的业务要求用户在编制了产品编号后,才进行生产计划的编制——这时其实生产编号已经指定,不再需要用户从下拉列表中选取了:

……

<tr class="prop">

                                <td valign="top" class="name">

                                    <label for="productNo">产品编号:</label>

                                </td>

                                <td valign="top" class="value ${hasErrors(bean:planningInstance,field:'productNo','errors')}">

${planningInstance?.productNo}

<input type="hidden" id="productNo" name="productNo.id" value="${planningInstance?.productNo?.id}" readonly/>

 …… 

所以这里直接就把新建的产品编号显示出来,然后用一个隐藏域传递产品编号的id值。

7、  修改show.gsp

由于生产计划的字段有点多,我们需要对show.gsp进行重新排版,使显示更加美观:

8、   

原文地址:https://www.cnblogs.com/encounter/p/2188554.html