Full Stack Java Developer Training
Full Stack Java Developer Training
Core Java & OOP: Employee classes with inheritance and abstraction.
JDBC & SQL: Storing and querying employee data in a MySQL database.
Servlets & JSP: Web pages for employee registration/login using sessions.
Spring Core & MVC: A layered architecture (Controller-Service-DAO) managing employees.
Spring Boot REST APIs: Exposing employee CRUD operations as RESTful services.
Hibernate ORM: Mapping Employee objects to MySQL tables using JPA for data persistence.
ReactJS Frontend: A single-page application that consumes the REST APIs to list, add, update, and
delete employees.
Below is a high-level architecture of the final application, showing how the React frontend, Spring Boot
backend, and MySQL database interact:
Figure: Full Stack Architecture – React frontend calls Spring Boot REST APIs, which use Service and
Repository layers to perform CRUD on the MySQL database (via Hibernate/JPA). Controllers handle
HTTP requests, Services encapsulate business logic, and Repositories handle data access.
Java is an object-oriented programming language that enables us to model real-world entities as objects
within our code. In OOP, we bundle data and the functions that operate on that data into classes, which
act as blueprints for objects. This approach makes code more modular, reusable, and easier to maintain.
Java strictly follows principles like DRY (“Don’t Repeat Yourself”), meaning common logic is written once
(e.g., in a parent class) and reused, resulting in code that is easier to maintain and debug.
Classes and Objects: A class in Java is a template that defines state (attributes) and behaviour (methods)
for objects. An object is an instance of a class, representing a specific entity with actual values for the
attributes. For our EMS project, we will start with an Employee class. Each Employee object will represent
an individual employee with properties such as id, name, email, etc., and possibly behaviours like “display
details” or “compute salary”.
// Constructor
public Employee(int id, String name, String email, String position, float salary) {
this.id = id;
this.name = name;
this.email = email;
this.position = position;
this.salary = salary;
}
This class encapsulates the data (fields are private, and accessed via public getters/setters), which is an
example of encapsulation – hiding internal details and requiring access through methods. In the code
above, the displayDetails() method prints the object's state; running the main would produce output like:
Employee: Alice
Position: Developer
Salary: 50000.0
(In a real application, we might not have a main in the model class, but this is just to illustrate usage.)
Java’s OOP has four core principles: Abstraction, Encapsulation, Inheritance, Polymorphism.
Encapsulation: As seen, we use private fields and public methods to protect data. This ensures that,
for example, Employee data can’t be arbitrarily changed from outside the class without using its
methods. In our Employee class, fields like id and name are private, and we provide setName(), getName(),
etc. for controlled access. This “protective shield” around data improves code reliability.
Inheritance: Inheritance allows a new class to reuse and extend the behavior of an existing class. We
could have specialized employee types. For instance, suppose we create a subclass HRManager that
extends Employee. An HR Manager is an employee who can perform additional actions, like hiring
new employees. Using inheritance, HRManager will automatically have all properties of Employee (id,
name, etc.) and can define extra methods or override behavior.
// Child class
class HRManager extends Employee {
public HRManager(float salary) {
super(salary); // call parent constructor
}
@Override
public void work() {
System.out.println("Managing employees and HR tasks.");
}
public void addEmployee(Employee e) {
System.out.println("Hiring new employee: " + e.getName());
// ... (logic to add employee to system)
}
}
In this snippet, HRManager inherits the getSalary() method and salary field from Employee. It overrides
work(): if we call work() on an Employee instance, it prints "Working as a regular employee.", but on an
HRManager instance it prints "Managing employees and HR tasks." – demonstrating polymorphism
(the same method call work() behaves differently based on the object type). Also, HRManager
introduces a new method addEmployee() that regular employees don’t have. This models real life – an
HR Manager can hire people, whereas a general employee might not.
Polymorphism: This term literally means “many forms.” In Java, it appears when a subclass
overrides a method of its superclass, or when one interface is implemented by different classes. For
example, if we had a work() method in multiple subclasses (Developer.work(), HRManager.work(), etc.), we
could treat all employees generally but each will execute its own version of work() at runtime. We
could write:
Employee e1 = new Employee(40000f);
Employee e2 = new HRManager(70000f);
e1.work(); // prints "Working as a regular employee."
e2.work(); // prints "Managing employees and HR tasks."
Even though e2 is declared as Employee, it references an HRManager object, so Java will invoke the
overridden work() of HRManager – this is runtime polymorphism via method overriding.
Abstraction: Abstraction means exposing only the necessary features of an object while hiding
complex implementation details. In Java, abstraction is often achieved using abstract classes or
interfaces. For example, we might define an EmployeeDAO interface with abstract methods like
save(Employee e), delete(int id), etc., without specifying how these work. Different implementations (e.g.,
one using a file, another using a database) can provide the details. In Week 3 and Week 8, we’ll see
abstraction in action when designing data access layers (using interfaces for DAO and implementing
them with JDBC or JPA). At this stage, just note that abstraction helps separate what an object does
from how it does it.
By the end of Week 1, you should be able to create simple Java classes and use them. As a hands-on
exercise:
Define the Employee class (as above or with your own fields). Ensure proper encapsulation (private
fields, getters/setters).
Create a few Employee instances in a main method (or a separate test class) to simulate some
operations:
o Instantiate an Employee and set its properties.
o Print out its details.
o (Optional) Create a subclass like Manager or HRManager that extends Employee. Override a
method (e.g., toString() or a custom work() method) and demonstrate the difference in behavior.
Run these tests in Eclipse to familiarize yourself with editing, compiling, and running Java programs.
Practice using the Eclipse debugger to inspect object states at runtime.
Summary – Week 1: We established the foundation of our EMS by modeling employees as Java objects.
Key takeaways:
Java classes encapsulate data and provide methods to operate on that data (e.g., Employee with fields
and methods).
OOP principles make our code organized: we can extend base classes to create specialized ones
(inheritance), and override methods for polymorphic behavior.
We now have an Employee class that we will enrich in future weeks and eventually persist and
manage through various layers of the application.
In real-world programs (and certainly in a full stack app), things can go wrong: a database might not
connect, or an input might be invalid. Java’s exception handling mechanism allows us to catch runtime
errors and handle them without crashing the program. In other words, “Exception handling lets you catch
and handle errors during runtime – so your program doesn’t crash.”
Try-Catch: We wrap code that might throw an exception in a try block, and provide one or more
catch blocks to handle specific exceptions. “The try statement allows you to define a block of code to
be tested for errors while it is being executed. The catch statement allows you to define a block of
code to be executed if an error occurs in the try block.” For example, reading from a file or parsing
input could throw exceptions.
Example: Suppose we have a text file "employees.txt" and we want to load employees from it. We can
use a try-catch when opening and reading the file:
try {
BufferedReader reader = new BufferedReader(new FileReader("employees.txt"));
String line;
while ((line = reader.readLine()) != null) {
// parse the line and create Employee object
}
reader.close();
} catch (FileNotFoundException e) {
System.err.println("Error: File not found: " + e.getMessage());
} catch (IOException e) {
System.err.println("Error reading file: " + e.getMessage());
}
Here we catch two types of exceptions: FileNotFoundException if the file is missing, and a general
IOException for other I/O errors. Instead of the program crashing, we handle the error by printing a
message. We could also recover or default to some behavior if needed.
Finally: An optional finally block can be added after catch blocks. Code in finally runs regardless of
whether an exception occurred, typically used for cleanup (like closing a file or database
connection). For instance, in the above code we might move the reader.close() call to a finally block to
ensure the file is closed even if an error occurs.
By using exceptions properly, we make our EMS more robust. For example, when adding a new
employee via console input, we can catch invalid inputs (like non-numeric ID or salary) and prompt the
user again, rather than the program terminating with an error.
In Week 1, our data was in-memory only (when the program ends, the employees are gone). Before we
introduce databases, a simple way to persist data is using files. File I/O in Java is done through classes in
java.io package such as FileReader, FileWriter, BufferedReader, BufferedWriter, etc.
Writing to a File: We can save employee records in a text file (CSV format, for instance). Example:
FileWriter fw = new FileWriter("employees.txt", true); // true for append mode
BufferedWriter bw = new BufferedWriter(fw);
// Suppose emp is an Employee object
bw.write(emp.getId() + "," + emp.getName() + "," + emp.getEmail() + "," + emp.getPosition());
bw.newLine();
bw.close();
Reading from a File: As shown earlier, use BufferedReader to read each line, then split by comma and
create Employee objects:
String line = "101,Alice,[email protected],Developer";
String[] parts = line.split(",");
int id = Integer.parseInt(parts[0]);
String name = parts[1];
String email = parts[2];
String position = parts[3];
Employee emp = new Employee(id, name, email, position, 0.0f);
(We used a dummy salary of 0.0 here since our file didn’t include salary.)
Using file I/O, you can implement a simple persistence for the EMS console application:
Keep in mind, file I/O is slower than in-memory operations and lacks structure (no query language), so as
we move to Week 3 and beyond, we’ll migrate this data to a relational database. But understanding file
I/O is important (for configuration files, logging, etc., in real apps).
Java provides the Collections Framework (in java.util) to handle groups of objects dynamically (resizable
arrays, sets, maps, etc.). In an EMS, you might have a list of all employees or a map of employees by their
ID.
List (ArrayList): An ordered collection (like a dynamic array). We can use ArrayList<Employee> to store
Employee objects. It allows duplicates and preserves insertion order. Example:
List<Employee> employees = new ArrayList<>();
employees.add(emp1);
employees.add(emp2);
System.out.println("Total employees: " + employees.size());
You can loop through the list to display or process each Employee.
Set (HashSet): Unordered collection that disallows duplicates. This could be useful to ensure no two
employees with the same ID or email are added (though more typically you’d enforce uniqueness
by checking before add, or using a Map).
Map (HashMap): A key-value structure. We could map employee ID to the Employee object:
Map<Integer, Employee> empMap = new HashMap<>(); empMap.put(emp.getId(), emp);. This makes lookups by
ID efficient (constant time on average). For example, empMap.get(101) would directly give Employee
with ID 101 if present.
In our console EMS, we might maintain an ArrayList<Employee> as the master list of employees in memory
while the program runs. When adding or removing employees, we update this list (and also update the
file for persistence). Using collections makes it easy to manage the dynamic set of employees without
worrying about array resizing or other low-level details.
Collections also come with utility methods and algorithms (sorting, searching, etc.). You could, for
instance, sort the employees list by name or salary using Collections.sort with a custom Comparator.
Threads allow a program to do multiple tasks concurrently. While a full EMS might not need complex
multithreading at this stage, it’s useful to know the basics for scenarios like handling multiple user requests
simultaneously on a server (which web servers do using threads).
A thread in Java is a separate path of execution. The main program runs on the "main thread". You
can create new threads by either extending Thread class or implementing Runnable.
Why threads? “Threads allow a program to operate more efficiently by doing multiple things at the same
time… perform complicated tasks in the background without interrupting the main program.” For
example, in a UI application, one thread can handle user interface events while another thread performs a
long computation in background.
Creating a Thread – Example: Suppose we want to simulate a background task in EMS, like sending a
notification email whenever a new employee is added, without pausing the main flow of the program.
class EmailNotifier implements Runnable {
private Employee newEmp;
public EmailNotifier(Employee emp) { this.newEmp = emp; }
@Override
public void run() {
// Simulate sending email
System.out.println("Sending welcome email to " + newEmp.getEmail());
// ... actual email sending code (omitted)
System.out.println("Email sent for new employee " + newEmp.getName());
}
}
In this snippet, EmailNotifier implements Runnable (so it can be executed by a Thread). We create a new Thread
with this task and call start(), which causes the run() method to execute in a separate thread. The main
thread can continue (e.g., accept next input) concurrently. The output might intermix, for example:
Main thread continues while email is being sent...
Sending welcome email to [email protected]
Email sent for new employee Alice
demonstrating parallel execution.
For our training, it's sufficient to grasp how to start threads and the concept of race conditions (if multiple
threads access shared data like the employees list, we’d need to synchronize access). In a web context, each
client request might be handled in its own thread by the server – we won't manage those threads directly,
but knowing the concept helps in writing thread-safe code.
Exception Handling: Wrap user input parsing in try-catch. For example, if prompting for an
employee’s salary which should be a number, catch NumberFormatException and ask again rather than
crashing.
File Persistence: Implement saving the employees list to a file (e.g., upon program exit or after each
add/delete operation). Also implement loading from the file at program start. This way, data is
retained between runs.
Use Collections: Instead of an array or individual variables, use an ArrayList<Employee> to store
employees in memory. Implement functions to add, remove, search, and list employees using this
list.
(Optional) Threads: As an extra challenge, simulate a background task such as periodic autosave.
For instance, start a thread that every 30 seconds writes the current employees list to file (you can
use Thread.sleep(30000) within a loop for this thread). Ensure proper termination of this thread when
program exits (or mark it as daemon thread).
Adding multiple employees, exiting and restarting to see if file load works.
Trying invalid inputs (letters where a number is expected) to see if exceptions are handled.
Searching for an employee that doesn't exist, etc.
Exceptions: You learned to use try-catch to make the program handle errors (like file not found or
bad input) gracefully.
File I/O: We added rudimentary persistence by reading/writing a text file with employee data.
Collections: Instead of fixed arrays, we manage dynamic lists of employees using ArrayList, making it
easier to scale our program as the number of employees grows.
Threads: Gained a basic understanding of multi-threading, which will be useful when we move to
server-side programming (where numerous client requests may be handled in parallel).
At this point, we have a simple EMS running in the console, with data persistence via a flat file.
Starting next week, we transition to using a database for more robust data storage and begin
building a web interface.
Relational Database & SQL: MySQL is a popular open-source relational database. In a relational database,
data is organized into tables consisting of rows and columns. SQL (Structured Query Language) is used to
define and manipulate data in these tables. For our EMS, we will create a table employees to store
employee records.
id– integer, primary key (unique identifier for each employee, perhaps auto-incremented).
name – varchar (string for name).
email – varchar (string for email, unique constraint can be added if needed).
position – varchar (job title).
salary – float (or decimal for salary).
We can execute these commands using MySQL client or a tool like MySQL Workbench. (Note: We might
add UNIQUE(email) if we want to ensure no duplicate emails.)
Example SQL:
INSERT INTO employees (name, email, position, salary)
VALUES ('Alice', '[email protected]', 'Developer', 50000.00);
JDBC is a standard Java API for database connectivity. Using JDBC, our Java program can send SQL
statements to MySQL and get the results. To use JDBC with MySQL, you need the MySQL Connector/J
driver (a JAR file). If using Maven, adding the dependency mysql:mysql-connector-java will include it. In
Eclipse, you can add the JAR to the classpath. (If using an IDE with build tools, this may be handled by
Maven/Gradle – but ensure the connector is available.)
1. Load the JDBC driver: In old JDBC, you would call Class.forName("com.mysql.cj.jdbc.Driver"); to load the
driver class. In modern JDBC (JDBC 4+), this is not always required explicitly, as the driver can
auto-register.
2. Obtain a Connection object: Use DriverManager.getConnection(url, username, password). The URL format for
MySQL is jdbc:mysql://<host>:<port>/<database>. For example:
3. String url = "jdbc:mysql://localhost:3306/EMS";
4. Connection conn = DriverManager.getConnection(url, "root", "password");
This attempts to connect to the database EMS on local MySQL server at port 3306 with user "root"
and the given password.
5. Create a Statement: There are two main ways – using a Statement or a PreparedStatement (the latter is
preferred for parameterized queries and to prevent SQL injection). For now, we can use Statement for
simplicity:
6. Statement stmt = conn.createStatement();
7. Execute SQL queries: For a query that returns results (SELECT), use stmt.executeQuery(sql) which returns
a ResultSet. For updates (INSERT/UPDATE/DELETE), use stmt.executeUpdate(sql) which returns the
number of affected rows.
8. Process results: If we got a ResultSet (from SELECT), iterate over it to read rows:
9. ResultSet rs = stmt.executeQuery("SELECT * FROM employees");
10. while(rs.next()) {
11. int id = rs.getInt("id");
12. String name = rs.getString("name");
13. // ... fetch other columns
14. System.out.println(id + ": " + name);
15. }
16. rs.close();
17.Close connection: When done, call conn.close() to release the database resource (or use try-with-
resources to auto-close).
Example – Reading from DB via JDBC: Suppose we want to retrieve all employees and print their names:
try {
Class.forName("com.mysql.cj.jdbc.Driver"); // load driver (optional in modern JDBC)
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/EMS", "root", "password");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT id, name, email, position, salary FROM employees");
while(rs.next()) {
System.out.println(rs.getInt("id") + ": " + rs.getString("name") +
" - " + rs.getString("position"));
}
rs.close();
stmt.close();
conn.close();
} catch(Exception e) {
e.printStackTrace();
}
This snippet shows the core pattern: open connection → execute query → process results → close. We
handle exceptions with a broad catch for simplicity (printing stack trace), but in a real application, you’d
have specific error handling (like if connection fails, maybe retry or display a user-friendly message).
illustrates similar code: establishing connection and querying a table using JDBC.
Create (INSERT): When a new employee is added via the program (or later via a web form),
instead of writing to file, use an INSERT SQL. For example:
String sql = "INSERT INTO employees (name, email, position, salary) VALUES ('"
+ emp.getName() + "','" + emp.getEmail() + "','"
+ emp.getPosition() + "'," + emp.getSalary() + ")";
stmt.executeUpdate(sql);
Using string concatenation like this is functional but not secure (SQL injection risk). A better
approach is:
PreparedStatement ps = conn.prepareStatement(
"INSERT INTO employees(name, email, position, salary) VALUES (?, ?, ?, ?)");
ps.setString(1, emp.getName());
ps.setString(2, emp.getEmail());
ps.setString(3, emp.getPosition());
ps.setDouble(4, emp.getSalary());
ps.executeUpdate();
The ? placeholders and corresponding setX methods prevent SQL injection and handle quoting for
us.
Read (SELECT): We already saw selecting all employees. If we need to find one employee by ID:
PreparedStatement ps = conn.prepareStatement("SELECT * FROM employees WHERE id = ?");
ps.setInt(1, searchId);
ResultSet rs = ps.executeQuery();
if(rs.next()) {
// found the employee
Employee emp = new Employee(rs.getInt("id"),
rs.getString("name"),
rs.getString("email"),
rs.getString("position"),
rs.getFloat("salary"));
// use emp as needed
} else {
System.out.println("Employee not found!");
}
Update: Suppose we want to give a raise or change an employee’s position:
PreparedStatement ps = conn.prepareStatement("UPDATE employees SET salary = ? WHERE id = ?");
ps.setDouble(1, newSalary);
ps.setInt(2, empId);
int rows = ps.executeUpdate();
if(rows > 0) {
System.out.println("Employee salary updated.");
}
Delete:
PreparedStatement ps = conn.prepareStatement("DELETE FROM employees WHERE id = ?");
ps.setInt(1, empId);
ps.executeUpdate();
System.out.println("Employee deleted from database.");
Ensure to handle exceptions around these calls (e.g., SQL syntax errors, unique constraint violations, etc.).
Tip: Use try-with-resources for Connection, Statement, and ResultSet to auto-close them:
try(Connection conn = DriverManager.getConnection(url, user, pass);
PreparedStatement ps = conn.prepareStatement("...")) {
// use ps, maybe get a ResultSet
} catch(SQLException ex) {
// handle
}
This pattern is cleaner and ensures resources are closed even if exceptions occur.
In Eclipse, you should configure the MySQL Connector/J on your project’s build path. If using Maven,
include in pom.xml:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version> <!-- or latest available -->
</dependency>
(If not using Maven, manually adding the JAR to the project’s libraries is needed.)
Testing the database connection: Before integrating with your EMS logic, write a small test in main to
ensure you can connect and run a simple query:
public static void main(String[] args) {
try(Connection conn = DriverManager.getConnection(url, user, pass)) {
System.out.println("Database connection successful!");
} catch(SQLException e) {
e.printStackTrace();
}
}
If this prints the success message, you’re connected. If not, check that MySQL is running, the
URL/credentials are correct, and the connector JAR is properly included.
Once confirmed, replace file I/O operations in your EMS with JDBC calls:
On program start, populate the employees list by selecting all records from DB (or you can directly
operate on the database without keeping an in-memory list, but caching in a list could still be useful
for quick read access – however, consider sync issues if direct DB updates).
On adding an employee, do an INSERT.
On removing, do a DELETE.
For listing, either use the cached list or fetch fresh from DB via SELECT.
For search, use a SELECT with WHERE id = ... or filter the cached list.
This essentially turns your console EMS into a two-tier application (client and database) instead of relying
on flat files.
The curriculum mentions SQL joins – while our EMS at this point has a single employees table, in a more
realistic scenario, you might have related tables (e.g., a department table and an employee table, where each
employee is linked to a department). A JOIN in SQL allows combining rows from two or more tables
based on a related column between them.
For example, if we had departments table (dept_id, dept_name) and employees had a dept_id foreign key, we
could run:
SELECT e.name, e.position, d.dept_name
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id
WHERE d.dept_name = 'Engineering';
This would list all employees in the Engineering department (combining data from both tables). While our
current EMS doesn’t involve joins yet, understanding them is crucial as systems grow (and we will see
some join-like behavior when we use Hibernate relationships in Week 8).
For now, ensure you understand one-to-many relationships conceptually (one department has many
employees, one employee belongs to one department, etc.), since it will inform how we structure data
later.
By the end of this week, your EMS should be backed by MySQL. Complete the following:
Install MySQL (if not already) and create the EMS database and employees table as discussed.
Migrate data: If you had employees in the file, consider writing a small script or program to read
the file and INSERT them into the database so you don’t start from scratch.
Update EMS code: Remove or bypass file storage; use JDBC for all data operations.
Implement all CRUD operations in the console menu (e.g., add employee, view all, search by ID,
update salary/position, delete employee).
Test thoroughly: Add a few employees, restart program (it should load from DB the existing
entries), update an employee and ensure changes reflect in DB, delete an employee and check DB,
etc. Use MySQL Workbench or the MySQL command-line to verify data in the table after each
operation.
Set up a MySQL database and used SQL to define the employees table.
Learned fundamental SQL commands (CREATE TABLE, INSERT, SELECT, UPDATE, DELETE) and
how to use them to manage data.
Used JDBC in Java to connect to the database and execute SQL statements from our program.
We built a simple “DB CRUD App” for employees – our EMS can now create, read, update, and
delete employee records in MySQL.
This foundation will support our web application development. From next week onward, we shift
focus to building a web interface (first using Servlets/JSP, then Spring) while continuing to use this
database for persistence.
A Servlet is a Java class that runs on a server and responds to HTTP requests (usually from a web
browser). It’s part of Java’s Java EE (Jakarta EE) specifications for building dynamic web content. Think of
a servlet as similar to a console main program, but instead of reading/writing from the console, it reads
HTTP requests and writes HTTP responses (usually HTML).
JSP is a templating technology that allows writing HTML pages with embedded Java code or expressions.
JSPs are translated by the server into servlets under the hood. They make it easier to generate HTML than
writing out print statements in a servlet.
We will use a combination of servlets and JSP to implement a simple MVC-like pattern:
A Servlet acts as a Controller (processing input, interacting with the model – e.g., using JDBC/DB –
and deciding what view to display).
A JSP acts as the View (presenting data in HTML).
Using Eclipse, you can create a Dynamic Web Project or use Maven’s archetype for a webapp. Key pieces:
Deployment Descriptor (web.xml): Defines servlet mappings if not using annotations, and other
config. For simplicity, we may skip detailed web.xml by using servlet 3.0+ annotations
(@WebServlet).
Tomcat Server: In Eclipse, add Tomcat to server runtime. When you run the web project, Eclipse
will deploy it to Tomcat.
Ensure your project structure has a WebContent (or src/main/webapp for Maven) folder for HTML/JSP files, and
that you have the servlet API available (Eclipse adds it if you target a runtime like Tomcat).
In this servlet, we gather data and then forward to a JSP. The JSP will use the employeeList attribute to
display the data. Forwarding is done with RequestDispatcher.forward() which transfers control to the JSP on the
server side (the browser’s URL still shows /listEmployees – the user isn’t redirected). This is a common
pattern: Servlet as controller, JSP as view.
This JSP uses Java code within <% ... %> to iterate over the list and <%= ... %> to output values. This is
one approach; a better approach is to use JSP Expression Language (EL) and JSTL tags to avoid Java
scriptlets. For example, with JSTL, one could do:
<c:forEach var="e" items="${employeeList}">
<tr><td>${e.id}</td> ... </tr>
</c:forEach>
assuming proper taglib directives. The codejava excerpt shows an example of using ${name} in JSP after
setting it in servlet, and using <c:forEach> to iterate collections.
For simplicity, our example above used scriptlets, but be aware that modern JSP practice favors JSTL/EL.
This form will send a POST request to the URL addEmployee (which we will map to AddEmployeeServlet).
Here we use request.getParameter("fieldName") to retrieve form data. We parse numeric fields as needed. After
saving the new employee (via some DAO or direct JDBC inside servlet), we use sendRedirect to redirect the
client to the list page. We choose redirect here (as opposed to forward) because after a form submission,
a redirect implements the PRG pattern (Post-Redirect-Get) to avoid duplicate form submissions on refresh.
The user’s browser will now issue a GET for /listEmployees and get the updated list.
This ensures only logged-in users can access employee pages. We can also display the username in
JSP by pulling from session via EL: ${sessionScope.username}.
A logout link can invalidate the session: request.getSession().invalidate() and redirect to login.
This is a rudimentary illustration of session usage; real apps use frameworks or filters for authentication,
but understanding the HttpSession API is valuable. A session allows data to persist across multiple requests
from the same client (like a user login state).
Deploy on Tomcat: In Eclipse, run the project on the server. Ensure the context path is correct
(often the project name).
Access in browser: Open http://localhost:8080/YourApp/listEmployees (or whatever initial page you set,
maybe login.jsp). You should see either a login or the list page. Initially the list might be empty (if
DB has no data).
Test adding employees: Navigate to "Add New Employee" page, fill the form, submit. The browser
should redirect to the list page now showing the new employee. Check the database table to
confirm the row is inserted.
Session test (if implemented): Try accessing a page without login (should redirect to login). Login
and then try the pages (should succeed). Log out and test protection again.
Error handling: If any exceptions occur in servlets (e.g., DB connection issues), the default is Tomcat
will show a stack trace page. We can configure error pages in web.xml if desired, but at this stage
the focus is on functionality. Monitor the server console for any System.out.println or stack traces for
debugging.
Develop the JSP pages for list and add (and optionally login).
Write the corresponding servlets to handle data retrieval and submissions.
Reuse your JDBC code from Week 3 within servlets or, better, refactor a bit: you might create a
utility class or DAO (Data Access Object) class for Employee that has static methods like findAll(),
save(Employee), etc., to avoid duplicating JDBC logic in multiple servlets.
Use sessions to handle a simple authentication state (optional but recommended for learning).
Ensure that after adding or deleting an employee via the web interface, the changes reflect in the
database and in subsequent page views.
This week essentially produces a “Mini web app” version of the EMS. While it may not be pretty (basic
HTML styling) or fully secure, it demonstrates the end-to-end flow: client -> HTTP -> servlet -> JDBC ->
database, and back to client with dynamically generated content.
Learned what Servlets are and how the Servlet container (like Tomcat) routes requests to our code.
Created dynamic JSP pages and used them to present employee data nicely in an HTML table.
Handled form input and learned to retrieve form parameters in servlets.
Implemented navigation flows using request forwarding (server-side inclusion of JSP) and
redirection (instructing the browser to issue a new request).
Used HttpSession to maintain user login state across requests, illustrating how sessions allow us to
treat multiple HTTP requests as a continuous interaction.
Our EMS can now be accessed via a web browser, making it far more user-friendly than the old
console version. Up next, we will improve the design and structure of our application using the
Spring framework, which will streamline many of these tasks and prepare our app for even more
features.
As our application grows, managing object creation and dependencies becomes complex. For instance, in
Week 4, servlets might directly call JDBC code. Ideally, we want a cleaner separation:
The Spring Framework helps by providing an IoC container which will create and manage the lifecycle of
objects (called beans) and inject dependencies where needed. “Inversion of Control (IoC) is a design
principle where the control of object creation and lifecycle is managed by a framework or container
rather than by the developer. Spring's IoC container creates, configures, and manages objects (beans) and
injects their dependencies.”. This means instead of our code doing new EmployeeDAO() or new EmployeeService(),
we declare these in Spring config, and Spring will supply them to our classes.
Dependency Injection (DI) is the mechanism by which the IoC container provides the needed
dependencies to a bean. Rather than a bean looking up or creating its collaborators, it is given to it
(injected) by the container. “Dependency Injection allows objects to be injected with their dependencies
rather than creating those dependencies themselves”. This leads to looser coupling and easier unit testing
(we can swap out mock dependencies, etc.).
In simpler terms: We define beans (like EmployeeService, EmployeeRepository) in Spring configuration, and we
declare in our code what we need (e.g., EmployeeService needs an EmployeeRepository). Spring will inject an
instance of EmployeeRepository into EmployeeService automatically, as long as it knows about both and the
relationship (via annotations or XML config).
We will use Spring Framework (version 5 or above). If using Maven, include dependencies for Spring
Context:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.30</version>
</dependency>
Also include spring-beans and spring-core (these might be transitively included with context). Since eventually
we want Spring MVC, we will include those in the next week, but for now focusing on core.
Defining Beans: There are two main ways – XML configuration or annotation-based with classpath
scanning. Modern Spring favors annotations:
Annotations: Use @Component (or @Service, @Repository, etc.) on classes to mark them as beans. Enable
component scanning so Spring finds them.
XML: Define <bean id="employeeService" class="com.example.EmployeeService"> etc. and their property
injection in an XML file (like beans.xml).
Here @Service and @Repository are specializations of @Component (they act the same for component
scanning but convey the role). @Autowired on the repo field tells Spring to inject an EmployeeRepository
bean here.
Create a configuration:
We can either use an XML file or a Java config class. For variety, let's use a Java config:
@Configuration
@ComponentScan(basePackages="com.example")
public class AppConfig {
// If any manual bean definitions needed, we could add @Bean methods here.
}
indicates this class defines beans (it can also just be used to trigger scanning).
@Configuration
@ComponentScan tells Spring which packages to scan for @Component classes.
In a web app, Spring’s ContextLoaderListener or Spring Boot (next weeks) would manage this for
us. But for now, to see IoC in action, you might write a test or a main method to retrieve beans
from the container.
What happens: Spring scans com.example package, finds EmployeeService and EmployeeRepository
classes with relevant annotations. It creates one EmployeeRepository bean and one EmployeeService bean. When
creating EmployeeService, it sees the @Autowired dependency on EmployeeRepository and injects the instance
of EmployeeRepository into it. Now, whenever we request EmployeeService from context, we get a fully
initialized object with its repo field set.
This inversion of control means our service class didn’t have to find or create the repository – it just
declares it needs one. The container took care of supplying it.
Bean scopes: By default, Spring beans are singleton (one instance for the container). So both our service
and repository are singletons used throughout. Spring supports other scopes (prototype, request, session,
etc.) but default singleton is fine for now.
We will refactor EMS to use a Service layer and Repository (DAO) layer:
EmployeeRepository (DAO): Responsible for all data operations (previously done via JDBC code).
Could use JDBC template or raw JDBC calls. For now, we can wrap our Week 3 JDBC logic here.
For example:
@Repository
public class EmployeeRepository {
// DataSource or Connection info could be here (or better, use JdbcTemplate)
public List<Employee> findAll() {
// JDBC code to SELECT * FROM employees and return list
}
public void save(Employee emp) {
// JDBC code to insert emp
}
// etc. (findById, update, delete)
}
EmployeeService: Sits above repository. It can contain business rules. For example, before saving,
maybe check that email is unique (could call repository to find by email). Or could apply some logic
like setting default values.
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository repo;
public List<Employee> getAllEmployees() {
return repo.findAll();
}
public void addEmployee(Employee emp) {
// perhaps some validation:
if(emp.getName() == null || emp.getName().isEmpty()) {
throw new IllegalArgumentException("Name is required");
}
repo.save(emp);
}
// similarly updateEmployee, deleteEmployee, etc.
}
Now our servlets (or later controllers) will interact only with EmployeeService, not directly with JDBC. This
separation improves maintainability:
If we change how data is stored (say move from JDBC to JPA/Hibernate), we only change
EmployeeRepository implementation.
If we add business rules, they go into EmployeeService without cluttering the web layer.
Integrating Spring with the Web (in this basic app): We can use Spring in servlets by either using Spring’s
WebApplicationContext or by manually fetching beans. A simple approach: in ListEmployeeServlet, we could
obtain the Spring context from a context listener or static initializer. Alternatively, use
WebApplicationContextUtils:
Then set request attribute and forward to JSP. However, since next week we move to Spring MVC, we
might not spend too much time integrating servlets with Spring manually. It’s good to conceptually know
that in a Spring MVC app, controllers are Spring beans themselves and automatically get their
dependencies injected.
For now, you can simulate usage of the service in a unit test or a main outside of the web context:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
EmployeeService service = ctx.getBean(EmployeeService.class);
// test retrieval
service.getAllEmployees().forEach(emp -> System.out.println(emp.getName()));
ctx.close();
}
This should print names of employees from DB, using the repository bean inside service.
Note: For repository to connect to DB, we need a DataSource bean or similar. We might configure a
DataSource in Spring (via XML or Java config) and inject it into EmployeeRepository. Or the repository can use
DriverManager internally (not ideal for resource mgmt). A simple approach: use Spring’s
DriverManagerDataSource:
@Bean
public DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/EMS");
ds.setUsername("root");
ds.setPassword("password");
return ds;
}
Then in EmployeeRepository, use @Autowired DataSource ds; and get Connection from it. Or better, use Spring’s
JdbcTemplate which simplifies JDBC usage.
However, since Spring Boot (Week 7) will simplify a lot of this configuration, we might not delve deep
here. It's enough to grasp IoC/DI concept.
Create EmployeeRepository and EmployeeService classes annotated with @Repository and @Service.
Move JDBC code from servlets to EmployeeRepository.
Add any needed business logic in EmployeeService (even if just a pass-through for now).
Configure Spring (via XML or annotations) and initialize the Spring container when the web app
starts. You can add Spring’s ContextLoaderListener in web.xml to load AppConfig:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.example.AppConfig</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
This will start Spring’s root WebApplicationContext when the webapp deploys.
Modify servlets to obtain beans from Spring context instead of creating their own connections or
logic:
e.g., EmployeeService service = (EmployeeService) ctx.getBean("employeeService"); (bean name defaults to class
name with lowercase first letter if not specified).
Test the application – functionality should remain same (list/add works) but now powered by
Spring-managed beans.
IoC Container: We let Spring create and manage objects, which decouples component creation
from usage.
Dependency Injection: Used @Autowired to have Spring supply dependencies rather than using new in
our code. This makes it easy to swap implementations (for example, we could have a different
EmployeeRepository impl for testing that doesn’t hit a real DB).
Layering: We structured the app into a Service layer and Repository (DAO) layer, each a Spring
bean. This makes our design more modular and in line with enterprise best practices (Controller →
Service → DAO).
Although our web interface still used servlets, we’ve prepared the backend to be consumed easily
by Spring MVC controllers next week. We also get benefits like easier unit testing (we could test
EmployeeService by injecting a mock EmployeeRepository thanks to DI) and clearer separation of concerns.
Next, we will leverage Spring MVC to replace our servlets and JSPs, integrating smoothly with these
service beans.
DispatcherServlet: The front controller provided by Spring. It intercepts all incoming requests (via a
URL pattern mapping, usually "/*" or specific suffix).
Controller (@Controller classes): These are analogous to servlets, but as Spring beans with
annotation mappings. They handle requests and prepare data for view.
Model: Typically the data (like our Employee objects, or a list of employees) that the controller
adds to the model for the view to render.
View: The UI representation, often a JSP or Thymeleaf template, responsible for presenting the
model data.
The flow:
This is somewhat similar to what we did with servlets + JSP, but Spring MVC abstracts a lot of boilerplate:
@Autowired
private EmployeeService employeeService;
@Controller designates this class as a web controller (so it's also a candidate for component scanning
like @Component).
We injected EmployeeService so we can call business methods.
@GetMapping and @PostMapping are shorthand for @RequestMapping(method=GET/POST). The paths are
relative to a base (depending on how DispatcherServlet is mapped, often root).
Methods return String which is interpreted as view name. We could also return ModelAndView or other
types, but simple string is fine for static view name.
We directly use Model parameter to add attributes. Spring will provide a Model object when we
declare it in method signature.
In the POST handler, we used @ModelAttribute("employee") Employee emp. This tells Spring to bind form
fields to an Employee object (property names must match). So if our employeeForm.jsp has form fields
with name="name", name="email", etc., Spring will populate a new Employee instance with those values
for us. This avoids manually calling request.getParameter.
After saving, we return "redirect:/employees" to avoid double submission. redirect: prefix in view name
signals Spring to send a redirect.
Declare DispatcherServlet in web.xml (if not using Spring Boot). For example:
<servlet>
<servlet-name>springDispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>/</url-pattern> <!-- map all to Spring -->
</servlet-mapping>
and create spring-mvc-config.xml or corresponding Java config. This config would enable component
scanning (for controllers & other beans) and configure view resolver:
<context:component-scan base-package="com.example"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
We also need to integrate with our Spring IoC context from Week 5. There are two contexts: one is
the root (loaded by ContextLoaderListener with AppConfig) which holds services and repos, and
one is the DispatcherServlet context (for web components). Usually, the root context is a parent of
the servlet context. In XML config, we might do:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
to load AppConfig. Then DispatcherServlet will load its own. Beans in root (services, repos) are
accessible in controllers because DispatcherServlet’s context inherits from root.
Or, simpler: we could combine all config in one if desired (for a small app), but knowing the
distinction is useful.
View (JSP) changes: Our JSP pages can now use Spring's model attributes and form tags:
employeeList.jspcan remain similar, accessing ${employeeList} in JSTL or using <c:forEach>. Because we add
employeeList to model, Spring automatically exposes it to JSP (which is processed by JSTL).
employeeForm.jsp (for new employee) could use Spring’s form tag library to bind to the employee
modelAttribute. For example:
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<form:form modelAttribute="employee" action="${pageContext.request.contextPath}/employees" method="post">
Name: <form:input path="name" /> <br/>
Email: <form:input path="email" type="email" /> <br/>
Position: <form:input path="position" /> <br/>
Salary: <form:input path="salary" type="number" step="0.01" /> <br/>
<input type="submit" value="Save"/>
</form:form>
The Spring form tags know to get and set values from the employee model attribute. If we had errors
(via Spring's validation), we could also use <form:errors> tags.
Benefits observed:
No need to manually extract request parameters or manually set as request attributes; Spring does
data binding and adds to Model behind scenes.
Clear separation: Controller methods are concise and declarative.
Easier to test controllers (you can test the method by simulating a Model and verifying outputs
without running a server, or using Spring’s MockMvc).
The DispatcherServlet with its config handles view resolving and more, reducing boilerplate mapping
code in each servlet.
Ensure all servlets from Week4 are removed or not conflicting (our DispatcherServlet mapping
might override them anyway).
Access http://localhost:8080/YourApp/employees. Spring’s DispatcherServlet will call our listEmployees()
method, which fetches data and returns view. If correctly configured, you should see the list page
with employees.
Try the "Add New Employee" flow:
o Go to /employees/new (we might have a link on list page pointing there). This should display the
form.
o Submit with details. Spring will bind to Employee, call saveEmployee(), and redirect. The new
employee should appear in the list (and in DB).
Test without logging in if you removed login filter (since we haven't integrated Spring Security yet,
we might keep app open or implement a simple check manually in controller if desired).
If any issue arises (like 404 mappings), check that your controller is being scanned and that the
DispatcherServlet mapping covers your URLs.
Create a Spring MVC configuration (either XML or Java class with @EnableWebMvc).
Define a controller (or multiple) for handling EMS web routes (/employees, /employees/new, etc.).
Remove the old servlets from deployment (or simply don’t invoke them).
Update JSPs if needed to use Spring’s tags or at least to match model attribute names.
Test all functionalities through the browser. The outcome should be identical to last week’s app
from user perspective, but internally, we are now using Spring MVC controllers calling Spring
services.
Summary – Week 6: Our application’s web layer is now powered by Spring MVC:
The DispatcherServlet is fronting all requests and dispatching to appropriate controller methods.
We defined Spring Controllers using annotations, which made the code succinct and removed a lot
of boilerplate. We utilize annotations like @GetMapping to map URLs to methods, and simple return
values for view names.
We leveraged Spring’s model binding to automatically populate objects from form data and
simplified form handling with @ModelAttribute and form tags.
The project structure is a proper 3-tier architecture now: controllers (web) -> services (business) ->
repositories (data). Spring manages the dependencies at each layer.
This layered, Spring-powered setup is more professional and scalable. We can easily add features
like validation (Spring’s JSR303 support) or internationalization if needed without changing the
fundamental structure.
With the core application in place, next we’ll use Spring Boot to further streamline configuration
and create RESTful APIs, and later integrate a modern frontend (React) to consume those APIs.
Spring Boot makes it easy to create stand-alone Spring applications with minimal fuss. “Spring Boot makes
it easy to create stand-alone, production-grade Spring-based applications with minimal configuration.”.
Key benefits:
Starter dependencies: Bring in a bunch of relevant libraries with one dependency. E.g., spring-boot-
starter-web includes Spring MVC, Tomcat, Jackson (for JSON).
Auto-configuration: Boot can auto-configure beans based on classpath. E.g., if mysql-connector-java is
on classpath and you set DB properties, it auto-configures a DataSource and JPA EntityManager (if
using Spring Data JPA).
Embedded server: You can run the app as a jar with embedded Tomcat (no need to deploy to
external server), great for development and microservices.
Actuator & more: Provides production features like monitoring with minimal setup (not our focus
now).
In short, Boot will reduce our boilerplate further. Our previous Spring MVC config can largely be replaced
by Boot's auto-config.
7.2 Initializing the Spring Boot Project
We typically create a Spring Boot project using Spring Initializr (start.spring.io) or via IDE. For a Full Stack
EMS, we want:
But we'll likely still use at least a simple HTML or two for demonstration or docs.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- etc., possibly thymeleaf or validation -->
</dependencies>
This includes everything for web (Spring MVC + Tomcat) and JPA (Hibernate + Spring Data), and MySQL
driver.
Running this main method will start the Spring Boot app (Tomcat listening on port 8080 by default).
Now, Boot will auto-configure a DataSource using these credentials, and because we have Spring Data
JPA, it expects entity classes.
Rather than our manual EmployeeRepository with JDBC, we can use JPA:
Mark Employee class with @Entity and @Table("employees"), and @Id on primary key field. Possibly use
@GeneratedValue(strategy=GenerationType.IDENTITY) if we rely on MySQL auto-increment.
Use a Spring Data JPA Repository interface:
import org.springframework.data.jpa.repository.JpaRepository;
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
// JpaRepository provides CRUD methods out of the box
// We can define custom query methods if needed, like:
List<Employee> findByNameContaining(String keyword);
}
Because we extend JpaRepository, Spring Data will provide implementation at runtime. Common
methods: findAll(), findById(), save(), deleteById(), etc., are inherited.
Spring Boot will scan for repositories if @SpringBootApplication is in the same package or a parent
package (it scans sub-packages by default). Otherwise use @EnableJpaRepositories.
We might not even need a separate service if logic is trivial, but it’s good practice to keep it for future
business logic or transaction management.
@Autowired
private EmployeeService service;
@GetMapping
public List<Employee> getAllEmployees() {
return service.getAllEmployees();
}
@GetMapping("/{id}")
public ResponseEntity<Employee> getEmployee(@PathVariable int id) {
Employee emp = service.getEmployeeById(id);
if(emp != null) {
return ResponseEntity.ok(emp);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}
}
@PostMapping
public Employee createEmployee(@RequestBody Employee emp) {
// Assuming employee JSON is sent without id (or id ignored)
return service.createEmployee(emp);
}
@PutMapping("/{id}")
public ResponseEntity<Employee> updateEmployee(@PathVariable int id, @RequestBody Employee empDetails) {
Employee existing = service.getEmployeeById(id);
if(existing == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}
// update fields
existing.setName(empDetails.getName());
existing.setEmail(empDetails.getEmail());
existing.setPosition(empDetails.getPosition());
existing.setSalary(empDetails.getSalary());
Employee updated = service.createEmployee(existing);
return ResponseEntity.ok(updated);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteEmployee(@PathVariable int id) {
Employee existing = service.getEmployeeById(id);
if(existing == null) {
return ResponseEntity.notFound().build();
}
service.deleteEmployee(id);
return ResponseEntity.noContent().build();
}
}
Notes:
@RestControlleris like @Controller + @ResponseBody on all methods, so each method returns data
(serialized to JSON) rather than a view.
We use @RequestMapping("/api/employees") at class level to prefix all routes, and then specific method
mappings.
@PathVariable to extract {id} from URL.
@RequestBody to bind JSON request body to Employee object (needs default constructor and setters or
Lombok @Data to work).
Return types: We use ResponseEntity for control over status codes (especially for not found, no
content). For simple cases (like GET all or POST returning created object), we can just return the
object (Spring will serialize and use 200 OK by default).
We might add @CrossOrigin annotation on class or methods to allow our React app (likely running on
a different port) to call these APIs (CORS).
Spring Boot by default returns JSON in response because it auto-configures Jackson (provided by starter-
web) and message converters.
We should also ensure to disable or secure the old JSP pages if any left. But since the final UI will be
React, we might drop server-side HTML altogether or keep just for admin/troubleshooting.
One more Boot feature: Spring Boot Actuator if we include it, can give health endpoints, metrics, etc.
Another nice feature is using spring-boot-devtools for auto-restart on code changes during dev, and using
application.properties to configure logging, etc.
Create a new Spring Boot project (or modify existing) with the necessary starters.
Configure database connection in application.properties.
Mark Employee as JPA @Entity and create a Spring Data JPA repository for it.
Replace manual JDBC code with repository calls. (Your service might now call
employeeRepository.findAll() etc. through Spring Data).
Implement REST controller(s) for employee operations as above.
Test all API endpoints thoroughly (GET all, GET one, POST, PUT, DELETE). Use Postman or curl
and verify database changes.
The output of this week is a RESTful backend ready for integration with a frontend. Optionally,
document the API (you could integrate Swagger via springdoc-openapi for interactive docs, as
mentioned).
(Optional) Also verify that if you run the Boot app, the old Spring MVC controllers or JSPs if
present are either working or disabled as needed. We mainly need the REST now.
Eliminated a lot of configuration overhead by using Boot’s auto-configuration and starters. Our app
is now packaged and run as a self-contained unit.
Built a full REST API for Employee management following REST principles (stateless operations on
resources identified by URIs, returning JSON).
Using Spring Data JPA, we abstracted away the low-level SQL. We defined an Employee entity and an
interface EmployeeRepository that Spring implemented at runtime, giving us CRUD operations with one
line of code.
Our service/business logic can still enforce rules, but much of the repetitive code is gone, thanks to
JPA and Boot.
We have set up the foundation for the frontend integration: the React app will communicate with
these /api/employees endpoints to display and modify data.
At this point, our backend is essentially complete and professional-grade: a Spring Boot RESTful service
with a proper structure (controllers, services, repositories, entities). Next, we will ensure some basics of
frontend (HTML, CSS, JS) are understood and then dive into building the ReactJS client for this API.
(Note: Much of this week overlaps with what we already did using Spring Data JPA in Week 7. We will
reinforce those concepts and go a bit deeper.)
Hibernate is the implementation of JPA we’re using (Spring Boot starter brings in Hibernate). It maps Java
classes to database tables and manages state of objects:
We annotate Java classes to represent tables, and relationships via annotations like @OneToMany,
@ManyToOne, etc.
JPA allows us to write queries in terms of objects (JPQL or HQL) instead of raw SQL.
In our EMS, Employee is mapped to employees table. If we had another entity, say Department, we might map
like:
@Entity
@Table(name="departments")
public class Department {
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
private String name;
@OneToMany(mappedBy="department", cascade=CascadeType.ALL)
private List<Employee> employees;
// getters, setters
}
And in Employee:
@ManyToOne
@JoinColumn(name="dept_id")
private Department department;
This indicates each employee row has a dept_id foreign key referencing departments(id). With
mappedBy="department" in Department, it means the employees list is mapped by the department field in
Employee. CascadeType.ALL means if we persist a Department, all its employees are persisted too (if
new).
Using Spring Data JPA, if we had a DepartmentRepository extends JpaRepository<Department, Integer> and
EmployeeRepository as before, we could:
Find all employees of a department either via a query method (findByDepartmentId(int deptId)) or by
accessing dept.getEmployees() if the collection is loaded.
We should consider fetch types: by default, @OneToMany is LAZY (meaning dept.getEmployees() will fetch
from DB only when accessed), and @ManyToOne is EAGER (employee.getDepartment() fetched
immediately). For efficiency, we might leave it default or tune as needed.
HQL/JPQL: These are query languages similar to SQL but use entity/class names and property names.
Example JPQL:
@Query("SELECT e FROM Employee e WHERE e.salary > :minSal")
List<Employee> findHighEarners(@Param("minSal") float minSal);
Spring Data allows writing such queries in @Query annotation on repository methods. Or we can use the
repository’s derived query keywords for many cases (e.g., findByDepartmentName(String name) – Spring will
auto-generate the query to join Employee and Department to filter by department’s name).
While our current application likely doesn't need custom JPQL since basic CRUD is enough, knowledge of
JPQL/HQL matters for more complex filtering or batch updates.
Transactions: Spring Data JPA repository methods are transactional by default (the starter configures
transaction manager). Our service can also use @Transactional if we want a method to ensure atomicity
across multiple operations.
For learning, one can enable SQL logging (we did with spring.jpa.show-sql=true) to see the queries being run.
It’s instructive to see how hibernate does lazy loading, etc. We might also use logging for
org.hibernate.SQL and org.hibernate.loader to understand performance.
Second-level cache (EhCache, etc.) or other advanced Hibernate features are beyond scope, but good to
be aware that JPA can cache entities across sessions to reduce DB hits if configured.
Confirm that Employee entity is correct and table is in sync. The ddl-auto=update property in Boot will
auto create/alter tables. For production, one might use migrations instead, but for dev this is fine.
If we had added Department entity, check Boot created the department table and the foreign key in
employees.
We might not actually implement departments in code since original scope didn’t specify, but it was listed
as Capstone idea suggestion. However, explaining it meets the Week 8 objective of showing relationships.
As an exercise:
Create a Department entity, repository, and a small piece of UI or API to link employees to departments.
For example:
Add a field deptId in Employee DTO/JSON to assign department when creating employee through
REST.
In EmployeeRestController.createEmployee, after service.createEmployee(emp), if you wanted to include
department, you’d need to fetch the Department object and set it to employee before saving, since
JPA needs actual object or id mapping.
If DepartmentRepository exists, dept = deptRepo.findById(deptId).orElseThrow(), then emp.setDepartment(dept).
This shows how ORM manages references: after saving, emp.getDepartment().getName() would be accessible,
possibly lazy loaded if not fetched earlier.
If not already, ensure Employee is a proper JPA entity with any needed annotations (@Id, etc.).
Expand the data model by adding a related entity (Department). Connect employees to
departments with JPA annotations.
Update the REST API if needed to handle department info (maybe add endpoints for departments,
or include department details in employee JSON).
Use repository methods or JPQL to implement a feature like "List employees by department" or "List
employees with salary above X".
Test these new queries or relationships via API calls.
Configured JPA/Hibernate through Spring Data JPA. Without writing SQL, we can manipulate
database records by operating on Java objects. For example, calling employeeRepository.save(emp)
behind the scenes performs an INSERT or UPDATE.
Mapped relationships using annotations like @OneToMany and @ManyToOne, which let us navigate
between entities in Java just as naturally as linking objects in memory, while Hibernate handles the
foreign key constraints in the DB.
Discussed HQL/JPQL, which provide database querying capability in object terms (e.g., using entity
attributes in queries rather than column names), making it easier to maintain queries when the
database schema or class structure changes.
Ensured our EMS is truly an ORM-backed model – meaning our primary way of interacting with the
data is via the JPA entity model (Employee objects and EmployeeRepository), not via hand-written SQL or
result sets.
These abstractions will let us develop faster (no need to convert between JDBC ResultSet and
objects manually) and with less error-proneness. We also gained database independence to a
degree; switching to another SQL database (PostgreSQL, etc.) would be as simple as changing the
JDBC URL and driver dependency, since our code is not MySQL-specific (this is one benefit of using
JPA).
Now our backend is fully fleshed out. In the final stretch, we turn to the frontend technologies: first
revisiting basics of web frontend (HTML, CSS, JS) in Week 10, then building the React app in Week 11,
and finally wrapping up the integrated project in Week 12.
(Weeks 9 and 10 in the curriculum focus on RESTful services (which we have done in week 7/8) and
frontend basics respectively. We've somewhat merged RESTful service creation into week 7. We'll proceed
to Week 10 now.)
Topics: Deepening understanding of REST principles, ensuring our API follows best practices (correct use
of HTTP methods and status codes, statelessness, resource-based URLs). Introduce Swagger/OpenAPI for
API documentation. Possibly mention using Postman or writing integration tests for the API.
Resource URI: /employees represents the collection, /employees/{id} for single resource.
HTTP Methods: GET for read, POST for create, PUT for update (replace) – though one might use
PATCH for partial update, and DELETE for delete.
Status Codes: We used 200 OK, 201 Created (if we adjust POST to return that), 204 No Content
for delete, 404 Not Found when appropriate. Using correct codes is important for clients to
interpret responses.
Hypermedia (HATEOAS): We won’t dive deep, but a truly RESTful service might include links in
responses for navigation. For example, each Employee JSON might contain a link to itself or related
resources. Spring has Spring HATEOAS to assist, but that’s advanced usage.
JSON format: We by default produce JSON via Spring Boot. It’s human-readable and widely accepted.
We might ensure consistency in field naming (maybe lower_case or camelCase, and not leaking internal
DB ids if not needed). In our case, the JSON of Employee likely includes all fields. We might also consider
not exposing certain fields (like if there were passwords, etc., but in EMS it's fine). Jackson can be
configured to ignore fields via annotations.
Versioning: Over time, one might need to version APIs (e.g., /api/v1/ vs /api/v2/ or via headers) – a note
for future extension but not needed now.
Security: Our API as is open. In real scenario, we'd protect it (via tokens, etc., often using Spring Security
+ JWT). This might be beyond our current scope, but it's worth noting that a production REST API should
have authentication/authorization so only authorized users can perform certain actions. Possibly an admin
role to add/delete employees, etc.
To help consumers of our API (like front-end developers), documenting it is crucial. Swagger (now
OpenAPI specification) is a standard for describing REST APIs. Springdoc or Springfox can generate a
Swagger UI and JSON spec from our controllers.
This auto-generates OpenAPI docs and provides Swagger UI at /swagger-ui/index.html (and JSON at /v3/api-
docs) by default in Spring Boot.
With these, the Swagger UI will show descriptions for each operation, parameter, response codes, etc.
Testing with Swagger UI: Run the app and navigate to http://localhost:8080/swagger-ui/index.html. You should
see a UI listing the Employee API endpoints, models, etc., and can even execute test calls from the
browser.
This makes it easier for developers (e.g., React devs) to understand how to use the API, without reading
code.
We likely have already tested with Postman. It’s good to mention using Postman:
You can create a collection of requests for employees, with examples for each operation, and even
write tests (Postman scripts) to automate API testing.
In a team, Postman collections or Swagger docs can be shared with frontend devs to guide their
integration.
Integrate Swagger/OpenAPI in the Spring Boot project. Verify that API docs are generated correctly.
Write meaningful annotations for documentation if time permits, or at least verify that the basics
(path, methods, etc.) appear.
Optionally, write a simple integration test or two using Spring’s MockMvc to programmatically call
the API and assert responses (this can ensure your API works as expected and catches regressions).
Prepare for front-end usage: e.g., if any adjustments needed to make the API more convenient for
the React app (maybe add an endpoint for a summary or something).
We ensured our design aligns with REST principles (proper use of methods, status codes, and
stateless handling of requests).
Implemented API documentation using Swagger/OpenAPI, which generates an interactive UI for
exploring and testing our API endpoints. This will be incredibly useful for front-end integration and
for any future developers on the project, as they can see what endpoints exist and what data they
expect.
Learned that thorough documentation and testing of an API are as important as the implementation
– they improve reliability and ease integration.
With a robust, well-documented Employee Management API in place, the stage is set to develop a
rich client-side application.
Now we pivot to the frontend side. Next, we will brush up on foundational web frontend technologies
(HTML, CSS, JavaScript) which underpin frameworks like React, and then proceed to build the React
application that will serve as the user interface for our EMS.
This knowledge will be directly useful when we start building with React (which uses JSX, similar to
HTML, and CSS for styling, and obviously JavaScript for logic).
HTML (HyperText Markup Language) provides the structure of web pages. It uses elements denoted by
tags (e.g., <p> for paragraph, <h1> for a top-level heading, etc.). HTML5 introduced semantic elements
like <header>, <footer>, <article>, <section> to better organize content.
<header>
<h1>Employee Management System</h1>
</header>
<main>
<section id="employee-list">
<h2>Employees</h2>
<table>
<tr><th>ID</th><th>Name</th><th>Position</th></tr>
<!-- Data rows will go here -->
</table>
</section>
<section id="employee-form">
<h2>Add Employee</h2>
<form id="addEmpForm">
Name: <input type="text" name="name" required><br/>
Email: <input type="email" name="email" required><br/>
Position: <input type="text" name="position"><br/>
Salary: <input type="number" name="salary" step="0.01"><br/>
<button type="submit">Add</button>
</form>
</section>
</main>
<footer>
<p>© 2025 My Company</p>
</footer>
</body>
</html>
Key points:
“HTML describes the structure of a web page”: indeed, we use tags to mark headings, paragraphs, lists,
etc. The content itself (text like "Employees") is inside these tags. For a developer, HTML is about
choosing the right tags for the content.
CSS (Cascading Style Sheets) controls the presentation (visual style) of HTML elements. You can set colors,
fonts, positioning, etc. CSS3 adds features like transitions, transforms, flexbox and grid for layout.
Example CSS:
body {
font-family: Arial, sans-serif;
margin: 20px;
}
header, footer {
background-color: #f8f9fa;
padding: 10px;
text-align: center;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
table, th, td {
border: 1px solid #ddd;
}
th, td {
padding: 8px;
}
th {
background-color: #343a40;
color: #fff;
}
form {
max-width: 300px;
background: #f1f1f1;
padding: 10px;
}
form input {
width: 100%;
margin-bottom: 5px;
padding: 5px;
}
This CSS:
Result: A modestly styled page: table headers have a dark band, there's spacing in table cells, form inputs
stretch the form width, etc.
Responsive design: Notice we included <meta viewport>. That ensures on small screens, the CSS is
interpreted relative to device width. We could use CSS media queries to adjust layout for mobile. E.g.:
@media (max-width: 600px) {
table, thead, tbody, th, td, tr {
display: block; /* stack cells vertically on narrow screens */
}
}
This would transform the table to a block layout on small devices (each cell as a row). However, often
we rely on frameworks like Bootstrap which handle a lot of responsiveness with classes.
CSS Flexbox and Grid: Modern CSS provides powerful layout. For example, to layout two sections side
by side:
main {
display: flex;
}
#employee-list, #employee-form {
flex: 1;
margin: 10px;
}
This would place the two sections side by side taking equal space, with margin around them.
JavaScript is the programming language of the web – it runs in the browser, enabling interactivity.
Modern JavaScript (ES6 and beyond) introduced many features (let/const, arrow functions, promises,
modules, etc.).
We will use JavaScript to call our REST API and update the HTML dynamically, which is essentially what
React does but React provides a structured way (with components and virtual DOM). Understanding
how to do it manually is enlightening:
Use DOM API to select elements and manipulate them (like filling the table with data).
Use Fetch API or XMLHttpRequest to make HTTP requests to our backend.
// Initial load
loadEmployees();
This script:
Defines loadEmployees() which fetches JSON from our API and populates the table by creating rows
and inserting into DOM.
Attaches a submit event to the form, which on submission collects the input values into an object
empData, sends a POST request with JSON body to the API, then on success, resets the form and
reloads the list.
Uses async/await for readability (ES2017 feature).
Relies on CORS being allowed – since our API is on localhost:8080 and if our static page is served from
say file:// or another port, the browser would block cross-origin. We would need to enable CORS on
the backend (@CrossOrigin or config) to allow this. In development, we might serve the front-end
from an HTTP server on a different port (React dev server uses 3000) – thus we'll configure CORS
or use a proxy.
This is essentially what a simplistic single-page app looks like without frameworks: manual DOM updates
and fetch calls. It's manageable for very small apps, but can become messy as the app grows (lots of DOM
manipulation, state management issues). This is why frameworks like React exist – they provide a structure
and automate the DOM updating (via re-rendering components when state changes).
Yet, understanding this under-the-hood mechanism is key to understanding what React abstracts.
In this course context, by Week 11 we’ll likely use Create React App to scaffold the React project, which
sets up all this tooling. It's good to mention that behind the scenes, CRA uses Babel (for transforming
JSX/ES6) and webpack (for bundling modules and assets).
Also mention VS Code as a popular editor for front-end (the curriculum listed VS Code as a tool,
presumably for front-end dev).
Create a basic static HTML/CSS/JS page as outlined, and try integrating it with the running backend
using pure JavaScript (this was somewhat done in the script above).
Ensure CORS is enabled on the Spring Boot app (@CrossOrigin(origins="*", allowedHeaders="*") on
controller or global config) so that the static page (if opened from file or another server) can call the
API. (In production, you'd serve the built React app from the same domain or properly configure
CORS for specific domains).
This exercise is partly for learning; once React app is built, we’ll replace this manual page with a
React-based one. But attempting this helps appreciate what React will simplify.
HTML5: The foundation that defines content structure. We saw how HTML is used to mark up
headings, tables, forms, etc., forming the skeleton of our pages. Semantic elements help convey
meaning, and we used them in structuring EMS page.
CSS3: The styling layer that makes the app visually appealing and responsive. We applied CSS rules
to style our EMS page (colors, spacing, layout) and touched on modern layout techniques like
flexbox for responsiveness. We also discussed using frameworks like Bootstrap to accelerate styling.
JavaScript: The engine of interactivity. We wrote a simple script to turn our static page into a
dynamic one that communicates with the back-end via AJAX (Fetch API) calls. This demonstrates
how to manipulate the DOM and handle user events (form submission) in vanilla JS.
We prepared ourselves for ReactJS by understanding how a basic single-page behavior can be
achieved with JS. React will use these same fundamentals (JS to manipulate DOM, fetch to get data)
but in a more structured and powerful way, allowing us to build a larger application with ease.
Having mastered these basics, we are ready to dive into ReactJS, using our knowledge of
components (like grouping HTML structure), state management, and API calls in the React
paradigm.
Week 11: ReactJS – Building the EMS Frontend
Topics: Introduction to ReactJS and modern front-end development. We will create a React application
for the EMS. Key concepts:
By end of week, we should have a working React front-end that interacts with our Spring Boot API to list,
add, update, delete employees.
src/index.js– entry point, renders <App /> component into the root DOM.
src/App.js – main component we can start editing, or we create our own.
We likely need to enable CORS or use a proxy for API calls. In package.json, we can add:
"proxy": "http://localhost:8080"
This way, when our React app calls /api/employees, the dev server will proxy to localhost:8080, avoiding CORS
issues in development.
Alternatively, use full URLs and configure backend CORS to allow localhost:3000. For development, CRA
proxy is easiest.
JSX: React's syntax extension that looks like HTML in JS. Eg:
function App() {
return (
<div>
<Header title="Employee Management System" />
<EmployeeList />
</div>
);
}
JSX is not a string or HTML, it's syntax that Babel transforms to React.createElement calls. But thinking of it as
writing HTML inside JavaScript is fine. We can embed variables with curly braces in JSX (e.g.,
<h2>{employee.name}</h2>).
For simplicity, perhaps we incorporate add and list in one page (like earlier static version). But let's think:
Maybe we want to demonstrate React Router:
Yes, it expects multi-component architecture and API integration, but maybe not necessarily separate
routes. But often they include Router as a key point to cover.
"/" shows EmployeeList component (which might also include a button/link to Add).
"/add" shows EmployeeForm for new employee.
"/edit/:id" shows EmployeeForm but for editing existing (we can detect if id param exists to fetch
that employee and populate form).
function EmployeeList() {
const [employees, setEmployees] = useState([]);
const navigate = useNavigate();
useEffect(() => {
fetch('/api/employees')
.then(res => res.json())
.then(data => setEmployees(data))
.catch(err => console.error("Error fetching employees", err));
}, []); // empty dependency -> runs once on mount
return (
<div>
<h2>Employees</h2>
<table border="1">
<thead>
<tr><th>Name</th><th>Position</th><th>Actions</th></tr>
</thead>
<tbody>
{employees.map(emp => (
<tr key={emp.id}>
<td>{emp.name}</td>
<td>{emp.position}</td>
<td>
<button onClick={() => navigate(`/edit/${emp.id}`)}>Edit</button>
<button onClick={() => deleteEmployee(emp.id)} style={{marginLeft:'5px'}}>Delete</button>
</td>
</tr>
))}
</tbody>
</table>
<button onClick={() => navigate('/add')}>Add Employee</button>
</div>
);
}
Here:
EmployeeForm component:
import { useState, useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
function EmployeeForm() {
const [employee, setEmployee] = useState({ name:'', email:'', position:'', salary:'' });
const navigate = useNavigate();
const { id } = useParams(); // will be undefined for add, or an id string for edit
useEffect(() => {
if(id) {
// fetch existing employee to edit
fetch(`/api/employees/${id}`)
.then(res => res.ok ? res.json() : Promise.reject("Failed to load"))
.then(data => setEmployee(data))
.catch(err => console.error(err));
}
}, [id]);
return (
<div>
<h2>{id ? "Edit Employee" : "Add Employee"}</h2>
<form onSubmit={handleSubmit}>
<div>
<label>Name: </label>
<input name="name" value={employee.name} onChange={handleChange} required />
</div>
<div>
<label>Email: </label>
<input name="email" type="email" value={employee.email} onChange={handleChange} required />
</div>
<div>
<label>Position: </label>
<input name="position" value={employee.position} onChange={handleChange} />
</div>
<div>
<label>Salary: </label>
<input name="salary" type="number" value={employee.salary} onChange={handleChange} />
</div>
<button type="submit">{id ? "Update" : "Save"}</button>
<button type="button" onClick={() => navigate('/')}>Cancel</button>
</form>
</div>
);
}
Explanation:
If id param exists, on mount (useEffect) we fetch that employee and populate form state.
We use employee state object to bind input values (value={employee.name} etc.) and a single handleChange
to update state on any input change. This is a controlled component approach.
On submit, we decide the method and URL based on whether we're editing or adding. We then
fetch with method and JSON body from state.
On success, navigate back to home (list) page, where changes should reflect (if we added new or
updated, the list ideally should refresh. We didn't explicitly refresh in code, but since we navigated
home, our EmployeeList useEffect will run again and fetch fresh data. Alternatively, we could have
passed state or updated a context, but fetching again is fine for simplicity).
Cancel button navigates back without saving.
One might wonder why not keep list in a global state or context so that when we add we can push to it
directly without re-fetching. That is an optimization which could be done (using React Context or a state
management library). For our scale, re-fetching isn't bad and keeps it simple.
Using Axios: Instead of fetch, many use axios for simpler syntax. It's not necessary but could mention:
axios.get('/api/employees').then(res => setEmployees(res.data));
and likewise for post, which returns promise etc. Our fetch usage is straightforward enough.
Styling in React: We can reuse our CSS from static version by importing in React. Or use Bootstrap by
adding it:
npm install bootstrap
and apply classes accordingly as per Bootstrap docs (like className in JSX).
Start the dev server (if not already). Ensure Spring Boot backend is running on 8080. The React app will
proxy API calls to 8080 because of our package.json proxy.
Test flows:
Load the app (http://localhost:3000). You should see "Employees" heading and maybe table (if DB
had employees).
Try Add Employee: click Add or go to /add (the link or button will do that). Fill the form, submit. It
should navigate to home, and the new employee appears in list.
Try Edit: if list shows entries with Edit button, click one. It should route to /edit/:id, form populates
(after fetch). Modify something, submit. Back to list, see changes.
Try Delete: clicking delete should remove entry from state immediately after successful response (we
did filter and set state).
Watch console for any errors. The usual issues: CORS (if proxy not set or if hitting wrong URL), maybe a
small bug in code. But overall, this is the typical pattern for CRUD.
Summary – Week 11: We successfully built a ReactJS frontend for our application:
Created multiple components to encapsulate our UI sections (list, form, etc.), demonstrating
component-based architecture. Each component manages its own state and UI rendering, making
the app modular and easier to maintain.
Used JSX to write components – which allows us to mix HTML-like syntax with dynamic
expressions, making UI code intuitive yet powerful.
Leveraged React Hooks: useState to handle component state (like the list of employees or form
inputs) and useEffect to perform data fetching on component load. This allowed us to hook into the
component lifecycle without class components.
Our React app communicates with the Spring Boot REST API using fetch/axios, demonstrating how
a modern SPA (Single Page Application) operates by asynchronously loading and modifying data in
the background. The UI updates smoothly based on state changes (e.g., after adding an employee,
we update state or re-fetch, and React re-renders the list).
Implemented React Router to handle client-side routing, enabling a multi-page feel without a full
page reload. We can navigate between the list view and the add/edit form within our React app,
which improves user experience (no need to manually find pages or use the browser’s back, it’s all a
seamless app).
The frontend is styled sufficiently with CSS/Bootstrap, making it presentable and responsive. We
practiced using CSS classes and possibly Bootstrap components to speed up UI development,
focusing more on functionality and React concepts.
At this stage, we have a fully functional full-stack application: a Spring Boot RESTful backend and a
ReactJS frontend, working together to provide a rich user experience for managing employee data.
Finally, we will integrate everything and discuss deployment considerations and wrap up the project in
Week 12.
Spring Boot app on 8080 (verify API endpoints via Swagger or Postman).
React dev server on 3000 (verify UI actions against real backend).
We already did that in dev mode. For production, one can build the React app:
npm run build
Copy the build files into src/main/resources/public or static in Spring Boot project. Spring Boot will serve
static files from there. If our API also serves under same domain, we avoid CORS issues.
Another approach: use a dedicated web server or a CDN to serve the static files and have API on
separate domain (then need CORS config). But many simpler deployments just bundle UI with
backend for simplicity.
If we copy build to static folder and rebuild Spring Boot jar, then running that jar will serve both API and
UI on port 8080. Accessing http://server:8080/ will load index.html from static resources, which then hits API
endpoints on same host.
Final demonstration scenario: user can navigate to the deployed app (maybe http://localhost:8080), see the
React UI, interact with it, and everything works (the React calls the Boot API which hits the DB).
Jar Deployment: Package Spring Boot as executable jar (with UI included as above). Then run it on
a server (Java 11 runtime or container).
WAR Deployment: If needed (less common with Boot, but possible if deploying to a servlet
container).
Docker: Containerize the app – e.g., a Dockerfile that uses a Java image to run the jar. Possibly
another container for MySQL. Docker Compose to link them. (This was optional in curriculum but a
nice to mention).
Cloud: deploying to AWS EC2 or Heroku (for instance) – basically similar to jar on a VM or using
cloud platform’s support for container images.
Could create a Jenkins pipeline to build the jar and run tests, then deploy to an AWS EC2 instance
or container service.
Or use GitHub Actions for CI.
But since it's a bit beyond creating the manual, we just note these possibilities.
A full stack employee management web application, with a ReactJS frontend and a Spring Boot +
MySQL backend.
We applied core Java and OOP principles in the design of our classes.
Used JDBC and later JPA/Hibernate to handle database interactions.
Created a web UI first with JSP/Servlets, then upgraded to Spring MVC and finally to a separate
React SPA, showing evolution of web architecture.
Learned to build and consume RESTful APIs, which is a cornerstone of modern full stack
development.
Integrated everything with Spring Boot serving as the glue (serving both APIs and static content in
production).
Merge the React build into the Spring Boot app (if deploying together).
Test the packaged application on a fresh environment (does it run by simply running jar and
connecting to a MySQL instance with proper config?).
Prepare a short presentation or demo of the project, highlighting features. In a corporate training
context, perhaps do a final demo where you run through adding/editing employees as an end user.
Clean up code: remove any console logs, ensure all TODOs are resolved, etc.
Write documentation (maybe a README) for how to run the app, how the architecture is, etc.
Summary – Week 12: We completed integration and delivered the final Full Stack Java Developer
Capstone Project:
The Employee Management System is fully implemented across all layers and is working as a
cohesive unit. We demonstrated how a user can perform operations from the React UI, which
triggers API calls to the Spring Boot server, which in turn uses JPA to interact with the MySQL
database, fulfilling the request and sending results back to the UI.
We discussed how to deploy the application in a production-like scenario. For instance, bundling
the React app with Spring Boot so that a single deployment unit can serve the frontend and
backend, or deploying them separately as needed. We also covered optional tooling like Docker for
containerization to simplify deployment and environment setup.
This final week was about ensuring everything works together (integration testing) and that the
project is production-ready (with documentation, proper configuration management, etc.). We also
identified potential improvements and extensions, reflecting on how the project can evolve beyond
the training.
The project showcases all the competencies targeted by the 12-week curriculum:
o OOP and Java fundamentals (seen in our entity and service designs).
o Data handling with JDBC/SQL and later JPA/Hibernate for ORM.
o Web development skills with Servlets/JSP initially and then with Spring MVC and ultimately a
modern React SPA.
o Use of Spring Framework for building scalable and maintainable applications (IoC, DI, Spring
MVC, Spring Boot auto-config).
o Building and consuming RESTful services, using JSON and HTTP effectively.
o Frontend development with React, managing state and making asynchronous calls to backend
APIs to provide a dynamic user experience.
By completing this capstone, we have traversed through all layers of a full stack application. This
comprehensive exercise has solidified the learner’s capability to develop end-to-end applications.
The result is a professional-grade training manual and project that can be used as a reference or
even a starting point for real-world projects.
Finally, congratulations on building the Employee Management System! Through this project, you have
gained practical experience and confidence in full stack Java development, positioning you strongly for
tackling real-world software development challenges.
(Throughout the material we provided inline citations to various sources for specific concepts and code
snippets. Key references include Java and Spring official docs, GeeksforGeeks articles for OOP and Spring
explanations, W3Schools for web basics, and more. Below we list some of them for further reading.)
(The citations in the text, like, refer to these sources for verification of facts and for deeper exploration on
those topics.)