5

Why does repository.save(myEntity) not return an updated entity with the updated audit fields?

The resulting instance from MyEntityRepository.save(myEntity) and subsequently, from MyEntityService.save(myEntity) does not have the updated updatedOn date. I have verified this is correctly set in the database, so I know that auditing is working. The returned instance's updatedOn date is correct for an insert, but not for an update. I prefer to not have to immediately do a findById after every save, especially if the intent is that save() returns the udpated, attached instance.

Assuming the setting of updatedOn is occurring through a @PreUpdate hook and this hook is triggered during the entityManager.merge() call via repository.save(), I don't follow why the value would not be set on the returned instance.

Example code:

@Entity
@DynamicUpdate
@DynamicInsert
@Table(name = "my_entity", schema = "public")
@SequenceGenerator(name = "pk_sequence", sequenceName = "my_entity_seq", allocationSize = 1)
@AttributeOverrides({@AttributeOverride(name = "id", column = @Column(name = "id", columnDefinition = "int"))})
@EntityListeners(AuditingEntityListener.class)
public class MyEntity {

    protected Integer id;

    @LastModifiedDate
    private Date updatedOn;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "pk_sequence")
    @Column(name = "id", nullable = false, columnDefinition = "bigint")
    public Integer getId() {
        return id;
    }

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

    @Version
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "updated_on")
    public Date getUpdatedOn() {
        return updatedOn;
    }

    public void setUpdatedOn(Date updatedOn) {
        this.updatedOn = updatedOn;
    }
}


public interface MyEntityRepository extends JpaRepository<MyEntity, Integer> { }

@Service
@Transactional(readOnly = true)
public class MyEntityService {

    @Autowired
    private MyEntityRepository repository;

    @Transactional
    public MyEntity save(MyEntity myEntity) {
        return repository.save(myEntity);
    }
}
ooozguuur
  • 3,396
  • 2
  • 24
  • 42
Jenna Pederson
  • 5,656
  • 1
  • 19
  • 20
  • 1
    Have you debugged into `AuditingEntityListener.touchForUpdate(…)` and looked whether the method is called at all? Are you sure the entity actually contains changes? The persistence provider might not even trigger the event if it can't find any changes on the entity, even if we call `EntityManager.merge(…)`. – Oliver Drotbohm Dec 05 '13 at 15:11
  • 1
    Yes, I have and it is getting called. I've actually implemented it using both an AuditingEntityListener and using my own @PreUpdate hook. Both approaches work - the change gets saved to the database with the correct updatedOn date. But the instance returned from repository.save or repository.saveAndFlush does not have these changed audit fields (all other changed data is however reflected correctly). – Jenna Pederson Dec 05 '13 at 15:53
  • That's weird. Any chance you can boil this down to a tiny test case and file a JIRA against Spring Data JPA? It'd also be useful to know which JPA provider you use. – Oliver Drotbohm Dec 06 '13 at 19:13
  • I will do that. Provider is Hibernate 4.2.6. – Jenna Pederson Dec 06 '13 at 21:33

2 Answers2

2

I faced with the same issue.

In my case the important items that helped me to solve this problem were:
1) use repository.saveAndFlush(...) method

2) use findAllById() or findByYourOwnQuery() (annotated with @Query).

Overall, my test case looked like this:

UserAccount userAccount = UserAccount.builder().username(username).build();
userAccountRepository.saveAndFlush(userAccount);

final LocalDateTime previousUpdateDate = userAccount.getUpdateDate();
....

List<BigInteger> ids = Arrays.asList(userAccountId);    
UserAccount updatedUserAccount = userAccountRepository.findAllById(ids).get(0);  // contains updated Audit data fields

...
assertThat(actual.getUpdateDate(), is(greaterThan(previousUpdateDate)));  // true

The important thing that you shouldn't use repository.findOne(...) because it caches the reference to the object - read more.

Leonid Dashko
  • 3,657
  • 1
  • 18
  • 26
0

I ran in to the exact same problem. I fixed it by using,

repository.saveAndFlush(myEntity);

instead of

repository.save(myEntity);
charithdk
  • 11
  • 1