We already know that Hibernate and Spring Data JPA are real helpers when working with databases. They take care of the boring stuff, but how do they do it? Let's look at the annotations that turn regular Java classes into full-fledged database entities.
Introduction to JPA annotations
Forget about writing SQL queries by hand! With JPA annotations you just tell the database: "Look, this Java class is a table, and its fields are columns." Pretty convenient, right?
JPA annotations act like smart translators. They take your objects and explain to the database how to turn them into tables and columns it understands.
Learning JPA annotations is like learning a new language, but way easier. Instead of writing complex SQL, you work with plain Java objects, and JPA handles all the translation to the database dialect. Let's break it down.
@Entity — creating a "table" from a class
The @Entity annotation is the starting point. It tells the ORM tool (for example, Hibernate) that this class represents a table in the database. For example:
import jakarta.persistence.Entity;
@Entity
public class User {
private Long id;
private String name;
private String email;
}
With @Entity, Hibernate understands that the User class corresponds to a table in the database. It's like saying: "Hey Hibernate, here’s my table — do your thing."
Key points:
- If you don't mark the class with
@Entity, Hibernate won't include it in ORM. That means the database won't even know this "table" exists. - By default the table name will match the class name. But you can change it, as we'll see later.
Common mistake: if you forget to add @Entity, Hibernate will just ignore your class, and you'll spend a lot of time wondering why the table isn't created.
@Table — configuring the table name
Although by default the table name matches the class name, sometimes that's inconvenient. For example, the database might already have a table with a different name. The @Table annotation helps you set the table name.
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
@Entity
@Table(name = "users")
public class User {
private Long id;
private String name;
private String email;
}
Now Hibernate will create or map the entity to the users table, not User.
Additional @Table settings:
schema: specifies the database schema.catalog: specifies the database catalog.uniqueConstraints: allows setting unique constraints at the table level.
We won't use this annotation too often, but it's good to know how to configure it.
@Id — table identifier
Every table needs a primary key to distinguish rows. In JPA the @Id annotation is used for that. It marks a field as the entity identifier.
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@Entity
public class User {
@Id
private Long id;
private String name;
private String email;
}
Now Hibernate knows that the id field in our class is the primary key field.
Common mistake: If you forget to add @Id, Hibernate will throw a runtime exception, because it won't be able to generate SQL commands to work with the table.
@GeneratedValue — generating identifiers
Often primary key values are generated automatically (for example, auto-increment in MySQL). In JPA you can configure this with @GeneratedValue.
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
}
Generation strategies:
GenerationType.IDENTITY: uses auto-increment.GenerationType.SEQUENCE: uses SQL sequences (especially useful in PostgreSQL).GenerationType.TABLE: generates keys via a special table.GenerationType.AUTO: Hibernate chooses for you (usually SEQUENCE or IDENTITY).
Typical mistake: If you choose GenerationType.IDENTITY on a database that doesn't support auto-increment, you'll get a runtime error. Make sure your DB supports the chosen strategy.
@Column — configuring columns
To link a class field to a table column, use @Column. By default the column name matches the field name, but @Column lets you change that.
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Column;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "full_name", nullable = false, length = 50)
private String name;
@Column(unique = true)
private String email;
}
@Column parameters:
name: the column name in the table.nullable: whether the column can containNULL.unique: sets a unique constraint.length: maximum string length.
Now the name column in the table will be called full_name, can't be empty, and will be at most 50 characters long.
Typical mistake: if the string length exceeds the value specified in length, you'll get an exception. Also, changing annotation parameters doesn't always automatically update the table schema. Don't forget to synchronize your changes!
@Transient — excluding a field from mapping
When a field shouldn't be saved to the database, mark it with @Transient. This is useful for computed or temporary data.
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Transient;
@Entity
public class User {
@Id
private Long id;
private String name;
@Transient
private String temporaryToken; // Not persisted to the database
}
The temporaryToken field will only exist in the User object in Java. It won't have any corresponding column in the database table.
Practical exercise
Task: create a Product entity with table and column configuration.
- Create an entity
Productwith fields:id— primary key, auto-increment.name— string, cannot be empty.price— number, must be greater than zero.description— text, NULL is allowed.
- Set the table name to
products. - Configure the
namecolumn with a maximum length of 100 characters.
Example solution:
import jakarta.persistence.*;
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(nullable = false)
private Double price;
@Column
private String description;
// Getters and setters
}
Summary
Today you learned how JPA annotations turn your Java classes into database tables. You configured identifiers with @Id and @GeneratedValue, managed columns using @Column, learned how to exclude fields from mapping with @Transient, and even set the table name with @Table.
Remember: it's easier than it looks to make a mistake with annotations, but Hibernate usually throws a clear error. The main thing — don't panic. 😄
GO TO FULL VERSION