﻿# Graphics, animation and a first database

> Drawing on a panel with paintComponent and animating it with one Swing Timer that steps a Swing-free model; then a first database – JDBC with PreparedStatement, SQLite by default or MySQL in Docker, and a UI → backend → repository split that tests without a database.

Source: https://laszlonemes.com/university/programming-technology/graphics-databases · Author: László Nemes

Session 8 · Programming Technology

Drawing on a panel with paintComponent and animating it with one Swing Timer that steps a Swing-free model; then a first database – JDBC with PreparedStatement, SQLite by default or MySQL in Docker, and a UI → backend → repository split that tests without a database.

20 min read `paintComponent and repaint()` `Timer-driven animation` `JDBC and PreparedStatement` `SQLite vs MySQL in Docker` `Code-first tables` `Repository, model and DTO`

Two new things this session, and both are the foundation of the **third assignment**. The first is **elementary graphics**: so far we built windows from buttons and labels; now we draw on a panel ourselves and animate the drawing with a `Timer`. The second is the **database**: the games of the third assignment save the player's result at the end and read back the top ten. Here we look at the smallest program that talks to a database; next session puts it into games.

The examples: `UFOs` (UFOs flying around a panel, everything in the view), `UFOsMV` (the same in model–view, with tests) and `HighScores` (a console leaderboard on top of SQLite or MySQL).

After this session you can

- Override paintComponent, draw with Graphics2D and ask for a redraw with repaint().
- Drive an animation with one Swing Timer that steps the model and repaints the view.
- Write and read a table with JDBC, PreparedStatement and try-with-resources.
- Choose between SQLite and MySQL, and run MySQL in a Docker container.
- Split a program into UI, backend and repository, and test the backend without a database.

> **Two ideas carry the whole session:** `paintComponent` only draws what the model says, and only the repository knows that a database exists. Everything else is detail.

## Drawing on a panel: paintComponent

Until now components drew themselves. To show something else, extend `JPanel` and **override `paintComponent`**:

```java
class DrawArea extends JPanel {
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);               // clears the area with the background colour
        Graphics2D g2 = (Graphics2D) g;
        g2.setColor(Color.RED);
        g2.fillOval(100, 50, 200, 30);         // top-left corner (x, y), width, height
    }
}
```

> **Fig. 1** · You ask, Swing decides. repaint() is a request; Swing calls paintComponent when it is ready – and also whenever the window needs it for its own reasons.
>
> *Diagram:* Your code never calls paintComponent. It calls repaint(), which only asks Swing for a redraw. Swing, on the event dispatch thread, merges the requests and calls paintComponent itself – also on first show, on resize and when the window is uncovered. paintComponent first clears the area with super.paintComponent, then draws what the model says.

| Rule | Why |
| --- | --- |
| **You never call** `paintComponent` | Swing calls it whenever the component must be drawn: the first time, after a resize, when another window uncovered it, or when you asked. |
| If what you draw changed, call `repaint()` | `repaint()` only *requests* a redraw; Swing soon calls `paintComponent` on the EDT. Several requests are merged into one. |
| Start with `super.paintComponent(g)` | It clears the area. Without it the previous frame stays, and the UFOs leave trails. |
| Don't change the component while painting | No `setBackground` in `paintComponent`: it requests another repaint, which calls `paintComponent` again… Draw the background as a filled rectangle. |
| `Graphics2D` is the pen | `setColor`, `fillOval`, `fillRect`, `drawLine`, `fillPolygon`, `drawString`, `drawImage`; an antialiasing rendering hint smooths the edges. |

> **Fig. 2** · Screen coordinates, not maths coordinates. Every shape method takes the top-left corner of its bounding box.
>
> *Diagram:* Swing coordinates start at (0, 0) in the top-left corner of the panel; x grows to the right and y grows downwards. fillOval(x, y, width, height) fills the oval that fits in the rectangle whose top-left corner is (x, y).

Good to know

**No flicker for free**

Swing **double-buffers**: `paintComponent` draws into an image in memory and the finished image goes to the screen, so the animation doesn't flicker. A `JPanel` does this by default – there is nothing to switch on.

## Animation: one Timer, one step

An animation is many still frames in a row. A `javax.swing.Timer` calls a listener at a fixed interval – here every 30 ms, about 33 frames per second – and that listener **steps the model and asks for a repaint**:

