Hibernate PhysicalNamingStrategy Explained: How to Control Database Identifier Names
Photo: Database and server code
Problem: Your @Table Annotation Gets Changed
You defined @Table(name = "UserInfo") in your entity class, but the database table becomes user_info? Or you wanted snake_case naming but got camelCase instead?
The key to understanding this is Hibernate’s PhysicalNamingStrategy.
Core Concept: Two Naming Strategies
Hibernate has two layers of naming strategies. Many developers confuse them:
┌─────────────────────────────────────────────────────────────────┐│ Entity Class ││ @Entity, @Table, @Column annotations │└──────────────────────────┬──────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────────┐│ ImplicitNamingStrategy ││ Determines logical names when no explicit name given ││ (e.g., entity class name → logical table name) │└──────────────────────────┬──────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────────┐│ Explicit Annotation Priority ││ @Table(name="xxx") overrides implicit strategy result │└──────────────────────────┬──────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────────┐│ PhysicalNamingStrategy ││ Final physical name transformation (processes all names) ││ (e.g., add prefix, case conversion) │└──────────────────────────┬──────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────────┐│ Database ││ Actual SQL execution │└─────────────────────────────────────────────────────────────────┘Key difference:
- ImplicitNamingStrategy: Only applies when no explicit name is specified, determines “logical names”
- PhysicalNamingStrategy: Applies final transformation to all names, including names specified in
@Table(name="xxx")
PhysicalNamingStrategy Interface
public interface PhysicalNamingStrategy { // Convert catalog name Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment context);
// Convert schema name Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment context);
// Convert table name Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context);
// Convert sequence name Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment context);
// Convert column name Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context);}Each method receives two parameters:
Identifier name: The incoming logical name (may come from implicit strategy or explicit annotation)JdbcEnvironment context: Database environment context, can get database dialect info
Practical Example: Snake Case Naming Strategy
The most common scenario is converting Java camelCase to database snake_case.
public class SnakeCaseNamingStrategy implements PhysicalNamingStrategy {
@Override public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment context) { return name; // catalog name usually stays as-is }
@Override public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment context) { return name; // schema name usually stays as-is }
@Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { if (name == null) return null; return Identifier.toIdentifier(toSnakeCase(name.getText())); }
@Override public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment context) { if (name == null) return null; return Identifier.toIdentifier(toSnakeCase(name.getText())); }
@Override public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) { if (name == null) return null; return Identifier.toIdentifier(toSnakeCase(name.getText())); }
private String toSnakeCase(String camelCase) { if (camelCase == null || camelCase.isEmpty()) { return camelCase; } // camelCase → snake_case return camelCase.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase(); }}Conversion effect:
UserInfo → user_infocreatedAt → created_atuserId → user_idisActive → is_activeSpring Boot Configuration
Option 1: application.yml Configuration
spring: jpa: hibernate: naming: physical-strategy: com.yourpackage.SnakeCaseNamingStrategyOption 2: Spring Boot Auto-configuration
@Configurationpublic class NamingStrategyConfig {
@Bean public PhysicalNamingStrategy physicalNamingStrategy() { return new SnakeCaseNamingStrategy(); }}Advanced Example: Adding Table Prefix
Sometimes you need to add a unified prefix to all tables (e.g., by module):
public class ModulePrefixNamingStrategy implements PhysicalNamingStrategy {
private static final String TABLE_PREFIX = "app_";
@Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { if (name == null) return null; // Skip if already has prefix if (name.getText().startsWith(TABLE_PREFIX)) { return name; } return Identifier.toIdentifier(TABLE_PREFIX + name.getText()); }
@Override public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) { return name; // Don't process column names }
// Other methods use default implementation...}Conversion effect:
User → app_userOrder → app_orderCommon Issues
Issue 1: @Table annotation name gets changed
Symptom: @Table(name = "T_USER") generates table name t_user
Reason: PhysicalNamingStrategy processes all names, including explicitly specified ones
Solution: Skip explicitly specified names in your strategy
@Overridepublic Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { if (name == null) return null;
// If name is all uppercase or has special markers, keep as-is if (name.getText().equals(name.getText().toUpperCase())) { return name; }
return Identifier.toIdentifier(toSnakeCase(name.getText()));}Issue 2: MySQL 8.0 Case Sensitivity
MySQL 8.0 on Linux systems is case-sensitive by default, which can cause “table not found” errors.
Solution: Use lowercase naming consistently
public class LowerCaseNamingStrategy implements PhysicalNamingStrategy {
@Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { if (name == null) return null; return Identifier.toIdentifier(name.getText().toLowerCase()); }
@Override public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) { if (name == null) return null; return Identifier.toIdentifier(name.getText().toLowerCase()); }
// Other methods similar...}Working with ImplicitNamingStrategy
Understanding how both strategies work together is important:
@Entitypublic class OrderItem { @Id private Long id;
private Date createTime; // No @Column annotation}Processing flow:
1. ImplicitNamingStrategy OrderItem → OrderItem (default: class name is table name) createTime → createTime (default: property name is column name)
2. PhysicalNamingStrategy (assuming SnakeCaseNamingStrategy) OrderItem → order_item createTime → create_time
Final SQL: CREATE TABLE order_item (id BIGINT, create_time TIMESTAMP)Spring Boot Default Strategy
Spring Boot 2.x/3.x default configuration:
spring.jpa.hibernate.naming.implicit-strategy= org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
spring.jpa.hibernate.naming.physical-strategy= org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategySpringPhysicalNamingStrategy behavior:
- Replaces dots with underscores
- Converts camelCase to snake_case
- All letters lowercase
So the problem at the beginning: @Table(name = "UserInfo") became user_info — that’s the default strategy’s effect.
Best Practice Recommendations
- Decide strategy early in the project: Later changes affect database migration scripts
- Document your strategy: Let team members understand naming rules
- Consider database compatibility: Different databases handle case and special characters differently
- Be careful overriding explicit annotations:
@Table(name="xxx")should be the final answer - Unit test validation: Verify naming through schema generation
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments