Update timestamp for each row in Hibernate - java

I have a table in my Postgres database which has a timestamp column. I would like to have it be automatically inserted every time I update a row. I wrote a database trigger:
CREATE FUNCTION update_last_edit_date() RETURNS trigger AS $update_last_edit_date$
BEGIN
NEW.last_edit_date := localtimestamp(0);
RETURN NEW;
END;
$update_last_edit_date$ LANGUAGE plpgsql;
CREATE TRIGGER update_last_edit_date BEFORE UPDATE ON employee
FOR EACH ROW
WHEN (OLD.* IS DISTINCT FROM NEW.*)
EXECUTE PROCEDURE update_last_edit_date();
Which works fine but I was wondering if there was an easier way to do this with jpa/hibernate annotations. I tried these different options:
#Preupdate
#PreUpdate
private void onUpdate(){
this.lastEditDate = new Date();
}
#UpdateTimestamp
#UpdateTimestamp
#Temporal(TemporalType.TIMESTAMP)
private Date lastEditDate;
But what I get is that when I update one row, the timestamps for all of the rows updated, so all of the timestamps in the table are always the same. What am I doing wrong here?

There are many ways to achieve this goal.
#EntityListener
You can have an #Embeddable to store the audit properties:
#Embeddable
public class Audit {
#Column(name = "created_on")
private LocalDateTime createdOn;
#Column(name = "updated_on")
private LocalDateTime updatedOn;
//Getters and setters omitted for brevity
}
Which requires an EntityListener that looks as follows:
public class AuditListener {
#PrePersist
public void setCreatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
if(audit == null) {
audit = new Audit();
auditable.setAudit(audit);
}
audit.setCreatedOn(LocalDateTime.now());
}
#PreUpdate
public void setUpdatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
audit.setUpdatedOn(LocalDateTime.now());
}
}
Your entities will have to implement the Audit interface:
public interface Auditable {
Audit getAudit();
void setAudit(Audit audit);
}
And the entities will look like this:
#Entity(name = "Tag")
#Table(name = "tag")
#EntityListeners(AuditListener.class)
public class Tag implements Auditable {
#Id
private String name;
#Embedded
private Audit audit;
//Getters and setters omitted for brevity
}
This is a very elegant solution since it extracts the audit logic from the main entity mapping.
#PrePersist and #PreUpdate
You can use the #PrePersist and #PreUpdate JPA annotations as well:
#Embeddable
public class Audit {
#Column(name = "created_on")
private LocalDateTime createdOn;
#Column(name = "updated_on")
private LocalDateTime updatedOn;
#PrePersist
public void prePersist() {
createdOn = LocalDateTime.now();
}
#PreUpdate
public void preUpdate() {
updatedOn = LocalDateTime.now();
}
//Getters and setters omitted for brevity
}
and add the Audit embeddable to the entity like this:
#Entity(name = "Tag")
#Table(name = "tag")
public class Tag {
#Id
private String name;
#Embedded
private Audit audit = new Audit();
//Getters and setters omitted for brevity
}
Hibernate-specific #CreationTimestamp and #UpdateTimestamp
#CreationTimestamp
#Column(name = "created_on")
private Date createdOn;
#Column(name = "updated_on")
#UpdateTimestamp
private Date updatedOn;
That's it!
Now, related to your comment:
But what I get is that when I update one row, the timestamps for all of the rows updated, so all of the timestamps in the table are always the same. What am I doing wrong here?
The timestamp will only be updated for the entity that gets modified, not for all rows. It does not make any sense to update the timestamp of all rows when only a single row gets modified. Otherwise, why would you have that column on the row itself?
If you want the last modification timestamp, just run a query like this:
SELECT MAX(updated_on)
FROM tags

If you are using spring-data then you do this over the auditing feature. Checkout #EnableJpaAuditing or read this article https://www.baeldung.com/database-auditing-jpa

Related

Is there any alternative for #CreationTimeStamp and #UpdateTimeStamp in hibernate 4.x.x? [duplicate]

