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?
CodePudding user response:
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.)
CodePudding user response:
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.