CodeGym /Courses /Spring Data JPA /@Entity and @Table: relationship with the table

@Entity and @Table: relationship with the table

Spring Data JPA
Level 5, Lesson 0
Available

1. From table to class

A connected PostgreSQL database and a configured DataSource do not create a persistence model by themselves. The application already knows how to reach the database, but it still does not know which Java class corresponds to which table. So the next step is very down to earth: we take one table and honestly describe the class that will represent it.

If development were a TV series, today's episode would be called "The first Entity appears, and everyone pretends to understand what is happening." Our goal is not to pretend. We take something familiar from SQL — a table — and make it so that Java gets a class that honestly and explicitly represents it, without the illusion that "Spring Data will read everything from your face."

Imagine a category table. In the database, it is a set of rows and columns. In Java, we want an object Category whose fields reflect those columns. For Hibernate (as a JPA provider) to understand: "Aha, this is part of the persistence model," the class needs the right status. That status is exactly @Entity.

A small diagram so your brain does not drift into philosophy:

flowchart TD
    A["PostgreSQL: category table"] --> B["JPA mapping: metadata"]
    B --> C["Java: Category class (@Entity)"]
    C --> D["Hibernate generates SQL (INSERT/SELECT)"]
    D --> A

For now, we are intentionally keeping the model simple: one class — one table, fields — simple values. No links to other tables and no "object graphs." First we pour the concrete, then we do not try to put up the roof.

2. The @Entity annotation

Sometimes it seems that @Entity is just "another annotation so Spring does not complain." In reality, it is more serious: without it, the class remains an ordinary Java class, and Hibernate ignores it. With it, the class gets into the persistence unit, and then the discussion about mapping and SQL begins.

Technically, @Entity tells the JPA provider: "This class needs to be included in the model that we will persist in the database." At application startup, Hibernate scans classes and builds a metamodel: which fields exist, what the table is called, how the key is structured, what types the columns have. From this, the SQL queries you will see in the logs are born.

A minimal example — almost "Hello, database":

import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity // Mark the class as a JPA entity, otherwise Hibernate will ignore it
public class Category {

    @Id // An identifier is required: without it, ORM cannot distinguish rows
    private Long id;
}

Yes, this is not especially useful as an entity yet. But it already contains two key ideas: the class is marked as an entity, and it has an identifier. For JPA, an identifier is not an "optional detail," but the equivalent of a person's passport number: you can talk about identity for a long time, but when you need to know exactly "who this is," it hurts without a number.

Important: an entity is not a Spring bean. It does not need to be marked with @Component, @Service, or anything similar. An entity is created and lives in ORM mechanics, not in the Spring container. If you try to "inject" an entity through @Autowired, it will look like an attempt to "inject" a table row into a service — sounds impressive, makes no sense.

Where the entity lives in the project and how Spring Boot finds it

In learning projects, one of the most common problems sounds like this: "I wrote @Entity, but Hibernate seems not to see it." And then the magic starts: "maybe the version is wrong," "maybe the dependency was not connected," "maybe the moon is in Capricorn." In practice, the reason is usually boring and engineering-related: the class is not where Spring Boot scans for it, or the project structure was made more complicated than necessary.

By default, Spring Boot scans components and entities relative to the package where your class with @SpringBootApplication lives. If the main application class is in com.example.shopdatajpa, then everything inside com.example.shopdatajpa.* will usually be found. So for a learning project, the healthiest strategy is to keep entities inside the root package.

For our project, we have already chosen the package-by-feature structure. That means Category and Product should be placed here:

com/example/shopdatajpa/catalog
└─ entity

This matters not "for beauty," but for predictability. When the project grows, you will have inventory.entity, ordering.entity, and everything will be in clear places. Even if you are working alone for now, this saves you from the situation where "in a week I do not even understand where anything is."

A tiny example of what this looks like in code — with a package, so there are no surprises later:

package com.example.shopdatajpa.catalog.entity; // The package must fall within Spring Boot's scanning scope

import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity // Hibernate will see this class as an entity during scanning
public class Category {

    @Id // Without @Id, the entity is not considered a valid JPA model
    private Long id;
}

If you place the entity, for example, in the com.example.entities package, while the application starts from com.example.shopdatajpa, Boot may well not see this class. There are different ways to fix this: for example, @EntityScan. But in a learning project it is better to treat the cause, not the symptoms: keep the structure simple and predictable.

3. The @Table annotation

With @Entity, the class becomes part of the persistence model. But then the next question immediately appears: "Which table is it linked to?" This is where the territory of conventions begins. JPA and Hibernate can infer a table name "by default." For example, the Category class may become the category table, and the createdAt field may become the created_at column. But the key word here is "may." Many people end up arguing with ORM simply because they and ORM interpret naming rules differently.

So in a learning project we will practice useful fussiness: if the table name matters and must be exactly that, we fix it with @Table. This removes ambiguity and especially helps when you read SQL logs and want to immediately understand what this class belongs to.

An example with an explicit mapping:

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity // Declare: this is an entity
@Table(name = "category") // Explicitly fix the table name so we do not depend on naming rules
public class Category {

    @Id // Primary key of the entity
    private Long id;
}

It looks like "three extra lines." But in reality, it is insurance against surprises, especially when you start working with real tables that already exist and have predefined names.

It is useful to remember: @Entity and @Table are responsible for different things. @Entity says: "this class is an entity," while @Table clarifies: "and it is linked to this table." At an early stage, it is better not to rely on "it will figure itself out."

