Dharita Chokshi
Dharita Chokshi

Reputation: 1263

Hibernate: @Column(updatable = false) not working for @MappedSuperclass

I am using Spring Data JPA with Hibernate and am having trouble with the updatable=false property on the @Column annotation.

I have a base class for all of my @Entity objects with a createdDate defined like this:

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Setter
@Getter
@ToString
public abstract class BaseEntity {

    @CreatedBy
    @Column(name = "CREATE_USER_ID", nullable = false, updatable = false)
    protected String createdBy;

    @CreationTimestamp
    @Column(name = "CREATE_TS", columnDefinition = "TIMESTAMP WITH TIME ZONE", nullable = false, updatable = false)
    protected Instant createdDate; //Audit for data use replication conflict resolution (Oracle Golden Gate)

    @LastModifiedBy
    @Column(name = "LAST_UPDATE_USER_ID")
    protected String lastModifiedBy;

    @UpdateTimestamp
    @Column(name = "LAST_UPDATE_TS", columnDefinition = "TIMESTAMP WITH TIME ZONE")
    protected Instant lastModifiedDate;

Following is my child entity

@Entity
@Table(name = "CUSTOMER")
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString(exclude = {""})
public class Customer extends BaseEntity 

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CUSTOMER_ID_SEQ")
    @SequenceGenerator(sequenceName = "Customer_ID_SEQ", allocationSize = 1, name = "CUSTOMER_ID_SEQ")
    @Column(name = "CUSTOMER_ID")
    private Long CustomerId;             //Primary identifier
    @Column(name = "CUSTOMER_NAME")
    private String customerName;        //Customer Name
    @Column(name = "COUNTRY")
    private String customerCountry;     //Customer Country
    @Column(name = "REGION")
    private String customerRegion;      //Customer Region

}

Code snippet from CustomerService.java

public CustomerDetails updateCustomer(CustomerDetails customerDetails, Long customerId) {
        Customer customer = customerEntityToDTOMapper.dtoToEntity(customerDetails);
        /**
            Some sort of business logic
        **/
        Customer updatedCustomer = customerRepository.saveAndFlush(customer);
        return customerEntityToDTOMapper.entityToDTO(updatedCustomer);
    }

Now when I call updateCustomer API from the postman, if I change the value of createdDate in request body, it is getting updated in database (the database will replace actual value of createdDate by the new value passed in the request body). This should ideally not happen. Irrespective of whatever valid value I pass in requestBody, createdDate in the database should not be used on updateCustomer API call.

Also in createCustomer request, if createdDate=null, JPA will set the createdDate as per expectation and I receive proper date in the response of repository.saveAndFlush() method. But in updateCustomer if I set createdDate=null in the entity, then createdDate comes as null in the response of repository.saveAndFlush() method even though it exists in the database.

Can someone help me with where am I going wrong?

Upvotes: 0

Views: 4688

Answers (2)

Collin
Collin

Reputation: 454

Ironically, simply removing the "updatable = false" attribute fixed the issue for me. The date / timestamp no longer gets updated once its been initially inserted now.

And for those who use a .hbm.xml file:

Before: <property name="createdOn" column="CREATED_ON" update="false"/>

After: <property name="createdOn" column="CREATED_ON"/>

Upvotes: 1

code_mechanic
code_mechanic

Reputation: 1148

The @Column annotation attributes are processed to create/update/validate etc the schema and define the constraints on your table from entities (depends on hbm2ddl.auto property).

Now insertable and updatable are used by @persistenceContext to issue the query and it will ignore the fields in its query if not provided explicitly and set to false.

Since in your case, you have not provided the code, how you are setting the values, I am assuming in case of update, you are setting createdDate null, which will not work because you have defined the field like

 @CreationTimestamp // This will add the timestamp if null when entity is new only
    @Column(name = "CREATE_TS", columnDefinition = "TIMESTAMP WITH TIME ZONE", nullable = false, updatable = false)
    protected Instant createdDate; 

So, your update is explicitly updating the createdDate as null, which is overriding the updatable=false and AFAIK there is some weird behaviour with updatable=false which returns null in entity (when you fetched after saving it) and when entity is saved again that null value is persisted (which will be saved irrespective of updatable=false, because hibernate will see the value is changed to null)

I would suggest remove the updatable=false and use only @CreationTimeStamp property only, which would not update the value See this and database constraint should be there to prevent null

Upvotes: 0

Related Questions