# Schema, entities, and why there's no `@ManyToOne` The schema is deliberately small: ```graphql type Query { books: [Book!]! } type Book { id: ID! title: String! author: Author! } type Author { id: ID! name: String! } ``` Two queryable types, one relationship. `books` returns every `Book`, and each `Book` carries a non-null `author`. That single non-null arrow (`author: Author!`, and for that matter `[Book!]!` on the list itself) does two jobs in this module: it's the ordinary shape of a schema-first API, and it's the trigger for docs/04-the-null-propagation-trap.md later on. On the JPA side, `Book` does **not** map that relationship as a `@ManyToOne`: ```java @Entity @Table(name = "BOOK") public class Book { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String title; private Long authorId; // ... } ``` `authorId` is a plain `Long` column, not a Hibernate association. That's not an oversight — it's the point of the module. If `Book.author` were a real `@ManyToOne`, Hibernate's own lazy-loading and fetch-join machinery would decide when the `AUTHOR` row gets loaded, and that decision would tangle with GraphQL Java's field-resolution order in ways that are hard to observe cleanly. Keeping `authorId` as an inert column means the *only* thing that decides when and how an `Author` gets loaded is the GraphQL resolver wired up for the `Book.author` field — which is exactly what docs/02 and docs/03 compare. ## Three resolver-relevant annotations `BookController` supplies the query root with an ordinary `@QueryMapping`: ```java @Controller public class BookController { private final BookRepository bookRepository; public BookController(BookRepository bookRepository) { this.bookRepository = bookRepository; } @QueryMapping public List books() { return bookRepository.findAll(); } } ``` Source: [`resolver/BookController.java`](../src/main/java/com/ankurm/graphqldataloader/resolver/BookController.java) That's one `SELECT` for the whole list — `select b1_0.id,b1_0.author_id,b1_0.title from book b1_0`, confirmed in every transcript under `docs/output/` (line 1 of each one). The interesting part is what happens next, when GraphQL Java has a list of `Book` objects and needs to resolve `author` on each of them. Two resolver classes answer that question differently, and both are wired into the same schema and the same entities — only the annotation and the method signature change. They're kept apart with Spring profiles (`@Profile("naive")` / `@Profile("batched")`) so a single running instance never has both active at once, and the test suite in docs/02–docs/04 boots one profile per test class to compare them under identical data. ## `application.yml` and `schema.sql` ```yaml spring: sql: init: mode: always jpa: hibernate: ddl-auto: none open-in-view: false graphql: graphiql: enabled: true ``` Source: [`src/main/resources/application.yml`](../src/main/resources/application.yml) `open-in-view: false` is not incidental. With OSIV on, a stray Hibernate session spanning the whole request can paper over exactly the kind of lazy-loading-triggered-from-the-wrong-place bug this module is built to make visible; turning it off means any accidental session access outside the transaction that a resolver runs in fails loudly instead of quietly working by accident. ```sql create table author ( id bigint auto_increment primary key, name varchar(200) not null ); create table book ( id bigint auto_increment primary key, title varchar(300) not null, author_id bigint not null ); ``` Source: [`src/main/resources/schema.sql`](../src/main/resources/schema.sql) No foreign key constraint on `book.author_id`. That's also deliberate — docs/04 seeds a `Book` row whose `author_id` matches no `Author` row at all, and a foreign key would make that impossible to set up in the first place.