```java
timer = new Timer(30, e -> {
    space.step();          // the model: every UFO moves on
    drawArea.repaint();    // the view: ask for a redraw
});
timer.start();
```

> **Fig. 3** · The game loop of every third-assignment game: the Timer is the clock, step() is the physics, paintComponent is the camera.
>
> *Diagram:* The animation loop: every 30 ms the Timer fires on the event dispatch thread, the model takes one step, the view requests a repaint, and paintComponent draws the model as it is now. Then it starts again. The starting version used two Timers; the model–view version needs one, because step() does everything.

The Timer's listener runs on the **event dispatch thread** (EDT), just like a button's, so it may touch both the components and the model – there is no threading problem to solve. The third assignment's games (moving sprites, a clock) work exactly like this.

### The mouse

Mouse events go to a component: `MouseListener` (click, press, release) and `MouseMotionListener` (move, drag). `MouseAdapter` implements both with empty methods, so you override only what you need. Dragging in the UFO program pulls every UFO to the mouse:

```java
addMouseMotionListener(new MouseAdapter() {
    @Override
    public void mouseDragged(MouseEvent e) {
        space.moveAllTo(e.getX() - Ufo.WIDTH / 2.0, e.getY() - Ufo.HEIGHT / 2.0);
        repaint();
    }
});
```

The keyboard (WASD control) comes next session.

In your assignment

**One Timer, one step()**

Your game's "physics" is **one method in the model** (`step` or `tick`) that the Timer calls once per tick; the view repaints afterwards. If several things happen "every now and then" – moving, recolouring, a new enemy – they all go into that one method, with a counter if needed ("every tenth tick"). Not a second Timer.

And `paintComponent` **only draws**: it doesn't compute, move, roll dice or change the component. It reads the model and paints it.

## UFOs in model–view

The starting `UFOs` program works, but it repeats every mistake of the flashcards session:

- **The rules are in the view.** How many UFOs there are, how a new one is made, how often one changes colour – all in `UFOGUI`. No test can reach them.
- **The model depends on AWT.** `UFO` stores a `java.awt.Color` and draws itself onto a `Graphics2D`.
- **Three classes share one `ArrayList`** (`UFOGUI`, `DrawArea`, the Timers) and none of them owns it.
- **Two Timers** do what one could.
- `setBackground` inside `paintComponent` – changing the component while drawing it.

> **Fig. 4** · A list that everyone changes has no rules. A list owned by the model changes only through the model's methods.
>
> *Diagram:* In the starting UFOs program the GUI class, the drawing panel and two Timers all change the same ArrayList, and none of them owns it. In UFOsMV the Space model owns a private list of UFOs; the GUI's single Timer calls step() and the drawing panel only reads getUfos().

In `UFOsMV`, `Space` is the model: it owns the list, the size, the background colour and the rules (`addUfos`, `removeUfos`, `step`, `moveAllTo`, `newBackground`). It gets its `Random` from the constructor, so a test can fix the seed. `step` is one tick of "physics", without Swing:

```java
public void step() {
    for (Ufo ufo : ufos) {
        ufo.move(width, height);                       // wraps around at the edges
        if (random.nextDouble() < RECOLOR_PROBABILITY) {
            ufo.setRgb(randomRgb());                   // 0xRRGGBB as an int, not a Color
        }
    }
}
```

The view reads the model in two places: in `paintComponent` (background, UFOs) and in the status bar (count). `drawUfo` is the only place that decides what a UFO looks like – the model doesn't know it is a dome on a saucer.

DrawArea.paintComponent – the view draws the model

```java
@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g;
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setColor(new Color(space.getBackgroundRgb()));   // the background is the model's, a filled rectangle
    g2.fillRect(0, 0, getWidth(), getHeight());
    for (Ufo ufo : space.getUfos()) {
        drawUfo(g2, ufo);                                 // the only place that knows what a UFO looks like
    }
}
```

|  | UFOs | UFOsMV |
| --- | --- | --- |
| Owner of the list | nobody – three classes change it | `Space` |
| Colour | `java.awt.Color` in the model | `int` (0xRRGGBB) in the model, `Color` in the view |
| Who draws a UFO | the `UFO` itself | `DrawArea.drawUfo` |
| Background | `setBackground` in `paintComponent` | the model's colour, a filled rectangle |
| Timers | two (move, recolour) | one – `step` does both |
| Size of the space | the Timer asks the panel | the model's; the panel calls `space.resize` |
| Randomness | `Math.random()` | `Random` from the constructor |
| Tests | none | 21 |

