【问题标题】:Saving object having many-to-many relationship in grails在grails中保存具有多对多关系的对象
【发布时间】:2021-02-23 20:33:32
【问题描述】:

我正在尝试保存具有多对多关系的对象。一个 SellingCompany 可以有多个帐户,一个帐户可以与多个 SellingCompanies 关联。所以存储在 SellingCompaniesAccount 中的表之间存在多对多关系

我的 Account_Info 域如下:

class AccountInfo { 
    static mapping ={
        table 'AccountInfo'
        version false
        //id column:'accountInfoID'
    }

    String evi_pass_phrase
    String evi_username
    String security_key

    // to make sure fields show up in a particular order

    static constraints = {
        //accountInfoID(insert:false,update:false)
        evi_pass_phrase()
        evi_username()
        security_key()

    }

    static hasMany = [sellingcompaniesaccount:SellingCompaniesAccount]


    String toString() {
        return "${evi_username}"
    }
}

我的 SellingComapanies 域如下:

class SellingCompanies 
{

    static mapping = {  
        table 'SellingCompanies'
        version false
    }

    String name

    //static belongsTo = AccountInfo

    //static hasMany = [accounts: AccountInfo]
    static hasMany = [sellingcompaniesaccount:SellingCompaniesAccount]

    static constraints = {

    name(blank:false, validator:
        { val, obj ->
            def similarSellingCompanies = SellingCompanies.findByNameIlike(val)
            return !similarSellingCompanies || (obj.id == similarSellingCompanies.id)
        })
    }

    //String toString() { name }
}

持有多对多关系的表如下:

class SellingCompaniesAccount {

    static constraints = {
        // ensure the group of sellingCompaneis and accountInfo values are unique
        agency_name(unique:['sellingCompanies','accountInfo'])
    }

    int agency_id
    String agency_name
    String consultant_id
    String code
    Boolean isActive
    String iata

    ContactInfo contactinfo

    static belongsTo = [sellingCompanies:SellingCompanies, accountInfo:AccountInfo]

        }

}

create.gsp 文件中的表单包含实际迭代所有不同 SellingCompanies 并显示为复选框的代码。

<g:form action="save" method="post">
    <div class="dialog">
    <table width="500px" border="0px" color="red">
        <tbody>

            <tr class="prop">
                <td valign="top" class="name"><label for="accountInfo"><g:message
                    code="sellingCompaniesAccount.accountInfo.label"
                    default="Account Info" /></label></td>
                <td valign="top"
                    class="value ${hasErrors(bean: sellingCompaniesAccountInstance, field: 'accountInfo', 'errors')}">
                <g:select name="accountInfo.id"
                    from="${content_hub_admin.AccountInfo.list()}" optionKey="id"
                    value="${sellingCompaniesAccountInstance?.accountInfo?.id}" /></td>
            </tr>

            <tr class="prop">
                <td valign="top" class="name"><label for="sellingCompanies"><g:message
                    code="sellingCompaniesAccount.sellingCompanies.label"
                    default="Selling Companies" /></label></td>
                <td valign="top"
                    class="">
                    <g:each in="${content_hub_admin.SellingCompanies.list()}" var="item" status="i">
                        ${++i}.  ${item.name}&nbsp;&nbsp;<g:checkBox name="sellingcompanies_${++i-1}" optionKey="id" value="${item.id}" /> <br>
                    </g:each>
                <!--  end here by rsheyeah -->
                </td>
            </tr>



            <tr class="prop">
                <td valign="top" class="name"><label for="code"><g:message
                    code="sellingCompaniesAccount.code.label" default="Code" /></label></td>
                <td valign="top"
                    class="value ${hasErrors(bean: sellingCompaniesAccountInstance, field: 'code', 'errors')}">
                <g:textField name="code"
                    value="${sellingCompaniesAccountInstance?.code}" /></td>
            </tr>

            <tr class="prop">
                <td valign="top" class="name"><label for="agency_name"><g:message
                    code="sellingCompaniesAccount.agency_name.label"
                    default="Agencyname" /></label></td>
                <td valign="top"
                    class="value ${hasErrors(bean: sellingCompaniesAccountInstance, field: 'agency_name', 'errors')}">
                <g:textField name="agency_name"
                    value="${sellingCompaniesAccountInstance?.agency_name}" /></td>
            </tr>

            <tr class="prop">
                <td valign="top" class="name"><label for="isActive"><g:message
                    code="sellingCompaniesAccount.isActive.label" default="Is Active" /></label>
                </td>
                <td valign="top"
                    class="value ${hasErrors(bean: sellingCompaniesAccountInstance, field: 'isActive', 'errors')}">
                <g:checkBox name="isActive"
                    value="${sellingCompaniesAccountInstance?.isActive}" /></td>
            </tr>

            <tr class="prop">
                <td valign="top" class="name"><label for="agency_id"><g:message
                    code="sellingCompaniesAccount.agency_id.label" default="Agencyid" /></label>
                </td>
                <td valign="top"
                    class="value ${hasErrors(bean: sellingCompaniesAccountInstance, field: 'agency_id', 'errors')}">
                <g:textField name="agency_id"
                    value="${fieldValue(bean: sellingCompaniesAccountInstance, field: 'agency_id')}" />
                </td>
            </tr>

            <tr class="prop">
                <td valign="top" class="name"><label for="iata"><g:message
                    code="sellingCompaniesAccount.iata.label" default="Iata" /></label></td>
                <td valign="top"
                    class="value ${hasErrors(bean: sellingCompaniesAccountInstance, field: 'iata', 'errors')}">
                <g:textField name="iata"
                    value="${sellingCompaniesAccountInstance?.iata}" /></td>
            </tr>

            <tr class="prop">
                <td valign="top" class="name"><label for="consultant_id"><g:message
                    code="sellingCompaniesAccount.consultant_id.label"
                    default="Consultantid" /></label></td>
                <td valign="top"
                    class="value ${hasErrors(bean: sellingCompaniesAccountInstance, field: 'consultant_id', 'errors')}">
                <g:textField name="consultant_id"
                    value="${sellingCompaniesAccountInstance?.consultant_id}" /></td>
            </tr>

            <tr class="prop">
                <td valign="top" class="name"><label for="contactinfo"><g:message
                    code="sellingCompaniesAccount.contactinfo.label"
                    default="Contactinfo" /></label></td>
                <td valign="top"
                    class="value ${hasErrors(bean: sellingCompaniesAccountInstance, field: 'contactinfo', 'errors')}">
                <g:select name="contactinfo.id"
                    from="${content_hub_admin.ContactInfo.list()}" optionKey="id"
                    value="${sellingCompaniesAccountInstance?.contactinfo?.id}" /></td>
            </tr>

        </tbody>
    </table>
    </div>
    <div class="buttons"><span class="button"><g:submitButton
        name="create" class="save"
        value="${message(code: 'default.button.create.label', default: 'Create')}" /></span>
    </div>
</g:form>

最后是处理保存和列表功能的控制器。

class SellingCompaniesAccountController {

    private static Logger log = Logger.getLogger(SellingCompaniesAccountController.class)

    //def index = { }
    //def scaffold = true

    def index = { redirect(action:list,params:params) }

    //To limit access to controller actions based on the HTTP request method.
    def allowedMethods = [save:'POST']

    //create.gsp exists
    def create = {
        render(view:"create")
    }

    //edit.gsp exists
    //def edit = {}

    //list.gsp exists
    def list = {
        [ sellingCompaniesAccountInstanceList: SellingCompaniesAccount.list( max:15) ]
        }

    //show.gsp exists
    //def show={}