For a certain Hibernate entity we have a requirement to store its creation time and the last time it was updated. How would you design this?
What data types would you use in the database (assuming MySQL, possibly in a different timezone that the JVM)? Will the data types be timezone-aware?
What data types would you use in Java (Date, Calendar, long, ...)?
Whom would you make responsible for setting the timestamps—the database, the ORM framework (Hibernate), or the application programmer?
What annotations would you use for the mapping (e.g. #Temporal)?
I'm not only looking for a working solution, but for a safe and well-designed solution.
If you are using the JPA annotations, you can use #PrePersist and #PreUpdate event hooks do this:
#Entity
#Table(name = "entities")
public class Entity {
...
private Date created;
private Date updated;
#PrePersist
protected void onCreate() {
created = new Date();
}
#PreUpdate
protected void onUpdate() {
updated = new Date();
}
}
or you can use the #EntityListener annotation on the class and place the event code in an external class.
You can just use #CreationTimestamp and #UpdateTimestamp:
#CreationTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "create_date")
private Date createDate;
#UpdateTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "modify_date")
private Date modifyDate;
Taking the resources in this post along with information taken left and right from different sources, I came with this elegant solution, create the following abstract class
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
#MappedSuperclass
public abstract class AbstractTimestampEntity {
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created", nullable = false)
private Date created;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "updated", nullable = false)
private Date updated;
#PrePersist
protected void onCreate() {
updated = created = new Date();
}
#PreUpdate
protected void onUpdate() {
updated = new Date();
}
}
and have all your entities extend it, for instance:
#Entity
#Table(name = "campaign")
public class Campaign extends AbstractTimestampEntity implements Serializable {
...
}
What database column types you should use
Your first question was:
What data types would you use in the database (assuming MySQL, possibly in a different timezone that the JVM)? Will the data types be timezone-aware?
In MySQL, the TIMESTAMP column type does a shifting from the JDBC driver local time zone to the database timezone, but it can only store timestamps up to 2038-01-19 03:14:07.999999, so it's not the best choice for the future.
So, better to use DATETIME instead, which doesn't have this upper boundary limitation. However, DATETIME is not timezone aware. So, for this reason, it's best to use UTC on the database side and use the hibernate.jdbc.time_zone Hibernate property.
What entity property type you should use
Your second question was:
What data types would you use in Java (Date, Calendar, long, ...)?
On the Java side, you can use the Java 8 LocalDateTime. You can also use the legacy Date, but the Java 8 Date/Time types are better since they are immutable, and don't do a timezone shifting to local timezone when logging them.
Now, we can also answer this question:
What annotations would you use for the mapping (e.g. #Temporal)?
If you are using the LocalDateTime or java.sql.Timestamp to map a timestamp entity property, then you don't need to use #Temporal since HIbernate already knows that this property is to be saved as a JDBC Timestamp.
Only if you are using java.util.Date, you need to specify the #Temporal annotation, like this:
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_on")
private Date createdOn;
But, it's much better if you map it like this:
#Column(name = "created_on")
private LocalDateTime createdOn;
How to generate the audit column values
Your third question was:
Whom would you make responsible for setting the timestamps—the database, the ORM framework (Hibernate), or the application programmer?
What annotations would you use for the mapping (e.g. #Temporal)?
There are many ways you can achieve this goal. You can allow the database to do that..
For the create_on column, you could use a DEFAULT DDL constraint, like :
ALTER TABLE post
ADD CONSTRAINT created_on_default
DEFAULT CURRENT_TIMESTAMP() FOR created_on;
For the updated_on column, you could use a DB trigger to set the column value with CURRENT_TIMESTAMP() every time a given row is modified.
Or, use JPA or Hibernate to set those.
Let's assume you have the following database tables:
And, each table has columns like:
created_by
created_on
updated_by
updated_on
Using Hibernate #CreationTimestamp and #UpdateTimestamp annotations
Hibernate offers the #CreationTimestamp and #UpdateTimestamp annotations that can be used to map the created_on and updated_on columns.
You can use #MappedSuperclass to define a base class that will be extended by all entities:
#MappedSuperclass
public class BaseEntity {
#Id
#GeneratedValue
private Long id;
#Column(name = "created_on")
#CreationTimestamp
private LocalDateTime createdOn;
#Column(name = "created_by")
private String createdBy;
#Column(name = "updated_on")
#UpdateTimestamp
private LocalDateTime updatedOn;
#Column(name = "updated_by")
private String updatedBy;
//Getters and setters omitted for brevity
}
And, all entities will extend the BaseEntity, like this:
#Entity(name = "Post")
#Table(name = "post")
public class Post extend BaseEntity {
private String title;
#OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
#OneToOne(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY
)
private PostDetails details;
#ManyToMany
#JoinTable(
name = "post_tag",
joinColumns = #JoinColumn(
name = "post_id"
),
inverseJoinColumns = #JoinColumn(
name = "tag_id"
)
)
private List<Tag> tags = new ArrayList<>();
//Getters and setters omitted for brevity
}
However, even if the createdOn and updateOn properties are set by the Hibernate-specific #CreationTimestamp and #UpdateTimestamp annotations, the createdBy and updatedBy require registering an application callback, as illustrated by the following JPA solution.
Using JPA #EntityListeners
You can encapsulate the audit properties in an Embeddable:
#Embeddable
public class Audit {
#Column(name = "created_on")
private LocalDateTime createdOn;
#Column(name = "created_by")
private String createdBy;
#Column(name = "updated_on")
private LocalDateTime updatedOn;
#Column(name = "updated_by")
private String updatedBy;
//Getters and setters omitted for brevity
}
And, create an AuditListener to set the audit properties:
public class AuditListener {
#PrePersist
public void setCreatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
if(audit == null) {
audit = new Audit();
auditable.setAudit(audit);
}
audit.setCreatedOn(LocalDateTime.now());
audit.setCreatedBy(LoggedUser.get());
}
#PreUpdate
public void setUpdatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
audit.setUpdatedOn(LocalDateTime.now());
audit.setUpdatedBy(LoggedUser.get());
}
}
To register the AuditListener, you can use the #EntityListeners JPA annotation:
#Entity(name = "Post")
#Table(name = "post")
#EntityListeners(AuditListener.class)
public class Post implements Auditable {
#Id
private Long id;
#Embedded
private Audit audit;
private String title;
#OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
#OneToOne(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY
)
private PostDetails details;
#ManyToMany
#JoinTable(
name = "post_tag",
joinColumns = #JoinColumn(
name = "post_id"
),
inverseJoinColumns = #JoinColumn(
name = "tag_id"
)
)
private List<Tag> tags = new ArrayList<>();
//Getters and setters omitted for brevity
}
With Olivier's solution, during update statements you may run into:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'created' cannot be null
To solve this, add updatable=false to the #Column annotation of "created" attribute:
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created", nullable = false, updatable=false)
private Date created;
You can also use an interceptor to set the values
Create an interface called TimeStamped which your entities implement
public interface TimeStamped {
public Date getCreatedDate();
public void setCreatedDate(Date createdDate);
public Date getLastUpdated();
public void setLastUpdated(Date lastUpdatedDate);
}
Define the interceptor
public class TimeStampInterceptor extends EmptyInterceptor {
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState,
Object[] previousState, String[] propertyNames, Type[] types) {
if (entity instanceof TimeStamped) {
int indexOf = ArrayUtils.indexOf(propertyNames, "lastUpdated");
currentState[indexOf] = new Date();
return true;
}
return false;
}
public boolean onSave(Object entity, Serializable id, Object[] state,
String[] propertyNames, Type[] types) {
if (entity instanceof TimeStamped) {
int indexOf = ArrayUtils.indexOf(propertyNames, "createdDate");
state[indexOf] = new Date();
return true;
}
return false;
}
}
And register it with the session factory
Thanks everyone who helped. After doing some research myself (I'm the guy who asked the question), here is what I found to make sense most:
Database column type: the timezone-agnostic number of milliseconds since 1970 represented as decimal(20) because 2^64 has 20 digits and disk space is cheap; let's be straightforward. Also, I will use neither DEFAULT CURRENT_TIMESTAMP, nor triggers. I want no magic in the DB.
Java field type: long. The Unix timestamp is well supported across various libs, long has no Y2038 problems, timestamp arithmetic is fast and easy (mainly operator < and operator +, assuming no days/months/years are involved in the calculations). And, most importantly, both primitive longs and java.lang.Longs are immutable—effectively passed by value—unlike java.util.Dates; I'd be really pissed off to find something like foo.getLastUpdate().setTime(System.currentTimeMillis()) when debugging somebody else's code.
The ORM framework should be responsible for filling in the data automatically.
I haven't tested this yet, but only looking at the docs I assume that #Temporal will do the job; not sure about whether I might use #Version for this purpose. #PrePersist and #PreUpdate are good alternatives to control that manually. Adding that to the layer supertype (common base class) for all entities, is a cute idea provided that you really want timestamping for all of your entities.
For those whose want created or modified user detail along with the time using JPA and Spring Data can follow this. You can add #CreatedDate,#LastModifiedDate,#CreatedBy and #LastModifiedBy in the base domain. Mark the base domain with #MappedSuperclass and #EntityListeners(AuditingEntityListener.class) like shown below:
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public class BaseDomain implements Serializable {
#CreatedDate
private Date createdOn;
#LastModifiedDate
private Date modifiedOn;
#CreatedBy
private String createdBy;
#LastModifiedBy
private String modifiedBy;
}
Since we marked the base domain with AuditingEntityListener we can tell JPA about currently logged in user. So we need to provide an implementation of AuditorAware and override getCurrentAuditor() method. And inside getCurrentAuditor() we need to return the currently authorized user Id.
public class AuditorAwareImpl implements AuditorAware<String> {
#Override
public Optional<String> getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication == null ? Optional.empty() : Optional.ofNullable(authentication.getName());
}
}
In the above code if Optional is not working you may using Java 7 or older. In that case try changing Optional with String.
Now for enabling the above Audtior implementation use the code below
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class JpaConfig {
#Bean
public AuditorAware<String> auditorAware() {
return new AuditorAwareImpl();
}
}
Now you can extend the BaseDomain class to all of your entity class where you want the created and modified date & time along with user Id
In case you are using the Session API the PrePersist and PreUpdate callbacks won't work according to this answer.
I am using Hibernate Session's persist() method in my code so the only way I could make this work was with the code below and following this blog post (also posted in the answer).
#MappedSuperclass
public abstract class AbstractTimestampEntity {
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created")
private Date created=new Date();
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "updated")
#Version
private Date updated;
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getUpdated() {
return updated;
}
public void setUpdated(Date updated) {
this.updated = updated;
}
}
Now there is also #CreatedDate and #LastModifiedDate annotations.
=> https://programmingmitra.blogspot.fr/2017/02/automatic-spring-data-jpa-auditing-saving-CreatedBy-createddate-lastmodifiedby-lastmodifieddate-automatically.html
(Spring framework)
If we are using #Transactional in our methods, #CreationTimestamp and #UpdateTimestamp will save the value in DB but will return null after using save(...).
In this situation, using saveAndFlush(...) did the trick
Following code worked for me.
package com.my.backend.models;
import java.util.Date;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import lombok.Getter;
import lombok.Setter;
#MappedSuperclass
#Getter #Setter
public class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
#CreationTimestamp
#ColumnDefault("CURRENT_TIMESTAMP")
protected Date createdAt;
#UpdateTimestamp
#ColumnDefault("CURRENT_TIMESTAMP")
protected Date updatedAt;
}
Just to reinforce: java.util.Calender is not for Timestamps. java.util.Date is for a moment in time, agnostic of regional things like timezones. Most database store things in this fashion (even if they appear not to; this is usually a timezone setting in the client software; the data is good)
A good approach is to have a common base class for all your entities. In this base class, you can have your id property if it is commonly named in all your entities (a common design), your creation and last update date properties.
For the creation date, you simply keep a java.util.Date property. Be sure, to always initialize it with new Date().
For the last update field, you can use a Timestamp property, you need to map it with #Version. With this Annotation the property will get updated automatically by Hibernate. Beware that Hibernate will also apply optimistic locking (it's a good thing).
As data type in JAVA I strongly recommend to use java.util.Date. I ran into pretty nasty timezone problems when using Calendar. See this Thread.
For setting the timestamps I would recommend using either an AOP approach or you could simply use Triggers on the table (actually this is the only thing that I ever find the use of triggers acceptable).
You might consider storing the time as a DateTime, and in UTC. I typically use DateTime instead of Timestamp because of the fact that MySql converts dates to UTC and back to local time when storing and retrieving the data. I'd rather keep any of that kind of logic in one place (Business layer). I'm sure there are other situations where using Timestamp is preferable though.
We had a similar situation. We were using Mysql 5.7.
CREATE TABLE my_table (
...
updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
This worked for us.
I think it is neater not doing this in Java code, you can simply set column default value in MySql table definition.

MongoDB add createAt updatedAt field java

I am interested in knowing how to have the createdAt and updatedAt field on mongo when using java sync driver.
I know how to do it in mongoose
const SchemaName = new Schema({
//myschema
}, {
timestamps: true
})
I wanted to know how can I achieved the same in java, I know one possibility of having the createdAt and updatedAt field in schema and updating the value during insertion and updation?
You can use javax.persistence annotations, with #PrePersist and #PreUpdate.
#PrePersist
It will be called before persist the data in the database, with updatable = false it will be recorded just once.
#PreUpdate
It will be called before insert the data in the database, and it is called every insert.
Your entity need to inherit the AbstractEntity class below:
#MappedSuperclass
public class AbstractEntity implements Serializable {
private static final long serialVersionUID = -8087154111957605234L;
#Column(name = "CREATED_AT", updatable=false)
private Date createdAt;
#Column(name = "UPDATED_AT")
private Date updatedAt;
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
#PrePersist
protected void onCreate() {
createdAt = new Date();
}
#PreUpdate
protected void onUpdate() {
updatedAt = new Date();
}
}

How to depict joins with #Query annotation in Spring JPA Repository method

I am using Spring-Boot with JPA and a MySQL backend. Now I got quite confused about the repositories Spring-Boot provides. I know these are quite powerful (and seem to be quite useful since they can shorten your code a lot). Still, I do not understand how to represent Joins within them, since the result-set should be a combination of specified attributes in the select of a few Entities.
Now let's assume we have three tables Book, Author, AuthorOfBook, where the last one is simply connecting Book and Author by a combined Primary key. I guess we had the following Java-Classes:
Entity Book:
#Entity
#Table(name="BOOK")
public class Book {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "TITLE")
private String title;
}
Entity Author
#Entity
#Table(name="AUTHOR")
public class Author {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "LASTNAME")
private String lastname;
#Column(name = "FIRSTNAME")
private String firstname;
//Let's assume some getters and setters and a constructor
}
Entity AuthorOfBook:
#Entity
#Table(name="BOOK")
public class Book {
#EmbeddedId
private AuthorOfBookId pk;
}
An Embedded ID
#Embeddable
public class AuthorOfBookId implements Serializable {
private int authorId;
private int bookId;
}
Repository
#Repository
public interface AuthorOfBookRepository extends JpaRepository<,AuthorOfBookId> {
}
Now how would I represent that query:
SELECT b.name, a.firstname, a.lastname from AuthorOfBook ab inner join Book b on b.id = ab.book_id inner join Author a on a.id = ab.author_id where a.lastname = :lastname;
in my repository? I know the signature would need to be like
#Query([the query string from above])
public (...) findAuthorAndBookByAuthorLastname(#Param("lastname") String lastname);
but I cannot make out what Type the return would be like. What is that method returning? (simply AuthorOfBook would not work I guess)
You don't want AuthorOfBook as a separate Entity. Book should have a field of type Author as a #ManyToOne relationship. That way, given any Book, you can find the author's details.
If you want to handle audits fields you can do something like this:
Audit class
#Embeddable
public class Audit {
#Column(name = "created_on")
private Timestamp createdOn;
#Column(name = "updated_on")
private Timestamp updatedOn;
#Column(name = "is_deleted")
private Boolean isDeleted;
//getters and setters
}
AuditListener to update automatically audits fields
public class AuditListener {
private Long loggedUser = 1001L;
/**
* Method to set the fields createdOn, and isDeleted when an entity is persisted
* #param auditable
*/
#PrePersist
public void setCreatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
if (audit == null) {
audit = new Audit();
auditable.setAudit(audit);
}
audit.setIsDeleted(Boolean.FALSE);
audit.setCreatedOn(Timestamp.from(Instant.now()));
}
/**
* Method to set the fields updatedOn and updatedBy when an entity is updated
* #param auditable
*/
#PreUpdate
public void setUpdatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
audit.setUpdatedOn(Timestamp.from(Instant.now()));
}
}
And add this to the entities
#EntityListeners(AuditListener.class)
public class Book implements Auditable {
#Embedded
private Audit audit;

Spring-Data-Jpa AuditingEntityListener createdDate updated upon saving existing entity

I have the following JPA Entity:
#EntityListeners(AuditingEntityListener.class)
#Entity
public class EntityWithAuditingDates {
#Id
#GeneratedValue
private Long id;
#Temporal(TemporalType.TIMESTAMP)
#CreatedDate
private Date createdDate;
#Temporal(TemporalType.TIMESTAMP)
#LastModifiedDate
private Date lastModified;
private String property;
// getters and setters omitted.
}
And the following CrudRepository:
#Service
public interface EntityWithAuditingDatesRepository extends CrudRepository<EntityWithAuditingDates, Long> {
}
And the following test:
#SpringApplicationConfiguration(classes = FooApp.class)
#RunWith(SpringJUnit4ClassRunner.class)
public class AuditingEntityListenerTest {
#Autowired
private EntityWithAuditingDatesRepository entityWithAuditingDatesRepository;
#Test
public void test() {
EntityWithAuditingDates entityWithAuditingDates = new EntityWithAuditingDates();
entityWithAuditingDates.setProperty("foo");
assertNull(entityWithAuditingDates.getCreatedDate());
assertNull(entityWithAuditingDates.getLastModified());
entityWithAuditingDatesRepository.save(entityWithAuditingDates);
assertNotNull(entityWithAuditingDates.getCreatedDate());
assertNotNull(entityWithAuditingDates.getLastModified());
assertEquals(entityWithAuditingDates.getLastModified(), entityWithAuditingDates.getCreatedDate());
entityWithAuditingDates.setProperty("foooo");
entityWithAuditingDatesRepository.save(entityWithAuditingDates);
assertNotEquals(entityWithAuditingDates.getCreatedDate(), entityWithAuditingDates.getLastModified());
}
}
The last condition fails. Shouldn't be the createdDate and the lastModifiedDate be different after updating the entity?
Thanks!
I faced the same issue but figured out a workaround for now. On #Column, I have set updatable=false to exclude create* fields on update.
#CreatedBy
#NotNull
#Column(name = "created_by", nullable = false, length = 50, updatable = false)
private String createdBy;
#CreatedDate
#NotNull
#Column(name = "created_date", nullable = false, updatable = false)
private ZonedDateTime createdDate = ZonedDateTime.now();
#LastModifiedBy
#Column(name = "last_modified_by", length = 50)
private String lastModifiedBy;
#LastModifiedDate
#Column(name = "last_modified_date")
private ZonedDateTime lastModifiedDate = ZonedDateTime.now();
It's not necessary to do another query to see fields updated. The repository's save method returns an object, which the documentation says that you should always use for further operations. The returned object should pass that last assertion. Try this:
entityWithAuditingDates = entityWithAuditingDatesRepository.save(entityWithAuditingDates);
If you retrieve the entity from the database after the update operation, the fields are set correctly. The test case below passes. Still, I wonder why they are set correctly on the first save operation, but then incorrectly upon the second. And you only get the correct information in the end when you retrieve the record from the database. I guess this is related to the hibernate cache.
#Test
public void test() throws InterruptedException {
EntityWithAuditingDates entityWithAuditingDates = new EntityWithAuditingDates();
entityWithAuditingDates.setProperty("foo");
assertNull(entityWithAuditingDates.getCreatedDate());
assertNull(entityWithAuditingDates.getLastModified());
entityWithAuditingDatesRepository.save(entityWithAuditingDates);
assertNotNull(entityWithAuditingDates.getCreatedDate());
assertNotNull(entityWithAuditingDates.getLastModified());
assertEquals(entityWithAuditingDates.getLastModified(), entityWithAuditingDates.getCreatedDate());
entityWithAuditingDates.setProperty("foooo");
Thread.sleep(1000);
entityWithAuditingDatesRepository.save(entityWithAuditingDates);
EntityWithAuditingDates retrieved = entityWithAuditingDatesRepository.findOne(entityWithAuditingDates.getId());
assertNotNull(retrieved.getCreatedDate());
assertNotNull(retrieved.getLastModified());
assertNotEquals(retrieved.getCreatedDate(), retrieved.getLastModified());
}

Spring Data Repository Save Not Returning Instance With Updated Audit Fields

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);
}
}
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.
I ran in to the exact same problem. I fixed it by using,
repository.saveAndFlush(myEntity);
instead of
repository.save(myEntity);

Categories

Resources