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();
Related
I have a JPA entity, which contains two column, the createdTime and modifiedTime. I'd like to use MySQL's DEFAULT CURRENT_TIMESTAMP feature to generate the proper time value for this two column, so i write my entity class like:
#Entity
public class Blog {
// ...
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_time", updatable = false, insertable = false, nullable = false,
columnDefinition = "datetime default CURRENT_TIMESTAMP")
private Date createdTime;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "modified_time", updatable = false, insertable = false, nullable = false,
columnDefinition = "datetime default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
private Date modifiedTime;
}
Then i do some experiment on my application, when i check the database, the value of this two column in the database is updated properly.
However, when i check the java entity return by BlogRepository::save, the value of this two column is not the newest value. For example, when i insert a new Blog entity, i get null for this two properties on the BlogRepository::save return value.
I've read some post like https://stackoverflow.com/a/12237110/8510613, understand that if i mark the column as updatable = false, this situation will happen. So how should i fix this? Simply remove the updatable = false/insertable = false seems have no help, but will trigger the column's not null constraint violation. But if i remove the not null constraint on this two column, the value of this two column are just simply null.
You should use #Generated annotation in this case.
2.3.18. Generated properties
Generated properties are properties that have their values generated by the database. Typically, Hibernate applications needed to refresh objects that contain any properties for which the database was generating values. Marking properties as generated, however, lets the application delegate this responsibility to Hibernate. When Hibernate issues an SQL INSERT or UPDATE for an entity that has defined generated properties, it immediately issues a select to retrieve the generated values.
So, you should correct your mapping something like this:
#Temporal(TemporalType.TIMESTAMP)
#Generated(value = GenerationTime.INSERT)
#Column(name = "created_time", updatable = false, insertable = false, nullable = false,
columnDefinition = "datetime default CURRENT_TIMESTAMP")
private Date createdTime;
#Temporal(TemporalType.TIMESTAMP)
#Generated(value = GenerationTime.ALWAYS)
#Column(name = "modified_time", updatable = false, insertable = false, nullable = false,
columnDefinition = "datetime default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
private Date modifiedTime;
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 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.
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.
I have a boolean property in my entity. Here's my annotations for it:
#Column(name = "IS_ACTIVE", nullable = false, columnDefinition="BIT DEFAULT 1", length = 1)
public Boolean getActive() {
return isActive;
}
But columnDefinition="BIT DEFAULT 1" doen't work perfectly. Here's SQL code I get as result for generated table:
IS_ACTIVE BIT(1) NOT NULL,
What am I doing wrong?
And so when I try to save an instance of this class to the database I get the exception:
`com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'IS_ACTIVE' cannot be null`
If I remove nullable = false property:
#Column(name = "IS_ACTIVE", columnDefinition="BIT DEFAULT 1", length = 1)
public Boolean getActive() {
return isActive;
}
so I can save a created object in this case. But it's still the default value is not set and I get NULL in the value of this field in database.
Any ideas please? I use MySQL Server 5.1 if it's important. I would be very grateful for any help. Thanks in advance!
Try using BOOLEAN data type, define your #Column annotation like that:
#Column(name = "IS_ACTIVE", columnDefinition = "boolean default true", nullable = false)
private Boolean active = true;
reference to the correct answer in this link How to set default value in Hibernate
If you want a real database default value, use columnDefinition - #Column(name = “myColumn”, nullable = false, columnDefinition = “int default 100"). Notice that the string in columnDefinition is database dependent. Also if you choose this option, you have to use dynamic-insert, so Hibernate doesn't include columns with null values on insert. Otherwise talking about default is irrelevant.
The key of the solution is dynamic-insert annotation. You can add it to your entity class as this example: #org.hibernate.annotations.Entity(dynamicInsert = true)