Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    JPA Datasource with unidirectional oneToMany relations

    Hello,

    i'm using Smartgwt EE and have an issue with a JPA Datasource and a unidirectional oneToMany relation.

    How can i achieve that i can see corresponding entities? As an example i have a ListGrid with Users and i want to show the Roles that the User has (in the same ListGrid with nested ListGrid, MasterPage or something else).

    I have two Java Classes with JPA Annotations and corresponding *.ds.xml.

    User.java
    Code:
     
    package com.c1.perpetuom.domain;
    
    import java.io.Serializable;
    import java.util.Set;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.JoinColumn;
    import javax.persistence.JoinTable;
    import javax.persistence.OneToMany;
    import javax.persistence.Table;
    
    @Entity
    @Table (name="Users")
    public class User implements Serializable{
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        @Column(name = "id")
        private Long id;
    	  private Long key;
    	  private String name;
        private String surname;
        private String login;
        private String password;
        
        @OneToMany
        @JoinTable(name="user_roles", joinColumns=@JoinColumn(name="UserID", referencedColumnName="ID" ),
        inverseJoinColumns=@JoinColumn(name="RoleID", referencedColumnName="roleID"))
        private Set<Role> roles;	
    	
    	public Long getKey() {
    		return key;
    	}
    
    	public void setKey(Long key) {
    		this.key = key;
    	}
    
    	public String getName() {
            return this.name;
        }
    
    	public void setName(String name) {
            this.name = name;
        }
    
    	public String getSurname() {
            return this.surname;
        }
    
    	public void setSurname(String surname) {
            this.surname = surname;
        }
    
    	public String getLogin() {
            return this.login;
        }
    
    	public void setLogin(String login) {
            this.login = login;
        }
    
    	public String getPassword() {
            return this.password;
        }
    
    	public void setPassword(String password) {
            this.password = password;
        }
    
    	public Set<Role> getRoles() {
        return roles;
      }
    
      public void setRoles(Set<Role> roles) {
        this.roles = roles;
      }
    
      public Long getId() {
            return this.id;
        }
    
    	public void setId(Long id) {
            this.id = id;
        }
    }
    User_datasource.ds.xml
    Code:
    <DataSource
        ID="User_datasource"
        serverConstructor="com.isomorphic.jpa.JPADataSource"
        beanClassName="com.c1.perpetuom.domain.User"
        >
    	<fields>
            <field primaryKey="true" name="id" type="sequence"  hidden="true"   />
            <field name="key"       type="integer"  hidden="true" required="false" />
            <field name="name"      type="text" title="Name"      required="false" />
            <field name="surname"   type="text" title="Surname"   required="false" />
            <field name="login"     type="text" title="Login"     required="false" />
            <field name="password"  type="text" title="Password"  required="false" />
            <field name="roleID"    type="Role_datasource"     required="false"
             foreignKey="Role_datasource.roleID" multiple="true"/>
    	</fields>
    </DataSource>
    Role.java
    Code:
     
    package com.c1.perpetuom.domain;
    
    import java.io.Serializable;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    @Entity
    @Table (name="Roles")
    public class Role implements Serializable {
      
      @Id
      @GeneratedValue(strategy = GenerationType.AUTO)
      @Column(name = "roleID")
      public long getRoleID() {
        return roleID;
      }
      public void setRoleID(long roleID) {
        this.roleID = roleID;
      }
      public String getDescription() {
        return description;
      }
      public void setDescription(String description) {
        this.description = description;
      }
      private long roleID;
      private String description;
    
      public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("RoleID: ").append(getRoleID()).append(", ");
        sb.append("Description: ").append(getDescription());
        return sb.toString();
    }
    }
    Role_datasource.ds.xml
    Code:
     
    <DataSource
        ID="Role_datasource"
        serverConstructor="com.isomorphic.jpa.JPADataSource"
        beanClassName="com.c1.perpetuom.domain.Role"
        >
      <fields>
            <field primaryKey="true" name="roleID" type="sequence"  hidden="true"   />
            <field name="description" title="Description" type="text" required="true" />
      </fields>
    </DataSource>
    regards Pawel

    #2
    Check out smartgwtee-2.3/samples/ds-jpa

    It contains simple master detail example displaying master-details in two grids.

    Alius.

    Comment


      #3
      Hey Alius,

      thank you for reply.
      I know the sample but there is a reverse ManyToOne Relationship, it doesn't fit with my.

      //Pawel

      Comment


        #4
        Hi Pawel,

        I've just changed sample:
        commented out ManyToOne in City.java (and getter/setter)
        Code:
        //    @ManyToOne
        //    @JoinColumn(name="countryId", referencedColumnName="countryId", nullable=false, insertable=false, updatable=false)
        //    private Country country;
        added to Country.java (and getter/setter)
        Code:
            @OneToMany
            private List<City> cities;
        and run sample - everything works.

        Alius.

        Comment


          #5
          Hi Alius,

          what are the corresponding *ds.xml?

          Meantime i have (almost) a solution, for clearer understanding i will explain what i want to do.

          I have several user and each user can have one or N roles. My entities looks so that i have a user class with a set of roles (this set is annotated with @OneToMany you can see this also in my first post). The role class has no direct relation to the user Class, consequently it's a undirectional relation.

          I've changed the *.ds.xml since my first post.

          User_datasource.ds.xml
          Code:
          <DataSource
              ID="User_datasource"
              serverConstructor="com.isomorphic.jpa.JPADataSource"
              beanClassName="com.c1.perpetuom.domain.User"
              >
          	<fields>
                  <field primaryKey="true" name="userid" type="sequence"    />
                  <field name="key"       type="integer"  hidden="true" required="false" />
                  <field name="name"      type="text" title="Name"      required="false" />
                  <field name="surname"   type="text" title="Surname"   required="false" />
                  <field name="login"     type="text" title="Login"     required="false" />
                  <field name="password"  type="password" title="Password"  required="false" />
                  <field name="roles" 
                         title="Roles"
                         canSave="false"   
                         type="Role_datasource"
                         javaCollectionClass="com.c1.perpetuom.domain.Role"
                         multiple="true"
                         foreignKey="Role_datasource.roleid"/>
          	</fields>
          </DataSource>
          and the
          Role_datasource.ds.xml
          Code:
          <DataSource
              ID="Role_datasource"
              serverConstructor="com.isomorphic.jpa.JPADataSource"
              beanClassName="com.c1.perpetuom.domain.Role"
              >
            <fields>
                  <field primaryKey="true" name="roleid"  type="sequence" />
                  <field name="name"        title="Name"        type="text" required="true" />
                  <field name="description" title="Description" type="text" required="false" />
            </fields>
          </DataSource>
          Userdetails Form
          Code:
          public class UserDetails extends Details {
            final ListGrid currentRoles;
            
            public UserDetails() {    
              ds = DataSource.get("User_datasource");
              form.setDataSource(ds);    
             
              TextItem surname = new TextItem("surname");
              surname.setTitle("Surname");
              surname.setHint("Fill in the users surname");
                  
              TextItem name = new TextItem("name");
              name.setTitle("Name");
              name.setHint("Fill in the users name");
              
              TextItem login = new TextItem("login");
              login.setTitle("Login");
              login.setHint("Fill in the users login value");
              
              PasswordItem password = new PasswordItem("password");
              password.setTitle("Password");
              password.setHint("Fill in the users password");
              
             
              
              // role selection area
              DataSource rolesDS =  DataSource.get("Role_datasource");
              final ListGrid availableRoles = new ListGrid();
              availableRoles.setDataSource(rolesDS);
              availableRoles.setCanDragRecordsOut(false);
              availableRoles.setDragDataAction(DragDataAction.COPY);
              availableRoles.setAutoFetchData(true);   
              availableRoles.setPreventDuplicates(true);
              ListGridField roleNameField = new ListGridField("name");
              availableRoles.setFields(roleNameField);
              
              currentRoles = new ListGrid();    
              currentRoles.setCanAcceptDroppedRecords(true);
              currentRoles.setCanRemoveRecords(true);    
              currentRoles.setPreventDuplicates(true);
              ListGridField roleNameField2 = new ListGridField("name");
              currentRoles.setFields(roleNameField2);    
              
              TransferImgButton arrowImg = new TransferImgButton(TransferImgButton.RIGHT);
              arrowImg.addClickHandler(new ClickHandler() {
                public void onClick(ClickEvent event) {
                  currentRoles.transferSelectedData(availableRoles);
                }
              });
          
              HStack hStack = new HStack(10);
              hStack.setLayoutTopMargin(30);
              hStack.setHeight(150);    
              LayoutSpacer spacer1 = new LayoutSpacer();
              spacer1.setWidth(92);    
              hStack.addMember(spacer1);
              hStack.addMember(availableRoles);
              hStack.addMember(arrowImg);
              hStack.addMember(currentRoles);
          
              
              form.setFields(surname, name, login, password);
             
              VLayout main = new VLayout();
              main.addMember(form);
              main.addMember(hStack);
              addMember(main);
            }
            
            @Override
            public void save() {
              form.setValue("roles", currentRoles.getRecords());
              //call  form.submit();  
              super.save();
            }
          }
          So with this configuration it almost works.
          When i start the application 3 tables will be generated one for the user, role and the mapping table for the userid and roleid. Additional i have a Userdetails Form, at this Point i can set the user properties and assign several roles to the user with two ListGrids (one for available roles and one for selected roles). After the save action i have the matching entries in the mapping-table and duplicate entries in the role table.

          This is very confusing, i don't know why the application create new roles with the same name but other ids, instead of using the existing roles. Have somebody any idea whats wrong?

          Sry for my gramma I'm not a English native speaker.

          //Pawel

          Comment


            #6
            Hi,

            Here is what happens:
            When you press save - you set list of roles for user.
            User arrives to server.
            User entity is loaded from DB and properties are updated.
            User have property roles which is loaded from DB.
            Updating sets roles property to new object (new Set ()) with list of roles (instantiated but not loaded from DB) - from persistence point of view - it is new records (not managed).

            When persistence provider saves user entity it cascades and saves set of roles.
            Role entities are new (not managed) so persistence provider does insert (not update).
            Primary key for Role (roleId) is auto-generated so it discards current value and generates new one.

            Your problem is not related to SmartGWT - it is application design problem at the server side. You would hit the same problem with any web front-end.

            PS. corresponding *ds.xml files are in smartgwtee-2.3/samples/ds-jpa/war/ds

            Alius.

            Comment


              #7
              Hi,

              i solved the problem.
              I've implemented my own operation Bindings for persist and merge/update. The methods does all the stuff in a single transaction and they fetch the roles before, by using the transfered role ids.

              //Pawel

              Comment


                #8
                Yes,

                That is correct way to do it.

                Alius.

                Comment


                  #9
                  Hi,
                  I have a similar but slight different issue. I have a table call Product, and product can have Multiple Platform.

                  On the DB side I have 3 tables. Product, ProductOSRef, and OSLookup.

                  Essentially we have a many to many relationship which is bound by the ProductOSRef table. What's the ds.xml approach for this case?

                  Thanks,

                  Comment


                    #10
                    What type of DataSource are you using - SQL, Hibernate, JPA?

                    Comment


                      #11
                      I can user SQL or Hibernate. I need an example for onetomany and manytomany?

                      Comment


                        #12
                        Hi,
                        I am currently using SQL. However, if I need to, I can use JPA or Hibernate? Can I mix SQL with JPA. If so what are the steps. I tried this on one of my object and I am getting a "Failed to initialize EMF provider". Anyway, I will check my server.properties and web.xml file. However, let me know if mixing SQL and JPA would be a good idea for this or can I handle this manytomany and onetomany relationship with SQL or SQL/DMI.

                        Thanks,

                        Comment


                          #13
                          You can mix them, but you generally should not, since we don't currently support a mixed SQLDataSource + HibernateDataSource transaction (among other possible issues). What we recommend is the SQLDataSource.

                          This sample shows many-to-many group assignment - just change the "projectCode" field of "teamMembers" to a foreignKey to a third table, and you've got your scenario.

                          Comment


                            #14
                            Hi,
                            I've took a look at the example you sent. I think can make this work for the relationship via SQL. However, I am trying build the GUI like what you have here http://www.smartclient.com/smartgwtee/showcase/#row_drag_save . I am hoping I don't need to customize. One issue I see already is that, I actually have 3 table I am dealing with. A lookup table with then name of all my Platforms, a product table and cross reference table.

                            I have a form of the product and a list of the platform lookup on one side and the reference table on another side. I retrieved all the the Platform lookup and the reference base on the the productId. However, it seem like when I move the row from the lookup table to the reference table it look like the item on the lookup got delete from my DB?

                            Anyway reason why this is or why would a SQL statement need to be issue here.

                            You must run into this case all the time. I just the easiest way to handle this.

                            TIA,

                            Comment


                              #15
                              Very strange - you just said that you looked at the sample we sent, but that you need the UI to look like another sample. But then you pointed to the same sample?

                              As stated:

                              This sample shows many-to-many group assignment - just change the "projectCode" field of "teamMembers" to a foreignKey to a third table, and you've got your scenario.
                              If you're having trouble, we'll need to see all the DataSources and code to tell you what you did wrong.

                              Comment

                              Working...
                              X