Warm-up

Leak a column you never exposed

10 minjunior015 yrs

One concept, guided. Near-impossible to fail.

What this teaches

  • A serialiser walks every field, including the ones you did not mean to publish
  • Adding a column to a table changes the API with no code change
  • A bidirectional mapping is a cycle, and a serialiser has no idea where to stop
  • Serialising a lazy association issues a query, outside the transaction

Starter

Starter.java
import java.lang.reflect.*;
import java.util.*;

/**
 * Warm-up: publish a password hash without meaning to.
 *
 * No Jackson and no Hibernate. A serialiser is, for this purpose, a thing that
 * reflects over fields and emits all of them — which is the only property that
 * matters here.
 */
public class Starter {

    public static void main(String[] args) throws Exception {

        // TODO 1: write serialise(Object) using reflection over
        // getDeclaredFields(). Emit every field as a JSON key. Recurse into
        // objects and collections, and pass a depth so you can stop.
        //
        // Note: getDeclaredFields() order is unspecified by the JLS. It is
        // stable in practice for a given class file, and worth knowing you are
        // relying on that.

        // TODO 2: write a User entity with id, email and displayName. Serialise
        // it. This is your API response.

        // TODO 3: now add passwordHash and internalNotes to the entity, as a
        // migration would. Do not touch the serialising code. Print the JSON
        // again and compare.
        //
        // Write one sentence about which review would have caught that.

        // TODO 4: define a record UserResponse(long id, String displayName),
        // populate it from the entity, and serialise that instead. Add another
        // field to the entity and confirm the response does not change.

        // TODO 5: build a bidirectional pair — Author has books, Book has an
        // author — and serialise the author. You will need a depth limit.
        // Say what Jackson does instead of stopping, and why the mapping is
        // still not the thing that is wrong.

        // TODO 6: give the entity a lazy field: a wrapper holding a Supplier
        // that increments a counter the first time it is read. Serialise the
        // entity and print the counter before and after.
        //
        // Then say where that query happens relative to the transaction, and
        // which of your timers would have seen it.

        // TODO 7: finally, the direction people forget. If a controller
        // accepted this entity as a request body, list the fields a caller
        // could set. Say what setting `id` would do to a save().
    }
}

Run it locally:

cd exercises/java/jpa-hibernate/entity-at-the-boundary/01-warmup
javac Starter.java -d /tmp/out && java -cp /tmp/out Starter

Done when

  • You added a field to an entity and watched it appear in the JSON
  • You produced the recursion, and can say what real Jackson does instead of stopping
  • You showed serialisation triggering a lazy load
  • You showed a DTO leaving the JSON unchanged when the entity gains a field

← Back to Should you return a JPA entity from a REST controller?