Spring Data UpdateTimestamp for specific column - java

I use Spring Boot and Spring Data for my project. In one of the tables I have a column called "itemStatus". Now I want to add another column with a timeStamp something like "itemStatus_LastModifiedDate" to keep the date of the latest update to the itemStatus column.
Is there a way to do this cleanly with the help of some annotations like:
#LastModifiedDate
#UpdateTimestamp
private Date itemStatusLastModifiedDate
Because as far I know these above only work when there is an update to the entity and not to a specific column.

1 You can not use annotation #UpdateTimestamp to track the update of a single field.
2 You can do a custom update using #SqlUpdate annotation with a custom update query.
Something like this (I didn't check this code)
#SqlUpdate(update item set item_status = :itemStatus, itemStatusModDate = case when (item_status = :itemStatus) then sysdate() else item_status_mod_date where id = :id)
public class Item {
#GeneratedValue
private Long id;
private String itemStatus;
private Date itemStatusModDate;
..................................................
}

Related

Hibernate Tools (hbm2java): Column Default Value is Missing [duplicate]

This question already has answers here:
How to set a default entity property value with Hibernate
(18 answers)
Closed 26 days ago.
I am using hbm2java, which is part of Hibernate Tools, to reverse engineer a database into JPA entity classes.
I run the tool via ./mvnw clean generate-sources and the entity classes are generated and saved to target/generated-sources.
In the UserAccount database table, the Created column is defined like this. Note the default value:
Created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
When hbm2java reverse engineers that column, the default value is not included:
...
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "CREATED", nullable = false, length = 29)
public Timestamp getCreated() {
return this.created;
}
public void setCreated(Timestamp created) {
this.created = created;
}
...
As a result, a DataIntegrityViolationException is thrown when trying to save a UserAccount entity to the database:
org.springframework.dao.DataIntegrityViolationException: not-null
property references a null or transient value :
com.example.UserAccount.created
Really hoping there is a way around this as I have quite a few database columns with default values, the most complex being:
DEFAULT 'User' + CAST(NEXT VALUE FOR SeqUserAccountUsername as VARCHAR(19))
...that just generates a string such as User13.
I'm still learning Spring Boot and Hibernate and could use some advice on the best approach to solving this problem.
My current research:
The same question was asked back in 2007 in the Hibernate Forums but a solution was not provided.
This documentation talks about using the "default-value" attribute to set the "Default initialization value for a field". Is that the correct approach?
I believe that the following mapping should work in all recent-ish versions of Hibernate:
#Generated(INSERT)
#ColumnDefault("CURRENT_TIMESTAMP")
#Column(nullable = false)
public Timestamp getCreated() {
return this.created;
}
If that doesn't work, let me know.
(It's certainly true that the reverse engineering tool doesn't know anything about default values.)
At first, you need to find someone who will able to clarify how that database works. The problem is SQL column definition like TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL means following:
you never select null
you may not insert/set null
you may insert/set not null
you may either omit column in DML statement or specify DEFAULT, in that case DB generates value according to default expression (CURRENT_TIMESTAMP in your case)
The simplest option to get exactly the same functionality/capabilities in Hibernate, is to use JPA Callbacks, smth. like:
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "CREATED", nullable = false, /* updatable = false, */ length = 29)
public Timestamp getCreated() {
return this.created;
}
#PrePersist
protected void onPrePersist() {
if (this.created == null) {
this.created = Timestamp.from(Instant.now());
}
}
that allows you to specify arbitrary created timestamp, and only if it is null Hibernate will use current time - that is exactly what DB allows you to do.
Other options do something similar, but not the same, however some of them may suit you.
#CreationTimestamp:
#CreationTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "CREATED", nullable = false, length = 29)
public Timestamp getCreated() {
return this.created;
}
it ignores value of this.created and inserts current time, calculated on Java side, into DB.
#Generated(GenerationTime.INSERT)
#Generated(GenerationTime.INSERT)
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "CREATED", nullable = false, length = 29)
public Timestamp getCreated() {
return this.created;
}
it ignores value of this.created and delegates generation of value to DB, basically it omits corresponding column in INSERT statement (well... "generate" is not correct definition here).
if you choose to use either #CreationTimestamp or #Generated(GenerationTime.INSERT) remove corresponding setter void setCreated(Timestamp created) in order to avoid any confusion about mutability of created field.

How to avoid auto creation column into database? Spring

I am using Spring and it's automatically creating columns. I want some of columns be created but some not.Here is example where Spring creating all 3 columns but I want only money to be created which is annotated with #Cloumn:
#Column(name = FLD_MONEY,unique = false,nullable = true)
private FastMoney money;
private String currencyUnit;
private BigDecimal moneyn;
How I can do that?
Another question is that how I can tell to store String instead of Binary Data for this column:
#Column(name = FLD_MONEY,unique = false,nullable = true)
private FastMoney money;
In database it's writing binary data but I want to see String, is it possible or not?
For first question answer is just add annotation #Transient and it won't store in database.Answer gave in comments below the question

Hibernate Envers - select "all" changes for entity ID

