Add jdbc-vs-jpa module: Spring Data JDBC vs JPA on a shared Order aggregate
Same Customer/Order/OrderItem domain modelled with Hibernate/Spring Data JPA and Spring Data JDBC side by side, both running through a shared StatementLoggingDataSource so SQL-statement counts are directly comparable. 11 tests, 11 captured transcripts, 6 doc chapters. Companion repo for the ankurm.com article on when to drop the ORM.
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
# 01 — The shared domain
|
||||
|
||||
[← README](../README.md) · [next: the JPA side →](02-the-jpa-side.md)
|
||||
|
||||
Both stacks in this module model the same thing: a `Customer` who places `Order`s, each with a
|
||||
list of `OrderItem`s. It is deliberately the smallest domain that has everything worth arguing
|
||||
about — a one-to-many collection, a reference to a second aggregate, and a field a reader will
|
||||
want to mutate in place.
|
||||
|
||||
| Concept | JPA package (`com.ankurm.jdbcvsjpa.jpa`) | JDBC package (`com.ankurm.jdbcvsjpa.jdbc`) |
|
||||
|---|---|---|
|
||||
| Table prefix | `jpa_*` | `JDBC_*` (see the identifier-casing note below) |
|
||||
| Order → Customer | `@ManyToOne(fetch = LAZY)` object reference | `AggregateReference<Customer, Long>` — a typed foreign key, never a loaded object |
|
||||
| Order → items | `@OneToMany(mappedBy, cascade = ALL, orphanRemoval = true)`, `List<OrderItem>` | `@MappedCollection(idColumn, keyColumn)`, `List<OrderItem>` |
|
||||
| Identity of a child row | `@ManyToOne` back-reference to its parent | none — a JDBC `OrderItem` has no idea which Order owns it |
|
||||
| Optimistic locking | `@Version Long version` | `@Version Long version` (same annotation, same package: `org.springframework.data.annotation`) |
|
||||
|
||||
Both sides run against the **same H2 database** through the **same
|
||||
[`StatementLoggingDataSource`](../src/main/java/com/ankurm/jdbcvsjpa/support/StatementLoggingDataSource.java)**
|
||||
— see [03 — the SQL log](03-the-sql-log.md) for why that matters for the numbers in the article.
|
||||
|
||||
## A casing trap that cost a debugging session
|
||||
|
||||
Spring Data JDBC treats an explicit `@Table("some_name")` or `@Column("some_name")` value as a
|
||||
*literal, quoted* SQL identifier — it is rendered exactly as given, in double quotes. Properties
|
||||
with no explicit annotation go through the H2 dialect's default identifier processing instead,
|
||||
which upper-cases them. H2 itself upper-cases any *unquoted* identifier in DDL. The result: an
|
||||
unquoted `create table jdbc_customer(...)` in `schema.sql` produces a table H2 privately calls
|
||||
`JDBC_CUSTOMER`, but `@Table("jdbc_customer")` generates `INSERT INTO "jdbc_customer" (...)` —
|
||||
quoted, lower-case, and therefore a different identifier as far as H2 is concerned. The fix used
|
||||
throughout this module is to give every explicit `@Table`/`@Column`/`@MappedCollection` value in
|
||||
upper case, matching H2's own folding, so the literal and the folded name agree. See
|
||||
[`jdbc/Order.java`](../src/main/java/com/ankurm/jdbcvsjpa/jdbc/Order.java) for the annotations
|
||||
and [`schema.sql`](../src/main/resources/schema.sql) for the DDL. This is exactly the kind of
|
||||
thing that never shows up in a tutorial that only ever runs one save and eyeballs the console.
|
||||
|
||||
Continue to [02 — the JPA side](02-the-jpa-side.md).
|
||||
Reference in New Issue
Block a user