The labs on this site need JavaScript to run. The same content is written out below.
Learn › JPA › Entity mapping
Entity mapping and primary keys
How do the annotations on an entity change the table and the values that are stored?
JPA reads the fields and annotations of an entity class and pairs them with the columns of a table. This is called mapping.
Checked against real execution · All 96 combinations of entities were compiled and run on Hibernate 7.4.5 and H2, and the create table statements and the stored values were checked against the results
Concepts in this lab
- @Column
- This annotation specifies in detail which column of the table an entity field is paired with. name sets the column name, nullable = false forbids null, unique = true forbids duplicates, and length sets the maximum length of a string. A field is paired with a column even without it, and in that case a string has a length of 255 and allows null and duplicates. nullable, unique, and length are information that is applied when JPA creates the table (the create table statement). If you use a table that already exists as it is, unique and length prevent nothing at run time.
- @Enumerated
- This annotation specifies what value an enum field is stored as in the database. EnumType.ORDINAL stores the ordinal of the constant (its position in the enum declaration, counting from 0) as a number, and EnumType.STRING stores the name of the constant as text. The default when you do not add it is ORDINAL. If you add a constant at the front or in the middle of the enum after saving with ORDINAL, the numbers shift, and values that are already stored are read back as different constants. No exception is thrown, so it is hard to notice, and using STRING is the safer choice.
- @Transient
- This annotation leaves a field of an entity out of the mapping. No column is created for this field, and it is not used when saving or when reading. Every field of an entity is paired with a column unless you mark it otherwise, so add this to temporary values and values used only for calculation that do not need to be in the database. In an object that is read back from the database, this field is empty (null if it is a reference type). The Java transient keyword was meant to leave a field out of serialization, but JPA also does not store transient fields.
- Primary key generation strategy (@GeneratedValue)
- This is the setting that decides who creates the value of the primary key (@Id) of an entity, and how. If you add only @Id, the code must set the value itself, and saving without a value throws an exception. If you add @GeneratedValue as well, the value is created automatically. IDENTITY leaves it to an auto-increment column of the database and is commonly used with MySQL, and SEQUENCE takes the number from a sequence of the database. If you set it to automatic generation and then set a value yourself and save with em.persist, that also causes an exception.
- Entity
- A Java object that corresponds to a row of a database table. It is defined as a class annotated with @Entity and has an identifier (@Id). JPA tracks the state changes of these objects and runs the matching SQL.
Try these
- Default settings — Almost no annotations are used. VIP is stored as the number 1.
- Adding a value at the front of the enum — The VIP member that was saved is read back as BASIC.
- Storing as STRING — Even if the order of the enum changes, the member is still read back as VIP.
- nullable = false on name — The table that is created gets not null.
- @Transient on memo — No memo column is created, and the value is not stored.
- Letting the database choose the primary key — Even if you do not set id, the database assigns 1.
- Without setting id — If you leave a field that has only @Id empty, saving throws an exception.
Running with the default settings
- When the application starts: Hibernate reads the Member class and generates the create table statement (when the application is set to create tables automatically). The id field marked with @Id becomes the primary key column. Because there is only @Id, it is an ordinary not null column. The code must supply the value.
- name field: There is no @Column, so the column is created with the default settings. The length is 255 characters, and both null and duplicates are allowed.
- grade field: ORDINAL stores the ordinal of the constant (its position in the enum declaration, counting from 0), so the column becomes a small integer column (tinyint). BASIC is 0 and VIP is 1. A condition (check) that the number is between 0 and 1 is also added.
- memo field: There is no annotation, but a column is still created. Every field of an entity is paired with a column unless it is a static or transient field or a field you leave out with @Transient.
- em.persist(m) and commit: An INSERT is sent and one row is saved. id is 100, exactly as the code set it.
- Values stored in the database: the grade column contains the number 1. This is because Grade.VIP is the second constant in the enum (ordinal 1). The text "VIP" does not appear anywhere in the database. The memo column contains "joined today".
- em2.find(Member.class, 100L): It is read from the database again. When the 1 in the grade column is converted to the constant whose ordinal is 1 in the current enum, the result is VIP. memo is "joined today".
Result of this run
- ORDINAL stores the ordinal of the constant, that is, its position in the enum declaration. A number takes little space, but what the number means depends on the order in which the constants are written in the enum.
- A string field without @Column is varchar(255) and allows null and duplicates. If a value is required or has a length limit, write it in @Column so that it appears as a constraint on the table.
- A field without any annotation also becomes a column. Add @Transient to a field that you do not want to store.
Code
enum Grade { BASIC, VIP }
@Entity
class Member {
@Id
Long id;
String name;
@Enumerated(EnumType.ORDINAL)
Grade grade;
String memo;
}
// Save one member
Member m = new Member();
m.setId(100L);
m.setName("Alice");
m.setGrade(Grade.VIP);
m.setMemo("joined today");
em.persist(m);
em.getTransaction().commit();
// Read it from the database again with a new em
Member r = em2.find(Member.class, 100L);
System.out.println(r.getGrade() + ", " + r.getMemo());
Assumptions of this model
For each of the 96 combinations, an entity class was generated as source code, compiled, run on real Hibernate 7.4.5 with an H2 database, and checked against the results. What was checked: all the column definitions in the create table statement that Hibernate generated, the values in the row stored in the database, grade and memo read back with a new em, and the saved id or the kind of exception. The following facts stated in the summary were also run and confirmed separately: an enum field without @Enumerated is created and stored exactly like ORDINAL; whether saving succeeds or fails for a member whose name is null, a duplicate name, or 21 characters (the kind of exception, and whether the INSERT reached the database); that unique and length do nothing when Hibernate does not create the table; and that an exception is thrown when you set the id yourself with the automatic generation strategy. Column definitions differ by database type (dialect) and Hibernate version. The tinyint and check condition, the enum type column, and "generated by default as identity" shown here were generated by Hibernate 7.4.5 for H2. On MySQL, for example, the automatic increment is written as auto_increment. The MySQL notation, and the fact that older Hibernate (5.x) created STRING as varchar(255), follow the documentation and were not run here. Even so, the point that "ORDINAL stores the ordinal and STRING stores the name" is the same. In the real create table statement, the columns appear in a different order from the screen, and unless you use Spring Boot, the table name is written exactly as the class name (Member). Only IDENTITY was covered for automatic generation, and the difference in when the INSERT is sent is covered in the persistence context lab. In production, the setting that creates tables automatically is usually not used, and then unique and length in @Column serve only as documentation.
Previous lab: Detached entities and merge · Next lab: Value types and embeddable types