A small table for reinforcement — without trying to become a thousand-page textbook:

What we describe Annotation What it fixes
"This is an entity" @Entity The class participates in the persistence model
"Here is the table" @Table(name = "...") The exact table name in the database

4. The minimal entity "skeleton"

An entity is not just "a class with fields." It is a class that ORM must be able to work with normally. That means it has a few basic requirements, and it is better to learn them right away than to later chase strange errors at the most unexpected moment. This is more like safety procedure than scary theory.

First, an entity must have an identifier — a field with @Id. We are not discussing how it is populated yet: manually or automatically. Right now the important thing is something else: without @Id, ORM cannot work with the object as a record. Second, the entity must be an ordinary class, not a record, and it needs a no-argument constructor. ORM creates the object by reflection, and if you block this path, Hibernate will get sad, and a sad Hibernate writes very long exceptions.

Another principle for today: the first entity class should be as simple as possible. Do not add links to other entities "for the future" and do not try to design the whole universe in advance. Right now we need to see a simple correspondence: table ↔ class.

A minimal example of the "right shape":

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity // Mark the class as an entity
@Table(name = "product") // Explicitly specify the table
public class Product {

    @Id // Row identifier
    private Long id;

    // A no-argument constructor is needed by Hibernate to create the object via reflection
    public Product() {
    }
}

Yes, for now it is almost empty. But the shape is already correct: Hibernate will be able to create the object, recognize the entity, map it to the product table, and then we will gradually add fields.

5. The first Category and Product

Now we will do what is often postponed for too long: calmly and carefully sketch the project's first entities so that we can already see which tables we have and what they are called. Functionally, this looks a little "poor," and that is normal: you are building a foundation, not an online store showcase. In learning projects, predictability and observability matter more.

Important: the fragments below are not final finished entity files, but the first skeletons. Right now we need the very relation table ↔ class, so we are deliberately not dragging in the full setup of id, columns, and types in this piece.

Category as a reflection of the category table

Let's create Category in the catalog.entity package and immediately fix the table name. For now, we will add only three fields: id, code, name. What matters here is not how "complete" the model is, but the fact that the class begins to reflect the meaning of the table: a category is a reference entity with a code and a name.

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity // An entity that will be mapped to a table
@Table(name = "category") // The database table associated with the class
public class Category {

    @Id // Primary key
    private Long id;

    // Business fields: correspond to table columns
    private String code;
    private String name;
}

If you are used to the idea that "fields should be private + getters/setters," then yes, that will be the case. But today we are focusing on the relationship "class ↔ table" itself. Details about columns and constraints (nullable, unique, lengths, and so on) are a separate topic for the day, and it is not worth mixing everything into one soup.

Product as a reflection of the product table

By analogy, let's create Product. Again: only the identifier, sku, and name. No prices, statuses, or anything else. Our task is to fix the shape.

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity // An entity for ORM
@Table(name = "product") // Explicit link to the product table
public class Product {

    @Id // Row identifier
    private Long id;

    // Domain fields that can later be refined via @Column, etc.
    private String sku;
    private String name;
}

At this stage it is very useful to keep in mind the SQL twin of this entity. Not because we are writing DDL by hand right now, but because the JPA world is still built around the schema:

create table product (
  id   bigint primary key, -- PK corresponds to the field with @Id
  sku  varchar(64),         -- example column for the sku field
  name varchar(120)         -- example column for the name field
);

It is rough and without nuances for now, but the logic is clear: an object becomes a table row.

6. Typical mapping mistakes

Mistake #1: add @Entity but forget @Id.
Beginners sometimes think ORM will "figure it out somehow" or that they can start without an identifier and add it later. In practice, Hibernate cannot work with an entity without a key: it does not understand how to distinguish one record from another and how to update correctly. So @Id is a mandatory part of the skeleton.

Mistake #2: rely on "default names" without understanding naming rules.
You named the class Category, expect the table category, and then see something else in the logs — and after that the magical thinking begins. The problem is not Hibernate, but expectations. In a learning project, it is better to fix the table name right away with @Table(name = "...") so you do not have to play guessing games, especially while you are still learning to read SQL.

Mistake #3: put the entity in a package that the application does not scan.
This is one of the most common scenarios of "why nothing works." If the main Spring Boot class is in one package and the entities are in another, outside the package tree, Hibernate will simply not see them. As a result, tables are not created, mapping does not work, and you suspect "broken Gradle." Almost always, this is fixed by moving the classes back into com.example.shopdatajpa.*.

Mistake #4: try to make an entity a Spring component.
Sometimes people want to add @Component or even @Service to an entity so that "Spring definitely finds it." But that is unnecessary and even harmful: an entity is not meant to be injected as a dependency. An entity is a data model, not a service. Let Spring manage services and repositories, and let Hibernate manage the life of objects inside ORM mechanics.

Mistake #5: make an entity a record or a final class because "it is modern."
Modern — yes. Appropriate for a JPA entity — most often no. ORM must be able to create an object and manage it in the standard way. If you make the class shape more complicated, the chance of getting strange errors out of nowhere only grows. At the start of the course, keep entities as ordinary Java classes.

1
Task
Spring Data JPA, level 5, lesson 0
Locked
First entity `Warehouse`
First entity `Warehouse`
1
Task
Spring Data JPA, level 5, lesson 0
Locked
Explicit table name `shop_branch`
Explicit table name `shop_branch`
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION