Search code examples
javaspringhibernatejpa

Fix naming convention to remove underscores from Hibernate JPA


Why is the JPA/Hibernate prepending the child class name to the name of the parent class user_profile_id? Alternately why is it taking loginCount and converting to snake case login_count?

We have class UserProfile that extends BaseModel with an id column defined. Without any changes, it was prepending the class name to the id. (user_profile_id). What I wanted was just id in the table. When I changed the naming strategy it seem to fix that issue, but present another.

With making the following change:

        <property name="hibernate.implicit_naming_strategy" value="org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl"/>
        <property name="hibernate.physical_naming_strategy" value="org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl"/>

It used ID, but it took regular UserProfile of loginCount as login_count

To test it I did customize the naming on both and I see LoginCount, but I get the following error.

Caused by: java.sql.SQLException: Field 'login_count' doesn't have a default value
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:121)

Which is MySQL's way of saying the column is not defined? Even though the JPA SQL looks correct.

Hibernate: insert into user_profile (Created, Updated, UserCreated, UserUpdated, Account_notes, Admin, CancellationTxId, City, Country, CustomerNumber, Email, ExternalId, FirstName, Group_Id, Image_Id, LastLogin, LastName, LastSyncedToCrm, **LoginCount**, Password, PayerEmail, Phone, Prefs_Id, Role_name, Role, State, Status, Street, Subscription_Id, SyncedToCrm, Timezone, Zip, Id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-framework-bom</artifactId>
        <version>5.3.37</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-bom</artifactId>
        <version>5.8.13</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-bom</artifactId>
        <version>2021.2.18</version>
        <scope>import</scope>
        <type>pom</type>
    </dependency>
</dependencies>

    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.4.0</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate.javax.persistence</groupId>
        <artifactId>hibernate-jpa-2.1-api</artifactId>
        <version>1.0.2.Final</version>
    </dependency>

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>5.6.15.Final</version>
    </dependency>

persistence

    <property name="hibernate.implicit_naming_strategy" value="com.smartsteps.hibernate.ImplicitNamingStrategy"/>
    <property name="hibernate.physical_naming_strategy" value="com.smartsteps.hibernate.PhysicalNamingStrategy"/>

BaseModel

@MappedSuperclass
@JsonIgnoreProperties({ "created", "updated", "userUpdated", "formattedCreated"})
public abstract class BaseModel {
    @Id
    protected String id = UUID.randomUUID().toString();

UserProfile

@Entity
@Table(name="user_profile")
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id",scope=UserProfile.class)
@JsonIgnoreProperties({ "created", "userCreated", "updated", "userUpdated", "formattedCreated", "syncedToCrm", "loginCount", "subscriptionCustomerId", "lastSyncedToCrm", "payerEmail", "cancellationTxId", "lastLogin", "password"})
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class UserProfile extends BaseModel {\
    @Column(name="logincount")
    private int loginCount = 0;

ImplicitNamingStrategy

public class ImplicitNamingStrategy extends ImplicitNamingStrategyJpaCompliantImpl {
    @Override
    public Identifier determineBasicColumnName(ImplicitBasicColumnNameSource source) {
        String originalName = source.getAttributePath().getFullPath();
        String newName = getNewName(originalName);
        System.out.println("Implicit: " + originalName + " -> " + newName);
        return new Identifier(newName, false);
    }
    private static String getNewName(String originalName) {
        return StringUtils.left(originalName, 1).toUpperCase() + originalName.substring(1, originalName.length());
    }
}

PhysicalNamingStrategy

public class PhysicalNamingStrategy extends PhysicalNamingStrategyStandardImpl {
    @Override
    public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
        String originalName = name.getText();
        String newName = StringUtils.left(originalName, 1).toUpperCase() + originalName.substring(1, originalName.length());
        System.out.println("Physical " + originalName + " -> " + newName);
        return new Identifier(newName, false);
    }
}

UPDATE:

I've tried many combinations of Naming Strategies, @Column name casing, and even renaming the field to logincount It still gives me the same error in Amazon Aurora MySQL in AWS. Both with MySQL version 5 and 8. And both older and newer code (pasted above). Any help would be much appreciated.


Solution

  • @Tim Biegelesien was correct to specify the answer java.sql.SQLException: Field 'supplier_id' doesn't have a default value, however it didn't explain why I was seeing the issue. I didn't think the database could change but hibernate.hbm2ddl.auto was set to update, which is how the login_count got added as non-nullable integer since it was declared as a int and had no @Column annotation.

    The actual answer to avoid the Spring 3->5 snake case can be found here

    Set the property hibernate.naming.physical-strategy to org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl