Java Spring MVC+;Hibernate:带有@manytomany关系复选框的表单

Java Spring MVC+;Hibernate:带有@manytomany关系复选框的表单,java,hibernate,spring-mvc,spring-webflow,Java,Hibernate,Spring Mvc,Spring Webflow,一个星期以来,我一直在尝试做一件非常简单的web开发工作:一个带有复选框的表单,我可以在其中勾选参与竞赛的机构 我找到了一个解决办法。我不认为这是最好的,但它是有效的: 在视图中,我为所有复选框(chk_服务、chk_机构)命名 在转换到下一步时,我使用检查过的ID(id1,id2,…)获取POST生成的逗号分隔字符串,并将其传递给一个方法 它分割字符串,对于每个id,从db中获取相关实体,并将它们放入我的多对多实体集合(服务) (我在执行下一步和上一步时都会执行此操作) 在进入视图时,

一个星期以来,我一直在尝试做一件非常简单的web开发工作:一个带有复选框的表单,我可以在其中勾选参与竞赛的机构


我找到了一个解决办法。我不认为这是最好的,但它是有效的:

  • 在视图中,我为所有复选框(chk_服务、chk_机构)命名

  • 在转换到下一步时,我使用检查过的ID(id1,id2,…)获取POST生成的逗号分隔字符串,并将其传递给一个方法 它分割字符串,对于每个id,从db中获取相关实体,并将它们放入我的多对多实体集合(服务)

<评估 expression=“contestFlow.converterCheckboxToSetContestService(flowScope.contest, requestParameters.chk_服务) result=“flowScope.contest.contesticservices”>

(我在执行下一步和上一步时都会执行此操作)

  • 在进入视图时,我希望再次选中复选框。为此,我需要将我的集合转换为ID列表:
使用我的方法对Flow bean进行竞争:

@Component
public class ContestFlow {  

    static Logger logger = LoggerFactory.getLogger(ContestFlow.class);

    @Autowired
    private ServiceService ServiceService;

    @Autowired
    private AgencyService AgencyService;

    /**
     * input: a comma separated string with all ids checked from the POST
     * 
     * @param contest:      contest object that i will add at the end of the webflow
     * @param ids_string:   comma separated string with checked ids
     * @return
     */
    public Set<ContestService> converterCheckboxToSetContestService(Contest contest, String ids_string)
    {

        Set<ContestService> contestServices = new HashSet<ContestService>(0);   

        if (ids_string != null)
        {
            String[] arr_ids = ids_string.split(",");

            /*
             * for each record i get the Service
             */
            for (int i = 0; i < arr_ids.length; i++)
            {
                try
                {
                    //get the Service
                    Service service = ServiceService.getService(Integer.parseInt(arr_ids[i]));

                    logger.info("Aggiungo il service id [" + arr_ids[i] + "]");

                    //creation of the Id object
                    ContestServiceId contestServiceId = new ContestServiceId();             
                    contestServiceId.setService(service);
                    contestServiceId.setContest(contest);

                    //record population
                    ContestService contestService = new ContestService();
                    contestService.setService(service);
                    contestService.setContest(contest);
                    contestService.setPk(contestServiceId);

                    //add the record
                    contestServices.add(contestService);

                }
                catch(Exception ex)
                {
                    ex.printStackTrace();
                    logger.info("Service id [" + arr_ids[i] + "] not found!");

                }       

            }       
        }           
        return contestServices;
    }


    /**
     * input: Set of ContestAgency (many-to-many) checked 
     * and returns a List<String> of ids to be used to select checkboxes 
     * in thymeleaf view with th:checked="${#lists.contains(checked_agencies, '' + agency_el.id)}"
     * 
     * i can't return a List<Integer> because it doesn't check the checkboxes
     * 
     * @param contestAgencies
     * @return
     */
    public List<String> converterSetContestServiceToCheckbox(Set<ContestService> contestServices)
    {
        List<String> result = new ArrayList<String>();

        if (contestServices != null)
        {
            Iterator<ContestService> iterator = contestServices.iterator();
            while(iterator.hasNext()) 
            {           
                ContestService contestService = iterator.next();            

                Integer id = contestService.getService().getId();

                result.add(id.toString());
            }  
        }

        return result;
    }




