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