Reverse Engineering
Reverse engineering is the process of scaffolding JPA entity classes based on a database schema.
tip
This functionality relies on the Reverse Engineering plugin, which is bundled in IntelliJ IDEA Ultimate. Prior to 2024.1, support for reverse engineering was included in the JPA Buddy plugin.
If a database connection is not established, create one.
note
In Spring, Micronaut, and Quarkus projects, you can quickly create a database connection right from your application.properties or application.yml file by clicking in the gutter.
In the Database tool window, expand the JPA node, right-click a database or a specific table, and select New | JPA Entities from DB.
Select a database connection, tables, and attributes to be mapped. For more information, refer to Entities from DB Wizard.
While your IDE is open, the database can be modified by other clients. To get the latest data from the database, you can click in either the Entities from DB window or in the Database tool window.
note
If the JPA Buddy plugin is installed and enabled, you can also generate an entity from the project view: Right-click anywhere in the Project tool window and select New | JPA Entities from DB.
The menu on the top of the window allows you to configure:
Source root and package where the generated entities will be saved
Whether indexes and constraints need to be migrated
Whether schema name should be specified in the
@Table
annotation
Also, from the Other settings drop-down list, you can move to the entity declaration and reverse engineering settings.
On the left side of the window, you can see:
Mapped Relations: tables and views mapped to JPA entities
Tables: tables that exist in the DB but are not mapped to entities
Views: views that exist in the DB but are not mapped to entities
After selecting any element from the tree, a panel for migrating attributes from columns will appear. Also, you will be able to define a class name in the corresponding field.
The main part of the window allows you to configure everything related to attributes. You can choose which attributes you want to add and change all their params, except Column Name. Mapping type and attribute/converter/hibernate types are represented as drop-down lists.
All attributes are divided into 3 categories:
Migrated Columns - columns that are already present in the entity (available only for mapped relations)
Columns - new columns that are not yet mapped in the entity or parent @MappedSuperclass
References - optional associations that are not represented as a column in the observed table
IntelliJ IDEA offers the ability to define a parent entity by selecting a class annotated with @MappedSuperclass
from the Parent drop-down box. This allows the generated entities to extend from the parent class and automatically inherit all attributes that have the same name and type.
In cases where the column name in the @MappedSuperclass
doesn't match the child entity's table, we can still inherit the attribute using the @AttributeOverride
annotation. By simply selecting the attribute name and choosing the one to override, IntelliJ IDEA assists in managing the inheritance.
During entity generation, IntelliJ IDEA alerts us if any inherited attributes from the @MappedSuperclass
are missing in the database. To align the model with the database, access the Generate DDL by Entities action in the JPA Structure menu and select the Existing DB update option.
For attributes matching the String
or Integer
type, you can change the mapping type from Basic to Enum, and IntelliJ IDEA will create the corresponding Enum class in the project. You need to fill the enum with proper values manually.
For some SQL types, there is no exact match to Java classes. In this case, IntelliJ IDEA does not set the type to prevent generating non-working code. You will need to choose the attribute type yourself. You can also configure default type mappings for each DBMS in the settings.
If you have the HibernateTypes library in your project dependencies list, IntelliJ IDEA can automatically suggest suitable types from the library for the unsupported SQL types during reverse engineering:
If you want to postpone an attribute creation for specific columns, you can choose //todo
comment as the mapping type. IntelliJ IDEA will generate the //todo
comment with the corresponding quick-fix actions depending on the column type. You can call these actions by pressing Ctrl0B:
For known basic and association types you can:
Uncomment as is
Remove column mapping
For unknown column type you can:
Define target Java type
Uncomment as is
Remove column mapping
Here is an example of generated //todo
comment for the attribute with an unknown column type:
/*
TODO [Reverse Engineering] create field to map the 'description' column
Available actions: Define target Java type | Uncomment as is | Remove column mapping
@Column(name = "description", columnDefinition = "jsonb")
private java.lang.Object description;
*/
After calling the Define target Java type action, the following window will appear:
IntelliJ IDEA will remember data mappings for the subsequent reverse engineering actions. You can always change them in the settings.
IntelliJ IDEA follows all best practices providing the most efficient mapping for DB views while reverse engineering:
As DB views do not have a primary key, IntelliJ IDEA allows you to select a field or a set of fields to use as the identifier for the target entity.
Most DB views are immutable. So, IntelliJ IDEA adds
@Immutable
annotation to the entity and generates getters only. This helps to improve application performance.IntelliJ IDEA generates only a no-arg protected constructor for entities that are mapped to a DB view, as per JPA specifications, which prevents developers from creating a new instance of such entities in the business logic code
Some developers prefer the DB-first application development approach. First, they add columns directly to the database and then update the JPA model. IntelliJ IDEA can automate this process.
If a database connection is not established, create one.
In the Persistence tool window, expand the JPA node, right-click an entity and select New | JPA Entities from DB.
Alternatively, in your entity source code, click an entity icon in the gutter and select Create Entity Attributes from DB.
Select a database connection, a table or a view, and columns to be mapped. The attributes migration flow is identical to what is described in the Entities from DB wizard section.
If a database connection is not established, create one.
In the Persistence tool window, expand the Spring Data MongoDB node, right-click an entity and select New | MongoDB Fields from DB.
Select a database connection, a table or a view, and columns to be mapped. The attributes migration flow is identical to what is described in the Entities from DB wizard section.
note
If you map embedded (nested) MongoDB documents, IntelliJ IDEA automatically creates corresponding classes for documents inside other documents.
IntelliJ IDEA deeply understands your model. In certain cases, it's able to properly detect cardinality: @OneToOne
, @OneToMany
, @ManyToOne
, @ManyToMany
. The coolest thing is that IntelliJ IDEA can show references even when there are no corresponding columns in the current table.
Let's look more closely at each of these cases.
There are two situations where we can confidently assume the cardinality of the relation as @OneToOne
:
Table has a column with the unique constraint that refers to the primary key of another table
Primary key of the table is a foreign key
Case 1:
CREATE TABLE profiles
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
join_date date,
user_id BIGINT,
status VARCHAR(255),
bio VARCHAR(255),
CONSTRAINT pk_profiles PRIMARY KEY (id)
);
CREATE TABLE users
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
last_name VARCHAR(255),
first_name VARCHAR(255),
CONSTRAINT pk_users PRIMARY KEY (id)
);
ALTER TABLE profiles
ADD CONSTRAINT uc_profiles_user UNIQUE (user_id);
ALTER TABLE profiles
ADD CONSTRAINT FK_PROFILES_ON_USER FOREIGN KEY (user_id) REFERENCES users (id);
IntelliJ IDEA will generate a @OneToOne
association with a @JoinColumn
annotation in the User entity, and a @OneToOne
association with a mappedBy
parameter in the Profile entity:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "profile_id")
private Profile profile;
}
@Entity
@Table(name = "profiles")
public class Profile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@OneToOne(fetch = FetchType.LAZY, mappedBy = "profile")
private User users;
}
Case №2:
CREATE TABLE users
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
last_name VARCHAR(255),
first_name VARCHAR(255),
CONSTRAINT pk_users PRIMARY KEY (id)
);
CREATE TABLE profiles
(
user_id BIGINT NOT NULL,
status VARCHAR(255),
bio VARCHAR(255),
join_date date,
CONSTRAINT pk_profiles PRIMARY KEY (user_id)
);
ALTER TABLE profiles
ADD CONSTRAINT FK_PROFILES_ON_USER FOREIGN KEY (user_id) REFERENCES users (id);
Since @Id
should not be a persistence entity, IntelliJ IDEA will generate:
id
attribute of a basic type and mark it with@Id
annotationusers
@OneToOne
association and mark it with@MapsId
annotation
@Entity
@Table(name = "profiles")
public class Profile {
@Id
@Column(name = "user_id", nullable = false)
private Long id;
@MapsId
@OneToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "user_id", nullable = false)
private User users;
//...
}
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@OneToOne(fetch = FetchType.LAZY, mappedBy = "user")
private Profile profiles;
//...
}
If a table has the column that refers to the primary key of another table, it is most likely a @ManyToOne
association. But you are also able to change cardinality to @OneToOne
if required. So, depending on which table you call the reverse engineering action, IntelliJ IDEA will detect mapping type as @OneToMany
or @ManyToOne
:
CREATE TABLE users
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
last_name VARCHAR(255),
first_name VARCHAR(255),
CONSTRAINT pk_users PRIMARY KEY (id)
);
CREATE TABLE profiles
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
join_date date,
status VARCHAR(255),
bio VARCHAR(255),
user_id BIGINT,
CONSTRAINT pk_profiles PRIMARY KEY (id)
);
ALTER TABLE profiles
ADD CONSTRAINT FK_PROFILES_ON_USER FOREIGN KEY (user_id) REFERENCES users (id);
IntelliJ IDEA will generate the following code:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@OneToMany(mappedBy = "user")
private Set<Profile> profiles = new LinkedHashSet<>();
//...
}
@Entity
@Table(name = "profiles")
public class Profile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
//...
}
To establish a many-to-many relationship between two tables, you need to use a junction table. The junction table, in this case, contains only two columns - foreign keys. IntelliJ IDEA can automatically detect such a table and identify the relation cardinality between the two tables whose ids are represented as foreign keys in the junction table as @ManyToMany
.
CREATE TABLE users
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
last_name VARCHAR(255),
first_name VARCHAR(255),
CONSTRAINT pk_users PRIMARY KEY (id)
);
CREATE TABLE profiles
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
join_date date,
status VARCHAR(255),
bio VARCHAR(255),
CONSTRAINT pk_profiles PRIMARY KEY (id)
);
CREATE TABLE profiles_users
(
profile_id BIGINT NOT NULL,
users_id BIGINT NOT NULL,
CONSTRAINT pk_profiles_users PRIMARY KEY (profile_id, users_id)
);
ALTER TABLE profiles_users
ADD CONSTRAINT fk_prouse_on_profile FOREIGN KEY (profile_id) REFERENCES profiles (id);
ALTER TABLE profiles_users
ADD CONSTRAINT fk_prouse_on_user FOREIGN KEY (users_id) REFERENCES users (id);
If this association does not exist in any of the entities, IntelliJ IDEA will generate it in the entity for which the reverse engineering action was called.
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@ManyToMany
@JoinTable(name = "profiles_users",
joinColumns = @JoinColumn(name = "users_id"),
inverseJoinColumns = @JoinColumn(name = "profile_id"))
private Set<Profile> profiles = new LinkedHashSet<>();
//...
}
If this association already exists in one of the entities, then IntelliJ IDEA will generate the @ManyToMany
attribute with the mappedBy
parameter.
@Entity
@Table(name = "profiles")
public class Profile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@ManyToMany(mappedBy = "profiles")
private Set<User> users = new LinkedHashSet<>();
//...
}
File | Settings | Tools | JPA Reverse Engineering for Windows and Linux
IntelliJ IDEA | Settings | Tools | JPA Reverse Engineering for macOS
CtrlAlt0S
Fetch Type. To follow best practices and avoid potential performance issues, IntelliJ IDEA sets
FetchType.LAZY
for@OneToOne
and@ManyToOne
associations by default.Validation Annotations. Validation annotations give you another layer of protection in addition to the DB constraints. By default, IntelliJ IDEA will apply such annotations over entity attributes while reverse engineering.
Pluralization. By default, IntelliJ IDEA uses the singular form for entity names. For example, if you have a table called
users
, IntelliJ IDEA will generate aUser
entity. If you disable this option, IntelliJ IDEA will keep the original name of the table and only capitalize the first letter –Users
.Basic type attribute. When this option is enabled, IntelliJ IDEA will analyze the ORM references in the database schema and generate basic type attributes instead of creating associations or relationships between entities. This can be useful in certain scenarios where you prefer to have simple attribute types instead of complex associations.
To preserve comments added to the database objects, IntelliJ IDEA transfers them to the corresponding entity using the Hibernate @Comment
annotation or JavaDocs, depending on your settings.
tip
Please note that only
@Comment
annotations on entities can be included in the generated DDL scripts, depending on the settings, JavaDocs will be ignored anyway.
Often, DBA specialists adhere to certain naming conventions for database objects. For example, all table or column names have a specific prefix/suffix. Yet, Java developers usually prefer to drop these prefixes/suffixes for the JPA model. IntelliJ IDEA allows you to specify prefixes and suffixes to skip. Assume we set sys_
and p_
as prefixes to skip. After that, we apply reverse engineering for sys_user
and p_product
tables. As a result, prefixes will not appear in the corresponding entity names. The final entity names will be User
and Product
instead of SysUser
and PProduct
. Also, the database column names sometimes match the reserved Java keywords. E.g., public
, interface
, and so on. In this case, you can configure the field suffix so that IntelliJ IDEA will append it to the original column name. For example, for the Field
suffix, the resulting names will be publicField
and interfaceField
.
Despite the flexible options for configuring prefixes, suffixes, reserved words, and so on, in some cases this may still be insufficient. IntelliJ IDEA does not limit you only to these settings. You can write custom code for processing the names of database tables/columns. Moreover, you can not only write code in the current editor but also import an existing class and use its methods.
It is important to note that IntelliJ IDEA does not track changes in classes used in the naming algorithm in real-time. Therefore, after changing the class used in the algorithm, either update your settings or restart IntelliJ IDEA.
When the application works with several DBMSs, your schema might have slightly different data types for each of them.
Let's say the application needs to support both PostgreSQL and MS SQL and you need to store Unicode characters in string data. PostgreSQL supports Unicode chars in VARCHAR
, but MS SQL has a separate NVARCHAR
data type for it.
IntelliJ IDEA lets you specify type mappings for each DBMS. It is also possible to set mappings for JPA Converters and Hibernate Types:
See how you can configure type mappings for reverse engineering in IntelliJ IDEA to make use of the @JavaType
annotation from Hibernate 6:
Thanks for your feedback!