Common mistake

**Color and Point in the model**

Keep positions and colours in the model as numbers (`double x`, `int rgb`), not as `Color` or `Point`. Then the model has no AWT import and JUnit can test it; the view makes the `Color` with `new Color(rgb)` when it draws.

## Why a database?

In the third assignment the player's name and score are saved when the game ends, and a menu item shows the top ten. A text file could do that too, but:

| A file | A database |
| --- | --- |
| you invent the format and write the parser | the structure (table, columns, types) is given and enforced |
| "top 10" = read everything, sort, cut | `SELECT … ORDER BY score DESC LIMIT 10` – the database's job |
| two programs writing at once corrupt it | many users at once, transactions |
| a crash halfway leaves a half-written file | an operation happens completely or not at all |

In bigger programs – web, mobile, enterprise – data practically always lives in a database. For the assignment you need the basics: one table, insert, query.

## SQL in one minute

A **relational database** consists of tables. A **table** is like a spreadsheet: it has **columns** (with a name and a type) and **rows** (one row = one result). You talk to it in **SQL**:

```sql
-- Create the table (once). IF NOT EXISTS: no error if it's already there.
CREATE TABLE IF NOT EXISTS highscores (
    name       VARCHAR(50) NOT NULL,     -- text, at most 50 characters, required
    score      INTEGER     NOT NULL,     -- whole number
    created_at TIMESTAMP   NOT NULL      -- date and time
);

-- Insert a row
INSERT INTO highscores (name, score, created_at) VALUES ('Anna', 42, CURRENT_TIMESTAMP);

-- The top 10: highest score first, on a tie the earlier one wins
SELECT name, score, created_at
FROM highscores
ORDER BY score DESC, created_at ASC
LIMIT 10;

-- Delete every row
DELETE FROM highscores;
```

That is all the SQL the assignment needs. (If a task says "increase the winner's count, or insert a new row", you also need `UPDATE` – see next session.)

## JDBC: how Java talks to a database

**JDBC** (Java Database Connectivity) is part of the JDK: one interface for every database. The database-specific part is the **driver**, a jar that Maven downloads from the `pom.xml`. The program is the same whether SQLite or MySQL is behind it – only the **JDBC URL** differs.

> **Fig. 5** · The code above the line is written once. Below it, the URL decides which driver – and which kind of database – does the work.
>
> *Diagram:* The repository talks only to the JDBC API in java.sql, which is part of the JDK. The prefix of the JDBC URL picks the driver: jdbc:sqlite selects the SQLite driver, which contains the whole database engine and stores everything in highscores.db; jdbc:mysql selects the MySQL driver, which talks to a MySQL server running in a Docker container.

| Class (java.sql) | What it is for |
| --- | --- |
| `DriverManager.getConnection(url, user, password)` | opens a connection; the URL prefix (`jdbc:sqlite:`, `jdbc:mysql:`) selects the driver |
| `Connection` | one connection; **must be closed** → try-with-resources |
| `PreparedStatement` | an SQL statement with `?` placeholders; `setString(1, name)`, `setInt(2, score)`; `executeUpdate()` for INSERT/UPDATE/DELETE, `executeQuery()` for SELECT |
| `ResultSet` | the rows of a query, one at a time: `while (rows.next())`, `rows.getString("name")` |
| `SQLException` | checked exception: the server isn't running, wrong password, no such table … |

JdbcHighScoreRepository.save

```java
public void save(HighScore highScore) throws StorageException {
    try (Connection connection = connect();
         PreparedStatement statement = connection.prepareStatement(
                 "INSERT INTO highscores (name, score, created_at) VALUES (?, ?, ?)")) {
        statement.setString(1, highScore.name());
        statement.setInt(2, highScore.score());
        statement.setTimestamp(3, Timestamp.valueOf(highScore.createdAt()));
        statement.executeUpdate();
    } catch (SQLException e) {
        throw new StorageException("Could not save the score: " + e.getMessage(), e);
    }
}
```

JdbcHighScoreRepository.findTop – the core