    //save.gsp exists
    def save = { 
        log.info "Saving: " + params.toString()

        println("Saving: " + params.toString())
        def sellingCompaniesAccount = params.sellingCompaniesAccount
        println(sellingCompaniesAccount)

        def sellingCompanies = params.sellingCompanies

        log.info "sellingCompanies: " + sellingCompanies
        println(sellingCompanies)


        def sellingCompaniesAccountInstance = new SellingCompaniesAccount(name: params.name)

        println(params.name)

        params.each {
            if (it.key.contains("_sellingcompanies"))
            //sellingCompaniesAccountInstance.sellingCompaniesId << SellingCompanies.get((it.key - "sellingcompanies_") as Integer)
            if (it.key.contains("sellingcompanies_"))
                sellingCompaniesAccountInstance.sellingCompaniesId << SellingCompanies.get((it.key - "sellingcompanies_") as Integer)
        }
        log.info sellingCompaniesAccountInstance
        if (sellingCompaniesAccountInstance.save(flush: true)) {
            flash.message = "${message(code: 'default.created.message', args: [message(code: 'sellingCompaniesAccountInstance.label', default: 'sellingCompaniesAccountInstance'), sellingCompaniesAccountInstance.id])}"
            redirect(action: "show", id: sellingCompaniesAccountInstance.id)
            log.info sellingCompaniesAccountInstance
        }
        else {
            render(view: "create", model: [sellingCompaniesAccountInstance: sellingCompaniesAccountInstance])
        }


    }

}

现在,由于空的隐藏值显示为 _salescompanies_1 等,我收到以下错误:

错误日志:

Saving: ["accountInfo.id":"1", "accountInfo":["id":"1"], "_sellingcompanies_5":"", "_isActive":"", "code":"test", "agency_name":"test", "sellingcompanies_4":"4", "sellingcompanies_5":"5", "create":"Create", "isActive":"on", "iata":"test", "agency_id":"test", "contactinfo.id":"1", "contactinfo":["id":"1"], "consultant_id":"test", "sellingcompanies_2":"2", "_sellingcompanies_1":"", "sellingcompanies_3":"3", "_sellingcompanies_2":"", "_sellingcompanies_3":"", "sellingcompanies_1":"1", "_sellingcompanies_4":"", "action":"save", "controller":"sellingCompaniesAccount"]
null
null
null
2011-03-15 17:13:44,620 [http-8080-2] ERROR org.codehaus.groovy.grails.web.errors.GrailsExceptionResolver - For input string: "_5"
java.lang.NumberFormatException: For input string: "_5"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
    at java.lang.Integer.parseInt(Integer.java:449)
    at java.lang.Integer.valueOf(Integer.java:554)
    at content_hub_admin.SellingCompaniesAccountController$_closure4_closure5.doCall(content_hub_admin.SellingCompaniesAccountController:70)
    at content_hub_admin.SellingCompaniesAccountController$_closure4.doCall(content_hub_admin.SellingCompaniesAccountController:66)
    at content_hub_admin.SellingCompaniesAccountController$_closure4.doCall(content_hub_admin.SellingCompaniesAccountController)
    at java.lang.Thread.run(Thread.java:680)

首先,隐藏值从何而来,这种方法可以很好地提交 SellingCompaniesAccount 控制器类中的多对多关系信息。任何更好的技术来做到这一点。

create.gsp 在浏览器中对此进行了解析:

提前致谢

【问题讨论】:

  • 你应该限制相关代码,它有助于其他阅读更容易理解你的问题。
  • 正如日志指出的那样,这是一个数据类型错误。你看过 SellingCompaniesAccountController 第 70 行吗?
  • 感谢Hoang Long,经过调查,我发现复选框呈现了一个隐藏字段。如何禁用这个隐藏字段?有任何想法吗。以及如何在 SellingCompaniesAccount 表中为每个选中的复选框添加记录。
  • 很确定隐藏字段在那里,因此您可以“取消选中”该复选框。表单无法提交“未选中”的复选框值,因此如果复选框已选中,但您已取消选中,则必须存在隐藏字段以让属性解析器知道要选中的属性 USED。

标签: grails groovy save has-many


【解决方案1】:

如果其他人遇到同样的问题,那么 Daniel 的上述回答是绝对正确的,只是从 Grails 2.7.8 开始添加了一些更改。

所有复选框都将具有相同的value = "${item.id}"name ="sellingcompanies",如下所示:

