Released · improving
Spring Boot guide · 4/6
A Spring Boot server usually keeps no state in memory between requests; it lives in a database. This chapter shows how to work with data through Spring Data JPA, keep it consistent with transactions, validate input and manage the schema with Flyway.
For PostgreSQL, add these dependencies (in Initializr: data-jpa, postgresql, flyway and validation):
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-flyway")
implementation("org.flywaydb:flyway-database-postgresql")
runtimeOnly("org.postgresql:postgresql")
}Connection details go under spring.datasource.*. Let Flyway own the schema and have Hibernate only check that entities match the tables:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/demo
username: demo
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: validate
open-in-view: falseAn entity maps to a table row. JPA needs a no-argument constructor, so add a protected one.
@Entity
public class Todo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 200)
private String title;
private boolean done;
protected Todo() {}
public Todo(String title) { this.title = title; }
public void complete() { this.done = true; }
public Long getId() { return id; }
public String getTitle() { return title; }
public boolean isDone() { return done; }
}For a repository you declare only an interface; Spring Data generates the implementation. JpaRepository provides save, findById, findAll, deleteById and more, and method names can be turned into queries:
public interface TodoRepository extends JpaRepository<Todo, Long> {
List<Todo> findByDoneFalseOrderByIdAsc();
@Query("select t from Todo t where lower(t.title) like lower(concat('%', :keyword, '%'))")
List<Todo> search(@Param("keyword") String keyword);
}The service layer is where operations are grouped into units of work. A @Transactional method opens a transaction on entry, commits on normal return and rolls back on a runtime exception. Changes to an entity loaded inside the transaction are flushed at commit without calling save (dirty checking).
@Service
@Transactional(readOnly = true)
public class TodoService {
private final TodoRepository repository;
public TodoService(TodoRepository repository) {
this.repository = repository;
}
public List<Todo> open() {
return repository.findByDoneFalseOrderByIdAsc();
}
@Transactional
public void complete(Long id) {
Todo todo = repository.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
todo.complete();
}
}Two pitfalls: checked exceptions do not trigger a rollback by default, so use rollbackFor when needed; and because @Transactional works through a proxy, calling another method of the same class directly bypasses it.
Put Bean Validation constraints on the request type and @Valid on the controller parameter. Invalid requests are rejected with a 400 response before your method runs.
public record CreateTodo(@NotBlank @Size(max = 200) String title) {}
@PostMapping
public TodoResponse create(@Valid @RequestBody CreateTodo request) {
return service.create(request.title());
}To shape the error body, handle MethodArgumentNotValidException in a @RestControllerAdvice and return a ProblemDetail (the RFC 9457 format).
Flyway runs the SQL files in src/main/resources/db/migration in version order and records what it applied in the flyway_schema_history table. On startup it runs only the files that have not been applied yet.
-- src/main/resources/db/migration/V1__create_todo.sql
create table todo (
id bigint generated by default as identity primary key,
title varchar(200) not null,
done boolean not null default false
);Never edit a migration that has already been applied. Add a new file such as V2__add_due_date.sql instead.
JpaRepository gives you CRUD operations and derived queries.@Transactional on service methods to define transactions.@Valid.validate.
0 comments
Sign in · Sign in to leave a comment.
Be the first to comment.