Ternary expression causing JPA update problems?
bdepazJun 6 2013 — edited Jun 12 2013Hello,
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() : "") + "]";
}
}