<!-- ... snip ... -->
    <tr class="prop">
        <td valign="top" class="name"><label for="sellingCompanies"><g:message
            code="sellingCompaniesAccount.sellingCompanies.label"
            default="Selling Companies" /></label></td>
        <td valign="top"
            class="">
            <g:each in="${content_hub_admin.SellingCompanies.list()}" var="item" status="i">
                ${++i}.  ${item.name}&nbsp;&nbsp;<g:checkBox name="sellingcompanies" optionKey="id" value="${item.id}" /> <br>
            </g:each>
        <!--  end here by rsheyeah -->
        </td>
    </tr>
    <!-- ... snip ... -->

最好的部分是,在当前版本的 Grails 中,您不需要使用 Daniel 提到的 flatten() as string,这些值将由 Grails 自动处理,并保留在连接表中。您只需要确保您的复选框具有正确的名称和值。

希望对你有帮助!

【讨论】:

    【解决方案2】:

    问题出在这段代码上:

        params.each {
            if (it.key.contains("_sellingcompanies"))
            //sellingCompaniesAccountInstance.sellingCompaniesId << SellingCompanies.get((it.key - "sellingcompanies_") as Integer)
            if (it.key.contains("sellingcompanies_"))
                sellingCompaniesAccountInstance.sellingCompaniesId << SellingCompanies.get((it.key - "sellingcompanies_") as Integer)
        }
    

    表单帖子中的参数是:

    Saving: ["accountInfo.id":"1", "accountInfo":["id":"1"], "_sellingcompanies_5":"", "_isActive":"", "code":"test", "agency_name":"test", "sellingcompanies_4":"4", "sellingcompanies_5":"5", "create":"Create", "isActive":"on", "iata":"test", "agency_id":"test", "contactinfo.id":"1", "contactinfo":["id":"1"], "consultant_id":"test", "sellingcompanies_2":"2", "_sellingcompanies_1":"", "sellingcompanies_3":"3", "_sellingcompanies_2":"", "_sellingcompanies_3":"", "sellingcompanies_1":"1", "_sellingcompanies_4":"", "action":"save", "controller":"sellingCompaniesAccount"]
    

    循环中的测试首先检查参数键是否包含“_销售公司”,然后它什么也不做;第二部分检查参数键是否包含“sellingcompanies_”,然后尝试从该参数值中提取后缀数字。对于键值为“_salescompanies_5”的参数,第一个测试和第二个测试的结果都为真,因此在第二个测试中,您要从参数键值,您将得到“_5”,然后您尝试将其评估为整数。不幸的是,“_5”不是一个有效的整数值,因此是您给定的错误。

    您可以通过执行以下操作非常简单地解决此问题:

    params.each {
       if (it.key.startsWith("sellingcompanies")) {
          sellingCompaniesAccountInstance.sellingCompaniesId << SellingCompanies.get((it.key - "sellingcompanies_") as Integer)
       }
    }
    

    处理这个问题的更好方法可能是更改您的 gsp 以为每个销售公司提供相同的命名参数,然后循环遍历实际应用的值。像这样的:

            <!-- ... snip ... -->
            <tr class="prop">
                <td valign="top" class="name"><label for="sellingCompanies"><g:message
                    code="sellingCompaniesAccount.sellingCompanies.label"
                    default="Selling Companies" /></label></td>
                <td valign="top"
                    class="">
                    <g:each in="${content_hub_admin.SellingCompanies.list()}" var="item" status="i">
                        ${++i}.  ${item.name}&nbsp;&nbsp;<g:checkBox name="sellingcompanies" optionKey="id" value="${item.id}" /> <br>
                    </g:each>
                <!--  end here by rsheyeah -->
                </td>
            </tr>
            <!-- ... snip ... -->
    

    然后在您的控制器中执行以下操作:

    params.sellingcompanies = [params.sellingcompanies].flatten() as String[]
    
    sellingCompaniesAccountInstance.sellingCompaniesId = params.sellingcompanies.collect { SellingCompanies.get(it) }
    

    这应该确保您正确评估从模型对象传递的适当值,而不是在检索方法中进行黑客攻击。

    希望这会有所帮助!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-16
      • 1970-01-01
      • 1970-01-01
      • 2012-01-03
      • 2015-08-02
      相关资源
      最近更新 更多