Skip to main content

Week 5 Day 4 — Entity Relationships

Goal

Today I want to understand entity relationships in JPA and Spring Data JPA.

Main questions:

  1. What is an entity relationship?
  2. What is @ManyToOne?
  3. What is @OneToMany?
  4. What is the owning side?
  5. What does mappedBy mean?
  6. What is lazy loading?
  7. What is eager loading?
  8. What is cascade?
  9. What is orphan removal?
  10. What is the N+1 query problem?
  11. How should REST APIs handle entity relationships?
  12. What are common exam traps?

1. Quick Review from Week 5 Day 3

In Day 3, I learned:

  • A transaction is an all-or-nothing unit of work.
  • @Transactional defines a transaction boundary.
  • Transactions usually belong in the service layer.
  • The persistence context tracks managed entities.
  • Dirty checking updates managed entities automatically.
  • Runtime exceptions roll back by default.
  • Checked exceptions do not roll back by default.
  • readOnly = true is for read operations.
  • Lazy loading usually needs an open persistence context.

Memory sentence:

Persistence context tracks managed entities inside a transaction.

Today I learn what happens when entities are connected to other entities.


2. What Is an Entity Relationship?

An entity relationship means:

One entity is connected to another entity.

Example business relationship:

A client has many tasks.
A task belongs to one client.

Java model:

ClientEntity
TaskEntity

Database model:

clients table
tasks table

Relationship:

tasks.client_id references clients.id

Memory sentence:

Entity relationships map Java object connections to database foreign keys.


3. Main Relationship Types

JPA has these relationship annotations:

@OneToOne
@OneToMany
@ManyToOne
@ManyToMany

Common meanings:

AnnotationMeaning
@ManyToOnemany child rows belong to one parent
@OneToManyone parent has many children
@OneToOneone entity connected to one entity
@ManyToManymany entities connected to many entities

Most common in business apps:

@ManyToOne
@OneToMany

Example:

Many tasks belong to one client.
One client has many tasks.

4. Database Foreign Key First

Before Java annotations, understand the database.

Tables:

CREATE TABLE clients (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL
);

CREATE TABLE tasks (
id BIGSERIAL PRIMARY KEY,
client_id BIGINT NOT NULL,
title VARCHAR(255) NOT NULL,
status VARCHAR(50) NOT NULL,

CONSTRAINT fk_tasks_client
FOREIGN KEY (client_id)
REFERENCES clients(id)
);

Important:

The foreign key is in the tasks table.

Why?

Each task belongs to one client.
So each task row stores client_id.

Memory sentence:

In a many-to-one relationship, the many side usually has the foreign key.


5. @ManyToOne

@ManyToOne maps many entities to one entity.

Example:

Many tasks -> one client

Java:

@Entity
@Table(name = "tasks")
public class TaskEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String title;

private String status;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "client_id", nullable = false)
private ClientEntity client;

protected TaskEntity() {
}

public TaskEntity(String title, String status, ClientEntity client) {
this.title = title;
this.status = status;
this.client = client;
}
}

Meaning:

TaskEntity has a many-to-one relationship to ClientEntity.
The foreign key column is client_id.

Memory sentence:

@ManyToOne is usually placed on the child side.


6. @JoinColumn

@JoinColumn defines the foreign key column.

Example:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "client_id", nullable = false)
private ClientEntity client;

Meaning:

tasks.client_id points to clients.id

name = "client_id" means:

foreign key column name in the tasks table

nullable = false means:

a task must have a client

Memory sentence:

@JoinColumn tells JPA which foreign key column stores the relationship.


7. @OneToMany

@OneToMany maps one entity to many child entities.

Example:

One client -> many tasks

Java:

@Entity
@Table(name = "clients")
public class ClientEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

@OneToMany(mappedBy = "client")
private List<TaskEntity> tasks = new ArrayList<>();

protected ClientEntity() {
}

public ClientEntity(String name) {
this.name = name;
}
}

Meaning:

ClientEntity has many TaskEntity objects.
TaskEntity.client owns the relationship.

Memory sentence:

@OneToMany(mappedBy = "...") usually points to the field on the many side.


8. Full Relationship Example

Client:

@Entity
@Table(name = "clients")
public class ClientEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String name;