I have problem with Hibernate Envers.
I have classes like:
#Entity
#Table(name = "REVINFO")
#RevisionEntity(MyRevisionEntityListener.class)
public class RevEntity {
#Id
#RevisionNumber
#Column(name = "REV", nullable = false, updatable = false)
private Integer id;
#RevisionTimestamp
#Column(name = "REVTSTMP", nullable = false, updatable = false)
private Date timestamp;
#Column(name = "MODIFIED_BY", length = 100)
private String modifiedBy;
#Column(name = "COMMENT", length = 100)
private String comment;
public class MyRevisionEntityListener implements RevisionListener {
#Override
public void newRevision(Object revisionEntity) {
RevEntity a = (RevEntity) revisionEntity;
a.setComment("Some value");
}
}
How can i select every change for entity ID and their "REVINFO" object?
I've got something like this:
List resultList = AuditReaderFactory.get(entityManager)
.createQuery()
.forRevisionsOfEntityWithChanges(ClientType.class, true)
.add(AuditEntity.id().eq(entityId))
.getResultList();
And it's almost work good. I received every "change" but REVINFO looks strange. All fields are null - and there are 1 more object $$_hibernate_interceptor which actually hold "information" but i cannot acces it via code (or i dont know how). See example at the image.
So my question is:
1 - How can i get REVINFO values ?
2 - Do i realy have to use entityManager, or can it be achived with different approach ?
Edit 2:
Correct me if i am wrong, but does forRevisionsOfEntityWithChanges works as Lazy Initialization? I mean, if i try to receive for example modifiedBy field i actually get my data. Debugger log make me confused.
The call to forRevisionsOfEntityWithChanges returns an object array that contains:
Entity instance
Revision Entity
Revision Type
Property names that were changed.
How can i get REVINFO values ? 2 - Do i realy have to use entityManager, or can it be achived with different approach ?
So in your code, to get the revision info attributes, you would do the following. Note that in this code, the type of the revision-info object will depend on your configuration or if you're using a custom revision-info entity class in your deployment. Just be sure to cast it to the proper type.
for (Object entry : resultList) {
final Object[] row = (Object[]) entry;
final TheRevisionEntityClassType revisionInfo = row[1];
// now you can get the revision entity attributes from revisionInfo using getters
}
Correct me if i am wrong, but does forRevisionsOfEntityWithChanges works as Lazy Initialization? I mean, if i try to receive for example modifiedBy field i actually get my data. Debugger log make me confused.
Depending on the query, yes Hibernate may use proxies and its important to understand that in this case, the visual representation you get in the debugger may or may not be accurate depending if the object's internal state gets initialized by the debugger window or not.

Select BETWEEN dates returns wrong results

I have a Table in MySQL which has it's column definitions as below:
CREATE TABLE APPOINTMENT(
CD_APPOINTMENT BIGINT NOT NULL,
-- omitted for brevity
APPOINT_DATE DATE NOT NULL
);
My JPA entity is defined as:
#Entity
#Table(name = "APPOINTMENT")
public class Appointment {
protected Long id;
protected Date date = new Date();
// other atributes omitted for brevity
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "CD_APPOINTMENT")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Temporal(TemporalType.DATE)
#Column(name = "APPOINT_DATE", columnDefinition = "DATE")
public Date getDate() {
return date;
}
}
As I'm using Spring, I have benefits of Spring Data JPA. Following that line, I'm using Spring Data JPA Repositories.
I'm testing in 2019-07-12 (at my timezone [UTC-3]).
When I run:
appointmentRepository.save(appointment);
the Appointment is successfully (more or less) saved.
Fine! The column APPOINT_DATE has the value of 2019-07-12, yes? Well, it's seems ok.
When I run:
SELECT * FROM APPOINTMENT;
the retrieved rows looks as expected:
CD_APPOINTMENT|APPOINT_DATE
--------------|------------
1| 2019-07-12
The strange part appears when I try to filter BETWEEN dates.
If I run my JPQL:
SELECT ap FROM Appointment AS ap WHERE ap.date BETWEEN :startDate AND :endDate
startDate and endDate are parameters received in a #Param annotation in Spring and both of them have the value of 2019-07-12
I get 0 rows, but I was expecting to get one (the above inserted Appointment). Firstly, I thought it was a problem with the JPQL, but it's not. If I execute the same JPQL with a different RDBMS (like H2, for an example), the query works perfectly.
And if I run the same JPQL but in SQL, directly on the MySQL database:
SELECT * FROM APPOINTMENT where APPOINT_DATE BETWEEN '2019-07-12' AND '2019-07-12'
just like the JPQL it returns 0 rows.
If I run the now(); command at MySQL database, it return the CORRECT date time.
How can I fix it?
Has anybody seen something like that already? Because I have not.
BETWEEN '2019-07-12' AND '2019-07-13'
It is best not to use between for date/times. One reason is because there might be a time component that throws off the comparison.
I would suggest:
SELECT *
FROM APPOINTMENT
WHERE APPOINT_DATE >= '2019-07-12' AND
APPOINT_DATE < '2019-07-13'
This logic works with an without a time component. And it can take advantage of an index on the date column.
My MySQL instance is from Amazon RDS.
Their default Time Zone is UTC. Switched from UTC to Brazil/East and now it's working as expected.

How to use custom #Query for joining #ElementCollection column in Hibernate?

I have a stupid problem and thinking about it for 3 hours. I have #Entity class, for example:
private long id;
private Instant startTime;
private Instant lastTime;
private String name;
#ElementCollection
#Builder.Default
private Set<Integer> codes = new HashSet<>();
So, Hibernate creates new table with my main class id and codes. It is nice, works correct.
I have a problem with custom query.. I would like select given codes between given start and endTime.
It is not difficult to prepare query on my oracle db:
select id, startTime, lastTime, name from main_table mt left outer join created_table_codes cd on mt.id = cd.main_table_id
where cd.codes = 12345 and mt.startTime=‘date’ between ‘endDate’ ;
Unfortunately, in #Query it is doesnt work. Hibernate cant see table created from my #ElementCollection :(
Do you know how can I get data by #Query - using join and ma #ElementCollection table?

Categories