The Magic of @Entity: How One Annotation Creates Database Tables
What if I told you that adding a single annotation to a Java class automatically creates a database table with all the right columns, types, and constraints? Meet @Entity - the annotation that bridges Java objects and database tables.
The Transformationβ
Before: Just a Regular Java Classβ
// User.java
package com.ecommerce.app.entity;
public class User {
private Long id;
private String username;
private String email;
private String password;
// Getters and setters...
}
What this is:
- β A regular Java class (POJO - Plain Old Java Object)
- β
Can create instances:
new User() - β Has fields and methods
- β Nothing to do with database
- β No table created
Your database:
Tables: (empty)
After: Adding @Entityβ
// User.java
package com.ecommerce.app.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
@Entity // π― THE MAGIC ANNOTATION!
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
private String password;
// Getters and setters...
}
What changes:
- β Still a Java class
- β Can still create instances
- β¨ NOW: JPA Entity - mapped to database table
- β¨ NOW: Spring Boot recognizes it
- β¨ NOW: Database table created automatically!
Your database:
-- Automatically created!
CREATE TABLE user (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255),
email VARCHAR(255),
password VARCHAR(255)
);
What @Entity Actually Tellsβ
Three Different Listenersβ
When you add @Entity, three different systems react:
1. JPA/Hibernate (The ORM Framework)β
@Entity tells Hibernate:
"This class represents a database table"
Hibernate's response:
- Maps class name β table name (User β user)
- Maps fields β columns (username β username)
- Maps types β SQL types (String β VARCHAR, Long β BIGINT)
- Creates ORM mappings for CRUD operations
2. Spring Boot (The Application Framework)β
@Entity tells Spring Boot:
"Create a table for this when the app starts"
Spring Boot's response:
- Scans for @Entity classes at startup
- Registers with Hibernate
- Triggers schema creation (if ddl-auto=update)
- Makes it available for dependency injection
3. The Database (The Storage Layer)β
@Entity (through Hibernate) tells Database:
"Here's the structure to create"
Database's response:
- Creates table with matching structure
- Sets up primary key constraints
- Creates indexes as specified
- Ready to store data
The Complete Journey: Class to Tableβ
Step 1: You Write the Classβ
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
}
Step 2: Spring Boot Startsβ
Starting application...
Scanning for components...
Found @Entity: com.ecommerce.app.entity.User
Registering with Hibernate...
Step 3: Hibernate Analyzesβ
Analyzing User entity:
- Class name: User
- Table name: user (lowercase by default)
- Fields found:
* id (Long) β BIGINT, PRIMARY KEY, AUTO_INCREMENT
* username (String) β VARCHAR(255)
* email (String) β VARCHAR(255)
Step 4: Schema Generationβ
-- Hibernate generates and executes:
CREATE TABLE user (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255),
email VARCHAR(255)
);
Step 5: Ready to Useβ
@RestController
public class UserController {
@Autowired
private UserRepository userRepository;
@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userRepository.save(user); // Saves to database!
}
}
Understanding the Annotationsβ
@Entity - The Main Playerβ
@Entity
public class User {
// ...
}
What it does:
- Marks class as JPA entity
- Maps to database table
- Enables CRUD operations
- Required for all database-mapped classes
Optional parameters:
@Entity(name = "users") // Custom entity name
@Table - Custom Table Nameβ
@Entity
@Table(name = "app_users") // Table will be "app_users" not "user"
public class User {
// ...
}
Why use it?
usermight be a reserved keyword in some databases- Better naming conventions (e.g.,
app_usersinstead ofuser) - Legacy database compatibility
@Id - Primary Keyβ
@Entity
public class User {
@Id // Marks this as the primary key
private Long id;
}
Required! Every @Entity must have exactly one @Id.
@GeneratedValue - Auto-Generated IDsβ
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
Strategies:
| Strategy | How It Works | SQL Equivalent |
|---|---|---|
IDENTITY | Database auto-increment | AUTO_INCREMENT (MySQL), SERIAL (PostgreSQL) |
SEQUENCE | Database sequence | NEXTVAL('seq_name') |
AUTO | JPA chooses based on database | Varies |
TABLE | Separate table for ID generation | Custom table |
Most common: IDENTITY for auto-incrementing IDs
Field Mapping Examplesβ
Basic Typesβ
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // β BIGINT
private String name; // β VARCHAR(255)
private Double price; // β DOUBLE
private Integer stock; // β INTEGER
private Boolean active; // β BOOLEAN/TINYINT
private LocalDateTime created; // β TIMESTAMP
}
Generated SQL:
CREATE TABLE product (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
price DOUBLE,
stock INTEGER,
active BOOLEAN,
created TIMESTAMP
);
Custom Column Propertiesβ
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_name", nullable = false, length = 100)
private String username;
@Column(unique = true, nullable = false)
private String email;
@Column(name = "pwd", nullable = false)
private String password;
}
Generated SQL:
CREATE TABLE user (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
pwd VARCHAR(255) NOT NULL
);
What Happens at Runtimeβ
Scenario 1: First Application Startβ
1. Spring Boot starts
2. Scans for @Entity classes
3. Finds User entity
4. Checks if "user" table exists
β Result: NO
5. Creates table with structure
6. Application ready!
Console output:
Hibernate:
create table user (
id bigint generated by default as identity,
email varchar(255),
password varchar(255),
username varchar(255),
primary key (id)
)
Scenario 2: Second Application Start (Table Exists)β
1. Spring Boot starts
2. Scans for @Entity classes
3. Finds User entity
4. Checks if "user" table exists
β Result: YES
5. Validates schema matches entity
6. No changes needed
7. Application ready!
Console output:
Hibernate: No schema changes detected
Scenario 3: You Add a New Fieldβ
You update the entity:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
private String password;
private String phoneNumber; // π NEW FIELD!
}
Application starts:
1. Spring Boot starts
2. Scans User entity
3. Compares with database schema
4. Detects missing column: phone_number
5. Adds column (because ddl-auto=update)
6. Application ready!
Console output:
Hibernate:
alter table user
add column phone_number varchar(255)
Your existing data? β Safe! New column added with NULL values.
Common @Entity Patternsβ
Pattern 1: Base Entity (Timestamps)β
@MappedSuperclass
public abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
// Getters and setters...
}
@Entity
public class User extends BaseEntity {
private String username;
private String email;
// Inherits id, createdAt, updatedAt!
}
Pattern 2: Soft Deleteβ
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private Boolean deleted = false; // Soft delete flag
private LocalDateTime deletedAt;
}
Pattern 3: Enum Mappingβ
public enum UserRole {
ADMIN, USER, MODERATOR
}
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated(EnumType.STRING) // Store as "ADMIN", "USER", etc.
private UserRole role;
}
Generated SQL:
CREATE TABLE user (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
role VARCHAR(255) -- Will store "ADMIN", "USER", etc.
);
The Repository Magicβ
Once you have an @Entity, Spring Data JPA creates a repository interface:
// Just an interface - no implementation needed!
public interface UserRepository extends JpaRepository<User, Long> {
// Spring Data JPA generates implementation automatically!
}
What you get for free:
userRepository.save(user); // INSERT INTO user...
userRepository.findById(1L); // SELECT * FROM user WHERE id = 1
userRepository.findAll(); // SELECT * FROM user
userRepository.deleteById(1L); // DELETE FROM user WHERE id = 1
userRepository.count(); // SELECT COUNT(*) FROM user
Custom queries:
public interface UserRepository extends JpaRepository<User, Long> {
// Method name β SQL query!
User findByUsername(String username);
// β SELECT * FROM user WHERE username = ?
List<User> findByEmailContaining(String email);
// β SELECT * FROM user WHERE email LIKE %?%
}
Debugging: Seeing What Hibernate Doesβ
Enable SQL Loggingβ
# application.properties
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
Console output:
Hibernate:
select
user0_.id as id1_0_,
user0_.email as email2_0_,
user0_.password as password3_0_,
user0_.username as username4_0_
from
user user0_
Verify Table Structureβ
Option 1: H2 Console
1. Go to http://localhost:8080/h2-console
2. Connect to database
3. Run: SHOW COLUMNS FROM user;
Option 2: SQL Query
SELECT * FROM information_schema.columns
WHERE table_name = 'user';
Best Practicesβ
β Do Thisβ
1. Always use @Id
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // Required!
}
2. Use meaningful table names
@Entity
@Table(name = "app_users") // Avoid reserved words like "user"
public class User {
// ...
}
3. Add @Column for clarity
@Entity
public class User {
@Column(nullable = false, unique = true)
private String email;
}
4. Use Lombok to reduce boilerplate
@Entity
@Data // Generates getters, setters, toString, etc.
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
}
β Avoid Thisβ
1. No @Id annotation
@Entity
public class User {
private Long id; // β Missing @Id
}
// Error: No identifier specified for entity: User
2. Multiple @Id without @IdClass
@Entity
public class User {
@Id
private Long id;
@Id
private String email; // β Error! Need composite key setup
}
3. Forgetting getters/setters
@Entity
public class User {
@Id
private Long id;
private String name;
// β No getters/setters - JPA needs them!
}
Summaryβ
What @Entity Doesβ
@Entity = "This class is a database table"
Java Class β @Entity β Database Table
User β β¨ β user table
The Three Listenersβ
- JPA/Hibernate: Maps class to table
- Spring Boot: Creates table at startup
- Database: Stores the data
Key Annotationsβ
| Annotation | Purpose | Required? |
|---|---|---|
@Entity | Mark as JPA entity | β Yes |
@Id | Primary key | β Yes |
@GeneratedValue | Auto-generate ID | Recommended |
@Table | Custom table name | Optional |
@Column | Custom column properties | Optional |
The Magicβ
// You write this:
@Entity
public class User {
@Id
private Long id;
private String name;
}
// Hibernate creates this:
CREATE TABLE user (
id BIGINT PRIMARY KEY,
name VARCHAR(255)
);
// Spring Data JPA gives you this:
userRepository.save(new User());
userRepository.findAll();
userRepository.deleteById(1L);
All from one annotation! β¨
Remember: @Entity is the bridge between your Java objects and database tables. Master it, and you've mastered the foundation of JPA!
Tags: #spring-boot #jpa #hibernate #entity #database #annotations