@OneToMany(mappedBy = "client")
private List<TaskEntity> tasks = new ArrayList<>();

protected ClientEntity() {
}

public ClientEntity(String name) {
this.name = name;
}

public Long getId() {
return id;
}

public String getName() {
return name;
}

public List<TaskEntity> getTasks() {
return tasks;
}
}

Task:

@Entity
@Table(name = "tasks")
public class TaskEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String title;

@Column(nullable = false)
private String status;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "client_id", nullable = false)
private ClientEntity client;

protected TaskEntity() {
}

public TaskEntity(String title, String status, ClientEntity client) {
this.title = title;
this.status = status;
this.client = client;
}

public Long getId() {
return id;
}

public String getTitle() {
return title;
}

public String getStatus() {
return status;
}

public ClientEntity getClient() {
return client;
}
}

9. Owning Side

In JPA, the owning side is the side that controls the database relationship.

Simple definition:

The owning side is the side that owns the foreign key mapping.

In this example:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "client_id")
private ClientEntity client;

TaskEntity is the owning side because:

tasks table has client_id foreign key

Memory sentence:

The owning side is usually the side with @JoinColumn.


10. Inverse Side

The inverse side is the other side of a bidirectional relationship.

Example:

@OneToMany(mappedBy = "client")
private List<TaskEntity> tasks = new ArrayList<>();

This means:

ClientEntity.tasks is not the owning side.
TaskEntity.client owns the relationship.

mappedBy = "client" points to this field:

private ClientEntity client;

inside TaskEntity.

Memory sentence:

mappedBy means: the other side owns the relationship.


11. mappedBy

Example:

@OneToMany(mappedBy = "client")
private List<TaskEntity> tasks;

The value "client" means:

Look at the field named client in TaskEntity.
That field owns the relationship.

Common mistake:

@OneToMany(mappedBy = "client_id")

Wrong.

Why?

mappedBy uses Java field name, not database column name.

Correct:

@OneToMany(mappedBy = "client")

Memory sentence:

mappedBy uses the Java field name on the owning side.


12. Bidirectional Relationship

A bidirectional relationship means both entities reference each other.

Client has tasks:

@OneToMany(mappedBy = "client")
private List<TaskEntity> tasks;

Task has client:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "client_id")
private ClientEntity client;

This lets Java code navigate:

client.getTasks()
task.getClient()

But bidirectional relationships are more complex.

Memory sentence:

Bidirectional means both sides have references.


13. Unidirectional Relationship

A unidirectional relationship means only one side knows about the other.

Example:

@Entity
public class TaskEntity {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "client_id", nullable = false)
private ClientEntity client;
}

But ClientEntity has no tasks list.

This is simpler.

For many applications, this is enough.

Memory sentence:

Use bidirectional relationships only when you really need navigation from both sides.


14. Keeping Both Sides Consistent

In bidirectional relationships, Java object references must be kept consistent.

Example helper method:

@Entity
@Table(name = "clients")
public class ClientEntity {

@OneToMany(mappedBy = "client", cascade = CascadeType.ALL, orphanRemoval = true)
private List<TaskEntity> tasks = new ArrayList<>();

public void addTask(TaskEntity task) {
tasks.add(task);
task.setClient(this);
}

public void removeTask(TaskEntity task) {
tasks.remove(task);
task.setClient(null);
}
}

Task:

@Entity
@Table(name = "tasks")
public class TaskEntity {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "client_id")
private ClientEntity client;

void setClient(ClientEntity client) {
this.client = client;
}
}

Why helper methods?

They keep both Java sides synchronized.

Memory sentence:

In bidirectional relationships, update both sides in Java.


15. Lazy Loading

Lazy loading means:

Load related data only when it is accessed.

Example:

@ManyToOne(fetch = FetchType.LAZY)
private ClientEntity client;

When loading a task:

TaskEntity task = taskRepository.findById(id).orElseThrow();

JPA may load:

task row

but not immediately load:

client row

Only when code calls:

task.getClient().getName();

then JPA may load the client.

Memory sentence:

Lazy loading delays loading related data until needed.


16. Eager Loading

Eager loading means:

Load related data immediately together with the entity.

Example:

@ManyToOne(fetch = FetchType.EAGER)
private ClientEntity client;

