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();
}
}
Related
I have the following JPA entity:
#Entity
#Builder
#Table(name = "My_Table")
public class MyTableEntity {
#Column(name = "id")
private long id;
#Column(name = "creationdatetime")
private LocalDateTime creationDateTime;
#Column(name = "updatedatetime")
private LocalDateTime updateDateTime;
#PrePersist
protected void onCreate() {
this.creationDateTime = LocalDateTime.now();
this.updateDateTime = LocalDateTime.now();
}
}
I have a unit test where I am doing this:
LocalDateTime creationDate = LocalDateTime.now().minusDays(100);
MyTableEntity entity =
MyTableEntity.builder()
.id(1)
.build();
entity.setCreationDateTime(creationDate)
entity.setUpdateDateTime(creationDate)
However, later on in my unit test, the value I'm setting for creation date time and update date time is magically getting changed as per what is defined in the #PrePersist method in my JPA entity.
Solely for the purpose of unit testing, how can I stop #PrePersist from overriding values I'm explicitly setting during the unit test?
(If it, helps I am using Mockito.)
The easiest way is adding check in the MyTableEntity#onCreate method:
#PrePersist
protected void onCreate() {
if(creationDateTime == null){
this.creationDateTime = LocalDateTime.now();
}
if(updateDateTime == null){
this.updateDateTime = LocalDateTime.now();
}
}
Then the values you set in the unit test won't be rewritten.
Another option is passing onCreate code from outside.
#Entity
#Builder
#Table(name = "My_Table")
public class MyTableEntity {
/// ...
#Transient
private Consumer<MyTableEntity> onCreateConsumer = mte -> {
mte.creationDateTime = LocalDateTime.now();
mte.updateDateTime = LocalDateTime.now();
};
#PrePersist
protected void onCreate() {
onCreateConsumer.accept(this);
}
}
Then you will be able to set onCreateConsumer in the unit test:
MyTableEntity.builder()
.id(1)
.onCreateConsumer(e -> {}) // do nothing
.build();
I'm having the following entity class
#Entity
#Builder
#Table(name = "foo")
#Data
#NoArgsConstructor
public class Foo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
...
#Column(name = "created_at", updatable = false)
#CreationTimestamp
private Instant createdAt;
#Column(name = "updated_at")
#UpdateTimestamp
private Instant updatedAt;
}
And expect that the createdAt field will be set only once on the first save() action, and the updatedAt field will be updated on every save() action.
However, with the following test:
#Test
public void shouldUpdateFieldUpdatedAt() {
var savedFoo = fooRepository.save(new Foo());
var firstUpdate = savedFoo.getUpdatedAt();
var updatedFoo = fooRepository.save(savedFoo);
assertThat(firstUpdate).isBefore(updatedFoo.getUpdatedAt());
}
It fails all the time. And I can see in the debug that for two distinct Foo instances there updatedAt field is the same.
I'm using Hibernate 5.3.7 which does support Instant types. So, I have no clue what is the issue...
The DB is Postgres with the following table:
CREATE TABLE IF NOT EXISTS foo (
id SERIAL PRIMARY KEY,
...
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
);
Looks like the issue could be with the hibernate caching.
If between two save() actions I update some other field the test passes and the updatedAt is different.
#Test
public void shouldUpdateFieldUpdatedAt() {
var savedFoo = fooRepository.save(new Foo());
var firstUpdate = savedFoo.getUpdatedAt();
savedFoo.setTitle("New Title");
var updatedFoo = fooRepository.save(savedFoo);
assertThat(firstUpdate).isBefore(updatedFoo.getUpdatedAt());
}
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
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());
}
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);