Skip to content

Session 6

Hibernate and JPA

Updated View as Markdown

Hibernate maps entity classes to tables so that persistence code is annotations and a session or entity manager call rather than hand-written SQL. This session persists the student admission data and performs a batch update.

The student-admission project gets a proper schema: five entities, a fresh student_admission database, full CRUD on students and a batch approval that assigns enrolment numbers in one transaction.

Objectives

Do not copy. Read for understanding and the viva
  • Complete questions 25 to 28 of the manual: hibernate and jpa
  • Prepare the deliverable before the lab and finish it during the session
  • Be ready to explain every step in the viva

Questions Covered

Do not copy. Read for understanding and the viva
QuestionRequirementStatus
Q25Create a database for the student admission lifecycle and configure Hibernate and JPA…Complete
Q26Retrieve Student information using Hibernate and printing the value in the consoleComplete
Q27Create CRUD (Create/Save, Read/Fetch, Edit/Update, Delete) using Spring MVC and…Complete
Q28Apply batch update for student’s admission approval using Spring MVC and HibernateComplete

Preparation

Do not copy. Read for understanding and the viva
  • Draw the admission life-cycle tables first: Student, Programme, Course, StudentCourse, AdmissionStatus. Map each with @Entity, @Id, @OneToMany, @ManyToMany.
  • Configure hibernate.cfg.xml or Spring’s LocalSessionFactoryBean with the MySQL dialect and hbm2ddl.auto=update for the lab.
  • Batch update: loop with session.update and flush every 20 records, inside one transaction.

Question 25

Problem Statement

Write in lab record

Create a database for the student admission lifecycle and configure Hibernate and JPA with the Spring MVC Project along with all table entities.

Solution

Write in lab record

Life cycle: a student applies to one programme and picks courses (status PENDING); the office approves or rejects; every change is recorded. Tables and relations:

TableKey columnsRelation
programmeid, code, nameone programme has many courses
courseid, code, title, programme_idmany courses belong to one programme
studentid, enrolment_no, name, email, mobile, dob, address, hostel_required, programme_id, status, applied_onmany students apply to one programme
student_courseid, student_id, course_id, enrolled_onjoin entity between student and course (many-to-many with a date)
admission_statusid, student_id, status, changed_on, remarkone student has many status changes, oldest first

Steps

  1. Run student_admission.sql in MySQL (Workbench or mysql -u root -p < student_admission.sql). It creates the database, five tables and seeds two programmes and six courses.
  2. Point db.properties at student_admission.
  3. Replace JpaConfig.java (batch size and validation mode are new) and confirm pom.xml matches the final listing below.
  4. In com.ignou.lab.admission.entity replace Student.java and add Programme.java, Course.java, StudentCourse.java, AdmissionStatus.java.
  5. Delete Session 5’s AdmissionController.java, admission-form.jsp and admission-result.jsp; Q27 replaces them. Rebuild only after Q27’s files are in place, because AdmissionForm changes shape.

Program

Lab record: every tab is one file of the answer. Write all of them.

student_admission.sqlsql
-- Student admission life cycle: one programme has many courses; a student
-- applies to one programme, picks courses, and moves PENDING -> APPROVED/REJECTED.
CREATE DATABASE IF NOT EXISTS student_admission;
USE student_admission;

CREATE TABLE programme (
  id    BIGINT AUTO_INCREMENT PRIMARY KEY,
  code  VARCHAR(10) NOT NULL UNIQUE,
  name  VARCHAR(80) NOT NULL
);

CREATE TABLE course (
  id           BIGINT AUTO_INCREMENT PRIMARY KEY,
  code         VARCHAR(10)  NOT NULL UNIQUE,
  title        VARCHAR(120) NOT NULL,
  programme_id BIGINT       NOT NULL,
  FOREIGN KEY (programme_id) REFERENCES programme(id)
);

CREATE TABLE student (
  id              BIGINT AUTO_INCREMENT PRIMARY KEY,
  enrolment_no    VARCHAR(12) UNIQUE,
  name            VARCHAR(80)  NOT NULL,
  email           VARCHAR(120) NOT NULL,
  mobile          VARCHAR(10)  NOT NULL,
  dob             DATE         NOT NULL,
  address         VARCHAR(255) NOT NULL,
  hostel_required BIT(1)       NOT NULL,
  programme_id    BIGINT       NOT NULL,
  status          ENUM('PENDING','APPROVED','REJECTED') NOT NULL DEFAULT 'PENDING',
  applied_on      DATE         NOT NULL,
  FOREIGN KEY (programme_id) REFERENCES programme(id)
);

-- join table with its own data (when the course was taken), so it is an entity
CREATE TABLE student_course (
  id          BIGINT AUTO_INCREMENT PRIMARY KEY,
  student_id  BIGINT NOT NULL,
  course_id   BIGINT NOT NULL,
  enrolled_on DATE   NOT NULL,
  UNIQUE (student_id, course_id),
  FOREIGN KEY (student_id) REFERENCES student(id) ON DELETE CASCADE,
  FOREIGN KEY (course_id)  REFERENCES course(id)
);

-- every status change of every application, oldest first
CREATE TABLE admission_status (
  id         BIGINT AUTO_INCREMENT PRIMARY KEY,
  student_id BIGINT NOT NULL,
  status     ENUM('PENDING','APPROVED','REJECTED') NOT NULL,
  changed_on DATETIME(6) NOT NULL,
  remark     VARCHAR(255),
  FOREIGN KEY (student_id) REFERENCES student(id) ON DELETE CASCADE
);

INSERT INTO programme (code, name) VALUES
  ('MCA', 'Master of Computer Applications'),
  ('BCA', 'Bachelor of Computer Applications');

INSERT INTO course (code, title, programme_id) VALUES
  ('MCS-218', 'Data Communication and Computer Networks', 1),
  ('MCS-219', 'Object Oriented Analysis and Design',      1),
  ('MCS-220', 'Web Technologies',                         1),
  ('MCS-221', 'Data Warehousing and Data Mining',         1),
  ('BCS-011', 'Computer Basics and PC Software',          2),
  ('BCS-012', 'Basic Mathematics',                        2);
db.propertiesproperties
# src/main/resources/db.properties
db.driver=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/student_admission
db.username=root
db.password=Ignou@1234
JpaConfig.javajava
package com.ignou.lab.admission;

import java.util.Properties;
import javax.sql.DataSource;
import jakarta.persistence.EntityManagerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@PropertySource("classpath:db.properties")
public class JpaConfig {

    @Bean
    public DataSource dataSource(Environment env) {
        // ponytail: no connection pool; switch to HikariDataSource when more than one user hits the app
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName(env.getRequiredProperty("db.driver"));
        ds.setUrl(env.getRequiredProperty("db.url"));
        ds.setUsername(env.getRequiredProperty("db.username"));
        ds.setPassword(env.getRequiredProperty("db.password"));
        return ds;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
        emf.setDataSource(dataSource);
        emf.setPackagesToScan("com.ignou.lab.admission.entity"); // all five entities
        emf.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); // Hibernate 6 detects the MySQL dialect itself
        Properties props = new Properties();
        props.put("hibernate.hbm2ddl.auto", "update");   // "validate" once the tables exist and you want a strict check
        props.put("hibernate.show_sql", "true");
        props.put("hibernate.format_sql", "true");
        props.put("hibernate.jdbc.batch_size", "20");     // Q28: send UPDATEs to MySQL 20 at a time
        props.put("hibernate.order_updates", "true");
        props.put("jakarta.persistence.validation.mode", "none"); // the form is validated in the controller already
        emf.setJpaProperties(props);
        return emf;
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }
}
entity/Programme.javajava
package com.ignou.lab.admission.entity;

import java.util.ArrayList;
import java.util.List;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;

@Entity
@Table(name = "programme")
public class Programme {

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

    @Column(nullable = false, unique = true, length = 10)
    private String code;

    @Column(nullable = false, length = 80)
    private String name;

    @OneToMany(mappedBy = "programme")
    private List<Course> courses = new ArrayList<>();

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getCode() { return code; }
    public void setCode(String code) { this.code = code; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public List<Course> getCourses() { return courses; }
    public void setCourses(List<Course> courses) { this.courses = courses; }
}
entity/Course.javajava
package com.ignou.lab.admission.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;

@Entity
@Table(name = "course")
public class Course {

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

    @Column(nullable = false, unique = true, length = 10)
    private String code;

    @Column(nullable = false, length = 120)
    private String title;

    @ManyToOne(optional = false)
    @JoinColumn(name = "programme_id")
    private Programme programme;

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getCode() { return code; }
    public void setCode(String code) { this.code = code; }
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    public Programme getProgramme() { return programme; }
    public void setProgramme(Programme programme) { this.programme = programme; }
}
entity/Student.javajava
package com.ignou.lab.admission.entity;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderBy;
import jakarta.persistence.Table;
import com.ignou.lab.admission.entity.AdmissionStatus.Status;

@Entity
@Table(name = "student")
public class Student {

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

    /** assigned on approval (Q28); null while the application is pending */
    @Column(name = "enrolment_no", length = 12, unique = true)
    private String enrolmentNo;

    @Column(nullable = false, length = 80)
    private String name;

    @Column(nullable = false, length = 120)
    private String email;

    @Column(nullable = false, length = 10)
    private String mobile;

    @Column(nullable = false)
    private LocalDate dob;

    @Column(nullable = false, length = 255)
    private String address;

    @Column(name = "hostel_required", nullable = false)
    private boolean hostelRequired;

    @ManyToOne(optional = false)
    @JoinColumn(name = "programme_id")
    private Programme programme;

    /** current stage; the full history is in AdmissionStatus rows */
    @Enumerated(EnumType.STRING)
    @Column(nullable = false, length = 10)
    private Status status = Status.PENDING;

    @Column(name = "applied_on", nullable = false)
    private LocalDate appliedOn = LocalDate.now();

    /** cascade + orphanRemoval: saving/deleting the student saves/deletes its rows */
    @OneToMany(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<StudentCourse> courses = new ArrayList<>();

    @OneToMany(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true)
    @OrderBy("changedOn")
    private List<AdmissionStatus> history = new ArrayList<>();

    /** the only way to change the status: keeps the current column and the history in step */
    public void changeStatus(Status newStatus, String remark) {
        this.status = newStatus;
        history.add(new AdmissionStatus(this, newStatus, remark));
    }

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getEnrolmentNo() { return enrolmentNo; }
    public void setEnrolmentNo(String enrolmentNo) { this.enrolmentNo = enrolmentNo; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public String getMobile() { return mobile; }
    public void setMobile(String mobile) { this.mobile = mobile; }
    public LocalDate getDob() { return dob; }
    public void setDob(LocalDate dob) { this.dob = dob; }
    public String getAddress() { return address; }
    public void setAddress(String address) { this.address = address; }
    public boolean isHostelRequired() { return hostelRequired; }
    public void setHostelRequired(boolean hostelRequired) { this.hostelRequired = hostelRequired; }
    public Programme getProgramme() { return programme; }
    public void setProgramme(Programme programme) { this.programme = programme; }
    public Status getStatus() { return status; }
    public LocalDate getAppliedOn() { return appliedOn; }
    public List<StudentCourse> getCourses() { return courses; }
    public List<AdmissionStatus> getHistory() { return history; }
}
entity/StudentCourse.javajava
package com.ignou.lab.admission.entity;

import java.time.LocalDate;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;

/** Join entity between Student and Course; carries the enrolment date. */
@Entity
@Table(name = "student_course", uniqueConstraints = @UniqueConstraint(columnNames = { "student_id", "course_id" }))
public class StudentCourse {

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

    @ManyToOne(optional = false)
    @JoinColumn(name = "student_id")
    private Student student;

    @ManyToOne(optional = false)
    @JoinColumn(name = "course_id")
    private Course course;

    @Column(name = "enrolled_on", nullable = false)
    private LocalDate enrolledOn = LocalDate.now();

    protected StudentCourse() { }

    public StudentCourse(Student student, Course course) {
        this.student = student;
        this.course = course;
    }

    public Long getId() { return id; }
    public Student getStudent() { return student; }
    public Course getCourse() { return course; }
    public LocalDate getEnrolledOn() { return enrolledOn; }
}
entity/AdmissionStatus.javajava
package com.ignou.lab.admission.entity;

import java.time.LocalDateTime;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;

/** One row per status change: the life cycle of an application. */
@Entity
@Table(name = "admission_status")
public class AdmissionStatus {

    public enum Status { PENDING, APPROVED, REJECTED }

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

    @ManyToOne(optional = false)
    @JoinColumn(name = "student_id")
    private Student student;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false, length = 10)
    private Status status;

    @Column(name = "changed_on", nullable = false)
    private LocalDateTime changedOn = LocalDateTime.now();

    @Column(length = 255)
    private String remark;

    protected AdmissionStatus() { } // JPA needs a no-arg constructor

    public AdmissionStatus(Student student, Status status, String remark) {
        this.student = student;
        this.status = status;
        this.remark = remark;
    }

    public Long getId() { return id; }
    public Student getStudent() { return student; }
    public Status getStatus() { return status; }
    public LocalDateTime getChangedOn() { return changedOn; }
    public String getRemark() { return remark; }
}
pom.xml (final)xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.ignou.lab</groupId>
  <artifactId>student-admission</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>student-admission</name>
  <description>Student Admission: Spring MVC 6 + Hibernate 6 (JPA 3.1) on Tomcat 10.1</description>