When loading a task, JPA also loads the client.

This can sound convenient, but it can cause performance problems.

Memory sentence:

Eager loading loads related data immediately.


17. Lazy vs Eager

TopicLazyEager
When loadedwhen accessedimmediately
Performanceoften better by defaultcan load too much
Risklazy loading exceptionN+1 and large queries
Controlexplicit fetching when neededharder to control
Common recommendationprefer for associationsuse carefully

Important practical rule:

Prefer LAZY by default.
Fetch explicitly when needed.

18. Default Fetch Types

JPA defaults are important exam traps.

Common defaults:

@ManyToOne -> EAGER by default
@OneToOne -> EAGER by default
@OneToMany -> LAZY by default
@ManyToMany -> LAZY by default

Because @ManyToOne defaults to eager, many developers explicitly write:

@ManyToOne(fetch = FetchType.LAZY)

Memory sentence:

Many-to-one is eager by default, so explicitly set it to lazy in many real projects.


19. LazyInitializationException

Lazy loading needs an open persistence context.

Example problem:

public TaskDto getTask(Long id) {
TaskEntity task = taskRepository.findById(id).orElseThrow();

return new TaskDto(
task.getId(),
task.getTitle(),
task.getClient().getName()
);
}

If this happens outside an active transaction or persistence context, accessing:

task.getClient().getName()

may fail with lazy loading error.

Common name:

LazyInitializationException

Memory sentence:

Lazy relationships must be accessed while the persistence context is open.


20. Good Practice: Map to DTO Inside Transaction

Good service:

@Transactional(readOnly = true)
public TaskDto getTask(Long id) {
TaskEntity task = taskRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Task", id));

return new TaskDto(
task.getId(),
task.getTitle(),
task.getStatus(),
task.getClient().getName()
);
}

Why better?

mapping happens while transaction/persistence context is open
DTO is returned to controller
controller does not touch lazy entity relationships

Memory sentence:

Load and map needed data in the service layer.


21. Do Not Return Entities Directly from REST APIs

Bad:

@GetMapping("/api/clients/{id}")
public ClientEntity getClient(@PathVariable Long id) {
return clientRepository.findById(id).orElseThrow();
}

Problems:

lazy loading during JSON serialization
N+1 queries during serialization
infinite recursion
sensitive fields exposed
API coupled to database model
unexpected huge JSON response

Better:

@GetMapping("/api/clients/{id}")
public ClientDto getClient(@PathVariable Long id) {
return clientService.findById(id);
}

Memory sentence:

Return DTOs, not entities.


22. Infinite JSON Recursion

Bidirectional relationship:

ClientEntity -> tasks
TaskEntity -> client

If Jackson serializes entity directly:

client -> tasks -> task -> client -> tasks -> task -> ...

This can cause infinite recursion.

DTOs solve this cleanly.

Example DTO:

public record ClientDto(
Long id,
String name,
List<TaskSummaryDto> tasks
) {
}

public record TaskSummaryDto(
Long id,
String title,
String status
) {
}

No circular reference.


23. Cascade

Cascade means:

Apply an operation from a parent entity to related child entities.

Example:

@OneToMany(mappedBy = "client", cascade = CascadeType.ALL)
private List<TaskEntity> tasks = new ArrayList<>();

If I save a client, JPA may also save its tasks.

Common cascade types:

PERSIST
MERGE
REMOVE
REFRESH
DETACH
ALL

Memory sentence:

Cascade propagates entity operations to related entities.


24. CascadeType.PERSIST

Example:

@OneToMany(mappedBy = "client", cascade = CascadeType.PERSIST)
private List<TaskEntity> tasks = new ArrayList<>();

Meaning:

when persisting client, also persist new tasks

Useful when child lifecycle belongs to parent.


25. CascadeType.REMOVE

Example:

@OneToMany(mappedBy = "client", cascade = CascadeType.REMOVE)
private List<TaskEntity> tasks = new ArrayList<>();

Meaning:

when deleting client, also delete tasks

Danger:

can delete many rows accidentally

Use carefully.

Memory sentence:

Cascade remove is powerful and dangerous.


26. CascadeType.ALL

@OneToMany(mappedBy = "client", cascade = CascadeType.ALL)
private List<TaskEntity> tasks = new ArrayList<>();

