Updatable false behvior incosistent - java

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;
}

Related

Weblogic to Liberty w JPA upgrade - related entities intermittently not being queried

just a quick question please in case something stands out immediately.
We're migrating an EAR/EJB application from Weblogic 11g to latest WS Liberty (22.x) also upgrading several of the frameworks including JPA to 2.2. This also changes JPA implementation to eclipseLink. We came from com.oracle.weblogic.11g.modules:javax.persistence:1.0.0.0_1-0-2. Underlying DB is MS-SQL Server.
And I'm running into some weirdness with regards to related objects not being resolved/queried intermittently.
Just as an example we have entities where the columns hold reference data codes or similar lookups. Say I have an entity called PayemntRecordT and it has a status code which refers to a ref table that also holds a textual description. Something like this:
SQL:
CREATE TABLE [PAYMENT_RECORD_T](
[PAYMENT_ID] [int] NOT NULL,
...
[PAYMENT_STATUS_CD] [CHAR](8) NOT NULL,
...
)
ALTER TABLE [PAYMENT_RECORD_T] WITH CHECK ADD CONSTRAINT [FK_PAYM4] FOREIGN KEY([PAYMENT_STATUS_CD])
REFERENCES [RECORD_STATUS_T] ([REC_STAT_CD])
GO
CREATE TABLE [RECORD_STATUS_T] (
[RECORD_STAT_CD] [CHAR](8) NOT NULL,
[RECORD_STAT_DSC] [VARCHAR](60) NOT NULL
CONSTRAINT [PK_RECORD_STATUS_T] PRIMARY KEY CLUSTERED (
[RECORD_STAT_CD] ASC
)WITH (PAD_INDEX = OFF...) ON [PRIMARY]
) ON [PRIMARY]
GO
Java:
#Table(name = "PAYMENT_RECORD_T")
#Entity
public class PaymentRecordT {
...
#ManyToOne
#PrimaryKeyJoinColumn(name = "payment_status_cd", referencedColumnName = "REC_STAT_CD")
private RecordStatusT recordStatusT;
}
#Table(name = "RECORD_STATUS_T")
#Entity
public class RecordStatusT {
#Column(name = "REC_STAT_CD")
#Id
private String recStatCd;
#Column(name = "REC_STAT_DSC")
#Basic
private String recStatDsc;
}
Others relations in our app might not be primary key relations but loose relations in which case its just #JoinColumn but the pattern would be the same.
My 'weirdness' is the following:
So in this example I have a list of 10 'Payment Records' each of them have such a record status, which is actually NON NULL in the database. When I do the initial retrieval via EJB method it grabs the 10 records and I also get the correctly resolved/queried record statuses.
Then I add a new record via EJB method (TRANSACTION_REQUIERD). After the add method returns I can query the new payment record in the database via SSMS. Its committed and it looks 100% correct and it contains a correct record status code.
Now I run the retrieval method again and I get the 11 records as I would expect. Only the 11th (newly inserted) record will have recordStatusT as null.
When I restart the app all goes well again for the retrieval of all 11 records. But for subsequent additions the outcome seems again 'undefined'.
In JDBC logging I an see that during the original retrieval of the records the record_status_t table was queried but the 2nd time around it was not and I have no explanation why.
I played with FETCHTYPE.EAGER and read up on caching etc but I'm not going anywhere.
Any ideas?
Thanks for your time
Carsten
I solved the problem by ensuring that after inserts/updates the objects arent being queried from the cache.
In the end - rather than doing it with query hint - I disabled caching for the entity involved using the #Chacheable annotation, like so
#Table(name = "PAYMENT_RECORD_T")
#Entity
#Cacheable(false)
public class PaymentRecordT {
...
#ManyToOne
#PrimaryKeyJoinColumn(name = "payment_status_cd", referencedColumnName = "REC_STAT_CD")
private RecordStatusT recordStatusT;
}
I still feel like there should be a better solution. Eclipselink tracks the inserts/updates so it should be able track what needs rereading from the DB and what not. I still feel like I don't fully understand the entire picture, but this works for me and its reasonably clean.
I can leave the considerable amount of read-only data/objects chacheable and the few that are changeable as non-cacheable.
Thanks for reading
Carsten

