I am currently developing a web-application with spring, hibernate and mysql.
The problem was to add a created_at and updated_at timestamp to all the objects.
I have the following abstract #MappedSuperClass:
#MappedSuperclass
public abstract class AbstractTimestampEntity {
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_at", nullable = false)
private Date created;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "updated_at", nullable = false, updatable = false)
private Date updated;
...
}
And in Mysql defined CURRENT_TIMESTAMP for created_at and CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP for updated_at.
When I manually test it, it works fine after inserting an object and updating it the times in mysql are correct. However I wanted to write automatic tests and it does not retrieve the values from the DB and refresh the object.
// before update
sessionFactory.getCurrentSession().refresh(tester1);
Date before = tester1.getUpdated();
// update
userResource.updateUser(new UserDTO(tester1), tester1.getUserId());
// after update
sessionFactory.getCurrentSession().refresh(tester1);
Date after = ((User) sessionFactory.getCurrentSession().get(User.class, tester1.getUserId())).getUpdated();
// verify
assertTrue(before.before(after));
It will result in a nullpointer exception because the date of the tester1 object is never set. (Tester1 Object extends AbstractTimestampEntity)
I manually retrieved the date with a sql-query:
SQLQuery sqlQuery = sessionFactory.getCurrentSession().createSQLQuery("SELECT updated_at FROM smarterTestDB.User WHERE userId="+tester1.getUserId());
sqlQuery.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
List list = sqlQuery.list();
String test = list.get(0).toString();
And it gave me the set value from mysql:
Date: {updated_at=2017-01-06 12:25:59.0}
Why does it not write it in the object? And it does not give any error about TypeMissmatch or similar. Is the #Temporal(TemporalType.TIMESTAMP) not converting from sql.Timestamp to util.Date?
Kind regards
Update:
I got the error by passing the object to the userResource I construct a the UserDAO object the user would actually send. This one gets updated but not the original User-Object as long as it is not newly retrieved from the database. Simple pass by reference/value error.
Modifications made by the database (like CURRENT_TIMESTAMP or any kind of trigger) aren't recognized by the entity you are trying to save. All modifications have to be made by the java application. Doing so the correct values are in the database and in your entity without doing a "refresh". I've used the following code.
#Column(nullable = false)
#Temporal(TemporalType.TIMESTAMP)
#Version
private Date updated_at;
The database column is a simple "TIMESTAMP" without any further functionality.
So the database hasn't any logic, all is done by the application in one place. The application is responsible for all modifications. Maintaining your code will be easier this way.
Related
Let's suppose I have a primary key ts of type Timestamp. That field is generated automatically on insert into my PostgreSQL table. I want to tell JPA/Hibernate not to send or populate that field but just ignore it when the entity is being persisted.
...
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "mygenerator")
#TableGenerator(name = "mygenerator", table = "ms.time_series")
#Column(name = "ts", insertable = false, updatable = false, columnDefinition= "TIMESTAMP WITHOUT TIME ZONE")
private Timestamp ts;
...
I cannot get it to work. It looks like the #Id and #Columns(insertable = false, updatable = false) don't work together, that setting is being ignored and the framework is trying to modify or send the value in anyways.
I am using the latest Spring 5 and Spring Data 2.1.2 with Hibernate. Database is PostgreSQL.
Any idea what can be wrong? How can I set up the annotations properly for this to work?
I have one JPA entity that has a created date and a modified date column. On creation/persist, both the created date and modified date are generated by the default value given in the database, which is a timestamp. The creation works, however, when I try to do an update/merge, I cannot figure out how to change the modified date by using the default value in the database. Any advice? This is the current setup:
....
#Temporal(TemporalType.DATE)
#Column(name="CREATED_DATE", insertable = false, updatable = false)
private Date createdDate;
#Temporal(TemporalType.DATE)
#Column(name="MODIFIED_DATE", insertable = false)
private Date modifiedDate;
....
-
public Database changeDate(Database oldValues)
Database newvalues = new Database();
....
newValues.setCreatedDate(oldValues.getCreatedDate);
//newValues.setModifiedDate(); <-- (Should use the default value in the database)
....
em.merge(newValues); <-- (EntityManager)
em.getTransaction().commit();
** Just in case I didn't make myself clear, I just don't know how to make it update with the default value set in the database.
you want to set Null value to date. please add nullable property in your date annotation. it will work.
add it in your annotation nullable = true
#Temporal(TemporalType.DATE)
#Column(name="CREATED_DATE",nullable = true, insertable = false, updatable = false)
private Date createdDate;
#Temporal(TemporalType.DATE)
#Column(name="MODIFIED_DATE", nullable = true, insertable = false)
private Date modifiedDate;
There is an annotation you could use javax.persistence.Version
#Column(name="CREATED_DATE", updatable = false)
private java.sql.Timestamp createdDate = new java.sql.Timestamp(System.currentTimeMillis());
#Version
#Column(name="MODIFIED_DATE")
private java.sql.Timestamp modifiedDate;
Both fields need only have a getter, no setter. The JPA will compare and update the timestamp value on it's own. Otherwise if you rely on the database to generate it for you, you might as well try #GeneratedValue(strategy = GenerationType.IDENTITY) or AUTO, but I'm pretty sure that won't work in Hibernate. Maybe it will work in other JPA providers. In Hibernate, the AUTO is value always falls-back to SEQUENCE, so no chance of that strategy working.
If I understand the question correctly, you want to make sure that the MODIFIED_DATE column always gets set to the CURRENT_TIMESTAMP. Then, when a Database entity gets updated and it's other fields modified, the MODIFIED_DATE column will be set to the CURRENT_TIMESTAMP.
I assume that your DB table looks something like this:
CREATE TABLE Database (
ID int NOT NULL,
CREATED_DATE TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
MODIFIED_DATE TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
What you want to do is add a #PreUpdate method to your Database entity class and have it set the modifiedDate attribute to a new Date() each time. Then, whenever an update is made to the entity, the MODIFIED_DATE column will be set to a new date!
Here is an example:
#PreUpdate
protected void onUpdate() {
modifiedDate = new Date();
}
Try a simple example and see how it works:
em.getTransaction().begin();
Database db = new Database(1, "First Value");
em.persist(db);
em.getTransaction().commit();
em.getTransaction().begin();
Database db2 = em.find(Database.class, 1);
db2.setValue("New Value!");
em.getTransaction().commit();
I am using Hibernate to interface with SQL Server 2016/Azure SQL Server currently, and have been having a great time with it so far. In my database, I have implemented system versioned temporal tables. I want to map (preferably lazily) two more variables by annotation only to my Hibernate entity that represent the original ValidFrom and UpdatedBy fields from the temporal history of the appropriate table.
For example, I have a class and table for Accounts. The Account [minus nonrelated columns, constraints, etc] table is as follows:
CREATE TABLE [dbo].[Account] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[UpdatedBy] INT NOT NULL,
[ValidFrom] DATETIME2 (7) GENERATED ALWAYS AS ROW START DEFAULT (sysutcdatetime()) NOT NULL,
[ValidTo] DATETIME2 (7) GENERATED ALWAYS AS ROW END DEFAULT (CONVERT([datetime2],'9999-12-31 23:59:59.9999999')) NOT NULL,
CONSTRAINT [FK_Account.UpdatedById_Account.Id] FOREIGN KEY ([UpdatedBy]) REFERENCES [dbo].[Account] ([Id]),
PRIMARY KEY CLUSTERED ([Id] ASC),
PERIOD FOR SYSTEM_TIME ([ValidFrom], [ValidTo])
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE=[dbo].[AccountHistory], DATA_CONSISTENCY_CHECK=ON));
The SQL statement to get the data that I want looks like this (I imagine that I would select only UpdatedBy or ValidFrom per annotation, but they are together now to be concise):
SELECT UpdatedBy, ValidFrom FROM dbo.Account
FOR SYSTEM_TIME ALL
WHERE ValidFrom IN
(
SELECT MIN(ValidFrom) OVER (Partition BY Id) AS ValidFrom
FROM dbo.Account
FOR SYSTEM_TIME ALL
WHERE ID = $(passedInIdOfThisEntity)
)
Finally, my Hibernate entity/pojo looks something like this (again, redacting irrelevant variables):
#Entity
#Table(name = "Account")
public class Account implements Serializable {
#Id
#Column(name = "Id", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "UpdatedBy")
private Account updatedBy;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "ValidFrom", nullable = false, length = 27, insertable = false, updatable = false)
private Date validFrom;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "ValidTo", nullable = false, length = 27, insertable = false, updatable = false)
private Date validTo;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "updatedBy")
private Set<Account> accountsUpdated;
// This is a stub of what I'm hoping you can help me add
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "ValidFrom", fetch = FetchType.LAZY, insertable = false, updatable = false, somesqlselect = SQL_STATEMENT_FROM_ABOVE)
private Date createdOn;
#Column(name = "UpdatedBy", fetch = FetchType.LAZY, insertable = false, updatable = false, somesqlselect = SQL_STATEMENT_FROM_ABOVE)
private Account createdBy
// ... getters and setters below
}
I have been using Hibernate to a great extent, but have had trouble finding information on this, though I have found and used examples of implementing native queries for retrieving entities instead of using criteria queries. If you can help me solve this riddle to allow me to continue using criteria queries to retrieve data and populate these fields through annotation on demand, I would greatly appreciate it.
The temporal table constructs that you've described isn't something that I am aware that JPA or even Hibernate support natively. These are likely new features of the ANSI SQL standard which haven't made their way into proper support.
That said, that doesn't mean you cannot use frameworks like Hibernate to accomplish the task. As indicated in the comments, you can specify a named query and execute that in order to get the attributes you desire.
From a JPA 2.1 perspective, you use #SqlResultSetMapping and #ConstructorResult.
#SqlResultSetMapping(
name = "Account.getWithTemporalAttributes",
classes = {
#ConstructorResult(
targetClass = com.company.domain.AccountTemporalDetails.class,
columns = {
#ColumnResult(name = "col1"),
#ColumnResult(name = "col2")
})
})
To use this, you would do the following:
Query query = entityManager.createNativeQuery(
"SELECT a.col1 as col1, a.col2 as col2 FROM Account a",
"Account.getWithTemporalAttributes");
List<AccountTemporalDetails> results = query.getResultList();
That should allow you to use Native SQL queries, mapping them to a POJO which you can easily then use within your application without having to write boilerplate. The #ConstructorResult annotation is meant to mimic the JPQL SELECT NEW syntax. So you would just need to make sure that AccountTemporalDetails had a constructor that takes those arguments with the right types.
Can anyone suggest me the solution of fetching data on condition of current date which i am passing in dao layer method of type system current date.
for e.g I have to fetch data from table "X" which have a column "startdate" of Date type(Mysql) on the basis if that date is greater with the current date passed to the query in java.
I have tried with java.util.Date but not working and also my requirement is not to used database specific function like curr() or Now().
I found a relative post but none helped.Is there no alternative apart from using JodaTime.
Thanks.
In your Entity class mark 'startdate' field with
#Column(name = "startdate")
#Temporal(TemporalType.DATE)
private Date startdate;
And create query like this one:
#NamedQuery(name = "Entity.findAfterDate", query = "SELECT e FROM Entity e WHERE e.startdate >= :dateAfter")
In your EntityDAOImpl
public List<Entity> getEntitiesAfterDate(Date date) {
Query query = openSession().getNamedQuery("Entity.findAfterDate");
query.setParameter("dateAfter", date);
return query.list();
}
I have a persistent object with two Date fields, like this
#Temporal(TemporalType.TIMESTAMP)
private Date generated;
#Temporal(TemporalType.TIMESTAMP)
private Date expirationTime;
On object construction the expirationTime is initialized based on the generated field
this.expirationTime = new Date(generated.getTime() + ttlMillis);
Now I'm trying to delete all expired objects from the database with a JPA query
Query q = em.createQuery("delete from MyObject t where CURRENT_TIMESTAMP > t.expirationTime");
q.executeUpdate();
But running a simple test
MyObject o = new MyObject(somePastDate, someTTL);
em.persist(o);
... create the query above
q.executeUpate();
shows no row is being deleted. Any clue on what I'm doing wrong? I'm running tests on HSQLDb and using Hibernate as my JPA provider.
Edit: solved, but I'm not sure why this version should work differently.
Query q = em.createQuery("delete from MyObject t where :now > t.expirationTime");
q.setParameter("now", new Date());
q.executeUpdate();
What's the use of CURRENT_TIMESTAMP if comparisons with other fields fail?
You did not set any parameter, and the query has to get a named parameter:
Query q = em.createQuery("delete from MyObject t where CURRENT_TIMESTAMP > :expirationTime");
q.setParameter("expirationTime" , yourObject.getExpirationTime());//or your getter
q.executeUpdate();
I add a link to enforce the topic here.
Check if it works :-)