```java
try (Connection connection = connect();
     PreparedStatement statement = connection.prepareStatement(SELECT_TOP)) {
    statement.setInt(1, limit);
    try (ResultSet rows = statement.executeQuery()) {
        while (rows.next()) {
            result.add(new HighScore(rows.getString("name"), rows.getInt("score"),
                    rows.getTimestamp("created_at").toLocalDateTime()));
        }
    }
} catch (SQLException e) {
    throw new StorageException("Could not read the leaderboard: " + e.getMessage(), e);
}
```

Every resource is opened in a **try-with-resources** header, so it is closed even when an exception flies. And the repository wraps `SQLException` into its own `StorageException`: the caller doesn't depend on `java.sql` and doesn't care whether the failure came from an SQL server or a file.

Common mistake

**Why ? and not string concatenation**

In SQL built like `"… VALUES ('" + name + "', …)"` the user can type anything as a name – even another SQL statement (**SQL injection**). `PreparedStatement` passes the value in place of `?` as *data* and never interprets it as SQL. This is not an option, it is the rule: a value from the user **always** goes in through `?`. Next session covers it in detail.

## SQLite or MySQL?

The projects contain both, and the code is the same: three lines of `Database.java` decide which one is used.

|  | SQLite | MySQL |
| --- | --- | --- |
| What it is | a **file** (`highscores.db`) in the project folder; the driver contains the whole database engine | a **server** running in the background, accepting connections on a port |
| Installation | none: Maven downloads the driver | a running server – for us, a Docker container |
| User, password | none | yes |
| Good for | single-user desktop and mobile apps (browsers, phones, the assignment) | many users, web and enterprise systems |
| In the assignment | **the default**: always works, anywhere | an option: if Docker runs, one comment swap |

```java
// Database.java – SQLite (the default)
public static final String URL = "jdbc:sqlite:highscores.db";
public static final String USER = null;
public static final String PASSWORD = null;

// MySQL in Docker: comment out the three lines above, uncomment these
// public static final String URL = "jdbc:mysql://localhost:3306/progtech";
// public static final String USER = "progtech";
// public static final String PASSWORD = "progtech";
```

Each database stores values in its own way (SQLite keeps a timestamp as a number, MySQL as a date), but on the Java side `getTimestamp` returns the same `LocalDateTime` from both. Storage is the database's business; through JDBC the program sees one uniform picture.

Good to know

**A harmless warning**

The SQLite driver loads a native library, and newer JDKs print a warning about "restricted methods" when that happens. It is not an error. The example projects already pass `--enable-native-access=ALL-UNNAMED` in their run configurations and tests.

## Code-first: the program creates the table

The repository runs `CREATE TABLE IF NOT EXISTS` on its first connection. So **nothing has to be prepared in the database**: an empty SQLite file or a freshly started MySQL container is immediately fine.

```java
private Connection connect() throws SQLException {
    Connection connection = DriverManager.getConnection(url, user, password);
    if (!tableChecked) {                       // code-first: once per repository
        try (Statement statement = connection.createStatement()) {
            statement.execute(CREATE_TABLE);   // CREATE TABLE IF NOT EXISTS ...
            tableChecked = true;
        } catch (SQLException e) {
            connection.close();
            throw e;
        }
    }
    return connection;
}
```

- **Anyone can start it** who downloads the project: no SQL script to run, no database to click together. Including the person grading it.
- **The table's structure is in the code**, versioned with it. A new column changes the code and the table together.
- **The tests** run on a temporary, empty file – and the repository creates the table there too.