Means all cascade operations:

persist
merge
remove
refresh
detach

This is common in examples, but in real projects it must be used carefully.

Memory sentence:

Do not use CascadeType.ALL automatically everywhere.


27. Cascade on @ManyToOne Trap

Dangerous:

@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "client_id")
private ClientEntity client;

Why dangerous?

Many tasks share one client.
Deleting one task could cascade remove to the client.
That could affect other tasks.

Usually avoid cascade from child to parent.

Better:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "client_id", nullable = false)
private ClientEntity client;

Memory sentence:

Be very careful with cascade on @ManyToOne.


28. Orphan Removal

Orphan removal means:

If a child entity is removed from the parent collection, delete it from the database.

Example:

@OneToMany(
mappedBy = "client",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<TaskEntity> tasks = new ArrayList<>();

If I do:

client.removeTask(task);

then the task can be deleted from the database.

Memory sentence:

orphanRemoval = true deletes children removed from the parent collection.


29. Cascade Remove vs Orphan Removal

TopicCascade RemoveOrphan Removal
Triggerparent is removedchild removed from parent collection
Exampledelete client -> delete tasksremove task from client.tasks -> delete task
Use casechild cannot exist without parentchild lifecycle belongs to parent
Riskdeletes many childrenaccidental delete from collection

Memory sentence:

Cascade remove reacts to parent delete. Orphan removal reacts to child removal from collection.


30. When Is Orphan Removal Good?

Good example:

Order -> OrderItem
Invoice -> InvoiceLine
Client -> ClientContact maybe

If an OrderItem cannot exist without an Order, orphan removal makes sense.

Maybe not good:

Client -> Task

Why?

Maybe tasks should be archived, reassigned, or kept for audit.

Always ask:

Can the child exist without the parent?
Should removing it from collection delete it permanently?

Memory sentence:

Use orphan removal only when child lifecycle truly belongs to parent.


31. N+1 Query Problem

N+1 problem means:

One query loads parent records, then additional queries load related records one by one.

Example:

@Transactional(readOnly = true)
public List<TaskDto> listTasks() {
List<TaskEntity> tasks = taskRepository.findByStatus("OPEN");

return tasks.stream()
.map(task -> new TaskDto(
task.getId(),
task.getTitle(),
task.getClient().getName()
))
.toList();
}

Potential SQL:

select * from tasks where status = 'OPEN'; -- 1 query

select * from clients where id = 1; -- query 1
select * from clients where id = 2; -- query 2
select * from clients where id = 3; -- query 3
...

If there are 100 tasks:

1 query for tasks
100 queries for clients
= 101 queries

This is N+1.

Memory sentence:

N+1 means one main query plus one query per row for related data.


32. Why N+1 Is Bad

N+1 causes:

many database round trips
slow API responses
high database load
performance problems in production
hard-to-see bugs

Small dev database:

looks fine

Production database:

very slow

Memory sentence:

N+1 is a performance bug that often appears only with real data.


33. How to Detect N+1

Enable SQL logs in dev:

spring:
jpa:
show-sql: true

Better logging:

logging:
level:
org.hibernate.SQL: DEBUG
org.hibernate.orm.jdbc.bind: TRACE

Look for:

one query for list
many similar queries for related entity

Memory sentence:

SQL logs reveal N+1 problems.


34. Fix N+1 with Fetch Join

Repository:

@Query("""
select t
from TaskEntity t
join fetch t.client
where t.status = :status
""")
List<TaskEntity> findByStatusWithClient(@Param("status") String status);

Meaning:

Load tasks and clients in one query.

Service:

@Transactional(readOnly = true)
public List<TaskDto> listOpenTasks() {
return taskRepository.findByStatusWithClient("OPEN")
.stream()
.map(task -> new TaskDto(
task.getId(),
task.getTitle(),
task.getClient().getName()
))
.toList();
}

Memory sentence:

Fetch join loads needed relationships together.


35. join vs join fetch

JPQL join:

select t from TaskEntity t join t.client c

This joins for filtering.

JPQL fetch join:

select t from TaskEntity t join fetch t.client

This joins and loads the relationship.

Memory sentence:

join helps query. join fetch also loads the association.


36. Fix N+1 with EntityGraph

Repository:

@EntityGraph(attributePaths = "client")
List<TaskEntity> findByStatus(String status);

Meaning:

When finding tasks by status, also fetch client.

This is another way to define a fetch plan.

Memory sentence:

@EntityGraph tells Spring Data JPA which associations to fetch.


37. Fix N+1 with DTO Projection

Instead of loading full entities:

@Query("""
select new de.klarsync.tasks.TaskListDto(
t.id,
t.title,
t.status,
c.name
)
from TaskEntity t
join t.client c
where t.status = :status
""")
List<TaskListDto> findTaskListDtos(@Param("status") String status);

DTO:

public record TaskListDto(
Long id,
String title,
String status,
String clientName
) {
}

This returns exactly what the API needs.

Memory sentence:

DTO projection can avoid loading unnecessary entity graphs.


38. Fetch Join with Pagination Trap

Be careful with fetch joining collections and pagination.

Example risky query:

@Query("""
select c
from ClientEntity c
join fetch c.tasks
""")
Page<ClientEntity> findAllWithTasks(Pageable pageable);

Problem:

fetch joining collection relationships with pagination can produce wrong or inefficient results

Safer options:

use DTO projection
fetch only many-to-one relationships
use two-step query
use EntityGraph carefully
batch fetching
custom query design

Memory sentence:

Be careful with collection fetch joins and pagination.


39. @ManyToOne Usually Safe to Fetch Join

Fetching many-to-one is usually easier:

@Query("""
select t
from TaskEntity t
join fetch t.client
where t.status = :status
""")
Page<TaskEntity> findByStatusWithClient(
@Param("status") String status,
Pageable pageable
);

Because each task has one client.

But always test generated SQL and performance.


40. Relationship Design for REST APIs

Bad API shape:

{
"id": 1,
"name": "Client A",
"tasks": [
{
"id": 10,
"title": "Task 1",
"client": {
"id": 1,
"name": "Client A",
"tasks": [...]
}
}
]
}

Better DTO shape:

{
"id": 1,
"name": "Client A",
"tasks": [
{
"id": 10,
"title": "Task 1",
"status": "OPEN"
}
]
}

Memory sentence:

API response shape should be designed with DTOs, not automatically generated from entity relationships.


41. Good DTO Examples

Client detail:

public record ClientDetailDto(
Long id,
String name,
List<TaskSummaryDto> tasks
) {
}

Task summary:

public record TaskSummaryDto(
Long id,
String title,
String status
) {
}

Task detail:

public record TaskDetailDto(
Long id,
String title,
String status,
Long clientId,
String clientName
) {
}

Notice:

DTOs include only what the endpoint needs.

42. Example: Create Task for Client

Controller:

@PostMapping("/api/clients/{clientId}/tasks")
public ResponseEntity<TaskDetailDto> createTask(
@PathVariable Long clientId,
@Valid @RequestBody CreateTaskRequest request
) {
TaskDetailDto created = taskService.createTask(clientId, request);
URI location = URI.create("/api/tasks/" + created.id());

return ResponseEntity.created(location).body(created);
}

Service:

@Transactional
public TaskDetailDto createTask(Long clientId, CreateTaskRequest request) {
ClientEntity client = clientRepository.findById(clientId)
.orElseThrow(() -> new ResourceNotFoundException("Client", clientId));

TaskEntity task = new TaskEntity(
request.title(),
"OPEN",
client
);

TaskEntity saved = taskRepository.save(task);

return new TaskDetailDto(
saved.getId(),
saved.getTitle(),
saved.getStatus(),
client.getId(),
client.getName()
);
}

Important:

Find parent entity.
Set parent on child entity.
Save child entity.
Return DTO.

43. Example: List Tasks with Client Name

Repository:

@Query("""
select t
from TaskEntity t
join fetch t.client
where t.status = :status
order by t.createdAt desc
""")
List<TaskEntity> findByStatusWithClient(@Param("status") String status);

Service:

@Transactional(readOnly = true)
public List<TaskDetailDto> listTasks(String status) {
return taskRepository.findByStatusWithClient(status)
.stream()
.map(task -> new TaskDetailDto(
task.getId(),
task.getTitle(),
task.getStatus(),
task.getClient().getId(),
task.getClient().getName()
))
.toList();
}

This avoids N+1 for client names.


44. Example: Client with Task Summaries

Repository:

@Query("""
select c
from ClientEntity c
left join fetch c.tasks
where c.id = :id
""")
Optional<ClientEntity> findByIdWithTasks(@Param("id") Long id);

Service:

@Transactional(readOnly = true)
public ClientDetailDto getClientDetail(Long id) {
ClientEntity client = clientRepository.findByIdWithTasks(id)
.orElseThrow(() -> new ResourceNotFoundException("Client", id));

List<TaskSummaryDto> tasks = client.getTasks()
.stream()
.map(task -> new TaskSummaryDto(
task.getId(),
task.getTitle(),
task.getStatus()
))
.toList();

return new ClientDetailDto(
client.getId(),
client.getName(),
tasks
);
}

This can be fine for one client detail endpoint.

For paginated clients, be more careful with collection fetch joins.


45. @ManyToMany Warning

@ManyToMany exists, but use it carefully.

Example:

User <-> Role
Student <-> Course

In real business apps, a join entity is often better.

Instead of direct many-to-many:

@ManyToMany
private Set<RoleEntity> roles;

Often better:

UserEntity
UserRoleEntity
RoleEntity

Why?

join table may need extra fields
createdAt
assignedBy
status
metadata
better control

Memory sentence:

For complex many-to-many relationships, use a join entity.


46. Entity Equals and HashCode Warning

Entity relationships often use collections.

Be careful with equals() and hashCode().

Bad design can cause:

duplicate collection problems
lazy loading problems
unexpected behavior in Set
issues before ID is generated

Simple beginner rule:

Be careful using entities in HashSet.
Do not include lazy relationships in equals/hashCode.

For now, use List in examples and avoid complicated equality logic until needed.


47. Common Exam Traps

Trap 1

@ManyToOne is usually the owning side.


Trap 2

The owning side usually has @JoinColumn.


Trap 3

mappedBy uses the Java field name, not the database column name.


Trap 4

@ManyToOne is eager by default, so many projects explicitly set it to lazy.


Trap 5

Lazy loading needs an open persistence context.


Trap 6

Do not return entities directly from REST controllers.


Trap 7

Bidirectional relationships can cause infinite JSON recursion.


Trap 8

Cascade does not mean database foreign key cascade only. It is JPA entity operation cascade.


Trap 9

Do not use CascadeType.ALL everywhere automatically.


Trap 10

Be careful with cascade on @ManyToOne.


Trap 11

Orphan removal deletes child rows when removed from parent collection.


Trap 12

N+1 means one main query plus many relationship queries.


Trap 13

Fetch join can fix N+1, but collection fetch joins with pagination are dangerous.


Trap 14

DTO projections can be better than loading large entity graphs.


Trap 15

Use DTOs to design API response shape.


48. Real Exam Question: @ManyToOne

Question:

What does @ManyToOne mean?

Answer:

@ManyToOne means many instances of one entity are associated with one instance of another entity. For example, many tasks belong to one client.


49. Real Exam Question: @OneToMany

Question:

What does @OneToMany mean?

Answer:

@OneToMany means one entity has a collection of many related entities. For example, one client has many tasks.


50. Real Exam Question: Owning Side

Question:

What is the owning side of a JPA relationship?

Answer:

The owning side is the side that controls the database relationship, usually the side with the foreign key and @JoinColumn.


51. Real Exam Question: mappedBy

Question:

What does mappedBy mean?

Answer:

mappedBy marks the inverse side of a bidirectional relationship and points to the Java field name on the owning side.


52. Real Exam Question: Lazy Loading

Question:

What is lazy loading?

Answer:

Lazy loading means related entities are loaded only when they are accessed, not immediately when the parent entity is loaded.


53. Real Exam Question: Eager Loading

Question:

What is eager loading?

Answer:

Eager loading means related entities are loaded immediately together with the owning entity.


54. Real Exam Question: Default Fetch

Question:

What is the default fetch type for @ManyToOne?

Answer:

@ManyToOne is eager by default, so many real projects explicitly configure it as lazy.


55. Real Exam Question: Cascade

Question:

What is cascade?

Answer:

Cascade means that an entity operation, such as persist, merge, or remove, is propagated from one entity to its related entities.


56. Real Exam Question: Orphan Removal

Question:

What does orphanRemoval = true do?

Answer:

It deletes a child entity when it is removed from the parent collection, assuming the relationship is managed correctly.


57. Real Exam Question: N+1

Question:

What is the N+1 query problem?

Answer:

The N+1 problem happens when one query loads a list of entities, and then one additional query is executed for each entity to load a related association.


58. Real Exam Question: Fetch Join

Question:

How can a fetch join help with N+1?

Answer:

A fetch join loads the main entity and the needed association in the same query, reducing many additional relationship queries.


59. Interview Answer

Question:

Explain @ManyToOne and @OneToMany.

Good answer:

@ManyToOne means many child entities point to one parent entity, such as many tasks belonging to one client. This side usually has the foreign key and @JoinColumn, so it is often the owning side. @OneToMany is the parent-side collection, such as a client having many tasks. In a bidirectional relationship, @OneToMany(mappedBy = "client") points to the client field in the child entity.


60. Interview Answer

Question:

What is the owning side in JPA?

Good answer:

The owning side is the side that controls the relationship in the database. In a typical many-to-one relationship, the many side owns the relationship because it has the foreign key column. For example, TaskEntity has client_id, so TaskEntity.client is the owning side. The other side uses mappedBy.


61. Interview Answer

Question:

What is lazy loading and why can it be dangerous?

Good answer:

Lazy loading means JPA loads related entities only when they are accessed. It is useful because it avoids loading unnecessary data. But it can be dangerous if the persistence context is already closed, which can cause lazy loading exceptions. It can also cause N+1 query problems if code loops through many entities and accesses a lazy association one by one.


62. Interview Answer

Question:

What is the N+1 query problem?

Good answer:

N+1 happens when one query loads a list of entities, and then one extra query is executed for each entity to load related data. For example, one query loads 100 tasks, and then 100 more queries load each task’s client. This creates 101 queries and can seriously hurt performance. It can be fixed with fetch joins, entity graphs, DTO projections, or better query design.


63. Interview Answer

Question:

What is cascade and when should you be careful?

Good answer:

Cascade means that operations on one entity are propagated to related entities. For example, saving a parent can also save its children. It is useful when the child lifecycle belongs to the parent, like order items inside an order. But it is dangerous to use blindly, especially CascadeType.ALL or cascade remove. I avoid cascade from child to parent, such as @ManyToOne(cascade = ALL), because deleting a child could delete a shared parent.


64. Interview Answer

Question:

Why should REST APIs not return JPA entities directly?

Good answer:

Returning entities directly can expose internal database structure, leak sensitive fields, trigger lazy loading during JSON serialization, cause infinite recursion in bidirectional relationships, and create N+1 performance problems. I prefer mapping entities to DTOs inside the service layer and returning DTOs from controllers.


65. Tiny Code Practice

Entity relationship:

@Entity
public class TaskEntity {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "client_id", nullable = false)
private ClientEntity client;
}
@Entity
public class ClientEntity {

@OneToMany(mappedBy = "client")
private List<TaskEntity> tasks = new ArrayList<>();
}

