I have a table where I have a Date field which has a default value of current timestamp. This works fine when I create a row on that table.
When I update that row, I expect that timestamp to automatically be updated but it is not being updated. Appreciate any advice on what I am doing wrong.
This is the field at my Entity class.
// Date is of type import java.util.Date;
#UpdateTimestamp // expecting this to do the auto update.
#Temporal(TemporalType.TIMESTAMP)
private Date updatedAt;
This is the query at my Repository interface.
// I don't intend to pass in current timestamp as a 3rd param for updateAt field. I expect it to just auto update to current time stamp.
#Modifying
#Query("update table as t set t.title =?1 where t.Id = ?2")
void update(String title, long id);
The above query updates only title and id but not the updatedAt Date field. Also tried the following under the Entity class which makes no difference.
#PreUpdate
protected void onUpdate(){
updatedAt = new Date();
}
I suspected this is normal behavior because #PreUpdate is a JPA/Hibernate feature. When you just use a Query, you are just invoking "plain SQL" not going through Hibernate entity lifecycle. I looked around and found some confirmation:
This was answered here as well:
Spring Data JPA #PreUpdate not called when update using #Query from Repository
Related
I have a Spring Boot application running using JPA and Hibernate to automagically manage my entities. When I created this application, I used an older version of JPA that didn't have support for Java 8 DateTime API. However, without a lot of knowledge about JPA, I used LocalDateTime in my entities and it worked! Not having to know about the underlying database structure was great!
Until now...
I am upgrading JPA to a version that does support LocalDateTime, and I am facing an error with the way JPA is using this field. It used to save this object as a VARBINARY (tinyblob) field in my MySQL database, but now it is smart and expects it to be a TIMESTAMP type. Which means that when I start my application using the configuration spring.jpa.hibernate.ddl-auto=validate I get the error:
...
Caused by: org.hibernate.tool.schema.spi.SchemaManagementException:
Schema-validation: wrong column type encountered in column [answer_time] in table [user_answer];
found [tinyblob (Types#VARBINARY)], but expecting [datetime (Types#TIMESTAMP)]
So now I am kinda lost on how to convert these fields to their new timestamp types. I was thinking about using FlyWay to write a migration script, but I have no idea how JPA stored the object as blob. When print a VARBINARY field as string this is what it looks like:
’ sr
java.time.Ser]º"H² xpw ã
!;:;Ö#x
This is how my entity looks like (which was unchanged during the upgrade):
#Entity
#Table(name = "user_answer")
public class UserAnswer {
private Long id;
private LocalDateTime answerTime;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public LocalDateTime getAnswerTime() {
return answerTime;
}
public void setAnswerTime(LocalDateTime answerTime) {
this.answerTime = answerTime;
}
}
How can I update my database so it converts the old VARBINARY fields that it used to store LocalDateTime data to TIMESTAMP fields?
What I would try (after backing up the DB!) :
Keep the old JPA API + implementation (Hibernate) versions.
Keep the old LocalDateTime field.
Add another java.sql.Date field to your entity. Make sure to annotate it properly etc. so that Hibernate knows exactly how the column should be defined.
For all entities:
Load each entity, read the LocalDateTime, convert and store it to the DateTime field, merge().
Remove the DateTime field.
Remove the column for the old LocalDateTime from the table.
Change the type of the DateTime field to LocalDateTime.
Upgrade the JPA API + implementation (Hibernate) versions.
JPA impl (Hibernate?) should store the DateTime as TIMESTAMP.
The JDBC driver should be able to pick up the TIMESTAMP into LocalDateTime.
Also, consider ZonedDateTime rather than LocalDateTime.
let's assume we have a Spring Data repository interface with a custom method...
#Modifying
#Transactional
#Query("UPDATE MyEntity SET deletedAt = CURRENT_TIMESTAMP WHERE id = ?1")
void markAsSoftDeleted(long id);
This method simply sets the deletedAt field of the entity, ok. Is there any way to allow this method to return an updated version of the MyEntity?
Obviously...
#Modifying
#Transactional
#Query("UPDATE MyEntity SET deletedAt = CURRENT_TIMESTAMP WHERE id = ?1")
MyEntity markAsSoftDeleted(long id);
...does not work, since...
java.lang.IllegalArgumentException: Modifying queries can only use void or int/Integer as return type!
Does anyon know another way to easily allow that, except of course the obvious "add a service layer between repository and caller for such things"...
Set clearAutomatically attribute on #Modifying annotation.That will clear all the non-flushed values from EntityManager.
#Modifying(clearAutomatically=true)
#Transactional
#Query("UPDATE MyEntity SET deletedAt = CURRENT_TIMESTAMP WHERE id = ?1")
void markAsSoftDeleted(long id);
To flush your changes before committing the update latest spring-data-jpa has another attribute on #ModifyingAttribute. But I think its still in 2.1.M1 release.
#Modifying(clearAutomatically=true, flushAutomatically = true)
Please check corresponding jira bug request: https://jira.spring.io/browse/DATAJPA-806
Another approach can be you can implement custom repostiory Implementation and return your updated entity after done with the query execution.
Reference : Spring data jpa custom repository implemenation
There are two ways to do that:
The JPA idiomatic way to do this is to load the entities first, then changing them using Java code.
Doing this in a transaction will flush the changes to the database.
If you insist on doing a batch update you need to mark the entities as part of the update. Maybe with a timestamp, maybe the update itself already marks them. And then you reload them using a select statement that uses the marker set during the update.
Note that you have to ensure that the entities don't exist yet in your EntityManager, otherwise you will keep the old state there. This is the purpose of #Modifying(clearAutomatically=true) recommended by other answers.
#Modifying(clearAutomatically=true)
Its works for me.
It will never return void or your class type, add return type int or Integer like below,
#Modifying(clearAutomatically=true)
#Transactional
#Query("UPDATE MyEntity SET deletedAt = CURRENT_TIMESTAMP WHERE id = ?1")
Integer markAsSoftDeleted(long id);
My application uses view object-entity pair for the frontend-backend interaction. The frontend only uses VOs, while the backend only talks database with entities. And there are a VO <-> entities conversion
My entity class has 2 timestamp properties, createTimestamp and lastUpdateTimestamp, corresponding to two non-nullable columns in its data table. But the VO never has these 2 properties.
My current problem: since a VO doesn't contain Timestamp properties, the entity converted from the VO will have the 2 Timestamp properties to be null, and when I do that entity update, error occurs from the database because it thinks I am trying to set the Timestamp columns into null which is not permitted.
I like to know how do we deal with this issue. Is there some way to make database ingore these 2 Timestamp on update, or is there an "elegant" way to obtain the Timestamp values before I update the entity? I often need to update a list of entities in one shot.
Solution that I found
I added a attribute "updatable" under the #Column annotation, and it seems to solve my issue.
i.e. #Column(name = "CREATE_STAMP", nullable = false, updatable = false)
Hinted from this post
Creation timestamp and last update timestamp with Hibernate and MySQL
Set the default value against the DB column for timestamp columns, that means if provided in the INSERT query(through VO) it will take it, otherwise it will be default.
Update: You can use an Hibernate interceptor instead, that's what they are for. For example, the entities that need such fields could implement the following interface:
public interface Auditable {
Date getCreated();
void setCreated(Date created);
Date getModified();
void setModified(Date modified);
}
Then the interceptor always sets the modified field on save, and only sets the created field when it's not already set.
My Application model object contains a date field (time stamp):
#Entity
#Table(name = "MYTABLE")
public class Application {
private Date timeStamp;
...
}
I'm trying to construct a JPQL query that would select all applications that were changed today (i.e. their time stamp was changed anytime today). What is the best way to do this?
There is no perfect way with standard JPQL, JPQL doesn't provide Date arithmetic functions. But your provider might provide extensions (e.g. Hibernate and EclipseLink do). Or use a native SQL.
I need LastUpdatedDttm to be updated by SYSDATE whenever record is updated. But below annoataions do nt work as desired. SYSDATE is inserted only once and not updated for subsequent updations. Also, lastUpdDTTM is not part of sql generated by hibernate.
#Generated(GenerationTime.ALWAYS)
#Column(name="LAST_UPDATED_DTTM",insertable=false,updatable=true, columnDefinition ="timestamp default SYSDATE")
private Date lastUpdDTTM;
#Generated(GenerationTime.ALWAYS)
#Column(name="CREATED_DTTM", insertable=false, updatable=false)
private Date createdDTTM;
(...) SYSDATE is inserted only once and not updated for subsequent updates.
First of all, let me make something clear: Generated means that the field is generated by the database and that Hibernate needs to read it after insert/update to update the entity. Using default SYSDATE in the column definition works fine for an INSERT but for an UPDATE, you'll need a trigger.
Also, lastUpdDTTM is not part of sql generated by hibernate.
Well, you told Hibernate that the field is ALWAYS generated by the database so I'm not surpised that Hibernate doesn't include it in the generated SQL (actually, I believe that this somehow conflicts with udpatable = true, I would expect Hibernate to complain about it).
Anyway, as I said, it's not Hibernate that will update this field, it's the database and you need a trigger, Hibernate will just refresh the entity after an update to get the new value.
A different approach would be to use callback annotations, for example for the last update date:
#PreUpdate
protected void updateDates() {
lastUpdDTTM = new Date();
}
For better consistency, you could even use the same approach for the creation date with #PrePersit:
#PrePersist
#PreUpdate
protected void updateDates() {
Date now = new Date();
if (createdDTTM == null) {
createdDTTM = new Date(now.getTime());
}
lastUpdDTTM = now;
}