Hibernate model class config doesn't reflect in CREATE TABLE query with "hibernate.hbm2ddl.auto = update"

Viewed 534

I'm working on a Spring(4.x)-Hibernate(5.2.x) web-application project.

Model class

@Entity
@Table( name = "users" )
public class User {

    @Id
    @Column( name="ID" )
    @GeneratedValue( strategy = GenerationType.IDENTITY )
    private Long id;

    @Temporal( TemporalType.TIMESTAMP )
    @Column( name="CREATED_AT", insertable=false, nullable=false )
    @Generated( GenerationTime.INSERT )
    private Date createdAt;

    @Temporal( TemporalType.TIMESTAMP )
    @Column( name="UPDATED_AT", insertable=false, updatable=false, nullable=false )
    @Generated( GenerationTime.ALWAYS )
    private Date updatedAt;

    public enum Role {
        ADMIN,
        USER,
        GUEST
    }
    @NotNull
    @Column( name="ROLE" )
    @Enumerated( EnumType.STRING )
    private Role role;

    //getters & setters
}

Hibernate properties

hibernate.dialect = org.hibernate.dialect.MySQLDialect
hibernate.show_sql = true
hibernate.format_sql = true
hibernate.hbm2ddl.auto = update

Auto generated query

create table users (
    ID bigint not null auto_increment,
    CREATED_AT datetime not null,
    UPDATED_AT datetime not null,
    ROLE varchar(255) not null,
    primary key (id)
)

I expect a query which has

  • CREATED_AT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP and
  • UPDATED_AT DATETIME NOT NULL ON UPDATE CURRENT_TIMESTAMP and
  • ROLE ENUM('ADMIN','USER','GUEST') VARCHAR(255) NOT NULL

Is this the way it works or is it my mistake in configuration? Done some search, but no clear solution found, except these,

  • @Column(name="CREATED_AT", nullable = false,columnDefinition="TIMESTAMP default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP")
  • @ColumnDefault( "NOW()" )

But these definitions depends on the underlying database, that's what I think. Need a configuration which is more java/hibernate wise, like I used, but not working!

3 Answers

With current version of jpa (hibernate provider),

columnDefinition and @ColumnDefault

Identifies the DEFAULT value to apply to the associated column via DDL (Data definition language).

So it seem impossible to generate table which have column with default value base on java like you expected

CREATED_AT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAM

It's quite inconvenience if we want to switch to other db server(oracle ->mysql) because of DDL syntax might be different. But jpa might have this feature in the future (maybe)

The workaround's for your issue, you must use default value with DDL.

Another approach, developers often use to make sure your column always initialized before inserting. But it won't generate default value for column in database.

@PrePersist
void preInsert() {
   if (this.createdTime == null)
       this.createdTime = new Date();
}

and it might be failed if other developer use sql native to query.

Please correct if you found other solution and hope that help!

There some ways to solve your problem.

  1. Use @PrePersist
  2. Use @CreationTimestamp and @UpdateTimestamp Detail here
  3. Combine DDL with above solutions.

P/s: you should use database migration tools like Flyway, Liquibase, etc... these tools will help you a lot.

DDL is generally database specific. To have different DDL, you might want to look into Flyway, Liquibase, etc.

An alternative approach in java/hibernate is:

@Generated has been retrofitted to use the @ValueGenerationType meta-annotation. The @ValueGenerationType meta-annotation is used when declaring the custom annotation used to mark the entity properties that need a specific generation strategy. The actual generation logic must be added to the class that implements the AnnotationValueGeneration interface.

If the timestamp value needs to be generated in-memory, the following mapping must be used instead:

Entity:

@Entity(name = "Event")
public static class Event {

@Id
@GeneratedValue
private Long id;

@Column(name = "`timestamp`")
@FunctionCreationTimestamp
private Date timestamp;

//Constructors, getters, and setters are omitted for brevity
}

Timestamp Genearation class:

@ValueGenerationType(generatedBy = FunctionCreationValueGeneration.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface FunctionCreationTimestamp {}

public static class FunctionCreationValueGeneration
    implements AnnotationValueGeneration<FunctionCreationTimestamp> {

@Override
public void initialize(FunctionCreationTimestamp annotation, Class<?> propertyType) {
}

/**
 * Generate value on INSERT
 * @return when to generate the value
 */
public GenerationTiming getGenerationTiming() {
    return GenerationTiming.INSERT;
}

/**
 * Returns the in-memory generated value
 * @return {@code true}
 */
public ValueGenerator<?> getValueGenerator() {
    return (session, owner) -> new Date( );
}

/**
 * Returns false because the value is generated by the database.
 * @return false
 */
public boolean referenceColumnInSql() {
    return false;
}

/**
 * Returns null because the value is generated in-memory.
 * @return null
 */
public String getDatabaseGeneratedReferencedColumnValue() {
    return null;
}
}

When persisting an Event entity, Hibernate generates the following SQL statement:

INSERT INTO Event ("timestamp", id)
VALUES ('Tue Mar 01 10:58:18 EET 2016', 1)

As you can see, the new Date() object value was used for assigning the timestamp column value.

Reference: http://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#mapping-database-generated-value-example

PS: (Just a thought) You can even have database-specific implementations using if-else clause.

Related