โ† Back | Spring Data JPA & Hibernate
Week 2
Week 2 ยท Core Backend

Spring Data JPA & Hibernate

Spring Data JPA lets you interact with databases using Java objects instead of writing SQL. Hibernate handles the translation. Together they make database operations simple and safe.

๐Ÿ—„๏ธ JPA / Hibernate PostgreSQL / H2 Entity Relationships
๐Ÿง 
Concept
JPA, Hibernate, and Spring Data JPA
Analogy: Think of talking to a foreign country.
JPA = the diplomatic rulebook (what messages can be sent, the protocol).
Hibernate = the actual translator who knows the language (implements JPA, translates Java to SQL).
Spring Data JPA = your assistant who writes the diplomatic messages for you based on method names, so you don't have to write them yourself.
LayerWhat it isRole
JPAJava Persistence API โ€” a specification (interface)Defines the rules: @Entity, @Id, @OneToMany, etc.
HibernateJPA implementation (ORM framework)Translates Java objects โ†” SQL queries
Spring Data JPASpring abstraction over HibernateAuto-generates CRUD + custom queries from method names
โš™๏ธ
Step 1
Setup & Dependencies
pom.xml โ€” add these dependencies
<!-- Spring Data JPA (includes Hibernate) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- Azure SQL Database (SQL Server engine) --> <dependency> <groupId>com.microsoft.sqlserver</groupId> <artifactId>mssql-jdbc</artifactId> <scope>runtime</scope> </dependency>
application.properties โ€” WebhookFlow uses Azure SQL Database
# Azure SQL Database โ€” credentials set as environment variables spring.datasource.url=jdbc:sqlserver://${AZURE_SQL_SERVER}.database.windows.net:1433;databaseName=webhookflow;encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30 spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver spring.datasource.username=sqladmin spring.datasource.password=${AZURE_SQL_PASSWORD} # Show SQL queries in console (useful for learning) spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true # update: creates tables on first run, adds columns when entities change # never drops existing data โ€” safe for development and production spring.jpa.hibernate.ddl-auto=update
๐Ÿ“ฆ
Step 2
Entity Class
Mapping a Java class to a database table
@Entity // Tells Hibernate: map this class to a table @Table(name = "students") // Table name (default is class name) public class Student { @Id // Primary key @GeneratedValue(strategy = GenerationType.IDENTITY) // Auto-increment private Long id; @Column(nullable = false) // NOT NULL constraint private String name; @Column(unique = true, nullable = false) private String email; @Column(name = "phone_number") // Custom column name private String phone; @CreatedDate private LocalDateTime createdAt; // Constructors, getters, setters // OR use @Data from Lombok to auto-generate everything }
AnnotationPurpose
@EntityMarks class as a JPA entity (mapped to a table)
@TableCustomise table name and constraints
@IdMarks the primary key field
@GeneratedValueAuto-generate primary key (IDENTITY = DB auto-increment)
@ColumnCustomise column (name, nullable, unique, length)
@TransientField is NOT mapped to any column
๐Ÿ—ƒ๏ธ
Step 3
Repository Interface
Spring generates the SQL for you
Think of a repository as a library desk. You tell the librarian (Spring Data JPA) what you want: "give me all books by author X". The librarian figures out how to find them in the database. You never write the SQL yourself.
// Extend JpaRepository<EntityType, PrimaryKeyType> // Spring automatically provides: save, findById, findAll, delete, count, etc. @Repository public interface StudentRepository extends JpaRepository<Student, Long> { // Spring generates SQL from method name: // SELECT * FROM students WHERE email = ? Optional<Student> findByEmail(String email); // SELECT * FROM students WHERE city = ? List<Student> findByCity(String city); // SELECT * FROM students WHERE name LIKE '%keyword%' List<Student> findByNameContainingIgnoreCase(String keyword); // SELECT * FROM students WHERE city = ? ORDER BY name ASC List<Student> findByCityOrderByNameAsc(String city); // Boolean check boolean existsByEmail(String email); }
โšก
Step 4
CRUD Operations in Service Layer
@Service public class StudentService { private final StudentRepository repo; public StudentService(StudentRepository repo) { this.repo = repo; } // READ ALL public List<Student> findAll() { return repo.findAll(); } // READ ONE โ€” throws 404 if not found public Student findById(Long id) { return repo.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Student not found: " + id)); } // CREATE public Student save(Student student) { if (repo.existsByEmail(student.getEmail())) { throw new RuntimeException("Email already in use"); } return repo.save(student); // INSERT INTO students ... } // UPDATE public Student update(Long id, Student updated) { Student existing = findById(id); existing.setName(updated.getName()); existing.setEmail(updated.getEmail()); return repo.save(existing); // UPDATE students SET ... WHERE id = ? } // DELETE public void delete(Long id) { findById(id); // ensure it exists first repo.deleteById(id); // DELETE FROM students WHERE id = ? } }
๐Ÿ”
Advanced
Custom Queries with @Query
@Repository public interface StudentRepository extends JpaRepository<Student, Long> { // JPQL โ€” uses class/field names, not table/column names @Query("SELECT s FROM Student s WHERE s.city = :city AND s.age >= :minAge") List<Student> findByCityAndMinAge(@Param("city") String city, @Param("minAge") int minAge); // Native SQL โ€” use actual table/column names @Query(value = "SELECT * FROM students WHERE LOWER(name) LIKE LOWER(CONCAT('%',:name,'%'))", nativeQuery = true) List<Student> searchByName(@Param("name") String name); // Update/Delete query @Modifying @Transactional @Query("UPDATE Student s SET s.city = :newCity WHERE s.id = :id") int updateCity(@Param("id") Long id, @Param("newCity") String newCity); }
๐Ÿ”—
Important
Entity Relationships
@OneToMany, @ManyToOne, @ManyToMany

One-to-Many: One Course has Many Students

@Entity public class Course { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // One course has many enrollments // mappedBy = field name in Enrollment class that owns the relationship @OneToMany(mappedBy = "course", cascade = CascadeType.ALL) private List<Enrollment> enrollments = new ArrayList<>(); } @Entity public class Enrollment { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // Many enrollments belong to one course // @JoinColumn = the FK column in the enrollments table @ManyToOne @JoinColumn(name = "course_id") private Course course; @ManyToOne @JoinColumn(name = "student_id") private Student student; }
โšก
Performance
Lazy vs Eager Loading
Lazy Loading = "I'll get the data when I actually need it." Like downloading a movie only when you press play.
Eager Loading = "Get everything upfront." Like downloading all movies immediately when you open the app.
// LAZY (default for @OneToMany) โ€” students are NOT loaded with course @OneToMany(fetch = FetchType.LAZY) private List<Student> students; // EAGER (default for @ManyToOne) โ€” student IS loaded with enrollment @ManyToOne(fetch = FetchType.EAGER) private Course course;
N+1 Problem: With lazy loading and a loop, you might execute N additional queries (one per entity). Use @EntityGraph or JPQL JOIN FETCH to load related data in one query when you know you'll need it.
Best practice: Keep the defaults (LAZY for collections). Only use EAGER when you always need the related data. Prefer JOIN FETCH in specific queries that need the relationship.
๐ŸŽฏ
Interview Prep
Common Interview Questions
QWhat is JPA? How is it different from Hibernate?

JPA (Java Persistence API) is a specification โ€” a set of interfaces and annotations that define how Java objects should be mapped to relational databases. JPA itself is not a library, it's just a standard.

Hibernate is an implementation of the JPA specification. It's the most popular ORM (Object Relational Mapper) for Java. When Spring Boot sets up JPA, it uses Hibernate under the hood by default.

QWhat is the difference between @OneToMany and @ManyToOne?

@OneToMany โ€” One entity has a collection of another. E.g., one Course has many Enrollments. Placed on the "one" side.

@ManyToOne โ€” Many entities relate to one entity. E.g., many Enrollments belong to one Course. Placed on the "many" side. This side owns the relationship (holds the foreign key column in the database).

QWhat is lazy loading and what is the N+1 problem?

Lazy loading means related entities are not loaded from the database until they're accessed in code. This is the default for @OneToMany collections.

The N+1 problem occurs when you load N entities (1 query) and then access a lazy-loaded collection on each one in a loop โ€” causing N additional queries. E.g., loading 100 courses and then accessing each course's enrollments = 1 + 100 = 101 queries!

Fix: Use JPQL JOIN FETCH to load the relationship in one query, or use @EntityGraph to specify what to load eagerly for a specific query.

QWhat are the ddl-auto options in Spring JPA?
  • create โ€” drop and re-create schema on every startup (lose all data)
  • create-drop โ€” create on startup, drop on shutdown (for tests)
  • update โ€” update schema if entities change but don't drop existing data (development)
  • validate โ€” validate that schema matches entities but don't change it (production)
  • none โ€” do nothing, manage schema manually (recommended for production)
QWhat is cascading in JPA?

Cascading defines what happens to related entities when an operation is performed on the parent. cascade = CascadeType.ALL means all operations (PERSIST, MERGE, REMOVE, REFRESH) are cascaded to the children.

Example: If you delete a Course with CascadeType.ALL, all its Enrollments are also deleted automatically.

Common types: ALL, PERSIST (save children when saving parent), REMOVE (delete children when deleting parent), MERGE (update children when updating parent).