OpenJPA
  1. OpenJPA
  2. OPENJPA-1854

A 'find' or 'query' may return multiple instances of Entities which contain Embeddables, where the Embeddables use String identities, if the id value has trailing spaces.

    Details

    • Type: Bug Bug
    • Status: Resolved
    • Priority: Major Major
    • Resolution: Not a Problem
    • Affects Version/s: 1.2.2, 1.3.0, 2.0.0
    • Fix Version/s: None
    • Component/s: kernel
    • Labels:
      None

      Description

      This issue is very similar to that of OPENJPA-1501, with one twist.......I've found that when you have an Embeddable, which has Strings as the keys/ids, openJPA code puts the Embeddable into an ObjectID by design, rather than a StringID....recall, the fix to OJ1501 was to strip white space from the end of a user's String key when that String was placed into a StringID. So, the strings in an Embeddable would also need to be stripped, just as was done in OJ-1501, but unfortunately it would seem to be harder to do so for Embeddables since openJPA code doesn't necessarily know what types are contained in the Embed.
      I've created a JUnit test case which has three test methods. The test is attached here.

        Issue Links

          Activity

          Hide
          Heath Thomann added a comment -

          It has recently came to my attention that this JIRA is still open, and furthermore the details of a solution not documented. Let me do so now and explain the conclusion myself and Fay Wang came up with when working with a few customers on this issue. Basically the solution can be made via user code as I'll explain in a moment and is not a bug in OpenJPA code. I was a bit premature in opening a JIRA and assuming their was a bug in OpenJPA code. Let me first state that OPENJPA-1501 fixed an issue where a (single) Primay Key defined as a String contained trailing white space. In this case, OpenJPA knows the type of the field (String), and has options available to remove trailing white space. As such, a decision was made in the JIRA to strip the trailing white space from a String PK. This has been the solution since OpenJPA 2.0.x. In this JIRA, I assumed that the issue was similar and that OpenJPA could remove trailing white space from compound PKs, such as @EmbeddedId. However, in this case OpenJPA has less control over these objects, or I should say the fields contained within them (these are opaque object from OpenJPA's point of view). Furthermore, it is likely that any trailing white space for String fields in these objects are either a) put there by the user, or b) represents what is in the database. That is, for #a, when a user creates an @Embeddable, it is possible they may add trailing white space to String fields. OpenJPA has no control over this. So for a finder method where a user passes in an @Embeddable, OpenJPA simply uses whatever the user gives the finder. If the user pads a String field with white space, then that is what is used by OpenJPA. One customer may argue OpenJPA should attempt to remove white space, while another may find that problematic. For #b, if an @Embeddable's string fields are mapped to a char field in the database, most (all?) databases will return a string padded with white space up to the length of a char (e.g. for a char(10), and string 'test', the database will return 'test ' - a string with 6 white spaces). This is likely not the case if the field is mapped to a varchar. So if a user executes a query to retrieve an entity with an @EmbeddedId, and if the @Embeddable's string fields are mapped to a char field in the database, the resultant @Embeddable's string field will contain trailing white space up to the length of the char. Again, this is out of OpenJPA's control and is simply a representation of what is in the database. Obviously a solution is to use a varchar rather than char to avoid trailing white space.
          Having said all of that, the fix here is simple and can be performed in user code by doing one of the following two options:

          1) Permanently remove trailing white space from within the EmbeddableId's constructor AND setter methods.                          
          2) Permanently remove trailing white space from within the EmbeddableId's equals AND hashCode methods.                             

          As an example, if you look at the test I had provided in the JIRA (OPENJPA-1854-1.2.x.test), to allow the test to work we would remove the white space as follows:

          import org.apache.commons.lang.StringUtils;
          ....
          public ObjectIDEntityPK(String name, String version)

          { super(); this.name = (name == null ? name : StringUtils.stripEnd(name,null)); this.version = (version == null ? version : StringUtils.stripEnd(version,null)) ; }

          public void setName(String name)

          { this.name = (name == null ? name : StringUtils.stripEnd(name,null)); }

          public void setVersion(String version)

          { this.version = (version == null ? version : StringUtils.stripEnd(version,null)); }

          public int hashCode()

          { name = StringUtils.stripEnd(name,null); version = StringUtils.stripEnd(version,null); final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((version == null) ? 0 : version.hashCode()); return result; }

          public boolean equals(Object obj) {
          name = StringUtils.stripEnd(name,null);
          version = StringUtils.stripEnd(version,null);

          if (this == obj)
          return true;
          if (obj == null)
          return false;
          if (getClass() != obj.getClass())
          return false;
          ObjectIDEntityPK other = (ObjectIDEntityPK) obj;
          other.name = StringUtils.stripEnd(other.name,null);
          other.version = StringUtils.stripEnd(other.version,null);

          if (name == null)

          { if (other.name != null) return false; }

          else if (!name.equals(other.name))
          return false;
          if (version == null)

          { if (other.version != null) return false; }

          else if (!version.equals(other.version))
          return false;
          return true;
          }

          Thanks,

          Heath Thomann

          Show
          Heath Thomann added a comment - It has recently came to my attention that this JIRA is still open, and furthermore the details of a solution not documented. Let me do so now and explain the conclusion myself and Fay Wang came up with when working with a few customers on this issue. Basically the solution can be made via user code as I'll explain in a moment and is not a bug in OpenJPA code. I was a bit premature in opening a JIRA and assuming their was a bug in OpenJPA code. Let me first state that OPENJPA-1501 fixed an issue where a (single) Primay Key defined as a String contained trailing white space. In this case, OpenJPA knows the type of the field (String), and has options available to remove trailing white space. As such, a decision was made in the JIRA to strip the trailing white space from a String PK. This has been the solution since OpenJPA 2.0.x. In this JIRA, I assumed that the issue was similar and that OpenJPA could remove trailing white space from compound PKs, such as @EmbeddedId. However, in this case OpenJPA has less control over these objects, or I should say the fields contained within them (these are opaque object from OpenJPA's point of view). Furthermore, it is likely that any trailing white space for String fields in these objects are either a) put there by the user, or b) represents what is in the database. That is, for #a, when a user creates an @Embeddable, it is possible they may add trailing white space to String fields. OpenJPA has no control over this. So for a finder method where a user passes in an @Embeddable, OpenJPA simply uses whatever the user gives the finder. If the user pads a String field with white space, then that is what is used by OpenJPA. One customer may argue OpenJPA should attempt to remove white space, while another may find that problematic. For #b, if an @Embeddable's string fields are mapped to a char field in the database, most (all?) databases will return a string padded with white space up to the length of a char (e.g. for a char(10), and string 'test', the database will return 'test ' - a string with 6 white spaces). This is likely not the case if the field is mapped to a varchar. So if a user executes a query to retrieve an entity with an @EmbeddedId, and if the @Embeddable's string fields are mapped to a char field in the database, the resultant @Embeddable's string field will contain trailing white space up to the length of the char. Again, this is out of OpenJPA's control and is simply a representation of what is in the database. Obviously a solution is to use a varchar rather than char to avoid trailing white space. Having said all of that, the fix here is simple and can be performed in user code by doing one of the following two options: 1) Permanently remove trailing white space from within the EmbeddableId's constructor AND setter methods.                           2) Permanently remove trailing white space from within the EmbeddableId's equals AND hashCode methods.                              As an example, if you look at the test I had provided in the JIRA ( OPENJPA-1854 -1.2.x.test), to allow the test to work we would remove the white space as follows: import org.apache.commons.lang.StringUtils; .... public ObjectIDEntityPK(String name, String version) { super(); this.name = (name == null ? name : StringUtils.stripEnd(name,null)); this.version = (version == null ? version : StringUtils.stripEnd(version,null)) ; } public void setName(String name) { this.name = (name == null ? name : StringUtils.stripEnd(name,null)); } public void setVersion(String version) { this.version = (version == null ? version : StringUtils.stripEnd(version,null)); } public int hashCode() { name = StringUtils.stripEnd(name,null); version = StringUtils.stripEnd(version,null); final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((version == null) ? 0 : version.hashCode()); return result; } public boolean equals(Object obj) { name = StringUtils.stripEnd(name,null); version = StringUtils.stripEnd(version,null); if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ObjectIDEntityPK other = (ObjectIDEntityPK) obj; other.name = StringUtils.stripEnd(other.name,null); other.version = StringUtils.stripEnd(other.version,null); if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (version == null) { if (other.version != null) return false; } else if (!version.equals(other.version)) return false; return true; } Thanks, Heath Thomann
          Hide
          Michael Dick added a comment -

          Targeting for 1.2, 2.0 and 2.1

          Show
          Michael Dick added a comment - Targeting for 1.2, 2.0 and 2.1
          Hide
          Heath Thomann added a comment -

          As I stated in my opening remarks, this issue is similar to OJ-1501.

          Show
          Heath Thomann added a comment - As I stated in my opening remarks, this issue is similar to OJ-1501.

            People

            • Assignee:
              Heath Thomann
              Reporter:
              Heath Thomann
            • Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:

                Development