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();
Related
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();
}
}
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
Let's assume we have a database table (a JPA entity), which is created as follows
id|start_date |end_date |object_id
1 |2018-01-01 00:00:00|2018-07-01 20:00:00|1
I would like to run a specific method when end_date passes current_date() - and I want Spring/Java/DB to start that method, without using schedulers. Some pseudo code below:
when (end_date <= current_date()) -> myClass.executeMyMethod(object_id)
Is this even possible? I've searched through a lot of different sites and am yet to find an answer.
Edit - here is an entity for which I would like to do that:
#Entity
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "time_based_event", schema = "timing")
public class TimePassingByTrigger {
#Column(name = "id", nullable = false)
protected Long id;
#Column(name = "start_date", nullable = false)
protected LocalDateTime startDate;
#Column(name = "endDate", nullable = false)
protected LocalDateTime endDate;
#Column(name = "object_id", nullable = false)
protected Long objectId;
}
And for service call - let's assume that I would like to do something like:
public class TimeFiredEvent {
// this method should be executed for each objectId for
// which end_date has passed
public void thisShouldRunOnlyWhenDatePasses(Long objectId) {
System.out.println("Time has passed for object: " + objectId);
}
}
I think what you may want is an EntityListener
#Table(name="time_based_event", schema="timing")
#EntityListeners(class=TimeFiredEvent.class)
public class TimePassingByTrigger {
....
And you define the actual listener
public class TimeFiredEvent {
#PostPersist
public void checkTriggerTime(TimePassingByTrigger trig) {
if(trig.getEndDate().before(new Date()) {
thisShouldRunOnlyWhenDatePasses(trig.getId());
}
}
public void thisShouldRunOnlyWhenDatePasses(Long objectId) {
}
}
However, this is probably not what you actually need. An EntityListener is only going to execute when the entity is saved, retrieved, or deleted. (The valid callback points are #PrePersist, #PreRemove, #PostPersist, #PostRemove, #PreUpdate, #PostUpdate, #PostLoad)
But there is no mechanism for executing code just because time has continued its inexorable march forward. You will still have to have some kind of polling mechanism or a sleeping Java thread if you want to do a check at other times.
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);