Daten abfragen - Spring Boot 2

Daten abfragen, sortieren und paginieren


Nachdem ein generischer Datenlayer steht geht es nun an die Abfragen. Ein wichtiger Punkt ist die Sortierung und die Paginierung von Ergebnissen. Die kann man ebenfalls sehr weit schematisieren was zu weniger Fehlern führt und die Entwicklungszeit verringert.

Um Daten abzufragen werden ein Controller, ein Service, ein Repository sowie einige Hilfsklassen verwendet, die das Sortieren und Paginieren vereinfachen. Der Controller ist ein RestController, der Spring Method Security verwendet.

RestController mit schlanker Methode


package de.finait.start.backend.app.controller;

import java.util.List;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.data.domain.Pageable;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.annotation.JsonView;

import de.finait.start.app.model.Gender;
import de.finait.start.app.service.GenderService;
import de.finait.start.app.view.Views;
import de.finait.start.backend.data.criteria.GenderListCriteria;

@RestController("beGenderController")
@RequestMapping(value = { "/api/backend/app/gender" }, produces = MediaType.APPLICATION_JSON_VALUE)
@Validated
@Secured({ "ROLE_ADMIN" })
public class GenderController {

    @Autowired
    private GenderService genderService;

    @GetMapping(path = {"/list", "/list/{sort}/{direction}", "/list/{sort}/{direction}/{pageNo}"}, name = "backend-app-gender-list")
    @JsonView(Views.Public.class)
    public ResponseEntity<List<Gender>> list(@Valid GenderListCriteria spCriteria) {
        List<Gender> genderList = genderService.getAllWithTranslation(LocaleContextHolder.getLocale(), spCriteria.getPagable());

        return ResponseEntity.ok(genderList);
    }    
}

Zuerst werden drei mögliche Pfade definiert, über die die Methode angesprochen werden kann. Als Erstes eine einfache Liste ohne Sortierung, danach als Liste mit Sortierung aber ohne Seitennummer und zuletzt mit Seitennummer. Hier jeweils ein Beispiel:

  1. https://localhost:8080/api/backend/app/gender/list
  2. https://localhost:8080/api/backend/app/gender/list/t.name/desc
  3. https://localhost:8080/api/backend/app/gender/list/t.name/asc/1

Danach wird die Klasse GenderListCriteria verwendet, um die Parameter für das Suchen und Paginieren zu setzen und zu validieren.

Elternklasse von GenderListCriteria - SortingAndPagingCriteria


package de.finait.start.app.data.criteria;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;

@Getter(AccessLevel.PUBLIC)
@Setter(AccessLevel.PUBLIC)
public abstract class SortingAndPagingCriteria {

	@NotNull
	private String sort = "id";

	@Pattern(regexp = "asc|desc", flags = Pattern.Flag.CASE_INSENSITIVE)
	private String direction = "ASC";

	@Min(1)
	private int pageNo = 1;

	@Min(1)
	@Max(100)
	private int pageSize = 10;

	public void setDirection(String direction) {
		this.direction = direction.toUpperCase();
	}

	public Pageable getPagable() {
		return PageRequest.of(pageNo - 1, pageSize,
				direction.equals("ASC") ? Sort.by(Direction.ASC, sort) : Sort.by(Direction.DESC, sort));
	}
}

Hier werden Standardwerte für das Sortieren und Paginieren gesetzt. In nachfolgender konkreten Klasse müssen dann nur noch abweichende bzw. spezifische Werte gesetzt werden. Zum Beispiel möchte man nur 5 Ergebnisse pro Seite angezeigt bekommen anstatt 10 sowie die Felder, die sortierbar sein sollen. Natürlich empfiehlt es sich diese Werte aus einer Konfiguration zu lesen und z.B. mit der @Value Annotation zu setzen.

GenderListCriteria


package de.finait.start.backend.data.criteria;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

import de.finait.start.app.data.criteria.SortingAndPagingCriteria;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;

@Getter(AccessLevel.PUBLIC)
@Setter(AccessLevel.PUBLIC)
public class GenderListCriteria extends SortingAndPagingCriteria {

	@NotNull
	@Pattern(regexp = "id|t.name|t.shortName")
	private String sort = "id";
	
	@Min(1)
	@Max(100)
	private int pageSize = 5;

}

Nachdem die Daten sich im Objekt der Klasse GenderListCriteria befinden und validiert wurden, muss lediglich die Methode getPagable aufgerufen werden um ein Objekt der Klasse Pageable des Spring Frameworks zu erhalten. Hiermit macht man die eigentliche Sortierung und Paginierung. Anschließend wird der Service aufgerufen, der so aussehen könnte:

Service Interface


package de.finait.start.app.service;

import java.util.List;
import java.util.Locale;

import org.springframework.data.domain.Pageable;

import de.finait.start.app.model.Gender;

public interface GenderService {

	List<Gender> getAll();

	List<Gender> getAllWithTranslation(Locale locale, Pageable pageing);

}

Service Implementierung


package de.finait.start.app.service;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import de.finait.start.app.model.Gender;
import de.finait.start.app.repository.GenderRepository;

@Service
public class GenderServiceImpl implements GenderService {

	@Autowired
	GenderRepository genderRepository;


	@Override
	public List<Gender> getAllWithTranslation(Locale locale, Pageable paging) {
        Page<Gender> pagedResult = genderRepository.findAllWithTranslation(locale, paging);
        if(pagedResult.hasContent()) {
            return pagedResult.getContent();
        }
        return new ArrayList<Gender>();
	}

	@Override
	public List<Gender> getAll() {
		return genderRepository.findAll();
	}
}

Im Service wird eine Methode des Repositories aufgerufen, das so implementiert sein kann:

Repository Implementierung


package de.finait.start.app.repository;

import java.util.Locale;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import de.finait.start.app.model.Gender;

public interface GenderRepository extends JpaRepository<Gender, Long> {

    @Query(value = "SELECT g FROM Gender g INNER JOIN FETCH g.translations t WHERE t.locale = ?1",
    	   countQuery = "SELECT COUNT(g) FROM Gender g INNER JOIN g.translations t WHERE t.locale = ?1")
    public Page<Gender> findAllWithTranslation(Locale locale , Pageable pageable);
}

Das war es eigentlich. Läuft immer nach dem gleichen Schema ab. Eine schlanke Controller-Methode, ein Sorter wie beschrieben mit der Klasse GenderListCriteria . Ein Service mit zusätzlicher Geschäftslogik und ein Repository um die Daten zu selektieren. Weniger Code -> schneller fertig :-).

Zurück zur Kategorie: Spring
Zurück nach oben