How to upgrade database with newer JPA timestamp fields?

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.

Unable to update current time stamp upon database updates

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

Timestamp will lose during view object -> entity conversion

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.

Conditional insert with Spring JPA / Hibernate

I'm working on a project that runs in a clustered environment, where there are many nodes and a single database. The project uses Spring-data-JPA (1.9.0) and Hibernate (5.0.1). I'm having trouble resolving how to prevent duplicate row issues.
For sake of example, here's a simple table
#Entity
#Table(name = "scheduled_updates")
public class ScheduledUpdateData {
public enum UpdateType {
TYPE_A,
TYPE_B
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private UUID id;
#Column(name = "type", nullable = false)
#Enumerated(EnumType.STRING)
private UpdateType type;
#Column(name = "source", nullable = false)
private UUID source;
}
The important part is that there is a UNIQUE(type, source) constraint.
And of course, matching example repository:
#Repository
public class ScheduledUpdateRepository implements JpaRepository<ScheduledUpdateData, UUID> {
ScheduledUpdateData findOneByTypeAndSource(final UpdateType type, final UUID source);
//...
}
The idea for this example is that parts of the system can insert rows to be schedule for something that runs periodically, any number of times between said runs. When whatever that something is actually runs, it doesn't have to worry about operating on the same thing twice.
How can I write a service method that would conditionally insert into this table? A few things I've tried that don't work are:
Find > Act - The service method would use the repository to see if a entry already exists, and then either update the found entry or save a new one as needed. This does not work.
Try insert > Update if fail - The service method would try to insert, catch the exception due to the unique constraint, and then do an update instead. This does not work since the transaction will already be in a rolled-back state and no further operations can be done in it.
Native query with "INSERT INTO ... WHERE NOT EXISTS ..."* - The repository has a new native query:
#Repository
public class ScheduledUpdateRepository implements JpaRepository<ScheduledUpdateData, UUID> {
// ...
#Modifying
#Query(nativeQuery = true, value = "INSERT INTO scheduled_updates (type, source)" +
" SELECT :type, :src" +
" WHERE NOT EXISTS (SELECT * FROM scheduled_updates WHERE type = :type AND source = :src)")
void insertUniquely(#Param("type") final String type, #Param("src") final UUID source);
}
This unfortunately also does not work, as Hibernate appears to perform the SELECT used by the WHERE clause on its own first - which means in the end multiple inserts are tried, causing a unique constraint violation.
I definitely don't know a lot of the finer points of JTA, JPA, or Hibernate. Any suggestions on how insert into tables with unique constraints (beyond just the primary key) across multiple JVMs?
Edit 2016-02-02
With Postgres (2.3) as a database, tried using Isolation level SERIALIZABLE - sadly by itself this still caused constraint violation exceptions.
You are trying to ensure that only 1 node can perform this operation at a time.
The best (or at least most DB-agnostic) way to do this is with a 'lock' table.
This table will have a single row, and will act as a semaphore to ensure serial access.
Make sure that this method is wrapped in a transaction
// this line will block if any other thread already has a lock
// until that thread's transaction commits
Lock lock = entityManager.find(Lock.class, Lock.ID, LockModeType.PESSIMISTIC_WRITE);
// just some change to the row, it doesn't matter what
lock.setDateUpdated(new Timestamp(System.currentTimeMillis()));
entityManager.merge(lock);
entityManager.flush();
// find your entity by unique constraint
// if it exists, update it
// if it doesn't, insert it
Hibernate and its query language offer support for an insert statement. So you can actually write that query with HQL. See here for more information. http://docs.jboss.org/hibernate/orm/5.0/userguide/html_single/Hibernate_User_Guide.html#_hql_syntax_for_insert
It sounds like an upsert case, that can be handled as suggested here.
Find > Act - The service method would use the repository to see if a entry already exists, and then either update the found entry or save a new one as needed. This does not work.
Why does this not work?
Have you considered "optimistic locking"?
These two posts may help:
https://www.baeldung.com/jpa-optimistic-locking
https://www.baeldung.com/java-jpa-transaction-locks

Categories

Resources