Menu

using @ManyToMany with collection

Kabongo
2025-11-19
2025-12-12
  • Kabongo

    Kabongo - 2025-11-19

    Hi everyone; i am using a ManyToMany annotation on my program because i want the user to select as many element then add them to the collection; which works well but i noticed that on my main application it doesn't refresh the screen until i exit/save the transaction then come back to my application and then i can see the elements stored on the collection( this behaviour only appears on my main application) so i thought if i could save the collection and refresh the previous view that could help but i keep on getting the error impossible to execute add and refresh action:null.
    I am trying basically to have a behaviour like the one we see with @ElementCollection with the features to allow the user to select mutliple transactions and add them all at once; below are my models and action:

    package com.yourcompany.demorun.model;
    import java.util.*;
    

    import javax.persistence.*;

    import org.openxava.annotations.;
    import org.openxava.model.
    ;

    import lombok.*;

    @Entity
    @Getter @Setter
    @View(members="TestData{"
    
            + "invno;"
            + "invDate;"
            + "},"
            + "CollectionData{"
            + "myproduct;"
            + "},ThirdParty{"
            + "tp;"
            + "}")
    public class Invoice extends Identifiable{
    
        @Column
        int invno;
    
        @DateTime
        Date invDate;
    
        @ManyToMany
        @JoinTable(
            name = "Invoice_Product",
            joinColumns = @JoinColumn(name = "invoice_id"),           // FK to Invoice
            inverseJoinColumns = @JoinColumn(name = "proId")     // FK to Product
        )
    
        @NewAction("")
        //@AddAction("Invoice.addAndRefresh")
        @AddAction("Invoice.addAndRefresh")
        private Collection<Product> myproduct = new ArrayList<>();
    
        @ManyToMany
        Collection<ThirdParty>tp=new ArrayList<>();
    
    
    }
    

    package com.yourcompany.demorun.model;
    import java.math.*;

    import javax.persistence.*;

    import org.openxava.annotations.*;

    import lombok.*;
    @Entity
    @Getter @Setter
    @View(members="proId,productName,amount,description;")
    public class Product {

    @Id
    int proId;
    
    @Column
    String productName;
    
    @Money
    BigDecimal amount;
    
    @Column
    String description;
    

    }

    package com.yourcompany.demorun.model;

    import java.math.;
    import java.util.
    ;

    import javax.persistence.*;

    import org.openxava.annotations.*;

    @Entity
    @Table(name="tprecon")
    //@Tab(baseCondition="${tpref}!=''")
    @View(name="Simple",members="id,matid,capdte,code,amount,tpref,detail,tpvouch,tptrntax")
    public class ThirdParty{

    @Id
    @Column 
    int id;
    
    @Column
    String matid;
    
    @Column
    Date capdte;
    
    @Column(length=3)
    String code;
    
    @Column
    @Stereotype("MONEY")
    BigDecimal amount;
    
    @Column(length=20)
    String tpref;
    
    @Column
    String detail;
    
    @Column
    int tpvouch;
    
    @Column
    Integer tptrntax;
    
    @Column
    Date tptaxdte;
    
    
    public int getId() {
        return id;
    }
    
    public void setId(int id) {
        this.id = id;
    }
    
    public String getMatid() {
        return matid;
    }
    
    public void setMatid(String matid) {
        this.matid = matid;
    }
    
    
    public Date getCapdte() {
        return capdte;
    }
    
    public void setCapdte(Date capdte) {
        this.capdte = capdte;
    }
    
    public String getCode() {
        return code;
    }
    
    public void setCode(String code) {
        this.code = code;
    }
    
    public BigDecimal getAmount() {
        return amount;
    }
    
    public void setAmount(BigDecimal amount) {
        this.amount = amount;
    }
    
    public String getTpref() {
        return tpref;
    }
    
    public void setTpref(String tpref) {
        this.tpref = tpref;
    }
    
    public String getDetail() {
        return detail;
    }
    
    public void setDetail(String detail) {
        this.detail = detail;
    }
    
    public int getTpvouch() {
        return tpvouch;
    }
    
    public void setTpvouch(int tpvouch) {
        this.tpvouch = tpvouch;
    }
    
    public Integer getTptrntax() {
        return tptrntax;
    }
    
    public void setTptrntax(Integer tptrntax) {
        this.tptrntax = tptrntax;
    }
    
    public Date getTptaxdte() {
        return tptaxdte;
    }
    
    public void setTptaxdte(Date tptaxdte) {
        this.tptaxdte = tptaxdte;
    }
    
    @Override
    public String toString() {
        return tpref != null ? tpref : "No Ref";
    }
    

    }

    and my action is :
    package com.yourcompany.demorun.actions;

    import org.openxava.actions.*;

    public class AddAndRefresh extends AddElementsToCollectionAction {
    @Override
    public void execute() throws Exception {
    super.execute(); // perform the add

        getPreviousView().refresh();           // refresh invoice screen
    }
    

    }

    the stack error: SEVERE: null
    java.lang.NullPointerException
    at org.openxava.actions.SaveElementInCollectionAction.isKeyIncomplete(SaveElementInCollectionAction.java:142)
    at org.openxava.actions.SaveElementInCollectionAction.saveIfNotExists(SaveElementInCollectionAction.java:110)
    at org.openxava.actions.AddElementsToCollectionAction.execute(AddElementsToCollectionAction.java:34)
    at com.yourcompany.demorun.actions.AddAndRefresh.execute(AddAndRefresh.java:8)
    at org.openxava.controller.ModuleManager.executeAction(ModuleManager.java:596)
    at org.openxava.controller.ModuleManager.executeAction(ModuleManager.java:528)
    at org.openxava.controller.ModuleManager.execute(ModuleManager.java:486)
    at org.apache.jsp.xava.execute_jsp._jspService(execute_jsp.java:264)
    at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:64)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:623)
    at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:444)
    at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:354)
    at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:305)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:623)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:197)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:142)
    at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:619)
    at org.apache.catalina.core.ApplicationDispatcher.doInclude(ApplicationDispatcher.java:496)
    at org.apache.catalina.core.ApplicationDispatcher.include(ApplicationDispatcher.java:444)
    at org.openxava.web.servlets.Servlets.getURIAsStream(Servlets.java:72)
    at org.openxava.web.dwr.Module.getURIAsStream(Module.java:282)
    at org.openxava.web.dwr.Module.request(Module.java:61)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.directwebremoting.impl.CreatorModule$1.doFilter(CreatorModule.java:172)
    at org.directwebremoting.impl.CreatorModule.executeMethod(CreatorModule.java:184)
    at org.directwebremoting.impl.DefaultRemoter.execute(DefaultRemoter.java:353)
    at org.directwebremoting.impl.DefaultRemoter.execute(DefaultRemoter.java:306)
    at org.directwebremoting.dwrp.BaseCallHandler.handle(BaseCallHandler.java:110)
    at org.directwebremoting.servlet.UrlProcessor.handle(UrlProcessor.java:211)
    at org.directwebremoting.servlet.UrlProcessor.handle(UrlProcessor.java:185)
    at org.directwebremoting.servlet.DwrServlet.doPost(DwrServlet.java:144)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:555)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:623)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:197)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:142)
    at org.openxava.web.filters.ContentSecurityPolicyFilter.doFilter(ContentSecurityPolicyFilter.java:56)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:142)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:166)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:88)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:481)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:90)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:72)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:935)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1833)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:975)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:493)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
    at java.base/java.lang.Thread.run(Thread.java:834)

    is there a way for me to achieve that features( having the user select mutliple values and add them all to the collection at one) without using @ManyToMany?
    please help

     
  • Javier Paniza

    Javier Paniza - 2025-11-21

    Hi Kabongo,

    It does not work because @AddAction is the action executed when the user click on Add in the button in the collection, not in the button in the dialog to finish the addition. That is @AddAction is the action that shows the dialog. So, it should not extend AddElementsToCollectionAction but GoAddElementsToCollectionAction.

    Look at this doc that explains with detail how to refine add action for a collection:
    https://www.openxava.org/OpenXavaDoc/docs/references-collections_en.html#refining-the-list-for-adding-elements-to-a-collection

    On the other hand, why do you need to refine the standard add action? I don't understand well you point. Do you mean that when you add elements those elements are not displaying in the collection on close the dialog?


    Help others in this forum as I help you.

     
  • Kabongo

    Kabongo - 2025-11-21

    Hi Javier;
    "Do you mean that when you add elements those elements are not displaying in the collection on close the dialog?"

    Yes when i use the default add features the collection does not show the values until i save the transaction and then select the transaction to edit only at that time i can see the value on the collection please see attached images(you can see on the screenshot it say 2 where added to tprec2 but there is nothing on the collection until i exit and come back as in editing): below is the code of the program you can try it and see for yourself(the program should run from the batmst class):
    package com.yourcompany.marketpro.model;

    import java.math.;
    import java.util.
    ;

    import javax.persistence.;
    import javax.validation.constraints.
    ;

    import org.openxava.annotations.;
    import org.openxava.model.
    ;

    @Entity
    @Table( name="batmst")
    @View(members="Batch[#" +
    "posttype, capdte, pstdte; battot, inpvat, outvat;" +
    "batdetails]
    "
    )
    @View(name="Simple", members="batchno, battot, pstdte")

    public class Batmst extends Identifiable{

    @Column(name="user_name", length=30)
    public String user;
    
    @Column
    private Integer batchno;
    
    @NoModify
    @NoCreate
    @ManyToOne(fetch=FetchType.LAZY, optional=false)
    
    @DescriptionsList(descriptionProperties="postdesc" )
    private Posttype posttype;
    
    @Column(name="capdte", length=12)
    private Date capdte;
    
    @NotNull(message="Batch Total Must Not be Null")
    @Column(name="battot")
    
    private BigDecimal battot;
    
    @NotNull(message="Input VAT Total Must Not be Null")
    @Column(name="inpvat")
    @Stereotype("MONEY")
    private BigDecimal inpvat;
    
    @NotNull(message="Output VAT Total Must Not be Null")
    @Column(name="outvat")
    @Stereotype("MONEY")
    private BigDecimal outvat;
    
    @Column(name="pstdte", length=12)
    private Date pstdte;
    
    
    @OneToMany(
            mappedBy="parent",
            cascade=CascadeType.ALL)
    private Collection<Batdet> batdetails = new ArrayList<Batdet>();
    
    public Integer getBatchno() {
        return batchno;
    }
    
    public void setBatchno(Integer batchno) {
        this.batchno = batchno;
    }
    
    public String getUser() {
        return user;
    }
    
    public void setUser(String user) {
        this.user = user;
    }
    
    public Posttype getPosttype() {
        return posttype;
    }
    
    public void setPosttype(Posttype posttype) {
        this.posttype = posttype;
    }
    
    public Date getCapdte() {
        return capdte;
    }
    
    public void setCapdte(Date capdte) {
        this.capdte = capdte;
    }
    
    public BigDecimal getBattot() {
        return battot;
    }
    
    public void setBattot(BigDecimal battot) {
        this.battot = battot;
    }
    
    public BigDecimal getInpvat() {
        return inpvat;
    }
    
    public void setInpvat(BigDecimal inpvat) {
        this.inpvat = inpvat;
    }
    
    public BigDecimal getOutvat() {
        return outvat;
    }
    
    public void setOutvat(BigDecimal outvat) {
        this.outvat = outvat;
    }
    
    public Date getPstdte() {
        return pstdte;
    }
    
    public void setPstdte(Date pstdte) {
        this.pstdte = pstdte;
    }
    
    
    public Collection<Batdet> getBatdetails() {
        return batdetails;
    }
    
    public void setBatdetails(Collection<Batdet> batdetails) {
        this.batdetails = batdetails;
    }
    

    }

    package com.yourcompany.marketpro.model;

    import java.math.;
    import java.util.
    ;

    import javax.persistence.*;
    import javax.persistence.Entity;
    import javax.persistence.Table;

    import org.hibernate.annotations.;
    import org.openxava.annotations.
    ;

    import lombok.*;

    @Entity
    @Table(name="batdet")

    @View(members="optionFromParent; matters;" +
    "Transaction{" +
    "capdte;" +
    "detamt;" +
    "details; "+
    "},"

     + "ThirdParties{"
     + "tprec2;"
     + "}"
    )
    

    @Getter @Setter
    public class Batdet{

    @ManyToOne
    private Batmst parent;
    
    @Id @GeneratedValue(generator="system-uuid") @Hidden
    @GenericGenerator(name="system-uuid", strategy="uuid")
    @Column(length=32)
    
    
    private String oid;
    
    @ReadOnly
    @Transient 
    private String optionFromParent;
    
    @ManyToOne(fetch=FetchType.LAZY, optional=true)
    @NoModify
    @JoinColumn(name="matid")
    private Matters matters;
    
    
    @ManyToMany
    @JoinTable(
        name = "batdet_thirdparty",
        joinColumns = @JoinColumn(name = "batdet_id", referencedColumnName = "oid"),
        inverseJoinColumns = @JoinColumn(name = "thirdparty_id", referencedColumnName = "id")
    )
    @ListProperties("id, tpref, detail, amount")
    

    // @AddAction("Batdet.goAddThirdParty")

    private Collection<ThirdParty> tprec2 = new ArrayList<>();
    
    
    @Required
    @Column(name="capdte", length=12)
    private Date capdte;
    
    @Column(name="voucher", length=8)
    private Integer voucher;
    
    
    
    @Stereotype("MONEY")
    private BigDecimal detamt;
    
    @Stereotype("MONEY")
    private BigDecimal disbamt;
    
    //@Stereotype("MONEY")
    //private BigDecimal cost;
    
    @Column(name="vatinc")
    private Vatinc vatinc;
    public enum Vatinc{Exclusive, Inclusive, NoVat};
    
    @Column(name="vatchq")
    private Vatchq vatchq;
    public enum Vatchq{Inclusive, NoVat};
    
    @Column(name="vatinp")
    @Stereotype("MONEY")
    private BigDecimal vatinp;
    
    @Column(name="vatinpdis")
    @Stereotype("MONEY")
    private BigDecimal vatinpdis;
    
    @Column(name="vatout")
    @Stereotype("MONEY")
    private BigDecimal vatout;
    
    @Column(name="tpref", length=20)
    private String tpref;
    
    
    @Column(name="hoinv", length=20)
    private Integer hoinv;
    
    @Column(name="tpdesc", length=50)
    private String tpdesc;
    
    @TextArea
    @Column
    private String details;
    
    @Column(name="matres", length=1)
    //@OnChange(ChkMcack.class)
    
    private Matres matres;
    public enum Matres{Yes, No};
    
    @Column(name="s78inv", length=1)
    
    private S78 s78;
    public enum S78{Yes, No};
    
    @ReadOnly
    @Column(name="user_name", length=30)
    public String user;
    

    }
    package com.yourcompany.marketpro.model;

    import java.math.;
    import java.util.
    ;

    import javax.persistence.*;

    import org.openxava.annotations.*;

    @Entity
    @Table(name="tprecon")
    @View(name="Simple",members="id,matid,capdte,code,amount,tpref,detail,tpvouch,tptrntax")
    public class ThirdParty{

    @Id
    @Column 
    int id;
    
    @Column
    String matid;
    
    @Column
    Date capdte;
    
    @Column(length=3)
    String code;
    
    @Column
    @Stereotype("MONEY")
    BigDecimal amount;
    
    @Column(length=20)
    String tpref;
    
    @Column
    String detail;
    
    @Column
    int tpvouch;
    
    @Column
    Integer tptrntax;
    
    @Column
    Date tptaxdte;
    
    
    public int getId() {
        return id;
    }
    
    public void setId(int id) {
        this.id = id;
    }
    
    public String getMatid() {
        return matid;
    }
    
    public void setMatid(String matid) {
        this.matid = matid;
    }
    
    
    public Date getCapdte() {
        return capdte;
    }
    
    public void setCapdte(Date capdte) {
        this.capdte = capdte;
    }
    
    public String getCode() {
        return code;
    }
    
    public void setCode(String code) {
        this.code = code;
    }
    
    public BigDecimal getAmount() {
        return amount;
    }
    
    public void setAmount(BigDecimal amount) {
        this.amount = amount;
    }
    
    public String getTpref() {
        return tpref;
    }
    
    public void setTpref(String tpref) {
        this.tpref = tpref;
    }
    
    public String getDetail() {
        return detail;
    }
    
    public void setDetail(String detail) {
        this.detail = detail;
    }
    
    public int getTpvouch() {
        return tpvouch;
    }
    
    public void setTpvouch(int tpvouch) {
        this.tpvouch = tpvouch;
    }
    
    public Integer getTptrntax() {
        return tptrntax;
    }
    
    public void setTptrntax(Integer tptrntax) {
        this.tptrntax = tptrntax;
    }
    
    public Date getTptaxdte() {
        return tptaxdte;
    }
    
    public void setTptaxdte(Date tptaxdte) {
        this.tptaxdte = tptaxdte;
    }
    
    @Override
    public String toString() {
        return tpref != null ? tpref : "No Ref";
    }
    

    }
    package com.yourcompany.marketpro.model;
    import javax.persistence.*;

    import org.openxava.annotations.*;

    @Entity
    @Table( name="posttype")
    public class Posttype {

    @Id
    @Column(length=3)
    private String postcod;
    
    @Required
    @Column(length=25)
    private String postdesc;
    
    @Required
    @Column(length=2)
    private Integer postind;
    
    public String getPostcod() {
        return postcod;
    }
    
    public void setPostcod(String postcod) {
        this.postcod = postcod;
    }
    
    public String getPostdesc() {
        return postdesc;
    }
    
    public void setPostdesc(String postdesc) {
        this.postdesc = postdesc;
    }
    
    public Integer getPostind() {
        return postind;
    }
    
    public void setPostind(Integer postind) {
        this.postind = postind;
    }
    

    }

    The user will definitely want to see which values were added to the list in order to amend them if necessary, that is why i was looking for a way to refresh the view maybe.

     
  • Javier Paniza

    Javier Paniza - 2025-11-24

    Hi Kabongo,

    You're right, I tried your code and it fails. Please, add a bug with a link to this thread, so we can fix it for a future OpenXava release.

    I note that when a edit an existing detail and try to add thirdparties to it, it works nicely, showing the details after adding them. So I think it fails because the detail is not saved yet, so the relation in database is not created yet, therefore the lines are not shown. This is the reason because they are shown when you edit the detail again (the detail already exist in db), and also because in that case the lines are shown when added too.

    It means that you can solved your problem, before I could fix the bug, just creating the detail in database when you open the dialog to add lines. That is, create a @AddAction that refine the original add action to just create the current detail if it does not exist yet, before opening the dialog to add parties.

    Try it and tell me how it is going.


    Help others in this forum as I help you.

     
  • Kabongo

    Kabongo - 2025-11-25

    Hi Javier,
    I have added to the bug with the link to this thread.

    "Try it and tell me how it is going"

    that may be a little difficult based on the current setup as some information need to be provided by the user before creating the record, like matid; it is only after the user provide those , that he can add a third party and link it to the transaction

     
  • Javier Paniza

    Javier Paniza - 2025-11-27

    Hi Kabongo:

    some information need to be provided by the user before creating the record,

    In your @AddAction call to MapFacade.create before call to super, in this way:

    public void execute() throws Exception {
        MapFacade.create(getView().getModelName(), getView().getValues());
        super.execute();
    }
    

    Because MapFacade.create() validates before savings, so it will not continue to super.execute() and the validation errors will be shown to the user.


    Help others in this forum as I help you.

     
  • Kabongo

    Kabongo - 2025-12-01

    Hi Javier i have tried the implementation provided earlier it is saving it but unfortunately my view doesn't show it until i close the dialog and go back to the transaction just like before. please see below my action:

    import org.openxava.actions.;
    import org.openxava.model.
    ;

    public class AddAndRefresh extends AddElementsToCollectionAction {

      @Override
    public void execute() throws Exception{
          MapFacade.create(getView().getModelName(), getView().getValues());
    
          super.execute();
    
        }
    

    }

     
  • Javier Paniza

    Javier Paniza - 2025-12-03

    Hi Kabongo,

    If you, without close the dialog, go to Transaction tab, and then go back to Third parties tab, are the records displayed?


    Help others in this forum as I help you.

     
  • Kabongo

    Kabongo - 2025-12-10

    Hi Javier, apologies for delay, yes even when i go to another tab and come back to the third party tab i still dont see anything until i close/save the transaction then when i want to edit i can see the third party selected

     
  • Javier Paniza

    Javier Paniza - 2025-12-12

    Hi Kabongo,

    As I said above, I reproduced the bug and you filled it. So the it's a bug with @ManyToMany that we'll fix in the feature. Unfortunately the trick of saving the record before does not work, maybe that is not the caus of the bug.

    If you do a reload with the browser refresh button? Do the collections appear?
    If after you close the dialog that the record, in your action do a getView().refresh()? Does it work.

    If none of the above works, the solution is not to use @ManyToMany. You can turn @ManyToMany in @OneToMany creating an explicit entity to do the union (an entity with two @ManyToOne), with the same effect.


    Help others in this forum as I help you.

     

Log in to post a comment.

MongoDB Logo MongoDB