Spring JPA Dont Save If No Fields Modified

86 Views Asked by At

In Spring JPA, I did not find anything about saving entity only if there are modification to the fields

After I get the record from JPA, how can I verify, before I save the entity, if there are modification/changes to the fields. This is my approach

In the hasModifications function I can compare if obj.field!=cloneObject.field then only save

Class obj = repository.findById(id);
Class cloneObj = cloneObject(obj);
process(obj);
    if (hasModifications(obj,cloneObj))
       repository.save(obj);
     else
       //don't save

I tried adding @DynamicUpdate annotation at the class level. But it didnt work. Is there any other clean way to do this?

EDIT : I am asking this as I have a field called last_updated_at in MySql DB. And Whenever I call save this field is getting updated with the current timestamp but no other fields values are updated. So thats why I asked this question Here is the table definition

'CREATE TABLE `username` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `info` mediumtext,
  `status` varchar(255) NOT NULL,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `version` int NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_updated_at` (`updated_at`),
  KEY `idx_created_at` (`created_at`)
)

EDIT 2: I tried putting @Trasactional at the method and removed clone and save call. But still updated_at is getting updated in DB

@Transactional
public someMethod(){
Class obj = repository.findById(id);
process(obj);
}

Here is my obj entity

@Entity
@Table(name = "gratification_entity_task")
@JsonIgnoreProperties(
        value = {"createdAt", "updatedAt"},
        allowGetters = true)
@EntityListeners(AuditingEntityListener.class)
public class GratificationEntityTask {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Enumerated(EnumType.STRING)
    private TaskStatus status;
    @Convert(converter = InfoConverter.class)
    private Info info;
    @CreatedDate
    private Date createdAt;
    @LastModifiedDate
    private Date updatedAt;
    @Version
    private Integer version;
    private int retryCount;
    @Enumerated(EnumType.STRING)
    private BeneficiaryType beneficiaryType;
    private String beneficiaryId;
    @Convert(converter = GratificationConvertor.class)
    private GratificationData primaryGratification;
    @Convert(converter = GratificationScheduleConverter.class)
    private GratificationSchedule scheduleGratification;
    private String referenceId;
    private String redemptionType;
    private String clientId;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public TaskStatus getStatus() {
        return status;
    }

    public void setStatus(TaskStatus status) {
        this.status = status;
    }

    public Info getInfo() {
        return info;
    }

    public long getExpiryTimestamp() {
        if (scheduleGratification != null && scheduleGratification.getExpiryTimestamp() > 0) {
            return scheduleGratification.getExpiryTimestamp();
        }
        return 0;
    }

    public long getScheduleInSecondsInMillis() {
        if (scheduleGratification != null && scheduleGratification.getScheduleInSecs() > 0) {
            return scheduleGratification.getScheduleInSecs() * 1000;
        }
        return 0;
    }

    public void setInfo(Info info) {
        this.info = info;
    }

    public Date getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }

    public Date getUpdatedAt() {
        return updatedAt;
    }

    public void setUpdatedAt(Date updatedAt) {
        this.updatedAt = updatedAt;
    }

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    public int getRetryCount() {
        return retryCount;
    }

    public void setRetryCount(int retryCount) {
        this.retryCount = retryCount;
    }

    public BeneficiaryType getBeneficiaryType() {
        return beneficiaryType;
    }

    public void setBeneficiaryType(BeneficiaryType beneficiaryType) {
        this.beneficiaryType = beneficiaryType;
    }

    public String getBeneficiaryId() {
        return beneficiaryId;
    }

    public void setBeneficiaryId(String beneficiaryId) {
        this.beneficiaryId = beneficiaryId;
    }

    public GratificationData getPrimaryGratification() {
        return primaryGratification;
    }

    public void setPrimaryGratification(GratificationData primaryGratification) {
        this.primaryGratification = primaryGratification;
    }

    public GratificationSchedule getScheduleGratification() {
        return scheduleGratification;
    }

    public void setScheduleGratification(GratificationSchedule scheduleGratification) {
        this.scheduleGratification = scheduleGratification;
    }

    public String getReferenceId() {
        return referenceId;
    }

    public void setReferenceId(String referenceId) {
        this.referenceId = referenceId;
    }

    public String getRedemptionType() {
        return redemptionType;
    }

    public void setRedemptionType(String redemptionType) {
        this.redemptionType = redemptionType;
    }

    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    @Override
    public String toString() {
        return "GratificationEntityTask{" +
                "id=" + id +
                ", status=" + status +
                ", info=" + info +
                ", createdAt=" + createdAt +
                ", updatedAt=" + updatedAt +
                ", version=" + version +
                ", retryCount=" + retryCount +
                ", beneficiaryType=" + beneficiaryType +
                ", beneficiaryId='" + beneficiaryId + '\'' +
                ", primaryGratification=" + primaryGratification +
                ", scheduleGratification=" + scheduleGratification +
                ", referenceId='" + referenceId + '\'' +
                ", redemptionType='" + redemptionType + '\'' +
                ", clientId=" + clientId +
                '}';
    }
}
2

There are 2 best solutions below

0
Ash On BEST ANSWER

Ok, I found the issue which was causing the unnecessary update. While debugging I found the update query generated with Info column as one of the one columns set.

update
    com.paytm.gratification.commons.entity.GratificationEntityTask */ update
      gratification_entity_task 
    set
      info=?,
      updated_at=?,
      version=? 
    where
      id=? 
      and version=?

There was @Convert annotation at the entity level which was creating a new Object and causing a dirty read. Here is the link which mentions this issue https://stackoverflow.com/a/58181716/6780814

6
SpaceTrucker On

Spring won't don anything here. This is all dealt with by JPA. You don't need to do anything here. The JPA provider will determine by itself whether an entity is modified and therefore an update needs to be issued.

Also explicitly saving the entity is not required. Once it was retrieved from the repository it is managed by the entity manager. From that point on all changes to the entity are tracked and will be synchronized to the database once a database flush is triggered (like when the transaction completes).

So the only code you need is:

SomeEntity obj = repository.findById(id);
process(obj);

which must be executed in a transaction (like in @Transactional method).