    //same as above, for the Agencies:





    /**
     * input: a comma separated string with all ids checked from the POST
     * 
     * @param contest:      contest object that i will add at the end of the webflow
     * @param ids_string:   comma separated string with checked ids
     * @return
     */
    public Set<ContestAgency> converterCheckboxToSetContestAgency(Contest contest, String ids_string)
    {

        Set<ContestAgency> contestAgencies = new HashSet<ContestAgency>(0); 

        if (ids_string != null)
        {
            String[] arr_ids = ids_string.split(",");

            /*
             * for each record i get the Agency
             */
            for (int i = 0; i < arr_ids.length; i++)
            {
                try
                {
                    //get the Agency
                    Agency agency = AgencyService.getAgency(Integer.parseInt(arr_ids[i]));

                    logger.info("Adding agency id [" + arr_ids[i] + "]");

                    //creation of the Id object
                    ContestAgencyId contestAgencyId = new ContestAgencyId();                
                    contestAgencyId.setAgency(agency);
                    contestAgencyId.setContest(contest);

                    //record population
                    ContestAgency contestAgency = new ContestAgency();
                    contestAgency.setAgency(agency);
                    contestAgency.setContest(contest);
                    contestAgency.setPk(contestAgencyId);
                    contestAgency.setContractCount(0);  //my many-to-many relationship has an additional field

                    //add the record
                    contestAgencies.add(contestAgency);

                }
                catch(RecordNotFoundException ex)
                {
                    ex.printStackTrace();
                    logger.info("Agency id [" + arr_ids[i] + "] not found!");

                }       

            }       
        }       
        return contestAgencies;
    }


