Spring Boot: Ein Beispiel mit JPA und H2 DB und REST-Schnittstelle

07.02.2016

Spring Boot: Ein Beispiel mit JPA und H2 und REST-Schnittstelle

In diesem Artikel wird gezeigt, wie man eine SpringBoot-Anwendung mit der Annotation @RestController um eine REST-Schnittstelle erweitert, so dass die CRUD-Operationen unterstützt werden. Als SpringBoot-Anwendung wird der PersonManager aus dem Artikel Spring Boot: Ein Beispiel mit JPA und H2 verwendet.

Als erstes wird die Klasse PersonService um verschiedene Methoden erweitert, die das Anlegen, Ändern, Löschen, Finden und Auflisten von Personen-Objekten ermöglicht.


package org.hameister.personmanager.service;

import org.hameister.personmanager.model.Person;
import org.hameister.personmanager.repo.PersonRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.List;

/**
 * Created by Joern Hameister on 24.01.16.
 */
@Service
public class PersonService implements PersonInterface {


    @Autowired
    PersonRepository personRepository;

    public double  averageSalary() {

        double salarySum = 0;
        List<Person> personList= personRepository.findAll();

        for( Person person : personList) {

            salarySum =  salarySum +person.getSalary();


            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");

            if(person.getBirthday()!=null) {
                String birthday = person.getBirthday().format(formatter);
                System.out.println("Der Geburtstag von " + person.getName() + " ist: " + birthday+ " und der Verdienst ist:"+ person.getSalary());
            }
        }

        return  salarySum / personList.size();
    }

    public Person addPerson(Person person) {
        return personRepository.save(person);
    }

    @Override
    public Collection<Person> findAll() {
        return personRepository.findAll();
    }

    @Override
    public Person findOne(Long id) {
        return personRepository.findOne(id);
    }

    @Override
    public Person create(Person person) {

        if (person.getId() != null) {
            return null;
        }
        return personRepository.save(person);
    }

    @Override
    public Person update(Person person) {
        Person persistedPerson =personRepository.findOne(person.getId());

        if (persistedPerson == null) {
            return null;
        }

        return personRepository.save(person);
    }

    @Override
    public void delete(long id) {
        personRepository.delete(id);
    }

Die neuen Methoden werden nun durch ein Interface gekapselt, welches PersonInterface genannt wird.

package org.hameister.personmanager.service;

import org.hameister.personmanager.model.Person;

import java.util.Collection;

/**
 * Created by hameister on 31.01.16.
 */
public interface PersonInterface {

        Collection<Person> findAll();

        Person findOne(Long id);

        Person create(Person person);

        Person update(Person person);

        void delete(long id);
}

Als erstes wird in dem Package controller eine neue Klasse PersonController angelegt, die die Methoden vom PersonService über eine REST-Schnittstelle nach aussen verfügbar macht.

package org.hameister.personmanager.controller;

import org.hameister.personmanager.model.Person;
import org.hameister.personmanager.service.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Collection;

/**
 * Created by hameister on 31.01.16.
 */

@RestController
public class PersonController {

    @Autowired
    PersonService personService;