Questions:

  1. Which side owns the relationship?
  2. What does client_id mean?
  3. What does mappedBy = "client" point to?
  4. Is TaskEntity.client lazy or eager?
  5. Should this entity be returned directly from REST controller?

Answers:

  1. TaskEntity.client owns the relationship.
  2. It is the foreign key column in the tasks table.
  3. It points to the client field in TaskEntity.
  4. Lazy, because fetch = FetchType.LAZY.
  5. Usually no. Return DTOs.

66. Tiny Bug Practice 1

Problem:

@OneToMany(mappedBy = "client_id")
private List<TaskEntity> tasks;

Question:

What is wrong?

Answer:

mappedBy must use the Java field name on the owning side, not the database column name.

Correct:

@OneToMany(mappedBy = "client")
private List<TaskEntity> tasks;

67. Tiny Bug Practice 2

Problem:

@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "client_id")
private ClientEntity client;

Question:

Why is this dangerous?

Answer:

Many tasks can share one client. Cascading all operations from task to client can accidentally update or delete the shared client when operating on a task. Avoid cascade from child to parent unless you have a very specific reason.


68. Tiny Bug Practice 3

Problem:

@GetMapping("/api/clients/{id}")
public ClientEntity getClient(@PathVariable Long id) {
return clientRepository.findById(id).orElseThrow();
}

Question:

What can go wrong?

Answer:

Returning the entity directly can cause lazy loading errors, infinite JSON recursion, N+1 queries during serialization, sensitive data exposure, and tight coupling between API and database model. Return a DTO instead.


Practice Questions and Answers

Question 1

What is an entity relationship?

Answer:

An entity relationship maps a connection between Java entities to a database relationship, usually through a foreign key.


Question 2

What does @ManyToOne mean?

Answer:

@ManyToOne means many instances of one entity are associated with one instance of another entity, such as many tasks belonging to one client.


Question 3

What does @OneToMany mean?

Answer:

@OneToMany means one entity has a collection of many related entities, such as one client having many tasks.


Question 4

Where is the foreign key usually located in a many-to-one relationship?

Answer:

The foreign key is usually located on the many side, such as tasks.client_id.


Question 5

What does @JoinColumn do?

Answer:

@JoinColumn defines the foreign key column used for the relationship.


Question 6

What is the owning side?

Answer:

The owning side is the side that controls the database relationship, usually the side with @JoinColumn.


Question 7

What does mappedBy mean?

Answer:

mappedBy marks the inverse side of a bidirectional relationship and points to the field on the owning side.


Question 8

Does mappedBy use Java field name or database column name?

Answer:

mappedBy uses the Java field name, not the database column name.