    /**
     * input: Set of ContestAgency (many-to-many) checked 
     * and returns a List<String> of ids to be used to select checkboxes 
     * in thymeleaf view with th:checked="${#lists.contains(checked_agencies, '' + agency_el.id)}"
     * 
     * i can't return a List<Integer> because it doesn't check the checkboxes
     * 
     * @param contestAgencies
     * @return
     */
    public List<String> converterSetContestAgencyToCheckbox(Set<ContestAgency> contestAgencies)
    {
        List<String> result = new ArrayList<String>();

        if (contestAgencies != null)
        {
            Iterator<ContestAgency> iterator = contestAgencies.iterator();
            while(iterator.hasNext()) 
            {
                ContestAgency contestAgency = iterator.next();

                Integer id = contestAgency.getAgency().getId();

                result.add(id.toString());
            }       
        }   

        return result;
    }

}
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="
        http://www.springframework.org/schema/webflow
        http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

    <secured attributes="ROLE_USER" />

    <!-- creation of an empty object i will insert in db in the last step -->
    <on-start>
        <evaluate expression="ContestService.createContest()" result="flowScope.contest" />
    </on-start>

    <!-- 
        step 1: contest details
     -->
    <view-state id="contest-details" model="contest"> 
        <binder>
            <binding property="startDate" required="true" />
            <binding property="endDate" required="true"/>
            <binding property="bonus" required="true"/>
            <binding property="goal" required="true"/>
            <binding property="title" required="true"/>
        </binder>
        <transition on="proceed" to="contest-services">
        </transition>
        <transition on="cancel" to="cancel" bind="false" />
    </view-state>

    <!-- 
        step 2: i select which services are involved
    -->
    <view-state id="contest-services" model="contest">
        <on-entry>      
            <!--
            - in case i'm coming here from the step 3
            - injection of the list of ids previously checked
            -->
            <set name="viewScope.checked_services" value="contestFlow.converterSetContestServiceToCheckbox(flowScope.contest.ContestServices)" />

            <!-- 
             - i get the list of the Main Services
             - subservices will be scanned with getChildren method  
             -->
            <set name="viewScope.services_list" value="ServiceService.getMainServices()" />
        </on-entry>         

        <transition on="proceed" to="contest-agencies" >
            <!-- 
            - MY SOLUTION TO MANY-TO-MANY checkboxes form:
            - 
            - honestly not very elegant, but in 10 day i could't find better
            - 
            - conversion from String to Set<ContestService>
            -->
            <evaluate expression="contestFlow.converterCheckboxToSetContestService(flowScope.contest, requestParameters.chk_services)" result="flowScope.contest.ContestServices"></evaluate>  
        </transition>

        <transition on="cancel" to="contest-details">
            <!-- 
            - also if i go back in the flow, to the first step,
            - i need to remember which checkboxes were selected
            - 
            - and i need to save the checked services to the Contest entity, 
            - else, when i will call addContest method, 
            - it will not save the checked Services
            -->
            <evaluate expression="contestFlow.converterCheckboxToSetContestService(flowScope.contest, requestParameters.chk_services)" result="flowScope.contest.ContestServices"></evaluate>  
        </transition>
    </view-state>




    <!-- 
        step 3: i select which agencies are involved in contest.
        only agencies enabled for previously checked services are shown
    -->     
    <view-state id="contest-agencies" model="agencies">
        <on-entry>      
            <!--
            - in case i'm coming here from the step 3
            - injection of the list of ids previously checked
            -->
            <set name="viewScope.checked_agencies" value="contestFlow.converterSetContestAgencyToCheckbox(flowScope.contest.ContestAgencies)" />

            <!-- 
             - only agencies enabled for the step 2 checked services are shown
             -->
            <set name="viewScope.agencies_list" value="AgencyService.getEnabledAgenciesForServices(contestFlow.converterSetContestServiceToCheckbox(flowScope.contest.ContestServices))" />
        </on-entry>     

        <transition on="proceed" to="contest-confirm" >
            <!-- 
            - MY SOLUTION TO MANY-TO-MANY checkboxes form:
            - 
            - honestly not very elegant, but in 10 day i could't find better
            - 
            - conversion from String to Set<ContestAgency>
            -->
            <evaluate expression="contestFlow.converterCheckboxToSetContestAgency(flowScope.contest, requestParameters.chk_agencies)" result="flowScope.contest.ContestAgencies"></evaluate>  
        </transition>

        <transition on="cancel" to="contest-services">
            <!-- 
            - MY SOLUTION TO MANY-TO-MANY checkboxes form:
            - 
            - honestly not very elegant, but in 10 day i could't find better
            - 
            - conversion from String to Set<ContestAgency>
            - 
            - and i need to save the checked Agencies to the Contest entity, 
            - else, when i will call addContest method, 
            - it will not save the checked Agencies
            -->
            <evaluate expression="contestFlow.converterCheckboxToSetContestAgency(flowScope.contest, requestParameters.chk_agencies)" result="flowScope.contest.ContestAgencies"></evaluate>  
        </transition>
    </view-state>


    <!-- 
    - data confirmation before insert in db
     -->    
    <view-state id="contest-confirm" model="contest">       
        <transition on="proceed" to="contest-end" >
            <evaluate expression="ContestService.addContest(contest)" />
        </transition>
        <transition on="cancel" to="contest-agencies" />
    </view-state>   



    <!-- 
    end: redirect to list
     -->
    <end-state id="contest-end" view="externalRedirect:contextRelative:/contest/list"/>

    <!-- 
    cancella
     -->
    <end-state id="cancel"/>

</flow>
@组件
公共类{
静态记录器Logger=LoggerFactory.getLogger(ContestFlow.class);
@自动连线
私人服务;
@自动连线
私人代理服务代理服务;
/**
*输入:一个逗号分隔的字符串,其中所有ID都已从POST中检查
* 
*@param contest:contest对象,我将在webflow末尾添加
*@param ids_string:带有选中ID的逗号分隔字符串
*@返回
*/
公共集转换器checkbox设置竞赛服务(竞赛竞赛,字符串ID\u字符串)
{
Set contesticservices=newhashset(0);
if(ids\u string!=null)
{
String[]arr\u ids=ids\u String.split(“,”);
/*
*每一张唱片我都会得到服务
*/
对于(int i=0;i  < set name="viewScope.checked_services" value="contestFlow.converterSetContestServiceToCheckbox(flowScope.contest.ContestServices)" / >
public class WebAppConfig extends WebMvcConfigurerAdapter {

    //...

    @Bean
    public ContestFlow contestFlow()
    {
        return new ContestFlow();       
    }

}
@Component
public class ContestFlow {  

    static Logger logger = LoggerFactory.getLogger(ContestFlow.class);

    @Autowired
    private ServiceService ServiceService;

    @Autowired
    private AgencyService AgencyService;

    /**
     * input: a comma separated string with all ids checked from the POST
     * 
     * @param contest:      contest object that i will add at the end of the webflow
     * @param ids_string:   comma separated string with checked ids
     * @return
     */
    public Set<ContestService> converterCheckboxToSetContestService(Contest contest, String ids_string)
    {

        Set<ContestService> contestServices = new HashSet<ContestService>(0);   

        if (ids_string != null)
        {
            String[] arr_ids = ids_string.split(",");

            /*
             * for each record i get the Service
             */
            for (int i = 0; i < arr_ids.length; i++)
            {
                try
                {
                    //get the Service
                    Service service = ServiceService.getService(Integer.parseInt(arr_ids[i]));

                    logger.info("Aggiungo il service id [" + arr_ids[i] + "]");

                    //creation of the Id object
                    ContestServiceId contestServiceId = new ContestServiceId();             
                    contestServiceId.setService(service);
                    contestServiceId.setContest(contest);

                    //record population
                    ContestService contestService = new ContestService();
                    contestService.setService(service);
                    contestService.setContest(contest);
                    contestService.setPk(contestServiceId);

                    //add the record
                    contestServices.add(contestService);

                }
                catch(Exception ex)
                {
                    ex.printStackTrace();
                    logger.info("Service id [" + arr_ids[i] + "] not found!");

                }       

            }       
        }           
        return contestServices;
    }


    /**
     * input: Set of ContestAgency (many-to-many) checked 
     * and returns a List<String> of ids to be used to select checkboxes 
     * in thymeleaf view with th:checked="${#lists.contains(checked_agencies, '' + agency_el.id)}"
     * 
     * i can't return a List<Integer> because it doesn't check the checkboxes
     * 
     * @param contestAgencies
     * @return
     */
    public List<String> converterSetContestServiceToCheckbox(Set<ContestService> contestServices)
    {
        List<String> result = new ArrayList<String>();

        if (contestServices != null)
        {
            Iterator<ContestService> iterator = contestServices.iterator();
            while(iterator.hasNext()) 
            {           
                ContestService contestService = iterator.next();            

                Integer id = contestService.getService().getId();

                result.add(id.toString());
            }  
        }

        return result;
    }




    //same as above, for the Agencies:





    /**
     * input: a comma separated string with all ids checked from the POST
     * 
     * @param contest:      contest object that i will add at the end of the webflow
     * @param ids_string:   comma separated string with checked ids
     * @return
     */
    public Set<ContestAgency> converterCheckboxToSetContestAgency(Contest contest, String ids_string)
    {

        Set<ContestAgency> contestAgencies = new HashSet<ContestAgency>(0); 

        if (ids_string != null)
        {
            String[] arr_ids = ids_string.split(",");

            /*
             * for each record i get the Agency
             */
            for (int i = 0; i < arr_ids.length; i++)
            {
                try
                {
                    //get the Agency
                    Agency agency = AgencyService.getAgency(Integer.parseInt(arr_ids[i]));

                    logger.info("Adding agency id [" + arr_ids[i] + "]");

                    //creation of the Id object
                    ContestAgencyId contestAgencyId = new ContestAgencyId();                
                    contestAgencyId.setAgency(agency);
                    contestAgencyId.setContest(contest);

                    //record population
                    ContestAgency contestAgency = new ContestAgency();
                    contestAgency.setAgency(agency);
                    contestAgency.setContest(contest);
                    contestAgency.setPk(contestAgencyId);
                    contestAgency.setContractCount(0);  //my many-to-many relationship has an additional field

                    //add the record
                    contestAgencies.add(contestAgency);

                }
                catch(RecordNotFoundException ex)
                {
                    ex.printStackTrace();
                    logger.info("Agency id [" + arr_ids[i] + "] not found!");

                }       

            }       
        }       
        return contestAgencies;
    }


    /**
     * input: Set of ContestAgency (many-to-many) checked 
     * and returns a List<String> of ids to be used to select checkboxes 
     * in thymeleaf view with th:checked="${#lists.contains(checked_agencies, '' + agency_el.id)}"
     * 
     * i can't return a List<Integer> because it doesn't check the checkboxes
     * 
     * @param contestAgencies
     * @return
     */
    public List<String> converterSetContestAgencyToCheckbox(Set<ContestAgency> contestAgencies)
    {
        List<String> result = new ArrayList<String>();

        if (contestAgencies != null)
        {
            Iterator<ContestAgency> iterator = contestAgencies.iterator();
            while(iterator.hasNext()) 
            {
                ContestAgency contestAgency = iterator.next();

                Integer id = contestAgency.getAgency().getId();

                result.add(id.toString());
            }       
        }   

        return result;
    }

}
<ul class="list-unstyled">
        <!-- 
        - parent and children are saved in the same table, so i'm not worried about ids overlapping
        -->

        <li th:each="service_el : ${services_list}" >
            <input type="checkbox" name="chk_services" th:value="${service_el.id}" th:checked="${#lists.contains(checked_services, '' + service_el.id)}"/>
            <label th:text="${service_el.title}" th:for="'chk_services' + ${service_el.id}">service</label>

            <ul class="list-unstyled-padding">
                <li th:each="subservice_el : ${service_el.children}">
                    <input type="checkbox" name="chk_services" th:value="${subservice.id}" th:checked="${#lists.contains(checked_services, '' + subservice.id)}"/>
                    <label  th:text="${subservice.title}" th:for="'chk_services' + ${service_el.id}">subservice</label>
                </li>
            </ul>              
        </li>
    </ul>
<ul class="list-unstyled">
    <li  th:each="agency_el : ${agencies_list}">
        <input name="chk_agencies" type="checkbox" th:id="'chk_agencies' + ${agency_el.id}" th:value="${agency_el.id}" th:checked="${#lists.contains(checked_agencies, '' + agency_el.id)}" />
        <label th:text="${agency_el.name}" th:for="'chk_agencies' + ${agency_el.id}">agency</label>            
    </li>
</ul>
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="
        http://www.springframework.org/schema/webflow
        http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

    <secured attributes="ROLE_USER" />

    <!-- creation of an empty object i will insert in db in the last step -->
    <on-start>
        <evaluate expression="ContestService.createContest()" result="flowScope.contest" />
    </on-start>

    <!-- 
        step 1: contest details
     -->
    <view-state id="contest-details" model="contest"> 
        <binder>
            <binding property="startDate" required="true" />
            <binding property="endDate" required="true"/>
            <binding property="bonus" required="true"/>
            <binding property="goal" required="true"/>
            <binding property="title" required="true"/>
        </binder>
        <transition on="proceed" to="contest-services">
        </transition>
        <transition on="cancel" to="cancel" bind="false" />
    </view-state>

    <!-- 
        step 2: i select which services are involved
    -->
    <view-state id="contest-services" model="contest">
        <on-entry>      
            <!--
            - in case i'm coming here from the step 3
            - injection of the list of ids previously checked
            -->
            <set name="viewScope.checked_services" value="contestFlow.converterSetContestServiceToCheckbox(flowScope.contest.ContestServices)" />

            <!-- 
             - i get the list of the Main Services
             - subservices will be scanned with getChildren method  
             -->
            <set name="viewScope.services_list" value="ServiceService.getMainServices()" />
        </on-entry>         

        <transition on="proceed" to="contest-agencies" >
            <!-- 
            - MY SOLUTION TO MANY-TO-MANY checkboxes form:
            - 
            - honestly not very elegant, but in 10 day i could't find better
            - 
            - conversion from String to Set<ContestService>
            -->
            <evaluate expression="contestFlow.converterCheckboxToSetContestService(flowScope.contest, requestParameters.chk_services)" result="flowScope.contest.ContestServices"></evaluate>  
        </transition>

        <transition on="cancel" to="contest-details">
            <!-- 
            - also if i go back in the flow, to the first step,
            - i need to remember which checkboxes were selected
            - 
            - and i need to save the checked services to the Contest entity, 
            - else, when i will call addContest method, 
            - it will not save the checked Services
            -->
            <evaluate expression="contestFlow.converterCheckboxToSetContestService(flowScope.contest, requestParameters.chk_services)" result="flowScope.contest.ContestServices"></evaluate>  
        </transition>
    </view-state>




    <!-- 
        step 3: i select which agencies are involved in contest.
        only agencies enabled for previously checked services are shown
    -->     
    <view-state id="contest-agencies" model="agencies">
        <on-entry>      
            <!--
            - in case i'm coming here from the step 3
            - injection of the list of ids previously checked
            -->
            <set name="viewScope.checked_agencies" value="contestFlow.converterSetContestAgencyToCheckbox(flowScope.contest.ContestAgencies)" />

            <!-- 
             - only agencies enabled for the step 2 checked services are shown
             -->
            <set name="viewScope.agencies_list" value="AgencyService.getEnabledAgenciesForServices(contestFlow.converterSetContestServiceToCheckbox(flowScope.contest.ContestServices))" />
        </on-entry>     

        <transition on="proceed" to="contest-confirm" >
            <!-- 
            - MY SOLUTION TO MANY-TO-MANY checkboxes form:
            - 
            - honestly not very elegant, but in 10 day i could't find better
            - 
            - conversion from String to Set<ContestAgency>
            -->
            <evaluate expression="contestFlow.converterCheckboxToSetContestAgency(flowScope.contest, requestParameters.chk_agencies)" result="flowScope.contest.ContestAgencies"></evaluate>  
        </transition>

        <transition on="cancel" to="contest-services">
            <!-- 
            - MY SOLUTION TO MANY-TO-MANY checkboxes form:
            - 
            - honestly not very elegant, but in 10 day i could't find better
            - 
            - conversion from String to Set<ContestAgency>
            - 
            - and i need to save the checked Agencies to the Contest entity, 
            - else, when i will call addContest method, 
            - it will not save the checked Agencies
            -->
            <evaluate expression="contestFlow.converterCheckboxToSetContestAgency(flowScope.contest, requestParameters.chk_agencies)" result="flowScope.contest.ContestAgencies"></evaluate>  
        </transition>
    </view-state>


    <!-- 
    - data confirmation before insert in db
     -->    
    <view-state id="contest-confirm" model="contest">       
        <transition on="proceed" to="contest-end" >
            <evaluate expression="ContestService.addContest(contest)" />
        </transition>
        <transition on="cancel" to="contest-agencies" />
    </view-state>   



    <!-- 
    end: redirect to list
     -->
    <end-state id="contest-end" view="externalRedirect:contextRelative:/contest/list"/>

    <!-- 
    cancella
     -->
    <end-state id="cancel"/>

</flow>