Skip to Content
Course content

137: Entity Mapping Basics

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Up until now, we've been treating our Java objects as temporary things that live and die with the application's runtime. But in the real world, data needs to persist. That's where Entity Mapping comes in. We're essentially telling Java: "This class isn't just a container for data; it's a blueprint for a table in my database."

I like to think of JPA (Java Persistence API) as a translator. It maps the object-oriented world—where we have classes and references—to the relational world, where we have tables and foreign keys. Let's build a simple project tracking system to see this in action.

Defining our Project entity

First, let's create a Project class. To make it an entity, we need the @Entity annotation. This tells the JPA provider (like Hibernate) that this class should be mapped to a database table. I'll also give it a unique ID, because every row in a relational table needs a primary key.

import jakarta.persistence.*;
import java.time.LocalDate;

@Entity
public class Project {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private LocalDate deadline;

    public Project(String name, LocalDate deadline) {
        this.name = name;
        this.deadline = deadline;
    }

    // Getters and setters...
}

Pretty straightforward, right? I've used GenerationType.IDENTITY here because I want the database to handle the ID incrementing for me. It's cleaner than trying to manage unique IDs manually in Java code.

The 'No-Args' stumbling block

Now, if I tried to run this right now, I'd hit a wall. I'd get a HibernateException complaining that the entity doesn't have a default constructor. I make this mistake more often than I'd like to admit.

Here's the deal: when JPA retrieves a record from the database, it doesn't use your custom constructor. It uses reflection to instantiate the object first and then populates the fields using the setters or direct field access. If there's no no-argument constructor, the whole process crashes.

I'll add a protected no-args constructor. I make it protected so that other parts of my application can't accidentally create "empty" projects, but JPA can still get in there and do its job:

// Inside the Project class
protected Project() {
    // Required by JPA
}

Connecting Projects to Tasks

A project without tasks is just a wish list. To make this useful, I need a Task entity. This is where mapping gets interesting because we're moving from simple fields to relationships. One project will have many tasks.

I'll create the Task entity first, then link it back to the Project. I'm using @ManyToOne here because, from the perspective of a single task, it belongs to exactly one project.

@Entity
public class Task {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String description;
    private boolean isCompleted;

    @ManyToOne
    @JoinColumn(name = "project_id")
    private Project project;

    protected Task() {}

    public Task(String description, Project project) {
        this.description = description;
        this.project = project;
    }
    
    // Getters and setters...
}

The @JoinColumn is key. It tells the database to create a column called project_id in the Task table to store the foreign key. If I left that out, JPA would try to create a join table, which is overkill for a simple one-to-many relationship. I prefer keeping the schema lean.

Finally, if I want to be able to call project.getTasks(), I can add a @OneToMany list to the Project class, using the mappedBy attribute to tell JPA that the Task entity is the one actually controlling the relationship.




📋 Practical Task

Mapping a Customer and Order Relationship

Now it's your turn to apply these concepts. You need to build a small persistence model for an e-commerce system.

  • Create a Customer entity with an id, fullName, and email.
  • Create an Order entity with an id, orderDate, and totalAmount.
  • Establish a relationship where one Customer can have multiple Orders, but each Order belongs to only one Customer.
  • Ensure both entities have the necessary constructors to avoid the reflection errors we discussed.
  • Use @JoinColumn to explicitly name the foreign key in the Order table as customer_id.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.