COIT13235
Week 1 · Tutorial

Your First Enterprise Project

Build a Movie Database app with Java, JDBC, the Repository pattern & HTTP
0 / 28 steps

🎬 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.

1
Project Setup
~5 min

Create a new Spring Boot project exactly as you did in the setup guide. This time we'll add one dependency.

  1. In VS Code: File → New File → New Java Project → Spring Boot.
  2. Choose: Maven → 4.1.0 → Java → com.example → moviedb → Jar → 26.
  3. At the Dependencies step, search and add Spring Web. Then click Generate.
  4. Open the generated project folder in VS Code.
  5. Run MoviedbApplication.java — confirm the Spring Boot banner appears. Then stop the server (Ctrl+C).
Project structure: Maven created src/main/java/com/example/moviedb/ — all your Java files go here. The pom.xml at the root manages dependencies.
Checkpoint
2
Data Model with Java Records
~5 min

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.

  1. Inside com.example.moviedb, create a new file named Movie.java.
  2. Type the following code:
Movie.java
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
) {}
Why Records? Without Records you'd need ~40 lines for the constructor, getters, equals, hashCode, and toString. The record keyword generates all of it. Access fields with movie.title() (not movie.getTitle()).
Checkpoint
3
In-Memory Database with JDBC
~10 min

We use H2, an in-memory SQL database, so there's nothing to install. First, add the H2 dependency to your project.

  1. Open pom.xml and add this inside the <dependencies> block:
pom.xml (add inside <dependencies>)
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
  1. Now create DatabaseHelper.java — a helper class that creates the H2 connection and initialises the table:
DatabaseHelper.java
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);
    }
}
Singleton pattern in action! getConnection() returns the same connection object every time — exactly the pattern from today's lecture. This ensures only one database connection exists.
Checkpoint
4
Repository Pattern
~15 min

The Repository abstracts all SQL behind clean Java methods. Your application code never writes SQL directly — it talks to the Repository instead.

Web Server handleRequest() MovieRepository save(), findAll(), findById() H2 Database SQL queries
The Repository hides SQL. The web server only calls save(), findAll(), etc.
  1. First, create the interface — the contract that any Repository must satisfy. Create Repository.java:
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();
}
SOLID — D (Dependency Inversion): Our web server will depend on the 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.
  1. Now create the implementation. Create MovieRepository.java:
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;
    }
}
Generics in action: 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.
Checkpoint
5
HTTP Web Server
~15 min

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.

  1. Create MovieServer.java. This class creates the HTTP server, wires it to the Repository, and handles GET and POST requests:
MovieServer.java
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;
    }
}
  1. Now update MoviedbApplication.java to start the server:
MoviedbApplication.java
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();
    }
}
Checkpoint
6
Run & Test with HTML Forms
~10 min
  1. Run MoviedbApplication.java — you should see Server running at http://localhost:8080 in the terminal.
  2. Open your browser and go to http://localhost:8080.
  3. You should see a table with 3 seed movies (The Matrix, Inception, Spirited Away) and an "Add a Movie" form below.
  4. Fill in the form with a new movie:
    FieldValue
    ID4
    TitleParasite
    DirectorBong Joon-ho
    Year2019
  5. Click Add Movie. The page should reload with 4 movies in the table.
  6. Add 2 more movies of your choice.
If the page doesn't load: Check the VS Code terminal for error messages. Common issues: port 8080 already in use (stop other servers), or a typo in the code.
What's happening under the hood?
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 /.
Checkpoint
7
Document with UML & ERD
~15 min

Enterprise projects require documentation. In this section you'll draw diagrams for your Movie Database using what you reviewed today.

  1. 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
  2. Draw a Class Diagram showing:
    • Movie record with its 4 fields
    • Repository<T> interface with 3 methods
    • MovieRepository implementing Repository<Movie>
    • MovieServer depending on Repository<Movie>
  3. Draw an ERD for the movie table. 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)
    Sketch both the current ERD and the extended version with Genre and Studio.
Checkpoint
8
Review & Reflect
~10 min

Run through the complete application and verify every concept from today's lecture is present.

Final checklist: Walk through each item below. If anything fails, go back to the relevant section.
Concepts applied
Extension challenges (for Assessment 1 preparation):
  • Add a /search?id=2 GET endpoint that returns a single movie
  • Add a deleteById(int id) method to the Repository interface and implement it
  • Create a Genre record and a movie_genre intersection table
  • Initialise a Git repository and commit your work — you'll need Git for all assessments