5

I'm using SpringBoot with JPA/Hibernate and I'd like to add auditing.

I have created an abstract entity with the following :

@CreatedDate
@NotNull
@Column(columnDefinition = "DATETIME(3)", updatable = false)
private ZonedDateTime createdDate;

@LastModifiedDate
@Column(columnDefinition = "DATETIME(3)")
private ZonedDateTime lastModifiedDate;

The application

I'm developping a RESTful API with a simple CRUD on entities. My resources(controller) are using the save() method on jpa repositories for create and update

The problem :

When I'm calling repository.save() on an Entity to update it, the "createdDate" is ignored (that's normal) but the entity which is returned by the method has its"createdDate" set to null.

Then the entity is put in the cache and the next get will have its createdDate value to null also.

How can I update an entity without allowing the modification of the field createdDate and using JpaCrudRepository ?

Update

Repository definition :

public interface ModelRepository extends JpaRepository<Model, Long> {

REST Controller :

public ResponseEntity<Model> createModel(@RequestBody Model model) throws URISyntaxException {
        Model result = modelRepository.save(model);
        return ResponseEntity.created(new URI("/api/v1/models/" + result.getId()))
            .body(result);
}

The Json returned by the endpoint contains "createdDate: null"

Update 2

To fix this issue I have created a service like this :

    @Override
    public Model save(Model model) {
        Model result = modelRepository.save(model);
        return result;
    }

    @Override
    public Model update(Long id, Model model) {
        Model existingModel = modelRepository.findOne(id);
        if (existingModel == null) {
            return null;
        }
        // Non updatable fields
        model.setId(id);
        model.setCreatedDate(existingModel.getCreatedDate());
        Model result = modelRepository.save(model);
        return result;
}

To make this work, i have also removed the "updatable = false" from createdDate field to prevent the result from having a null date after the save.

I don't understand the purpose of having "updatable = false" if we have to handle it manually after.

John D
  • 81
  • 1
  • 1
  • 6

3 Answers3

1

If we consider just plain Hibernate for a moment, it is perfectly legal to specify updatable=false on a mapping but obviously the value must be initialized somewhere for Hibernate to know what value to place in the column at insertion.

Your solution by setting the value yourself is exactly what users of plain Hibernate must do.

But I notice your model uses a @CreatedDate annotation, which from my brief googling appears to be something that Spring Data exposes and has nothing to do with Hibernate. That to me implies that something may not be properly configured here.

I've never used this from Spring data, but you can find another post here. Perhaps it can help highlight where your configuration may be off and why the @CreatedDate annotation isn't initializing the field as you intended.

UPDATE

In situations where an external source supplies you with state that you need to apply atop of an existing entity such as the case of your update method, you first need to retrieve the entity and overlay your input accordingly.

// Some controller or business method
public Object updateModel(Model input) {
  final Model model = modelRepository.findOne( input.getId() );
  if ( model != null ) {
    // replicate business case fields
    // sometimes this can be done using frameworks like ObjectMapper, etc.
    // this is just a simple case.
    model.setField1( input.getField1() );
    model.setField2( input.getField2() );
    return modelRepository.update( model );
  }
  throw new UnknownObjectException( "No Model found with id: " + input.getId() );
}

Hope that helps.

Community
  • 1
  • 1
Naros
  • 19,928
  • 3
  • 41
  • 71
  • @CreatedDate is working fine from what I understand of it. It's the merging of a fully created entity (not retrieved) persisted with updatable false which is not updating(select from DB) its original value. I can understand why it is not done, but I don't really know which is the best approach here and what people do. Retrieving the object and updating every field except the non updatable ? Or Retrieving the object and forcing (like in my example) the non updatable field for the new object ? Or finally, after the save, refetch (refresh) the entity via an entityManager ? – John D Feb 03 '17 at 15:24
  • Your post showed calling `save` and not `update`, hence my confusion. I'll update my answer. – Naros Feb 03 '17 at 15:58
  • There is only the "save" method on JpaRepository, that's why I was using save(). But we agree that by doing this, we cannot use "updatable=false" since the problem remains the same once we persist. If updatable is set to false, the field would be null. I don't really like to have to do an update method for every entity and a full mapping just because i don't want to update a field. How can a simple thing as auditing and readonly create date can generate so much complexity – John D Feb 03 '17 at 16:17
  • That is why I mentioned using a controller or business method to handle the idea of creating versus updating a record. You don't necessarily have to push that concept to the repository level, although technically it makes sense. When your `/updateModel` URI is called, you call the repository, fetch the model, overlay the changes, then call the repository's save method. That should yield you the results you seek. – Naros Feb 03 '17 at 16:34
0

I was able to get around this problem by flushing and clearing the entityManager then re-selecting the data.

entityManager.flush(); entityManager.clear();

David
  • 1
  • 1
0

Maybe @CreatedDate (org.springframework.data.annotation.CreatedDate) is the wrong annotation? My little spring-boot JPA/Hibernate CRUD server uses annotation @CreationTimestamp (org.hibernate.annotations.CreationTimestamp) on the creation-date column:

@CreationTimestamp
@Column(name = "CREATED_DATE", nullable = false, updatable = false, columnDefinition = "TIMESTAMP")
private Date created;

Requests arrive with null creation date. Upon calling repo.save() method the magic happens and the resulting domain object has non-null creation date. I do no fiddling with the entity manager, no flush, nada.

I note that despite the annotation of "updatable = false" on the column, changing the created-date value on an existing entity and calling the repo.save() method persists that change without complaint. That surprised me.

chrisinmtown
  • 3,571
  • 3
  • 34
  • 43