The fastest way to learn Hibernate and JPA is to build one small, realistic data project, map its tables to entities by hand, and then read the SQL your code generates. This article gives you a 14-day worksheet for a library-lending project, a table-to-entity mapping sheet, a lazy loading guide and a checklist of N+1 query symptoms to look for in your logs.
Why a small data project teaches JPA better than reading about it
JPA (the Jakarta Persistence API) is a specification, and Hibernate is the most widely used implementation of it. Most of what trips students up is invisible in the code: how many SQL statements actually run, when a collection is loaded, and why a save did not reach the database. A small project with four or five tables makes those behaviours visible, because you can count the queries on your own screen.
Keep the scope tight. A project with five tables, two relationships of each major kind and a handful of queries teaches more than a sprawling one you cannot reason about. The worksheet below is built around that idea.
The project: a small library-lending database
Use a library-lending system. It is familiar, it has natural one-to-many and many-to-many relationships, and it is easy to seed with test data. The schema has five tables:
- author: id, name
- book: id, title, isbn, author_id (foreign key to author)
- member: id, full_name, email
- loan: id, member_id, book_id, loaned_on, returned_on
- category and a join table book_category: book_id, category_id (many-to-many)
Use any relational database you can run locally, such as PostgreSQL, MySQL or H2 for quick starts. Write the table definitions in SQL first, then the entities. Doing it in that order forces you to think about the mapping instead of letting a tool generate it for you.
Free 2-Week CareerSwami Internship
Online, 14 days, with a free offer letter and a free completion certificate once you finish the work. Check with your college that it fits your requirement before you enrol.
Mapping sheet: from each table to an entity
Fill in a sheet like the one below before writing any Java. It is illustrative, so adapt column names to your own schema.
| Table | Entity class | Key mapping decisions | Relationship annotation |
|---|---|---|---|
| author | Author | Primary key with @Id and a generation strategy; name column |
@OneToMany(mappedBy = "author") to books |
| book | Book | isbn as unique column; title not null | @ManyToOne(fetch = LAZY) to author; @ManyToMany to categories |
| member | Member | email unique; no business logic in the entity yet | @OneToMany(mappedBy = "member") to loans |
| loan | Loan | Dates as LocalDate; returned_on nullable |
@ManyToOne(fetch = LAZY) to member and to book |
| category / book_category | Category | Join table declared with @JoinTable on the owning side |
@ManyToMany(mappedBy = "categories") on Category |
Questions to answer for every entity
- Which side owns the relationship (the side with the foreign key column)?
- Is the association required or optional, and does the column allow null?
- Do you need the association in both directions, or will one direction do?
- Which fetch type will you use, and why?
- Have you defined
equalsandhashCodein a way that does not trigger lazy loading?
Most beginner bugs come from the first question. If you put mappedBy on the wrong side, the foreign key is never written, and nothing in the code looks wrong.
Relationships and cascades: decisions that cause real bugs
- One-to-many and many-to-one: make the many-to-one side the owner. Add helper methods such as
addBookon Author that set both sides of the link. - Many-to-many: prefer
SetoverListfor the collection to avoid unnecessary row churn. If the join table needs its own columns, promote it to an entity, as with Loan. - Cascade: use cascade only where the child cannot exist without the parent. Cascading remove from Book to Category would delete shared categories, which is almost never what you want.
- Orphan removal: try it on a true parent-child pair and observe the delete statements it produces.
Lazy loading: what it is and where it bites
Lazy loading means Hibernate defers fetching an association until your code first touches it. By default, @OneToMany and @ManyToMany are lazy, while @ManyToOne and @OneToOne are eager. Many teams set @ManyToOne to lazy explicitly so that loading one entity does not silently pull in a chain of others.
Lazy loading produces two classic problems:
- LazyInitializationException: you access a lazy collection after the persistence session has closed, typically in a controller or while serialising to JSON. The fix is to load what you need inside the transaction, by using a fetch join or an entity graph, or to return a DTO instead of the entity.
- Hidden queries: inside a loop, each lazy access can fire a separate SQL statement. That leads directly to the N+1 problem.
The N+1 problem explained with the library schema
Suppose you fetch all authors with one query, then print each author’s books. Hibernate runs one statement for the authors, then one more per author to load the books. With 50 authors that is 51 statements. The result looks correct on screen, which is why the problem hides until data grows.
The usual remedies, which you should try one at a time and compare in the logs:
- Fetch join:
select a from Author a join fetch a.booksloads both in one statement. Be careful when combining it with pagination. - Entity graph: declare which associations to load for a particular query without changing the default mapping.
- Batch fetching: Hibernate’s batch size setting loads lazy collections for several parents at once, turning N+1 into roughly N/batch + 1.
- Projection or DTO query: select only the columns the screen needs.
Checklist: N+1 symptoms to spot in your SQL logs
Turn on SQL logging in your development configuration (in Spring Boot this is a property for showing SQL, and Hibernate also offers statistics). Then work through this checklist while exercising one screen or endpoint at a time.
| Symptom in the log | What it usually means | First thing to try |
|---|---|---|
The same select ... from book where author_id = ? repeated many times with different ids |
Lazy collection touched inside a loop | Fetch join or batch fetching |
| Statement count grows with row count, not with the number of screens | Classic N+1 | Count statements for 5 rows, then 50 rows, and compare |
| Many single-row selects by primary key right after one big select | Eager @ManyToOne loading each parent separately |
Set the association to lazy and join fetch where needed |
| A list page triggers selects on tables it does not display | Eager associations or entity serialisation walking the graph | Return a DTO or projection |
| Response time rises sharply after seeding more test data | Query count scaling linearly | Enable Hibernate statistics and read the statement count |
| Duplicate parent rows after adding a fetch join | Join over a collection multiplies rows | Use distinct or a different fetch strategy |
| Warning about pagination applied in memory | Fetch join of a collection combined with paging | Page the parent ids first, then fetch the children |
Worked example (illustrative): measuring a loan history screen
This is a hypothetical walk-through, not recorded output. You seed 20 members and 200 loans, and build a screen listing each member with the titles of books they have borrowed.
- Baseline: your first version calls
memberRepository.findAll()and loops throughmember.getLoans(), thenloan.getBook().getTitle(). - Read the log: you would expect one select for members, then one select for loans per member, then possibly one per book if it is not already loaded. Write the total statement count in your notebook.
- Change one thing: replace the repository method with a query that uses
join fetchfor loans and book. - Re-measure: the statement count should drop to a small, fixed number. Record before and after.
- Scale test: double the seed data and confirm the count does not grow.
The habit matters more than the numbers: change one variable, measure, and write down what happened.
A 14-day plan for the mini-project
If you are doing a two-week online internship, this schedule keeps the project small enough to finish. The free two-week CareerSwami internship is a 14-day online programme with a free offer letter and a free completion certificate after successful completion, and a data-focused project like this one fits that length well.
| Days | Focus | Deliverable |
|---|---|---|
| 1-2 | Schema design and SQL scripts; project setup with dependencies | Five tables created and seeded |
| 3-4 | Entities for author, book, member; basic repositories | Mapping sheet completed; save and find tests |
| 5-6 | Loan entity, many-to-many categories, helper methods | All relationships working in both directions where needed |
| 7-8 | Cascade and orphan removal experiments; transactions | Short notes on what each setting did |
| 9-10 | Lazy loading, LazyInitializationException reproduction and fix |
One endpoint returning DTOs |
| 11-12 | N+1 hunt using the checklist; fetch joins, entity graphs, batch size | Before and after statement counts |
| 13 | Clean-up, README, diagram of entities | Repository ready to share |
| 14 | Review against the mapping questions; write a one-page summary | Final submission |
Bhagwant Global University students: placing this project in your own coursework
Bhagwant Global University is a private university established by a state statute. The Bhagwant Global University Act, 2016 describes a private university at Kotdwar in Pauri Garhwal, Uttarakhand, and the state’s published copy of the Act places it in Chaur, Tehsil Kotdwar. Both are excerpt-level references from 2016, so read the full text if you need exact wording.
What this means for you in practice: it is neither an affiliating university nor an autonomous college, so its syllabus and project rules are likely to come from the university itself. The university’s website has a Syllabus & Ordinance page that appears to list a B.Tech (CS) syllabus. That is only a search-result description, so open the page yourself and check which scheme and year apply to your batch. Be careful with the similarly named bgu.ac.in site, which belongs to a different institution, Birla Global University in Bhubaneswar.
Whether a self-run Hibernate project can count toward any internship or project requirement is not something we can state. Rules on duration, evaluation and documentation have not been verified, so treat this worksheet as skill-building and portfolio work unless your department says otherwise. Check the following before you plan around it:
- Open the Syllabus & Ordinance page on the university site and find your programme, batch and scheme. Note whether Java, database or advanced programming courses already cover JPA.
- Look in the ordinance for any clause on industrial training, internships or project work, and note the duration and evaluation method.
- Email your head of department asking in writing whether an online internship or self-directed project is accepted, and what documents (offer letter, certificate, report) are required.
- Ask your project coordinator which Java framework versions and databases the labs support, if you want to demonstrate the project there.
- Ask the training and placement office, in writing, how project work is presented to recruiters, and keep their reply.
- Confirm that every document you rely on comes from a bgukotdwar.ac.in page or a signed notice from your department.
Presenting the finished project
- Include the SQL scripts, an entity diagram and the filled mapping sheet in the repository.
- Add a short “Performance notes” section in the README with your measured statement counts before and after fixing N+1.
- Describe one bug you hit, such as a
LazyInitializationException, and how you fixed it. Interviewers tend to ask about exactly this. - Do not claim features you did not build, and do not present illustrative numbers from this article as your results.
Frequently Asked Questions
Should I learn plain Hibernate or Spring Data JPA first?
Learn the JPA concepts first: entities, the persistence context, relationships and fetch types. Spring Data JPA sits on top of them and hides much of the detail, so understanding the underlying behaviour makes its repositories far easier to debug. A good compromise is to use Spring Data JPA while logging the SQL it generates.
Which database should I use for this mini-project?
H2 is the quickest to set up for the first few days. Move to PostgreSQL or MySQL before you test lazy loading and N+1 seriously, since real databases show how joins and indexes behave. The mapping itself stays the same.
Why does my collection throw LazyInitializationException?
The session closed before you touched the lazy collection. Load the data you need inside the transactional method using a fetch join or entity graph, or map to a DTO before leaving the service layer. Switching everything to eager loading usually trades one problem for N+1 queries.
Is eager loading always wrong?
No. Eager loading is fine for a small association that you nearly always need, such as a lookup value. The problem is eager loading by default on large or rarely needed associations, because it cannot be switched off for a particular query as easily as a lazy one can be fetched.
How do I confirm I really fixed an N+1 problem?
Count SQL statements for a fixed request before and after, then double the seed data and count again. If the count stays flat, the problem is fixed. Hibernate statistics or a test that asserts the statement count make this repeatable.
Can this project count as my college internship or project work?
That depends on your department’s rules, which this article cannot confirm. Ask your head of department in writing what is accepted and which documents are needed. The CareerSwami internship provides an offer letter and a completion certificate, but it does not promise academic credit.
Do I need to use bidirectional relationships everywhere?
No. Unidirectional mappings are simpler and have fewer ways to go wrong. Add the reverse side only when your queries or domain logic genuinely need to navigate it, and then keep both sides in sync with helper methods.
What should I put on my resume from this project?
Describe the schema, the relationship types you mapped, and one measurable improvement, such as reducing the statements for a screen after replacing lazy access in a loop with a fetch join. Only include figures that came from your own measurements.
Related Java & Enterprise Software guides
- IIIT Bangalore Students: Using a Java Internship to Explore Microservices Without Overreaching
- Geetanjali University Students: A LinkedIn Update Plan for Your Java Internship
- JSS Science & Technology University: Writing Clean Java Code a Mentor Will Approve
- All Java & Enterprise Software internship guides
Ready to put this into practice?
Join the free two-week CareerSwami online internship in Java & Enterprise Software. The internship, the offer letter and the completion certificate are free.










