Typical Spring project
- JPA (usually Hibernate ORM),
@Entity
classes here and there, Flyway for DB migrations - Spring Data - SQL for above classes; especially
JpaRepository
- Entity-DTO mapping, usually done by hand
@Controller
or@RestController
exposing DTOs
This project
- Still with Flyway
- Still with Spring Data
- No JPA, no "manual" mappings, fancy REST (HATEOAS)
Perfect catalog-like CRUD application. The main (and only) entity is called Category
and thanks
to carwarninglights.net I made it displaying car segments.
From the first view it looks typical - there is a file-based H2 SQL database, it can be started easily and
with src/main/resources/Less popular Spring tricks.postman_collection.json
anyone can
use Postman to check it in action.
However, code itself can be a little confusing. There is no JPA, no Spring Data JPA dependency, you cannot extend a
typical JpaRepository
. There are way more interfaces than classes and a familiarly looking TraditionalController
doesn't seem to be used much. And git commits show it was built like that from the very beginning!
After working a year with Spring Data JDBC within the actual project, I liked it, but it's meant for the "command" part of the system and it's not a perfect choice for "query" (heavy reading). E.g. no way to prevent N+1 selects problem. I go with Spring Data JDBC in command and with
JdbcTemplate
in query parts of systems.
Do you know we don't need Hibernate to use @Table
annotation?
org.springframework.data.annotation
and org.springframework.data.relational.core.mapping
to the rescue!
I'd personally use Rest Repositories just for read operations. Preferably built on top of the pure
Repository
interface. It can be a powerful thing for "query" part of the system, especially combined with projections. However, in practice it seems sooner or later you'd move towards a traditional approach which is more customizable than@RepositoryRestResource
and@RestResource
options, default interface methods and evenRepositoryRestController
.
Spring Data REST specifically. With its detailed documentation.
I really like projections. In my eyes it's an easy opt-in solution which can be replaced with a hidden implementing class and
ObjectMapper
configs if needed.
Not only for above repositories, but also with a more "traditional" approach:
// 👇
interface CategoryRepository extends Repository<Category, Long> {
// 👇
List<TextCategoryVM> findTextualCategoriesBy();
}
@Getter
@NoArgsConstructor(access = PROTECTED)
@Table("categories")
class Category {
@Id
private Long id;
@NotBlank
private String code;
@NotBlank
private String name;
private String description;
private String imageUrl;
@Column("is_deprecated")
private boolean deprecated;
}
// getters "compatible" with the Category class
interface TextCategoryVM {
String getCode();
String getName();
String getDescription();
}
Further read: