🎬 Movie Database
Build a small enterprise-style application that lets you add, search, and list movies using an in-memory H2 database. You'll practice every concept from today's lecture: Java Records, interfaces, the Repository pattern, JDBC, HTTP, HTML forms, and UML documentation. Work through each section in order.
Create a new Spring Boot project exactly as you did in the setup guide. This time we'll add one dependency.
- In VS Code: File → New File → New Java Project → Spring Boot.
- Choose: Maven → 4.1.0 → Java → com.example → moviedb → Jar → 26.
- At the Dependencies step, search and add Spring Web. Then click Generate.
- Open the generated project folder in VS Code.
- Run
MoviedbApplication.java— confirm the Spring Boot banner appears. Then stop the server (Ctrl+C).
src/main/java/com/example/moviedb/ — all your Java files go here. The pom.xml at the root manages dependencies.Our application manages movies. Each movie has an ID, title, director and year. We'll use a Java Record — the concise immutable data class from today's lecture.
- Inside
com.example.moviedb, create a new file namedMovie.java. - Type the following code:
package com.example.moviedb; // A Java Record — the compiler generates constructor, // getters, equals(), hashCode(), and toString() for you. public record Movie( int id, String title, String director, int year ) {}
movie.title() (not movie.getTitle()).We use H2, an in-memory SQL database, so there's nothing to install. First, add the H2 dependency to your project.
- Open
pom.xmland add this inside the<dependencies>block:
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>
- Now create
DatabaseHelper.java— a helper class that creates the H2 connection and initialises the table:
package com.example.moviedb; import java.sql.*; public class DatabaseHelper { // Singleton — only one database connection private static Connection connection; public static Connection getConnection() throws SQLException { if (connection == null || connection.isClosed()) { connection = DriverManager.getConnection( "jdbc:h2:mem:moviedb", "sa", "" ); initTable(); } return connection; } private static void initTable() throws SQLException { String sql = """ CREATE TABLE IF NOT EXISTS movie ( id INT PRIMARY KEY, title VARCHAR(255), director VARCHAR(255), year INT ) """; connection.createStatement().execute(sql); // Seed with sample data String seed = """ MERGE INTO movie VALUES (1, 'The Matrix', 'Wachowskis', 1999), (2, 'Inception', 'Christopher Nolan', 2010), (3, 'Spirited Away', 'Hayao Miyazaki', 2001) """; connection.createStatement().execute(seed); } }
getConnection() returns the same connection object every time — exactly the pattern from today's lecture. This ensures only one database connection exists.The Repository abstracts all SQL behind clean Java methods. Your application code never writes SQL directly — it talks to the Repository instead.
- First, create the interface — the contract that any Repository must satisfy. Create
Repository.java:
package com.example.moviedb; import java.util.List; // Interface — defines WHAT a repository does, not HOW public interface Repository<T> { void save(T item); T findById(int id); List<T> findAll(); }
Repository<Movie> interface, not the concrete class. If we later swap H2 for PostgreSQL, we only change the implementation — the web server code stays the same.- Now create the implementation. Create
MovieRepository.java:
package com.example.moviedb; import java.sql.*; import java.util.*; public class MovieRepository implements Repository<Movie> { @Override public void save(Movie m) { try { String sql = "MERGE INTO movie VALUES (?, ?, ?, ?)"; PreparedStatement ps = DatabaseHelper.getConnection().prepareStatement(sql); ps.setInt(1, m.id()); ps.setString(2, m.title()); ps.setString(3, m.director()); ps.setInt(4, m.year()); ps.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); } } @Override public Movie findById(int id) { try { String sql = "SELECT * FROM movie WHERE id = ?"; PreparedStatement ps = DatabaseHelper.getConnection().prepareStatement(sql); ps.setInt(1, id); ResultSet rs = ps.executeQuery(); if (rs.next()) { return new Movie( rs.getInt("id"), rs.getString("title"), rs.getString("director"), rs.getInt("year") ); } } catch (SQLException e) { throw new RuntimeException(e); } return null; } @Override public List<Movie> findAll() { List<Movie> movies = new ArrayList<>(); try { ResultSet rs = DatabaseHelper.getConnection() .createStatement() .executeQuery("SELECT * FROM movie ORDER BY year"); while (rs.next()) { movies.add(new Movie( rs.getInt("id"), rs.getString("title"), rs.getString("director"), rs.getInt("year") )); } } catch (SQLException e) { throw new RuntimeException(e); } return movies; } }
Repository<T> uses a generic type parameter. MovieRepository implements Repository<Movie> binds T to Movie. Later you could create ActorRepository implements Repository<Actor> with the same interface.Now we build the web layer. This server handles HTTP requests and returns HTML responses — exactly like the Java web server example from the lecture.
- Create
MovieServer.java. This class creates the HTTP server, wires it to the Repository, and handles GET and POST requests:
package com.example.moviedb; import com.sun.net.httpserver.*; import java.io.*; import java.net.*; import java.util.*; import java.util.stream.*; public class MovieServer { // Depend on the INTERFACE, not the concrete class (SOLID D) private final Repository<Movie> repo; public MovieServer(Repository<Movie> repo) { this.repo = repo; } public void start() throws IOException { HttpServer server = HttpServer.create( new InetSocketAddress(8080), 0); server.createContext("/", this::handleHome); server.createContext("/add", this::handleAdd); server.start(); System.out.println("Server running at http://localhost:8080"); } // GET / — list all movies + show the add form private void handleHome(HttpExchange ex) throws IOException { List<Movie> movies = repo.findAll(); String rows = movies.stream().map(m -> "<tr><td>" + m.id() + "</td><td>" + m.title() + "</td><td>" + m.director() + "</td><td>" + m.year() + "</td></tr>" ).collect(Collectors.joining()); String html = """ <html><head><title>Movie DB</title></head> <body style="font-family:sans-serif;max-width:700px;margin:40px auto"> <h1>🎬 Movie Database</h1> <table border="1" cellpadding="8" style="border-collapse:collapse;width:100%%"> <tr><th>ID</th><th>Title</th> <th>Director</th><th>Year</th></tr> %s </table> <h2>Add a Movie</h2> <form method="POST" action="/add"> ID: <input name="id" type="number" required><br><br> Title: <input name="title" required><br><br> Director: <input name="director" required><br><br> Year: <input name="year" type="number" required><br><br> <button type="submit">Add Movie</button> </form> </body></html> """.formatted(rows); sendResponse(ex, 200, html); } // POST /add — save a new movie, then redirect to home private void handleAdd(HttpExchange ex) throws IOException { if (!ex.getRequestMethod().equals("POST")) { sendResponse(ex, 405, "Method Not Allowed"); return; } String body = new String(ex.getRequestBody().readAllBytes()); Map<String,String> params = parseForm(body); Movie movie = new Movie( Integer.parseInt(params.get("id")), params.get("title"), params.get("director"), Integer.parseInt(params.get("year")) ); repo.save(movie); // Redirect back to home (HTTP 302) ex.getResponseHeaders().set("Location", "/"); ex.sendResponseHeaders(302, -1); ex.close(); } // ── helpers ── private void sendResponse(HttpExchange ex, int code, String html) throws IOException { byte[] bytes = html.getBytes(); ex.getResponseHeaders().set("Content-Type", "text/html"); ex.sendResponseHeaders(code, bytes.length); ex.getResponseBody().write(bytes); ex.close(); } private Map<String,String> parseForm(String body) { Map<String,String> map = new HashMap<>(); for (String pair : body.split("&")) { String[] kv = pair.split("=", 2); map.put(URLDecoder.decode(kv[0]), URLDecoder.decode(kv[1])); } return map; } }
- Now update
MoviedbApplication.javato start the server:
package com.example.moviedb; public class MoviedbApplication { public static void main(String[] args) throws Exception { // Create the Repository (concrete implementation) Repository<Movie> repo = new MovieRepository(); // Start the web server (depends on interface, not class) new MovieServer(repo).start(); } }
- Run
MoviedbApplication.java— you should seeServer running at http://localhost:8080in the terminal. - Open your browser and go to
http://localhost:8080. - You should see a table with 3 seed movies (The Matrix, Inception, Spirited Away) and an "Add a Movie" form below.
- Fill in the form with a new movie:
Field Value ID 4 Title Parasite Director Bong Joon-ho Year 2019 - Click Add Movie. The page should reload with 4 movies in the table.
- Add 2 more movies of your choice.
1. Browser sends
GET / → server calls repo.findAll() → returns HTML table.2. You submit the form → browser sends
POST /add with id=4&title=Parasite&... in the body → server calls repo.save() → redirects to /.
Enterprise projects require documentation. In this section you'll draw diagrams for your Movie Database using what you reviewed today.
- Draw a Use Case Diagram (UCD) for the Movie Database. The system has one actor (User) who can: List Movies, Add a Movie, and Search by ID. Use PlantUML.com or draw on paper.
PlantUML syntax hint:
@startuml
left to right direction
actor User
rectangle "Movie DB" {
User -- (List Movies)
User -- (Add Movie)
User -- (Search by ID)
}
@enduml - Draw a Class Diagram showing:
Movierecord with its 4 fieldsRepository<T>interface with 3 methodsMovieRepositoryimplementingRepository<Movie>MovieServerdepending onRepository<Movie>
- Draw an ERD for the
movietable. This is simple for now (one entity), but think about how you'd extend it:- What if movies have multiple genres? (N:M — intersection table)
- What if each movie has one studio? (1:N — FK in movie table)
Run through the complete application and verify every concept from today's lecture is present.
- Add a
/search?id=2GET endpoint that returns a single movie - Add a
deleteById(int id)method to the Repository interface and implement it - Create a
Genrerecord and amovie_genreintersection table - Initialise a Git repository and commit your work — you'll need Git for all assessments