  <properties>
    <maven.compiler.release>17</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>6.1.14</spring.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate.orm</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>6.6.3.Final</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate.validator</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>8.0.1.Final</version>
    </dependency>
    <dependency>
      <groupId>com.mysql</groupId>
      <artifactId>mysql-connector-j</artifactId>
      <version>8.4.0</version>
    </dependency>
    <dependency>
      <groupId>jakarta.servlet</groupId>
      <artifactId>jakarta.servlet-api</artifactId>
      <version>6.0.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>jakarta.servlet.jsp.jstl</groupId>
      <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
      <version>3.0.0</version>
    </dependency>
    <dependency>
      <groupId>org.glassfish.web</groupId>
      <artifactId>jakarta.servlet.jsp.jstl</artifactId>
      <version>3.0.1</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>student-admission</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.13.0</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>3.4.0</version>
        <configuration>
          <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Output

Expected, checked by reading, not executed. SHOW TABLES in MySQL lists admission_status, course, programme, student, student_course. On deployment Tomcat’s log shows Hibernate starting with the five entities and, because hbm2ddl.auto=update finds every table already there, no create table statements:

HHH000412: Hibernate ORM core version 6.6.3.Final
HHH10001005: Database info: Database JDBC URL [jdbc:mysql://localhost:3306/student_admission] ... Database version: 8.0

Explanation

  • @Entity plus @Table name the table; @Id with IDENTITY uses MySQL auto-increment; @Column sets name, length and nullability so that generated DDL and the script agree.
  • @ManyToOne with @JoinColumn owns the foreign key (Course.programme, Student.programme, both sides of StudentCourse). @OneToMany(mappedBy = ...) is the inverse, read-only view of the same key.
  • Student to Course is many-to-many, but the join row has its own data (enrolled_on), so it is modelled as the entity StudentCourse rather than @ManyToMany. cascade = ALL, orphanRemoval = true on Student.courses means saving or deleting a student saves or deletes its rows.
  • AdmissionStatus is the life cycle: one row per change, ordered by @OrderBy("changedOn"). Student.status keeps the current stage as a column for cheap filtering; changeStatus() is the only method that writes it, so column and history never disagree. @Enumerated(STRING) stores PENDING, not 0.
  • JpaConfig is the Hibernate configuration: data source, entity package, provider, and the hibernate.* properties (hbm2ddl.auto, show_sql, jdbc.batch_size for Q28). Hibernate 6 detects the MySQL dialect from the connection, so no dialect property is needed. validation.mode=none stops Hibernate re-running the Bean Validation the controller already ran.

Question 26

Problem Statement

Write in lab record

Retrieve Student information using Hibernate and printing the value in the console.

Solution

Write in lab record

Steps

  1. Add ConsoleApp.java in com.ignou.lab.admission. It boots only JpaConfig, so no Tomcat is involved.
  2. Insert at least two students first (Q27’s form, or two INSERT statements in MySQL).
  3. Run: mvn -q compile exec:java -Dexec.mainClass=com.ignou.lab.admission.ConsoleApp, or Run As → Java Application.

Program

ConsoleApp.javajava
package com.ignou.lab.admission;

import java.util.List;
import java.util.stream.Collectors;
import jakarta.persistence.EntityManagerFactory;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.ignou.lab.admission.entity.Student;

/**
 * Q26: read students with the Hibernate Session API and print them on the console.
 * Run: mvn -q compile exec:java -Dexec.mainClass=com.ignou.lab.admission.ConsoleApp
 */
public class ConsoleApp {

    public static void main(String[] args) {
        try (var ctx = new AnnotationConfigApplicationContext(JpaConfig.class)) {
            SessionFactory sessionFactory = ctx.getBean(EntityManagerFactory.class).unwrap(SessionFactory.class);
            try (Session session = sessionFactory.openSession()) {
                List<Student> students = session.createQuery(
                        "select distinct s from Student s join fetch s.programme"
                        + " left join fetch s.courses sc left join fetch sc.course order by s.id",
                        Student.class).getResultList();

                System.out.printf("%-4s %-11s %-18s %-5s %-9s %s%n",
                        "ID", "ENROLMENT", "NAME", "PROG", "STATUS", "COURSES");
                for (Student s : students) {
                    String courses = s.getCourses().stream()
                            .map(sc -> sc.getCourse().getCode())
                            .collect(Collectors.joining(","));
                    System.out.printf("%-4d %-11s %-18s %-5s %-9s %s%n",
                            s.getId(), s.getEnrolmentNo() == null ? "-" : s.getEnrolmentNo(),
                            s.getName(), s.getProgramme().getCode(), s.getStatus(), courses);
                }
                System.out.println(students.size() + " student(s)");
            }
        }
    }
}

Output

Expected console output after two applications and one approval, checked by reading, not executed. The Hibernate: block with the SELECT (one query with three joins) prints first because show_sql is on, then:

ID   ENROLMENT   NAME               PROG  STATUS    COURSES
1    2026000001  Asha Verma         MCA   APPROVED  MCS-218,MCS-220
2    -           Rahul Singh        MCA   PENDING   MCS-219
2 student(s)

Explanation

  • The Spring-managed EntityManagerFactory is Hibernate underneath; unwrap(SessionFactory.class) exposes the native API, and openSession() gives a Hibernate Session.
  • The query is HQL. join fetch s.programme and left join fetch s.courses sc left join fetch sc.course load the related rows in the same SELECT; without them each getProgramme() or getCourses() would fire another query (the N+1 problem) or fail with LazyInitializationException once the session is closed.
  • select distinct collapses the duplicate Student rows the join produces (one per course).
  • printf with fixed widths makes the console a table; enrolment shows - while it is null, which is every pending application.

Question 27

Problem Statement

Write in lab record

Create CRUD (Create/Save, Read/Fetch, Edit/Update, Delete) using Spring MVC and Hibernation.

Solution

Write in lab record

Steps

  1. Replace StudentRepository.java (all persistence), AdmissionForm.java (now carries id, programmeId, courseIds) and StudentController.java (all URLs under /students).
  2. Replace students.jsp and add student-form.jsp in WEB-INF/views. head.jspf from Session 5 stays.
  3. In home.jsp change the “Apply for admission” link to /students/new.
  4. Rebuild, redeploy and walk the cycle: /students (empty) → New application → Save → row appears → Edit → change the mobile, tick another course → Save → Delete → confirm → row gone. Watch Tomcat’s console for the SQL at each step.
OperationURL and methodRepository callSQL Hibernate sends
CreateGET /students/new, POST /students/savesave(form) with id nullINSERT student, INSERT student_course per course, INSERT admission_status
ReadGET /studentsfindAll()one SELECT with joins
UpdateGET /students/7/edit, POST /students/savesave(form) with id 7UPDATE student, DELETE and INSERT student_course as needed
DeletePOST /students/7/deletedelete(7)DELETE admission_status, DELETE student_course, DELETE student

Program

Lab record: every tab is one file of the answer. Write all of them.

repo/StudentRepository.javajava
package com.ignou.lab.admission.repo;

import java.time.Year;
import java.util.List;
import java.util.NoSuchElementException;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.ignou.lab.admission.entity.AdmissionStatus.Status;
import com.ignou.lab.admission.entity.Course;
import com.ignou.lab.admission.entity.Programme;
import com.ignou.lab.admission.entity.Student;
import com.ignou.lab.admission.entity.StudentCourse;
import com.ignou.lab.admission.web.AdmissionForm;

/** Every public method runs in one transaction; commit happens when it returns. */
@Repository
@Transactional
public class StudentRepository {

    @PersistenceContext
    private EntityManager em;

    // ---- lookups for the form ----

    public List<Programme> programmes() {
        return em.createQuery("from Programme p order by p.code", Programme.class).getResultList();
    }

    public List<Course> courses() {
        return em.createQuery("from Course c order by c.code", Course.class).getResultList();
    }

    // ---- Read ----

    @Transactional(readOnly = true)
    public List<Student> findAll() {
        // join fetch: programme and courses are loaded now, so the JSP never touches a closed session
        return em.createQuery(
                "select distinct s from Student s join fetch s.programme"
                + " left join fetch s.courses sc left join fetch sc.course order by s.id",
                Student.class).getResultList();
    }

    public Student find(Long id) {
        Student s = em.find(Student.class, id);
        if (s == null) {
            throw new NoSuchElementException("No student with id " + id); // ponytail: 500 page is fine for the lab
        }
        return s;
    }

    /** the edit page needs the form filled from a managed entity (courses are lazy) */
    @Transactional(readOnly = true)
    public AdmissionForm formFor(Long id) {
        return AdmissionForm.from(find(id));
    }

    // ---- Create / Update ----

    public Student save(AdmissionForm f) {
        Student s = f.getId() == null ? new Student() : find(f.getId());
        s.setName(f.getName());
        s.setEmail(f.getEmail());
        s.setMobile(f.getMobile());
        s.setDob(f.getDob());
        s.setAddress(f.getAddress());
        s.setHostelRequired(f.getHostelRequired());
        s.setProgramme(em.getReference(Programme.class, f.getProgrammeId())); // no SELECT, just the FK
        s.getCourses().clear();                                                // orphanRemoval deletes the old rows
        for (Long courseId : f.getCourseIds()) {
            s.getCourses().add(new StudentCourse(s, em.getReference(Course.class, courseId)));
        }
        if (s.getId() == null) {
            s.changeStatus(Status.PENDING, "Application received");
            em.persist(s);                                                    // INSERT student + student_course + admission_status
        }                                                                     // managed entity: UPDATE happens on commit
        return s;
    }

    // ---- Delete ----

    public void delete(Long id) {
        em.remove(find(id)); // cascades to student_course and admission_status
    }

    // ---- Q28: batch approval ----

    /** Approves every pending id in ONE transaction; UPDATEs go to MySQL in batches of 20. */
    public int approve(List<Long> ids) {
        int approved = 0;
        for (Long id : ids) {
            Student s = find(id);
            if (s.getStatus() != Status.PENDING) {
                continue;
            }
            s.setEnrolmentNo(String.format("%d%06d", Year.now().getValue(), id)); // e.g. 2026000007
            s.changeStatus(Status.APPROVED, "Approved in batch");
            if (++approved % 20 == 0) {
                em.flush(); // push this batch of UPDATE/INSERT statements
                em.clear(); // drop them from memory before loading the next 20
            }
        }
        return approved; // commit flushes whatever is left
    }
}
web/AdmissionForm.javajava
package com.ignou.lab.admission.web;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Past;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import org.springframework.format.annotation.DateTimeFormat;
import com.ignou.lab.admission.entity.Student;
import com.ignou.lab.admission.entity.StudentCourse;

/** Session 6 version: programme and courses are database ids; id is set when editing. */
public class AdmissionForm {

    private Long id;

    @NotBlank
    @Size(min = 3, max = 80)
    private String name;

    @NotBlank
    @Email
    private String email;

    @NotBlank
    @Pattern(regexp = "[6-9][0-9]{9}", message = "must be a 10-digit Indian mobile number")
    private String mobile;

    @NotNull
    @Past
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate dob;

    @NotBlank
    @Size(max = 255)
    private String address;

    @NotNull(message = "choose a programme")
    private Long programmeId;

    @NotNull(message = "choose Yes or No")
    private Boolean hostelRequired;

    @NotEmpty(message = "pick at least one course")
    private List<Long> courseIds = new ArrayList<>();

    /** entity -> form, for the edit page */
    public static AdmissionForm from(Student s) {
        AdmissionForm f = new AdmissionForm();
        f.id = s.getId();
        f.name = s.getName();
        f.email = s.getEmail();
        f.mobile = s.getMobile();
        f.dob = s.getDob();
        f.address = s.getAddress();
        f.programmeId = s.getProgramme().getId();
        f.hostelRequired = s.isHostelRequired();
        for (StudentCourse sc : s.getCourses()) {
            f.courseIds.add(sc.getCourse().getId());
        }
        return f;
    }

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public String getMobile() { return mobile; }
    public void setMobile(String mobile) { this.mobile = mobile; }
    public LocalDate getDob() { return dob; }
    public void setDob(LocalDate dob) { this.dob = dob; }
    public String getAddress() { return address; }
    public void setAddress(String address) { this.address = address; }
    public Long getProgrammeId() { return programmeId; }
    public void setProgrammeId(Long programmeId) { this.programmeId = programmeId; }
    public Boolean getHostelRequired() { return hostelRequired; }
    public void setHostelRequired(Boolean hostelRequired) { this.hostelRequired = hostelRequired; }
    public List<Long> getCourseIds() { return courseIds; }
    public void setCourseIds(List<Long> courseIds) { this.courseIds = courseIds; }
}
web/StudentController.javajava
package com.ignou.lab.admission.web;

import java.time.LocalDate;
import java.util.List;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.ignou.lab.admission.entity.Course;
import com.ignou.lab.admission.entity.Programme;
import com.ignou.lab.admission.entity.Student;
import com.ignou.lab.admission.repo.StudentRepository;

/** Replaces Session 5's AdmissionController: create, read, update, delete and batch approve. */
@Controller
@RequestMapping("/students")
public class StudentController {

    private final StudentRepository repo;

    public StudentController(StudentRepository repo) {
        this.repo = repo;
    }

    @ModelAttribute("programmes")
    public List<Programme> programmes() {
        return repo.programmes();
    }

    @ModelAttribute("courseList")
    public List<Course> courseList() {
        return repo.courses();
    }

    @ModelAttribute("today")
    public LocalDate today() {
        return LocalDate.now();
    }

    // Read
    @GetMapping
    public String list(Model model) {
        model.addAttribute("students", repo.findAll());
        return "students";
    }

    // Create (form)
    @GetMapping("/new")
    public String createForm(Model model) {
        model.addAttribute("admission", new AdmissionForm());
        return "student-form";
    }

    // Update (form)
    @GetMapping("/{id}/edit")
    public String editForm(@PathVariable Long id, Model model) {
        model.addAttribute("admission", repo.formFor(id));
        return "student-form";
    }

    // Create or Update (submit)
    @PostMapping("/save")
    public String save(@Valid @ModelAttribute("admission") AdmissionForm form,
                       BindingResult result, RedirectAttributes redirect) {
        if (result.hasErrors()) {
            return "student-form";
        }
        Student saved = repo.save(form);
        redirect.addFlashAttribute("message", "Saved " + saved.getName() + " (id " + saved.getId() + ")");
        return "redirect:/students"; // POST-redirect-GET: refresh does not resubmit
    }

    // Delete
    @PostMapping("/{id}/delete")
    public String delete(@PathVariable Long id, RedirectAttributes redirect) {
        repo.delete(id);
        redirect.addFlashAttribute("message", "Deleted student " + id);
        return "redirect:/students";
    }

    // Q28: batch approval of the ticked rows
    @PostMapping("/approve")
    public String approve(@RequestParam(name = "ids", required = false) List<Long> ids,
                          RedirectAttributes redirect) {
        int n = ids == null ? 0 : repo.approve(ids);
        redirect.addFlashAttribute("message", n + " application(s) approved in one transaction");
        return "redirect:/students";
    }
}
WEB-INF/views/student-form.jsphtml
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<!DOCTYPE html>
<html lang="en">
<head>
  <%@ include file="head.jspf" %>
  <title>${empty admission.id ? 'New application' : 'Edit application'}</title>
</head>
<body>
<nav class="navbar navbar-dark"><div class="container"><span class="navbar-brand">IGNOU Student Admission</span></div></nav>
<div class="container">
  <div class="card card-form">
    <div class="card-body">
      <h1 class="h3 card-title">${empty admission.id ? 'New application' : 'Edit application'}</h1>

      <form:form modelAttribute="admission" method="post" action="${pageContext.request.contextPath}/students/save" cssClass="row g-3">
        <form:hidden path="id"/>

        <div class="col-12">
          <form:label path="name" cssClass="form-label required">Full name</form:label>
          <form:input path="name" cssClass="form-control" cssErrorClass="form-control is-invalid"
                      required="required" minlength="3" maxlength="80"/>
          <form:errors path="name" cssClass="invalid-feedback"/>
        </div>

        <div class="col-md-6">
          <form:label path="email" cssClass="form-label required">Email</form:label>
          <form:input path="email" type="email" cssClass="form-control" cssErrorClass="form-control is-invalid" required="required"/>
          <form:errors path="email" cssClass="invalid-feedback"/>
        </div>

        <div class="col-md-6">
          <form:label path="mobile" cssClass="form-label required">Mobile</form:label>
          <form:input path="mobile" cssClass="form-control" cssErrorClass="form-control is-invalid"
                      required="required" pattern="[6-9][0-9]{9}" title="10 digits, starting 6 to 9"/>
          <form:errors path="mobile" cssClass="invalid-feedback"/>
        </div>

        <div class="col-md-6">
          <form:label path="dob" cssClass="form-label required">Date of birth</form:label>
          <form:input path="dob" type="date" cssClass="form-control" cssErrorClass="form-control is-invalid"
                      required="required" max="${today}"/>
          <form:errors path="dob" cssClass="invalid-feedback"/>
        </div>

        <div class="col-md-6">
          <form:label path="programmeId" cssClass="form-label required">Programme</form:label>
          <form:select path="programmeId" cssClass="form-select" cssErrorClass="form-select is-invalid" required="required">
            <form:option value="" label="-- choose --"/>
            <form:options items="${programmes}" itemValue="id" itemLabel="name"/>
          </form:select>
          <form:errors path="programmeId" cssClass="invalid-feedback"/>
        </div>

        <div class="col-12">
          <form:label path="address" cssClass="form-label required">Address</form:label>
          <form:textarea path="address" rows="3" cssClass="form-control" cssErrorClass="form-control is-invalid"
                         required="required" maxlength="255"/>
          <form:errors path="address" cssClass="invalid-feedback"/>
        </div>

        <div class="col-md-6">
          <label class="form-label required">Hostel required?</label>
          <div class="form-check form-check-inline">
            <form:radiobutton path="hostelRequired" value="true" cssClass="form-check-input" label=" Yes" required="required"/>
          </div>
          <div class="form-check form-check-inline">
            <form:radiobutton path="hostelRequired" value="false" cssClass="form-check-input" label=" No"/>
          </div>
          <form:errors path="hostelRequired" cssClass="text-danger small d-block"/>
        </div>

        <div class="col-md-6">
          <label class="form-label required">Courses</label>
          <c:forEach items="${courseList}" var="course">
            <div class="form-check">
              <form:checkbox path="courseIds" value="${course.id}" cssClass="form-check-input" label=" ${course.code} ${course.title}"/>
            </div>
          </c:forEach>
          <form:errors path="courseIds" cssClass="text-danger small d-block"/>
        </div>

        <div class="col-12 text-end">
          <a class="btn btn-outline-secondary" href="${pageContext.request.contextPath}/students">Cancel</a>
          <button type="submit" class="btn btn-primary">Save</button>
        </div>
      </form:form>
    </div>
  </div>
</div>
</body>
</html>

Output

Expected, checked by reading, not executed. After saving a new application the list page shows a green alert Saved Asha Verma (id 1) and a row:

[ ]  1  -  Asha Verma  MCA  MCS-218, MCS-220  PENDING  2026-09-26  Edit Delete

Tomcat’s console for that save:

Hibernate: insert into student (address,applied_on,dob,email,enrolment_no,hostel_required,mobile,name,programme_id,status) values (?,?,?,?,?,?,?,?,?,?)
Hibernate: insert into student_course (course_id,enrolled_on,student_id) values (?,?,?)
Hibernate: insert into student_course (course_id,enrolled_on,student_id) values (?,?,?)
Hibernate: insert into admission_status (changed_on,remark,status,student_id) values (?,?,?,?)

Editing and saving with one course removed prints one update student ... and one delete from student_course where id=?. Delete prints the three DELETEs in child-first order.

Explanation

  • Create and update share one method. save(form) either makes a new Student() or loads the existing one with em.find, copies the fields, and rebuilds the course list. A loaded entity is managed: Hibernate compares it with the snapshot at commit and issues an UPDATE only for what changed (dirty checking). persist is called only for a new object.
  • em.getReference(Programme.class, id) returns a proxy holding just the id; setting it writes the foreign key without a SELECT.
  • Clearing s.getCourses() and adding new StudentCourse objects is enough: orphanRemoval deletes rows no longer in the list, cascade inserts the new ones.
  • formFor(id) converts entity to form inside the transaction because courses is lazy; converting in the controller would hit a closed session.
  • The controller follows POST-redirect-GET: after a POST it redirects to /students, so a browser refresh does not save twice; RedirectAttributes.addFlashAttribute carries the message across the redirect.
  • Delete is a POST button, not a link, so a crawler or a prefetching browser cannot delete rows. The Delete buttons use the HTML form attribute to point at small forms outside the approval form, because forms cannot nest.

Question 28

Problem Statement

Write in lab record

Apply batch update for student’s admission approval using Spring MVC and Hibernate.

Solution

Write in lab record

Steps

  1. StudentRepository.approve() and StudentController.approve() are in the Q27 listings; students.jsp below adds a checkbox to every PENDING row and an “Approve selected” button posting to /students/approve.
  2. JpaConfig (Q25) sets hibernate.jdbc.batch_size=20 and hibernate.order_updates=true.
  3. Create five applications, tick three, press Approve selected. The three rows turn green with enrolment numbers; the alert reads 3 application(s) approved in one transaction.
  4. Prove it is one transaction: temporarily change %06d in approve() to %s with a text longer than 12 characters so the third UPDATE fails on column length; after the exception none of the three is approved.

The method under test, from StudentRepository.java:

public int approve(List<Long> ids) {
    int approved = 0;
    for (Long id : ids) {
        Student s = find(id);
        if (s.getStatus() != Status.PENDING) {
            continue;
        }
        s.setEnrolmentNo(String.format("%d%06d", Year.now().getValue(), id));
        s.changeStatus(Status.APPROVED, "Approved in batch");
        if (++approved % 20 == 0) {
            em.flush();
            em.clear();
        }
    }
    return approved;
}

Program

WEB-INF/views/students.jsphtml
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<!DOCTYPE html>
<html lang="en">
<head>
  <%@ include file="head.jspf" %>
  <title>Students</title>
</head>
<body>
<nav class="navbar navbar-dark"><div class="container"><span class="navbar-brand">IGNOU Student Admission</span></div></nav>
<div class="container mt-4">
  <c:if test="${not empty message}">
    <div class="alert alert-success">${message}</div>
  </c:if>

  <div class="d-flex justify-content-between align-items-center mb-3">
    <h1 class="h3 m-0">Applications</h1>
    <a class="btn btn-primary" href="${pageContext.request.contextPath}/students/new">New application</a>
  </div>

  <%-- one form around the table: the ticked ids go to /students/approve --%>
  <form method="post" action="${pageContext.request.contextPath}/students/approve">
    <table class="table table-striped align-middle">
      <thead>
        <tr><th></th><th>Id</th><th>Enrolment</th><th>Name</th><th>Programme</th><th>Courses</th><th>Status</th><th>Applied</th><th></th></tr>
      </thead>
      <tbody>
        <c:forEach items="${students}" var="s">
          <tr>
            <td><c:if test="${s.status == 'PENDING'}"><input class="form-check-input" type="checkbox" name="ids" value="${s.id}"></c:if></td>
            <td>${s.id}</td>
            <td>${empty s.enrolmentNo ? '-' : s.enrolmentNo}</td>
            <td><c:out value="${s.name}"/></td>
            <td>${s.programme.code}</td>
            <td><c:forEach items="${s.courses}" var="sc" varStatus="st">${sc.course.code}<c:if test="${!st.last}">, </c:if></c:forEach></td>
            <td><span class="badge ${s.status == 'APPROVED' ? 'text-bg-success' : s.status == 'REJECTED' ? 'text-bg-danger' : 'text-bg-warning'}">${s.status}</span></td>
            <td>${s.appliedOn}</td>
            <td class="text-nowrap">
              <a class="btn btn-sm btn-outline-secondary" href="${pageContext.request.contextPath}/students/${s.id}/edit">Edit</a>
              <%-- the form attribute links this button to a form outside the approve form (forms cannot nest) --%>
              <button class="btn btn-sm btn-outline-danger" type="submit" form="delete-${s.id}"
                      onclick="return confirm('Delete student ${s.id}?')">Delete</button>
            </td>
          </tr>
        </c:forEach>
      </tbody>
    </table>
    <button type="submit" class="btn btn-success">Approve selected</button>
  </form>

  <c:forEach items="${students}" var="s">
    <form id="delete-${s.id}" method="post" action="${pageContext.request.contextPath}/students/${s.id}/delete"></form>
  </c:forEach>
</div>
</body>
</html>

Output

Expected, checked by reading, not executed. After approving ids 1, 2 and 4 the list shows:

     1  2026000001  Asha Verma    MCA  MCS-218, MCS-220  APPROVED  2026-09-26  Edit Delete
     2  2026000002  Rahul Singh   MCA  MCS-219           APPROVED  2026-09-26  Edit Delete
[ ]  3  -           Priya Nair    BCA  BCS-011           PENDING   2026-09-26  Edit Delete
     4  2026000004  Amit Kumar    MCA  MCS-220, MCS-221  APPROVED  2026-09-26  Edit Delete
[ ]  5  -           Sunita Devi   BCA  BCS-012           PENDING   2026-09-26  Edit Delete

Tomcat’s console prints the SELECT for each find, then at commit three update student set ... status=? where id=? statements followed by three insert into admission_status statements, grouped because of order_updates. SELECT * FROM admission_status in MySQL shows two rows for each approved student: PENDING from the application and APPROVED from this batch.

Explanation

  • The browser sends the ticked ids as repeated ids=1&ids=2&ids=4; @RequestParam List<Long> ids collects them. Untick everything and ids is absent, so it is required = false with a null check.
  • approve() is one @Transactional method, so the whole batch is one transaction: all rows are approved or, if any UPDATE fails, the rollback leaves every row PENDING.
  • Inside the loop nothing is sent to MySQL; each managed Student is marked dirty. At flush time Hibernate emits the UPDATEs, and with hibernate.jdbc.batch_size=20 the JDBC driver sends them in groups of 20 statements per round trip instead of one each.
  • flush() then clear() every 20 rows bounds memory: the persistence context does not hold thousands of entities for a long batch. For a lab-sized list the loop ends before the first flush and the commit does it all.
  • The status guard skips rows that are already approved or rejected, so re-submitting the same ids is harmless and returns a count of 0.
  • The enrolment number is year plus zero-padded id, unique because the id is; a real registry would take the next value from a sequence table inside the same transaction.

Viva Questions

Do not copy. Read for understanding and the viva
  • Q: Why is StudentCourse an entity instead of @ManyToMany? A: The join row carries enrolled_on; @ManyToMany cannot hold extra columns.
  • Q: What does orphanRemoval = true do? A: When a child is removed from the collection of a managed parent, Hibernate deletes its row at flush.
  • Q: Managed, detached, transient: what are they? A: Transient: new object, no row. Managed: loaded or persisted inside an open persistence context; changes are tracked. Detached: was managed, the context closed; changes are not tracked.
  • Q: What is LazyInitializationException? A: Touching a lazy collection after the session closed. Fix with join fetch or by doing the access inside the transaction.
  • Q: What is the N+1 problem? A: One query for the list and one more per row for a relation; join fetch turns it into one query.
  • Q: How does the batch approval stay atomic? A: One @Transactional method; commit at the end or rollback on exception, nothing in between is visible.
  • Q: Why flush and clear every 20? A: flush sends the pending statements, clear frees the managed entities; together they keep memory flat on large batches.
  • Q: Where is the Hibernate dialect configured? A: Nowhere; Hibernate 6 reads the database metadata from the connection and picks MySQLDialect itself.

Common Mistakes

Do not copy. Read for understanding and the viva
  • Accessing student.courses in the JSP after a findAll without join fetch, then reporting a LazyInitializationException as a Hibernate bug.
  • Calling em.merge on a detached entity for every update instead of loading and changing the managed one; the collection handling gets unpredictable.
  • Putting @Transactional on the controller instead of the repository, which drags the JSP rendering into the transaction.
  • Deleting through a GET link; browsers prefetch links and delete rows nobody clicked.
  • Setting hbm2ddl.auto=create in the lab and losing every row on each redeploy.
  • Forgetting distinct in the fetch-join query and showing each student once per course.

Session Summary

Write in lab record
  • Question 25: student_admission schema (five tables), entities Programme, Course, Student, StudentCourse, AdmissionStatus, Hibernate configured in JpaConfig
  • Question 26: ConsoleApp reading students through the Hibernate Session and HQL fetch joins, printed as a console table
  • Question 27: StudentRepository and StudentController with list, new, edit, save and delete, views students.jsp and student-form.jsp
  • Question 28: batch approval of ticked applications in one transaction with JDBC batching, enrolment numbers assigned, history rows written
Navigation

Type to search…

↑↓ navigate↵ selectEsc close