Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly.
Hypersistence Optimizer is that tool!
Introduction
In this article, you are going to learn how to map cameCase entity properties (e.g. phoneNumber) to snake_case column names (e.g. phone_number) using a Hibernate naming strategy.
While you could achieve the same goal with the name attribute of JPA @Column annotation, it’s much more convenient to use a custom Hibernate strategy to apply this naming convention consistently.
Domain Model
Let’s assume we are using the following BookAuthor and PaperBackBook entities in our application:
The JPA entities are mapped like this:
@Entity(name = "BookAuthor")
public class BookAuthor {
@Id
private Long id;
private String firstName;
private String lastName;
//Getters and setters omitted for brevity
}
@Entity(name = "PaperBackBook")
public class PaperBackBook {
@Id
@GeneratedValue(
strategy = GenerationType.SEQUENCE
)
private Long id;
@NaturalId
private String ISBN;
private String title;
private LocalDate publishedOn;
@ManyToOne(fetch = FetchType.LAZY)
private BookAuthor publishedBy;
//Getters and setters omitted for brevity
}
Database schema associated with the default JPA mapping
If we generate the database schema using the hbm2dll tool, the following DDL statements are going to be executed:
CREATE SEQUENCE hibernate_sequence
START WITH 1 INCREMENT BY 1
CREATE TABLE BookAuthor (
id BIGINT NOT NULL,
firstName VARCHAR(255),
lastName VARCHAR(255),
PRIMARY KEY (id)
)
CREATE TABLE PaperBackBook (
id BIGINT NOT NULL,
ISBN VARCHAR(255),
publishedOn DATE,
title VARCHAR(255),
publishedBy_id BIGINT,
PRIMARY KEY (id)
)
By default, Hibernate assumes the entity class name as well as the property names when mapping the JPA entities to the underlying database tables. However, while the cameCase naming convention is fine for Java code, we want to use the snake_case naming convention for the database schema. Luckily, Hibernate is very extensible so we can achieve this goal using a custom naming strategy.
Naming strategy
Since Hibernate 5, the database object naming strategy is represented by the PhysicalNamingStrategy interface which we can customize to automatically transform database identifiers from cameCase to snake_case.
public class CamelCaseToSnakeCaseNamingStrategy
extends PhysicalNamingStrategyStandardImpl {
public static final CamelCaseToSnakeCaseNamingStrategy INSTANCE =
new CamelCaseToSnakeCaseNamingStrategy();
public static final String CAMEL_CASE_REGEX = "([a-z]+)([A-Z]+)";
public static final String SNAKE_CASE_PATTERN = "$1\\_$2";
@Override
public Identifier toPhysicalCatalogName(
Identifier name,
JdbcEnvironment context) {
return formatIdentifier(
super.toPhysicalCatalogName(name, context)
);
}
@Override
public Identifier toPhysicalSchemaName(
Identifier name,
JdbcEnvironment context) {
return formatIdentifier(
super.toPhysicalSchemaName(name, context)
);
}
@Override
public Identifier toPhysicalTableName(
Identifier name,
JdbcEnvironment context) {
return formatIdentifier(
super.toPhysicalTableName(name, context)
);
}
@Override
public Identifier toPhysicalSequenceName(
Identifier name,
JdbcEnvironment context) {
return formatIdentifier(
super.toPhysicalSequenceName(name, context)
);
}
@Override
public Identifier toPhysicalColumnName(
Identifier name,
JdbcEnvironment context) {
return formatIdentifier(
super.toPhysicalColumnName(name, context)
);
}
private Identifier formatIdentifier(
Identifier identifier) {
if (identifier != null) {
String name = identifier.getText();
String formattedName = name
.replaceAll(
CAMEL_CASE_REGEX,
SNAKE_CASE_PATTERN)
.toLowerCase();
return !formattedName.equals(name) ?
Identifier.toIdentifier(
formattedName,
identifier.isQuoted()
) :
identifier;
} else {
return null;
}
}
}
You don’t even need to create the aforementioned naming strategy class. You can get it via the hibernate-types Maven dependency:
In order to use the CamelCaseToSnakeCaseNamingStrategy custom naming strategy, you need to supply it to Hibernate via the hibernate.physical_naming_strategy configuration property:
Now, when generating the database schema using the hbm2ll, Hibernate will execute the following DDL statements:
CREATE SEQUENCE hibernate_sequence
START WITH 1 INCREMENT BY 1
CREATE TABLE book_author (
id BIGINT NOT NULL,
first_name VARCHAR(255),
last_name VARCHAR(255),
PRIMARY KEY (id)
)
CREATE TABLE paper_back_book (
id BIGINT NOT NULL,
isbn VARCHAR(255),
published_on DATE,
title VARCHAR(255),
published_by_id BIGINT,
PRIMARY KEY (id)
)
Much better, right?
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
While it’s widely known that you can customize the JPA to DB identifier mapping using the name attribute of JPA @Column annotation, using a Hibernate strategy to apply a given naming convention automatically to tens or hundreds of entities is way more convenient.
And, you don’t even have to write the naming strategy yourself since you can get it via the hibernate-types open-source project, which even supports the old NamingStrategy Hibernate 4 contract.
Based on my book, High-Performance Java Persistence, this workshop teaches you various data access performance optimizations from JDBC, to JPA, Hibernate and jOOQ for the major rational database systems (e.g. Oracle, SQL Server, MySQL and PostgreSQL).
All the code is in my hibernate-types project and works like a charm. Check out the test cases and compare those to yours.
Why we have extended PhysicalNamingStrategyStandardImpl instead of implementing PhysicalNamingStrategy
Because I want to call the baseclass logic instead of copy/pasting it.
Is CamelCaseToSnakeCaseNamingStrategy similar to org.hibernate.cfg.ImprovedNamingStrategy?
Thanks for your effort to generate great content in your blog!
Yes, but that naming strategy is no longer available in 5.x.
Spring has it though https://stackoverflow.com/a/25293929/6166627
Thanks for mentioning it.