Select BETWEEN dates returns wrong results - java

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.

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.

JPA set timestamp generated by database but not CURRENTTIME or UPDATETIME

I have a class like this
#Entity
#Table(name = "Event")
public class Event {
#Transient
public static final long MAX_TIMESTAMP = 253402214400000L; // 9999-12-31 00:00:00
private Date creationTime;
private Date expiryTime;
private String otherValue;
public Event(int timeout, String otherValue){
this.creationTime = new Date();
this.expiryTime = (timeout == 0 ? new Date(MAX_TIMESTAMP) : new Date(creationTime.getTime() + SECONDS.toMillis(timeout)));
this.otherValue = otherValue;
}
}
I call save() methed in CrudRepository and save this data.
and I have a ScheduledExecutorService to find out some timeout events:
#Query("SELECT t FROM Event t WHERE t.expiryTime < CURRENT_TIMESTAMP")
List<Event> findTimeoutEvents();
this CURRENT_TIMESTAMP is database's time, but expiryTime is not. It means that I must make their time is same.sometimes, the application and database are not in the same machine, I can not make sure their time is same.
Can I set "expiryTime" generated by database? How can I pass the parameter "timeout" to database.
the database maybe postgresql or mysql.
thank you very much.
First of all I am not sure your code works, since instance of java.util.Date (if expiry time is java.util.Date object) can not be compared to int 0.
As for generating an expiryTime, yes, you obviously can. Check out how do triggers work.
Also I would like to add, that if you use spring-boot-starter-data-jpa, you may annotate creationTime field with #CreationTimestamp annotation. But I would personally set default value to CURRENT_TIMESTAMP() on db side.

Spring Data UpdateTimestamp for specific column

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

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?

Spring, Hibernate : Retrieve Objects based upon date, if not found, nearest date

I am working on a Spring-MVC project in which I am working on creating a backup of GroupNote object every night. Before creating a backup, I am checking if boolean flag indicating that the object was modified is true. If yes, then only a backup is created, and date is already set when the object was created.
Now, when a duplicate GroupNote object is created, it has the latest date set.
This way, the user can select a particular date and then retrieve the data-set for that day.
Now, the problem is :
If the object was not modified on Day 3,4,5, but directly on 6th, and then on 7th, once that is done, what if the User tries to retrieve the object from Day-3, it was not modified, but it was on 6th, so how I can implement this logic, where I have to retrieve the next best one.
The other problem I am facing is, GroupNotes have many-to-one mapping with GroupSection. How can I retrieve all GroupNote objects for a given GroupSection for a given date, which wont give all entities which are after Day-3, but only the 6th one.
Here is how every-night I am creating duplicates :
#Override
public void retrieveModifiedNotes() {
List<GroupNotes> groupNotesList = this.groupNotesDAO.listModifiedNotesForYesterday();
for(GroupNotes yesterdayNotes : groupNotesList){
yesterdayNotes.setLatestNote(false);
this.groupNotesDAO.editGroupNote(yesterdayNotes,yesterdayNotes.getOwnedSectionId());
GroupNotes newDayNotes = new GroupNotes();
BeanUtils.copyProperties(yesterdayNotes, newDayNotes);
newDayNotes.setLatestNote(true);
newDayNotes.setMnoticesid(0);
newDayNotes.setGroupNoteHistorySet(yesterdayNotes.getGroupNoteHistorySet());
newDayNotes.setNoteActivitySet(yesterdayNotes.getNoteActivitySet());
newDayNotes.setNoteSelectionSet(yesterdayNotes.getNoteSelectionSet());
newDayNotes.setUnreadNotesSet(null);
newDayNotes.setPrimaryNote(yesterdayNotes);
this.groupNotesDAO.addGroupNote(newDayNotes,yesterdayNotes.getOwnedSectionId());
}
}
The DAO me to get modified notes :
#Override
public List<GroupNotes> listModifiedNotesForYesterday() {
Session session = this.sessionFactory.getCurrentSession();
Calendar cal = Calendar.getInstance(); // Todays date
Query query = session.createQuery("from GroupNotes as gs where gs.noteModified=true and gs.latestNote=true and " +
"gs.noteSavedDate<:loadDate");
query.setParameter("loadDate", cal.gethodtTime());
return query.list();
}
Model GroupNotes :
#Entity
#Table(name = "groupnotes")
public class GroupNotes implements Serializable {
#Column(name = "note_modified", columnDefinition = "boolean default false")
private boolean noteModified;
#Column(name = "latest_note", columnDefinition = "boolean default true")
private boolean latestNote;
#Column(name = "note_save_date", columnDefinition = "date")
private Date noteSavedDate;
#ManyToOne
#JoinColumn(name = "msectionid")
#JsonIgnore
private GroupSection ownednotes;
}
Problematic DAO method :
#Override
#Transactional(readOnly = true)
public List<GroupNotes> listGroupNotesBySectionAndDate(int sectionId, Date dateToLoad) {
Session session = this.sessionFactory.getCurrentSession();
org.hibernate.Query query = session.createQuery("From GroupNotes as n where n.ownednotes.msectionid=:msectionid and ((n.noteDisabled=false and n.noteInActive=false "
+ "and n.privateNoteUser is null and n.noteSavedDate>:dateToLoad)) order by n.noteSavedDate asc");
query.setParameter("msectionid", sectionId);
query.setParameter("dateToLoad", dateToLoad);
return query.list();
}
As you can see in the above method, I have no way of limiting the query to only the object found on a specific date, I have a comparator operator, which will give data for any date after/before the one specified.
I hope the problem is clear, if there are any doubts, kindly let me know. Thank you. :-)
As far as I understand your problem, your dao method is perfectly fine, but you want to get only the best result (with closest date) not whole list, right?
In that case you should jpa hibernate's paging mechanism to limit the query result to only one row.
This question should be a help.

Categories