javaspringspring-data-jpaentitymanager

Spring JPA Dont Save If No Fields Modified


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 +
                '}';
    }
}

Solution

  • 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