Question 9

What is lazy loading?

Answer:

Lazy loading means related data is loaded only when it is accessed.


Question 10

What is eager loading?

Answer:

Eager loading means related data is loaded immediately together with the entity.


Question 11

What is the default fetch type for @ManyToOne?

Answer:

@ManyToOne is eager by default.


Question 12

What can cause LazyInitializationException?

Answer:

It can happen when code accesses a lazy relationship after the persistence context is closed.


Question 13

What is cascade?

Answer:

Cascade means entity operations are propagated from one entity to related entities.


Question 14

Why is CascadeType.ALL dangerous if used everywhere?

Answer:

Because it can propagate persist, merge, remove, refresh, and detach operations unexpectedly, possibly deleting or modifying related data accidentally.


Question 15

What is orphan removal?

Answer:

Orphan removal deletes a child entity when it is removed from the parent collection.


Question 16

What is the difference between cascade remove and orphan removal?

Answer:

Cascade remove deletes children when the parent is deleted. Orphan removal deletes a child when it is removed from the parent collection.


Question 17

What is the N+1 query problem?

Answer:

N+1 is when one query loads a list and then one additional query is executed for each item to load related data.


Question 18

How can fetch join help with N+1?

Answer:

A fetch join loads the main entity and the needed association in the same query, reducing extra queries.