    /**************
     FIND ALL
     **************/
    @RequestMapping(value = "/api/persons", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    ResponseEntity<Collection<Person>> getPersons() {
        Collection<Person> persons = personService.findAll();

        return new ResponseEntity<Collection<Person>>(persons, HttpStatus.OK);
    }




    /**************
     FIND ONE
     **************/
    @RequestMapping(value = "/api/persons/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    ResponseEntity<Person> getPerson(@PathVariable("id") long id) {

        Person person =personService.findOne(id);

        if(person == null) {

            return new ResponseEntity<Person>(HttpStatus.NOT_FOUND);
        }

        return new ResponseEntity<Person>(person, HttpStatus.OK);
    }



    /**************
     CREATE
     **************/
    @RequestMapping(value = "/api/persons", method = RequestMethod.POST, consumes =  MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    ResponseEntity<Person> createPerson(@RequestBody Person person) {


        Person saved  = personService.create(person);

        return new ResponseEntity<Person>(saved, HttpStatus.CREATED);
    }

    /**************
     UPDATE
     **************/
    @RequestMapping(value = "/api/persons/{id}", method = RequestMethod.PUT, consumes =  MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    ResponseEntity<Person> updatePerson (@RequestBody Person person) {

        if (person.getId() == null) {
            return  new ResponseEntity<Person>(HttpStatus.INTERNAL_SERVER_ERROR);
        }

        Person updatedGreeting = personService.update(person);

        if (updatedGreeting==null){
            return  new ResponseEntity<Person>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
        return  new ResponseEntity<Person>(updatedGreeting, HttpStatus.OK);
    }


    /**************
     DELETE
     **************/
    @RequestMapping(value = "/api/persons/{id}", method = RequestMethod.DELETE, consumes = MediaType.APPLICATION_JSON_VALUE)
    public  ResponseEntity<Person> deletePerson(@PathVariable("id") long id,  @RequestBody Person person) {
        personService.delete(id);

        return new ResponseEntity<Person>(HttpStatus.NO_CONTENT);
    }

}

Für Jede Methode wird ein Request-Mapping angegeben. D.h. der Pfad über den die Methode verfügbar sein soll. Außerdem wird definiert, welche REST-Operation aufgerufen werden muss (GET, PUT,POST, DELETE). Zusätzlich muss festgelegt werden, welche Datenformate akzeptiert werden. Mit der Annotation @consumes wird festgelegt, welche Formate konsumiert werden können. Die Annotation @produces gibt an, welches Format der Rückgabewert hat. In dem Beispiel wird ausschließlich JSON verarbeitet.

Da über die REST-Schnittstelle Objekte vom Typ Person ausgetauscht werden und diese Klasse eine Variable von Typ LocalDate beinhaltet, muss für die Serialisierung und Deserialisierung mit Jackson angegeben werden, welche Serializier und Deserializer verwendet werden sollen. Ausserdem sollte man noch ein Datumsformat angeben @DateTimeFormat. Siehe die Zeilen 31-33.

package org.hameister.personmanager.model;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import org.springframework.format.annotation.DateTimeFormat;

import javax.persistence.*;
import java.time.LocalDate;

/**
 * Created by Joern Hameister on 24.01.16.
 */
@Entity
@Table(name = "Person")
public class Person {

    @Id
    @GeneratedValue
    Long id;

    @Column(name = "name")
    String name;


    @Column(name = "salary")
    Long salary;


    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    @Column(name = "birthday")

    LocalDate  birthday;

    public Person() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getSalary() {
        return salary;
    }

    public void setSalary(Long salary) {
        this.salary = salary;
    }

    public LocalDate getBirthday() {
        return birthday;
    }

    public void setBirthday(LocalDate birthday) {
        this.birthday = birthday;

        System.out.print(birthday.toString());
    }
}

Da LocalDate Teil des JSR-310 (Date Time API) ist. Muss dies in der POM-Datei bekannt gemacht werden, damit Jackson die Serialisierung und Deserialisierung durchführen kann. Dafür wird die POM-Datei um folgende Dependency erweitert.

<dependency>
     <groupId>com.fasterxml.jackson.datatype</groupId>
     <artifactId>jackson-datatype-jsr310</artifactId>
     <version>2.4.0</version>
 </dependency>

Anschließend kann die Applikation gestartet werden und die REST-Schnittstelle ist verfügbar.

Im Folgenden wird der REST-Client Postman verwendet, um die Funktionen einzeln zu testen.

Als erstes werden alle Personen mit http://localhost:8080/api/persons abgefragt:

Um eine bestimmte Person anhand der id abzufragen, kann folgende URL verwendet werden: localhost:8080/api/persons/1. D.h. die id wird einfach in der URL ergänzt.

Als nächstes soll eine Person geändert werden. Beispielsweise kann das Geburtsdatum von Bart Simpson auf den 1. April 1980 gesetzt werden. Die URL dafür lautet http://localhost:8080/api/persons/1

Wichtig dabei ist, dass die Operation PUT verwendet wird. Ausserdem wird im Request-Body die JSON-Repräsentation der Person übergeben:

{
  "id": 1,
  "name": "Bart Simpson",
  "salary": 100,
  "birthday": [
    1980,
    4,
    1
  ]
}

Als Response wird die veränderte Person zurückgeliefert und der Return-Code 200, wenn alles geklappt hat.

Um eine neue Person anzulegen, wird die folgende URL verwendet: http://localhost:8080/api/persons, diesmal allerdings mit der Operation POST

Im Request-Body sind die Informationen über die Person enthalten:

{
  "name": "Donald Duck",
  "salary": 50,
  "birthday": [
    1960,
    5,
    1
  ]
}

Wenn das Anlegen erfolgreich war, dann steht in der Response die JSON-Repräsentation der neu angelegten Person und als Return-Code wird 201 (Created) zurückgeliefert

Die letzte Methode ist das Löschen einer Person. Das Löschen wird durch den Aufruf folgender URL erreicht: http://localhost:8080/api/persons/5, wichtig dabei ist, dass die Operation DELETE verwendet wird.

Im Request-Body steht nur die id der zu löschenden Person.

{
  "id": 5
}

Wenn das Löschen erfolgreich war, dann wird als Return-Code der Wert 204 (No Content) zurückgeliefert.

Der komplette Quellcode ist bei GitHub unter folgender URL zu finden: SpringBootPersonManager