4 Replies Latest reply: Jun 12, 2013 8:10 AM by JamesSutherland RSS

    Ternary expression causing JPA update problems?

    bdepaz
      Hello,

      We're having some weird (for me unexplainable) behaviour here:

      In our runtime environment, we are seeing the following stacktrace (shortened):

      Exception Description: *The attribute [id] of class [be.cm.apps.dbm.signalmgmt.domain.entities.NearingEndRecognitionSignal] is mapped to a primary key column in
      the database. Updates are not allowed.*
      at weblogic.transaction.internal.TransactionImpl.throwRollbackException(TransactionImpl.java:1884)
      at weblogic.transaction.internal.ServerTransactionImpl.internalCommit(ServerTransactionImpl.java:376)
      at weblogic.transaction.internal.ServerTransactionImpl.commit(ServerTransactionImpl.java:268)

      Although we were not updating any of the primary keys of the object, we still got the exception. So we started commenting code until the problem was gone, to see what was causing it. We stubled upon a method that was used in the @PrePersist fase entity. The implementation of this method was using "ternary expression" to implement an if/else structure and setting an entity attribute.

      @PrePersist
      @PreUpdate
      public void buildSignalId() {
      StringBuilder builder = new StringBuilder("signal:");
      //won't work
      //builder.append(this.recognitionEndDate != null ? this.recognitionEndDate : "");
      //does work
      if (this.recognitionEndDate != null) {
      builder.append(this.recognitionEndDate);
      }
      this.setSignalId(builder.toString());
      }

      The recognitionDate in the code above is a joda LocalDate object. Neither the recognitionEndDate nor the signalId are annotated as @Id classes.

      Simply replacing the ternary expression to a basic if/else structure seemed to solve our problem... wtf?

      Also, the problem only occurs while the code is being deployed in our weblogic environment. Using the same operations in a local transaction via a unit test did not seem to be a problem. We're using Weblogic 10.3.6 with Eclipselink 2.1.2 on jrockit_160_29_D1.2.0-10 with Oracle 10g DB.

      Although we found a workaround for this problem, I'm still a little intreagued by this behavior. So if anybody got any clue or has a logic explanation on how using a ternary expression instead of a regular if else could make a difference in how the JPA runtime behaves, be my guest and try to explain ;-)

      grtz,
      Bert

      Our related classes:
      ============
      @Entity
      @DiscriminatorValue("NEARING_END_RECOGNITION")
      @Inheritance(strategy = InheritanceType.JOINED)
      public class NearingEndRecognitionSignal extends BenefitDossierSignal implements Serializable {
      @Column(name = "NERS_CREHUM_TASKBUSID")
      private String createdHumanTaskId;

      @Converter(name = "localDateConverter",
      converterClass = be.cm.apps.dbm.domain.converters.JodaLocalDateConverter.class)
      @Convert("localDateConverter")
      @Column(name = "NERS_RECOEND_DATE")
      private LocalDate recognitionEndDate;

      @JoinColumn(name = "CRE_RECO_END_DEC")
      private RecognitionExtensionDecision createdRecognitionExtensionDecision;

      @JoinColumn(name = "CRE_MBRCALL")
      private MemberCall memberCall;

      public NearingEndRecognitionSignal() {
      super();
      this.setSignalType(BenefitDossierSignalType.NEARING_END_RECOGNITION);
      }

      @PrePersist
      @PreUpdate
      public void buildSignalId() {
      StringBuilder builder = new StringBuilder("signal:");

      //won't work
      //builder.append(this.recognitionEndDate != null ? this.recognitionEndDate : "");

      //works
      if (this.recognitionEndDate != null) {
      builder.append(this.recognitionEndDate);
      }

      this.setSignalId(builder.toString());
      }
      public String getCreatedHumanTaskId() {
      return this.createdHumanTaskId;
      }
      public void setCreatedHumanTaskId(String createdHumanTaskId) {
      this.createdHumanTaskId = createdHumanTaskId;
      }
      public LocalDate getRecognitionEndDate() {
      return this.recognitionEndDate;
      }
      public void setRecognitionEndDate(LocalDate recognitionEndDate) {
      this.recognitionEndDate = recognitionEndDate;
      }
      public RecognitionExtensionDecision getCreatedRecognitionExtensionDecision() {
      return this.createdRecognitionExtensionDecision;
      }
      public void setCreatedRecognitionExtensionDecision(
      RecognitionExtensionDecision createdRecognitionExtensionDecision) {
      this.createdRecognitionExtensionDecision = createdRecognitionExtensionDecision;
      }
      public MemberCall getMemberCall() {
      return this.memberCall;
      }
      public void setMemberCall(MemberCall memberCall) {
      this.memberCall = memberCall;
      }
      @Override
      public String toString() {
      return "NearingEndRecognitionSignal [createdHumanTaskId=" + this.createdHumanTaskId + ", recognitionEndDate="
                      + this.recognitionEndDate + ", recognitionExtensionDecision=" + this.createdRecognitionExtensionDecision
                      + ", memberCall=" + (this.memberCall != null ? " Appointment cabinet urn: "
                              + this.memberCall.getAppointmentCabinetUrn() + " Invitation cabinet urn "
                              + this.memberCall.getInvitationCabinetUrn() : "") + " " + super.toString() + "]";
      }
      @Override
      public String getHumanTaskId() {
      return getCreatedHumanTaskId();
      }
      @Override
      public void setHumanTaskId(String humanTaskId) {
      setCreatedHumanTaskId(humanTaskId);
      }
      }
      ===============super class=======================
      @Entity
      @Table(name = "SDISBEN_BENEF_DOS_SIG")
      @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
      @DiscriminatorColumn(name = "BENDOS_SIG_TYPE", discriminatorType = DiscriminatorType.STRING)
      public abstract class BenefitDossierSignal extends BaseEntity implements Serializable {
      protected static final String REQUIRED_FIELD_MESSAGE = "The originDate, the receptionDate, the signalId, " +
      "the benefitDossier, the signalType, the status, the signalOrigin are required ";
      @Id
      @Column(name = "BENEF_DOS_SIG_ID")
      @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "benDosSigIdSequence")
      @SequenceGenerator(name = "benDosSigIdSequence",
      sequenceName = "SEQ_SDISBEN_BEN_DOS_SIG_ID", allocationSize = 20)
      private long id;

      @Converter(name = "dateTimeConverter",
      converterClass = be.cm.apps.dbm.domain.converters.JodaDateTimeConverter.class)
      @Convert("dateTimeConverter")
      @Column(name = "ORIGIN_DATE")
      private DateTime originTimestamp;

      @Converter(name = "dateTimeConverter",
      converterClass = be.cm.apps.dbm.domain.converters.JodaDateTimeConverter.class)
      @Convert("dateTimeConverter")
      @Column(name = "RECEP_DATE")
      private DateTime receptionTimestamp;

      @Column(name = "SIGNAL_SID")
      private String signalId;

      @Column(name = "TREATMNT_SUM")
      private String treatmentSummary;

      @Converter(name = "dateTimeConverter",
      converterClass = be.cm.apps.dbm.domain.converters.JodaDateTimeConverter.class)
      @Convert("dateTimeConverter")
      @Column(name = "TREATMNT_DATE")
      private DateTime treatmentTimestamp;

      @Enumerated(EnumType.STRING)
      @Column(name = "DISDOS_SIGORG_TYPE")
      private DisabilityDossierSignalOrigin signalOrigin;

      @Enumerated(EnumType.STRING)
      @Column(name = "BENDOS_SIGSTAT_TYPE")
      private BenefitDossierSignalStatus status;

      @Enumerated(EnumType.STRING)
      @Column(name = "BENDOS_SIG_TYPE")
      private BenefitDossierSignalType signalType;

      @JoinColumn(name = "BENEF_DOSSIER_ID")
      private BenefitDossier benefitDossier;

      protected BenefitDossierSignal(DateTime originTimestamp, DateTime receptionTimestamp,
      DisabilityDossierSignalOrigin disabilityDossierSignalOrigin,
      BenefitDossierSignalStatus benefitDossierSignalStatus,
      BenefitDossier benefitDossier) {
      this.originTimestamp = originTimestamp;
      this.receptionTimestamp = receptionTimestamp;
      this.signalOrigin = disabilityDossierSignalOrigin;
      this.status = benefitDossierSignalStatus;
      this.benefitDossier = benefitDossier;
      }
      protected BenefitDossierSignal() {}

      public void validate() {
      if (this.originTimestamp == null || this.receptionTimestamp == null || StringUtils.isEmpty(signalId)
      || this.benefitDossier == null || this.signalType == null || this.status == null
      || this.signalOrigin == null) {
      throw new IllegalArgumentException(REQUIRED_FIELD_MESSAGE + this);

      }
      }

      public abstract MemberCall getMemberCall();
      public abstract String getHumanTaskId();
      public abstract void setMemberCall(MemberCall memberCall);
      public abstract void setHumanTaskId(String humanTaskId);

      public long getId() {
      return this.id;
      }
      public void setId(long id) {
      this.id = id;
      }
      public DateTime getOriginTimestamp() {
      return this.originTimestamp;
      }
      public void setOriginTimestamp(DateTime originTimestamp) {
      this.originTimestamp = originTimestamp;
      }
      public DateTime getReceptionTimestamp() {
      return this.receptionTimestamp;
      }
      public void setReceptionTimestamp(DateTime receptionTimestamp) {
      this.receptionTimestamp = receptionTimestamp;
      }
      public String getSignalId() {
      return this.signalId;
      }
      public void setSignalId(String signalId) {
      this.signalId = signalId;
      }
      public String getTreatmentSummary() {
      return this.treatmentSummary;
      }
      public void setTreatmentSummary(String treatmentSummary) {
      this.treatmentSummary = treatmentSummary;
      }
      public DateTime getTreatmentTimestamp() {
      return this.treatmentTimestamp;
      }
      public void setTreatmentTimestamp(DateTime treatmentTimestamp) {
      this.treatmentTimestamp = treatmentTimestamp;
      }
      public DisabilityDossierSignalOrigin getSignalOrigin() {
      return this.signalOrigin;
      }
      public void setSignalOrigin(DisabilityDossierSignalOrigin signalOrigin) {
      this.signalOrigin = signalOrigin;
      }
      public BenefitDossierSignalStatus getStatus() {
      return this.status;
      }
      public void setStatus(BenefitDossierSignalStatus status) {
      this.status = status;
      }
      public BenefitDossierSignalType getSignalType() {
      return this.signalType;
      }
      public void setSignalType(BenefitDossierSignalType signalType) {
      this.signalType = signalType;
      }
      public BenefitDossier getBenefitDossier() {
      return this.benefitDossier;
      }
      public void setBenefitDossier(BenefitDossier benefitDossier) {
      this.benefitDossier = benefitDossier;
      }
      @Override
      public String toString() {
      return "BenefitDossierSignal [id=" + this.id + ", originTimestamp=" + this.originTimestamp
                      + ", receptionTimestamp=" + this.receptionTimestamp + ", signalId=" + this.signalId
                      + ", treatmentSummary=" + this.treatmentSummary + ", treatmentTimestamp=" + this.treatmentTimestamp
                      + ", signalOrigin=" + this.signalOrigin + ", status=" + this.status + ", signalType=" + this.signalType
                      + ", benefitDossier=" + (this.benefitDossier != null ? this.benefitDossier.getDossierId() : "") + "]";
      }
      }
        • 1. Re: Ternary expression causing JPA update problems?
          bdepaz
          Update:
          We found that it is not solely related to a ternary expression, but it seems to be the combination of a ternary expression with joda-time in a weblogic (multi-threaded) environment. The following works:

          //works
          builder.append(this.recognitionEndDate != null ? this.recognitionEndDate.toString() : "");

          //results in The attribute [id] of class [be.cm.apps.dbm.signalmgmt.domain.entities.NearingEndRecognitionSignal] is mapped to a primary key column in
          //the database. Updates are not allowed.
          builder.append(this.recognitionEndDate != null ? this.recognitionEndDate : "");

          Why not using a toString in a ternary expression results in JPA throwing an exception suggestion that the ID of the object was changed, remains unclear for us...
          • 2. Re: Ternary expression causing JPA update problems?
            Cdelahun-Oracle
            Are you using the java agent in the test environment outside of WebLogic by setting the
            -javaagent:${workspace_loc:eclipselink.core.test}/../../eclipselink.jar
            property? If not does it make a difference if you use the accessor method, getRecognitionEndDate() instead of recognitionEndDate directly?

            If the problem does have to do with weaving, try setting the
            -Declipselink.weaving.output.path option to get EclipseLink to output the changed classes. Decompiling them might show what the issue is if a bug needs to be filed

            Best Regards,
            Chris

            Edited by: cdelahun on Jun 6, 2013 10:02 AM
            • 3. Re: Ternary expression causing JPA update problems?
              bdepaz

              Hello Chris,

               

              Thanks for your reply.

               

              Setting the -javaagent doesn't seem to be doing much different. We're still measuring the same result. Setting the weaving.output as you suggest is not making any difference either. I'm even wondering if I've set it up correctly since there's nothing being written to the weaving directory I've configured. I can't seem to make that work.

               

              Now, given the fact that invoking the .toString() method explicitly on the joda LocalDate object instead of concatenating the object as such seems to make a difference, we've also have been looking further into that: We've obtained the LocalDate code (through decompilation) and it seems like they have implemented a toString via annotation... perhaps that in combination with the eclipselink weaving is making the runtime go nuts or something.

               

              In any case, we have a workaround for now. But the issue is still intreaging to me...

               

              gr,

              Bert

              • 4. Re: Ternary expression causing JPA update problems?
                JamesSutherland

                My guess is it is an ASM issue.  EclipseLink uses ASM for byte code weaving, ASM probably encounter some byte-code it did not understand, causing it to generate something invalid.

                 

                You can try disabling weaving, using "eclipselink.weaving"="false" to confirm this.

                 

                You could try upgrading to 2.4 or 2.5 as there has been some ASM changes.  Otherwise, your workaround of removing the offending byte-code is probably best.