Skip to main content

The Magic of @Entity: How One Annotation Creates Database Tables

Β· 8 min read
Mahmut Salman
Software Developer

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?

  • user might be a reserved keyword in some databases
  • Better naming conventions (e.g., app_users instead of user)
  • 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:

StrategyHow It WorksSQL Equivalent
IDENTITYDatabase auto-incrementAUTO_INCREMENT (MySQL), SERIAL (PostgreSQL)
SEQUENCEDatabase sequenceNEXTVAL('seq_name')
AUTOJPA chooses based on databaseVaries
TABLESeparate table for ID generationCustom 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​

  1. JPA/Hibernate: Maps class to table
  2. Spring Boot: Creates table at startup
  3. Database: Stores the data

Key Annotations​

AnnotationPurposeRequired?
@EntityMark as JPA entityβœ… Yes
@IdPrimary keyβœ… Yes
@GeneratedValueAuto-generate IDRecommended
@TableCustom table nameOptional
@ColumnCustom column propertiesOptional

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