Question 19

Why can bidirectional relationships cause JSON recursion?

Answer:

Because parent references child and child references parent, so JSON serialization can loop endlessly.


Question 20

Why should REST APIs usually return DTOs instead of entities?

Answer:

DTOs avoid exposing internal database structure, prevent lazy loading and recursion problems, control response shape, and protect sensitive fields.

Final Memory Sentences

  • Entity relationships map Java object connections to database foreign keys.
  • @ManyToOne usually belongs on the child side.
  • @OneToMany usually represents a parent collection.
  • The many side usually owns the foreign key.
  • The owning side usually has @JoinColumn.
  • mappedBy points to the Java field on the owning side.
  • mappedBy does not use database column names.
  • Lazy loading loads related data only when accessed.
  • Eager loading loads related data immediately.
  • @ManyToOne is eager by default.
  • Prefer lazy associations and explicit fetch plans in many real projects.
  • Lazy loading needs an open persistence context.
  • Do not return entities directly from REST APIs.
  • Bidirectional relationships can cause infinite JSON recursion.
  • Cascade propagates entity operations.
  • Do not use CascadeType.ALL blindly.
  • Be careful with cascade on @ManyToOne.
  • Orphan removal deletes children removed from parent collections.
  • N+1 means one main query plus many relationship queries.
  • Fetch join, entity graph, and DTO projection can help avoid N+1.