This is what frameworks like JPA/Hibernate or Entity Framework call "code-first", in its simplest form. If you want an **id column** (a leaderboard doesn't need one, a "real" table usually has one), the two databases spell it differently, which is why the example leaves it out:

```sql
-- SQLite
id INTEGER PRIMARY KEY AUTOINCREMENT,

-- MySQL
id INT AUTO_INCREMENT PRIMARY KEY,
```

## MySQL in a Docker container

A database server is traditionally *installed*: an installer, a service always running in the background, config files, and traces left behind when you remove it. **Docker** runs the program in a **container** instead – an isolated, pre-packaged environment built from an **image** that contains MySQL and everything it needs. A container

- **starts with one command** and is deleted with one, without a trace;
- **is the same for everyone**: the MySQL server image gives the same server with the same settings on Windows, macOS and Linux;
- **is isolated**: it touches nothing else on the machine, and you see only the port you publish (3306);
- **is not a virtual machine**: no whole operating system runs in it, only the program, so it starts in seconds and uses little memory.

This is how development databases are run in industry today. Cloud providers offer managed MySQL too: there you also get a URL, a user and a password, and your program stays the same – only `Database.java` changes.

> **Fig. 6** · Three things to remember: the port mapping (how your program reaches the server), the environment variables (what the server creates on its first start) and the volume (where the data survives).
>
> *Diagram:* The Java program connects to localhost:3306 on your computer. Docker forwards that port to port 3306 inside the progtech-mysql container, where the MySQL server listens. The server keeps its data in /var/lib/mysql, which is mounted from the named volume mysql-data, so the data survives when the container is removed. The database and the user are created on the first start from environment variables.

### Starting it

Install **Docker Desktop** (free for personal and educational use; on Windows it uses WSL, which the installer enables), start it, and check that `docker version` prints both a Client and a Server part. Then start the container from the project's `compose.yaml`:

compose.yaml

```yaml
services:
  mysql:
    image: mysql                 # the official MySQL server image
    container_name: progtech-mysql
    environment:
      MYSQL_ROOT_PASSWORD: root  # the admin password
      MYSQL_DATABASE: progtech   # created on the first start ...
      MYSQL_USER: progtech       # ... with a user who may do anything in it
      MYSQL_PASSWORD: progtech
    ports:
      - "3306:3306"              # host:container
    volumes:
      - mysql-data:/var/lib/mysql   # the data survives the container

volumes:
  mysql-data:
```

```shell
docker version                      # Client and Server both answer? Docker is running
docker compose up -d                # start the container described by compose.yaml
docker logs progtech-mysql          # wait for "ready for connections"
docker ps                           # running containers; PORTS shows 0.0.0.0:3306->3306/tcp
docker exec -it progtech-mysql mysql -u progtech -p   # a MySQL prompt inside the container
docker compose stop                 # stop – the data stays
docker compose down -v              # delete the container AND the volume – a clean slate
```

The same container without a compose file is a single (long) command; the compose file is the readable version of it, kept next to the project:

```shell
docker run --name progtech-mysql -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=progtech -e MYSQL_USER=progtech -e MYSQL_PASSWORD=progtech -p 3306:3306 -d mysql
```

On the first start the server needs 10–20 seconds to initialise; `ready for connections` at the end of `docker logs` means it's done. To look inside, you don't need a MySQL client on your machine – the container has one (password: `progtech`):

```sql
USE progtech;
SHOW TABLES;
SELECT * FROM highscores ORDER BY score DESC;
EXIT
```

Graphical tools work too (DBeaver, MySQL Workbench, the database windows of IntelliJ or NetBeans): host `localhost`, port `3306`, database, user and password `progtech`. For the SQLite file, DB Browser for SQLite or DBeaver opens `highscores.db` directly.

### When it doesn't connect

| Message (from the SQLException) | Cause and fix |
| --- | --- |
| `Communications link failure` | the container isn't running (`docker ps`) or isn't up yet (`docker logs`), or the port isn't 3306 |
| `Access denied for user 'progtech'` | wrong user or password – check the container's environment (`docker inspect progtech-mysql`) |
| `Unknown database 'progtech'` | the container was started with a different `MYSQL_DATABASE` |
| `port is already allocated` | something else uses 3306 (another MySQL); stop it, or publish on `-p 3307:3306` and use `localhost:3307` in the URL |
| `No suitable driver found` | the driver isn't on the classpath: is `mysql-connector-j` in the `pom.xml`? Reload Maven. |
| `no such table` | the table wasn't created: does `connect()` run `CREATE TABLE IF NOT EXISTS`? |
| `database is locked` | SQLite: another program (a DB browser) holds the file open for writing – close one |

## Three layers: UI, backend, storage

`HighScores` is a console program: a menu to add a score, list the top ten and clear the board. It has three layers, and this structure carries straight into next session's games and into your assignment.

> **Fig. 7** · The UI sees only the backend; the backend sees only an interface. Swap what is behind the interface and nothing above it changes – which is exactly how the backend gets tested without a database.
>
> *Diagram:* HighScores has three layers. The console UI calls only the Leaderboard backend and receives HighScoreDto objects. The Leaderboard works with the HighScore model and stores it through the HighScoreRepository interface. JdbcHighScoreRepository implements that interface with SQL and is tested on a temporary SQLite file; LeaderboardTest uses an in-memory implementation and needs no database at all.

| Layer | Class | Job |
| --- | --- | --- |
| UI | `HighScores` | the console menu; calls only the backend and gets DTOs; no SQL, no connection, no SQLException |
| backend | `Leaderboard` | the rules: which name is acceptable, when a score is made, what the rank is; `save`, `top`, `clear` |
| backend | `HighScore` | the **model**: one result as the backend and the repository handle it – one row of the table (record) |
| backend | `HighScoreDto` | the **DTO**: one row as the UI receives it, with its `rank` (record) |
| backend | `HighScoreRepository` | **interface**: what the backend expects from storage – `save`, `findTop`, `deleteAll` |
| storage | `JdbcHighScoreRepository` | the interface with JDBC: **the only class with SQL in it**; creates the table |
| storage | `Database` | the connection details in one place |

The layers are wired together in `main`:

```java
Leaderboard leaderboard = new Leaderboard(new JdbcHighScoreRepository());
leaderboard.save(name, score);
for (HighScoreDto highScore : leaderboard.top(10)) { ... }
```

### Model and DTO: why two?

`HighScore` is the result as the backend and the repository handle it. `HighScoreDto` is what the UI receives: exactly what the display needs. Today they are nearly identical, but not the same:

- The DTO has something the model doesn't: the **rank**. The table doesn't know which place a row is in; the backend computes it from the query result (`toDto`).
- The model may hold things that don't belong on screen – in a bigger system internal ids, password hashes, audit data. The DTO carries none of them out.
- If the model changes (a new column, different storage), the DTO – and with it the UI – can stay the same. **The DTO is the contract** between backend and UI.

Leaderboard – validation, model in, DTO out

```java
public void save(String name, int score) throws StorageException {
    if (!isValidName(name)) {
        throw new IllegalArgumentException("Invalid name: '" + name + "'");
    }
    repository.save(new HighScore(name.strip(), score, LocalDateTime.now()));
}

static HighScoreDto toDto(int rank, HighScore highScore) {
    return new HighScoreDto(rank, highScore.name(), highScore.score(), highScore.createdAt());
}
```

### Validation in two places

The UI checks that the name is not empty or too long (`Leaderboard.isValidName`) and that the score is a number; if not, it prints a message and doesn't call the backend. The backend **checks the same again** and throws `IllegalArgumentException`. That is the real protection, because this UI is not the only caller – a game's view, a test, later a web API. The UI's check is for the user (a sensible message, not an exception); the backend's is for the data.

Good to know

**Why are the program's texts in English and ASCII?**

The IDE output windows and terminals use different code pages on different systems, and accented letters don't show up correctly everywhere. Keep runtime texts (menus, messages, window titles, exceptions) plain ASCII – in your assignment too.

In your assignment

**The database layer of your third assignment**

- **SQLite by default, MySQL optional.** At the presentation SQLite works on any machine; if Docker runs, MySQL is a comment swap. The code between the two doesn't change – that is the point of JDBC.
- **Code-first.** `CREATE TABLE IF NOT EXISTS` in the repository: whoever opens the project has nothing to prepare. A program should start without setup, with sensible defaults.
- **The view calls only the backend.** SQL only in the repository; the game's model and the view don't know a database exists.
- **`?` and `PreparedStatement`, always**, and try-with-resources around `Connection`, `PreparedStatement` and `ResultSet`.
- Add `*.db` to `.gitignore` – the SQLite file is created in the working directory and doesn't belong in git.

## Tests

Why is the repository an interface? Because then the backend can be tested **without a database**: `LeaderboardTest` gives the `Leaderboard` a repository backed by a list, and checks the name validation, the timestamp and the length of the list with no file and no server. This is the *repository pattern* of the Spring and .NET worlds, in its simplest form.

```java
/** A repository in a list – instead of the JDBC one, for the test. */
private static class InMemoryRepository implements HighScoreRepository {
    private final List<HighScore> scores = new ArrayList<>();

    @Override
    public void save(HighScore highScore) {
        scores.add(highScore);
    }
    // findTop: sort the list, limit; deleteAll: clear
}
```

The SQL is tested separately by `JdbcHighScoreRepositoryTest`. Every test gets an **empty, temporary SQLite file** and deletes it at the end, so the tests never write into the real `highscores.db` and don't depend on each other. For that the repository has a second constructor that takes the URL, user and password.

```java
@Before
public void createEmptyDatabase() throws IOException {
    file = Files.createTempFile("highscores-test", ".db");
    repository = new JdbcHighScoreRepository("jdbc:sqlite:" + file, null, null);
}

@After
public void deleteDatabase() throws IOException {
    Files.deleteIfExists(file);
}
```

The first test doubles as the code-first check (the empty file has no table; the repository creates it). Others check that a saved model comes back unchanged, the order and the `LIMIT`, that **SQL typed into the name stays plain text**, deleting, two repositories on one file seeing the same data, and that an unreachable server gives a `StorageException`, not an `SQLException`.

On the graphics side, `UfoTest` and `SpaceTest` check that a step adds the velocity, that a UFO wraps around at all four edges, that the same seed gives the same space, that after 10 000 steps every UFO is still inside, and that the list can't be modified from outside.

## Practice

Task 1

**Pause and speed**

Add a *Pause* button to `UFOsMV` that stops and restarts the animation, and two buttons that make the animation faster and slower. Which class does each change belong to?

**Approach:**

Both are about the clock, not the rules, so they stay in the view: `timer.stop()` / `timer.start()`, and `timer.setDelay(…)` for the speed. The model's `step` doesn't change at all.

```java
pauseButton.addActionListener(e -> {
    if (timer.isRunning()) {
        timer.stop();
    } else {
        timer.start();
    }
});
```

Task 2

**A new UFO every few seconds**

Every 100th step a new UFO should appear. Implement it without a second Timer, and write a test that the count grows by exactly one after 100 steps.

**Approach:**

A step counter in `Space`; `step()` checks it. In the test, create a `Space` with a fixed `Random`, call `step()` 99 times (no change), then once more.

```java
private long steps;

public void step() {
    steps++;
    if (steps % SPAWN_PERIOD == 0) {
        addUfos(1);
    }
    // ... move and recolour as before
}
```

Task 3

**A player's best score**

Add a menu item to `HighScores` that asks for a name and prints that player's best score, or "no scores yet". Extend every layer, and test the backend with the in-memory repository.

**Layer by layer:**

Interface first (`findBest(name)`), then the JDBC implementation with a `?` for the name, then the in-memory fake in the test (a stream with `filter` and `max`), then `Leaderboard.best(name)` with the same name validation as `save`, and finally the menu. Note that `MAX` of no rows is SQL `NULL` – check `rows.wasNull()`.

```java
// HighScoreRepository
OptionalInt findBest(String name) throws StorageException;

// JdbcHighScoreRepository – the name goes in through ?, never by concatenation
"SELECT MAX(score) AS best FROM highscores WHERE name = ?"
```

Task 4

**Switch to MySQL**

Start the MySQL container, switch `Database.java` to MySQL, save a few scores, then look at the table from inside the container. Finally stop the container and see what the program says.

**Commands:**

`docker compose up -d` in the project folder, wait for `ready for connections` in `docker logs progtech-mysql`, then `docker exec -it progtech-mysql mysql -u progtech -p` and `SELECT * FROM highscores;`. With the container stopped you should get a `StorageException` message, not a stack trace.

## Summary

- **paintComponent** – the method Swing calls to draw a component; you override it and never call it
- **repaint()** – a request for a redraw; Swing calls paintComponent soon, on the EDT
- **Timer** – calls a listener at a fixed interval on the EDT – the clock of an animation
- **JDBC** – the java.sql interface to every database; the driver and the URL choose the database
- **PreparedStatement** – an SQL statement with ? placeholders; values go in as data, never as SQL
- **Repository** – an interface for storage; the JDBC class implements it, a test can fake it
- **DTO** – the object the UI receives – exactly what the display needs, e.g. with the rank
- **Code-first** – the program creates its own tables (CREATE TABLE IF NOT EXISTS)

- paintComponent only draws the model; call repaint() when it changed, and start with super.paintComponent(g).
- One Timer calls one step() in the model, then repaint() – everything that happens over time goes into step().
- Positions and colours are numbers in the model; Color and drawing live in the view.
- JDBC + PreparedStatement + try-with-resources; wrap SQLException into your own exception.
- SQLite by default, MySQL in Docker as an option – only the URL changes.
- UI → backend → repository interface → JDBC; the backend is tested with an in-memory repository.
