Projekt Informator
Projekt informator to REST’owy web service, działający w oparciu o Spring i Hibernate. Jeśli chcesz przeczytać więcej o projekcie i jego założeniach zapraszam do wprowadzenia.
W jednym z poprzednich artykułów przeczytasz też o wdrożeniu projektu w chmurze.
Samouczek Programisty jest jednym z partnerów konferencji infoShare 2018.
infoShare 2018 to konferencja technologiczna odbywająca się 22-23 maja w Gdańsku. Na developerów czekają m.in. prelekcje z obszaru cybersecurity i machine learning, live coding oraz spotkania ze specjalistami, takimi jak: Filip Wolski, Trent McConaghy, Piotr Konieczny, Zbigniew Wojna czy Scott Helme. infoShare to także okazja do networkingu i udziału w imprezach towarzyszących. Sprawdź agendę i zarejestruj się na www.infoshare.pl.
Baza danych
W projekcie do mapowania obiektowo relacyjnego używam biblioteki Hibernate jako implementacji JPA (ang. Java Persistence API). W tym przypadku tworzenie schematu bazy danych zostawiam JPA. Poniżej widzisz konfigurację obiektu zarządzanego przez kontener Spring’a. Służy on do tworzenia instancji implementującej interfejs EntityManager
:
@Bean
LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
factory.setPackagesToScan("pl.samouczekprogramisty.informator.model");
factory.setDataSource(dataSource());
Properties jpaProperties = new Properties();
jpaProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
jpaProperties.setProperty("hibernate.show_sql", "true");
jpaProperties.setProperty("hibernate.format_sql", "true");
jpaProperties.setProperty("hibernate.hbm2ddl.auto", "validate");
// create database schema if missing
jpaProperties.setProperty("javax.persistence.schema-generation.database.action", "create");
factory.setJpaProperties(jpaProperties);
return factory;
}
Pobierz opracowania zadań z rozmów kwalifikacyjnych
Przygotowałem rozwiązania kilku zadań algorytmicznych z rozmów kwalifikacyjnych. Rozkładam je na czynniki pierwsze i pokazuję różne sposoby ich rozwiązania. Dołącz do grupy ponad 6147 Samouków, którzy jako pierwsi dowiadują się o nowych treściach na blogu, a prześlę je na Twój e-mail.
Zasilenie bazy danych
Niestety organizatorzy konferencji nie przygotowali źródła danych, które w łatwy sposób można użyć do zasilenia bazy danych. Jedyne źródło to oficjalna strona www konferencji. Na początku skupiłem się nad zasileniem tabeli zawierającej dane dotyczące prelegentów. W projekcie Informator prelegent reprezentowany jest przez instancję klasy Speaker
:
@Entity
public class Speaker {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "speaker_seq")
@SequenceGenerator(name = "speaker_seq")
private Integer id;
private Integer infoshareId;
private Category category;
private String name;
private URL linkedinProfile;
private URL twitterProfile;
private URL facebookProfile;
private URL githubProfile;
@Column(columnDefinition = "text")
private String description;
// getters/setters
}
Analizując zapytania HTTP, które są wykonywane w tle zauważyłem adres w postaci:
https://infoshare.pl/speaker2.php?cid=48&id=XXX&year=2018&agenda_id=99999&fancybox=true
W adresie tym XXX
zastąpione jest identyfikatorem prelegenta. Strona z prelegentami zawiera listę wszystkich osób występujących na każdej ze scen. Żeby wyciągnąć informacje o wszystkich prelegentach potrzeba ponad 200 zapytań.
Z racji tego, że jest to dość żmudne i czasochłonne zadanie napisałem skrypt1, który wyciąga niezbędne dane. W wyniku działania tego skryptu powstał plik speakers.sql
. Wewnątrz tego pliku znajdują się instrukcje SQL (ang. Structured Query Language), które dodają wiersze do tabeli speaker
. Przykładowe zapytanie z tego pliku wygląda następująco:
INSERT INTO speaker (
id,
infoshareid,
category,
description,
facebookprofile,
githubprofile,
linkedinprofile,
twitterprofile,
name
)
VALUES (
nextval('speaker_seq'),
954,
0, 'Stephen Haunts is a veteran sof(...)',
NULL,
NULL,
NULL,
'https://twitter.com/stephenhaunts',
'Stephen Haunt'
);
Formatowanie odpowiedzi
Mając rzeczywiste dane w bazie danych webservice może odpowiadać bardziej sensownymi danymi:
$ curl http://localhost:8080/speakers/7 -s | json_pp
{
"category" : "STARTUP",
"description" : "Kamila Wincenciak is a member of Ali(...)",
"name" : "Kamila Wincenciak",
"githubProfile" : null,
"twitterProfile" : null,
"facebookProfile" : null,
"linkedinProfile" : "https://www.linkedin.com/in/kamila-wincenciak-27560130/"
}
Zabrałem się za kolejny etap, czyli obsługę błędów. Przypadkami, które trzeba obsłużyć są brak rekordu w bazie i złe dane wprowadzone przez użytkownika. Oba przypadki pokazane są poniżej. Proszę zwróć uwagę na zwracane nagłówki i status odpowiedzi:
$ curl http://localhost:8080/speakers/-1 -vs | json_pp
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /speakers/-1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 404
< Content-Type: application/json
< Content-Length: 148
< Date: Wed, 20 Jun 2018 21:09:42 GMT
<
{ [148 bytes data]
* Connection #0 to host localhost left intact
{
"responseCode" : 404,
"exceptionClass" : "pl.samouczekprogramisty.informator.exceptions.NotFoundException",
"message" : "Speaker with id -1 wasn't found!"
}
$ curl http://localhost:8080/speakers/aa -vs | json_pp
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /speakers/aa HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 400
< Content-Type: application/json
< Content-Length: 108
< Date: Wed, 20 Jun 2018 21:09:16 GMT
< Connection: close
<
{ [108 bytes data]
* Closing connection 0
{
"message" : "For input string: \"aa\"",
"responseCode" : 400,
"exceptionClass" : "java.lang.NumberFormatException"
}
Konfiguracja Spring a obsługa błędów
Aby móc w ten sposób formatować błędy użyłem kombinacji adnotacji ControllerAdvice
i ExceptionHandler
:
@ControllerAdvice
@SuppressWarnings("unused")
@ResponseBody
public class InformatorExceptionHandler {
private static ObjectMapper mapper = new ObjectMapper();
public static class ErrorResponse {
private static final MultiValueMap<String, String> HEADERS = new LinkedMultiValueMap<>(
Collections.singletonMap(HttpHeaders.CONTENT_TYPE, Collections.singletonList(MediaType.APPLICATION_JSON_VALUE))
);
private final Exception exception;
private HttpStatus responseStatus;
ErrorResponse(HttpStatus responseStatus, Exception exception) {
this.exception = exception;
this.responseStatus = responseStatus;
}
ResponseEntity<String> buildResponse() {
try {
return new ResponseEntity<>(mapper.writeValueAsString(this), HEADERS, responseStatus);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
// getters
}
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<String> handleNotFound(NotFoundException exception) {
return new ErrorResponse(HttpStatus.NOT_FOUND, exception).buildResponse();
}
@ExceptionHandler(NumberFormatException.class)
public ResponseEntity<String> handleNumberFormat(NumberFormatException exception) {
return new ErrorResponse(HttpStatus.BAD_REQUEST, exception).buildResponse();
}
}
Klasa oznaczona adnotacją ControllerAdvice
zawiera w sobie metody, które są użyte w wielu kontrolerach. Możemy powiedzieć, że są to metody przekrojowe. Przykładem takich metod są te oznaczone adnotacją ExceptionHandler
. Każda z nich odpowiada za obsługę innego typu wyjątku.
Niestety w tym przypadku Spring nie deserializuje obiektu odpowiedzi do żądanego formatu dlatego napisałem klasę pomocniczą ErrorResponse
, która przygotowuje odpowiedź w formacie JSON.
Podsumowanie
Aplikacja aktualnie jest w stanie wyświetlić informacje o prelegencie na podstawie rzeczywistych danych pobranych ze strony organizatora konferencji. Dodatkowo aplikacja poprawnie reaguje na różnego rodzaju błędy odpowiadając w formacie JSON. Zachęcam Cię do przeanalizowania kodu źródłowego aplikacji, w ten sposób utrwalisz zdobytą wiedzę.
Po przeczytaniu tego artykułu i przejrzeniu kodu źródłowego wiesz w jaki sposób można obsługiwać błędy w webservice’ach. Poznałeś też sposób na zasilanie bazy danych na podstawie informacji umieszczonych na innych stronach.
Jeśli nie chcesz pominąć kolejnych artykułów na Samouczku proszę dopisz się do samouczkowego newslettera i polub Samouczka na Facebooku. Proszę podziel się linkiem do artykułu ze znajomymi, którym może on pomóc. Może to dzięki Tobie uda mi się dotrzeć do nowych czytelników? ;)
Do następnego razu!
Pobierz opracowania zadań z rozmów kwalifikacyjnych
Przygotowałem rozwiązania kilku zadań algorytmicznych z rozmów kwalifikacyjnych. Rozkładam je na czynniki pierwsze i pokazuję różne sposoby ich rozwiązania. Dołącz do grupy ponad 6147 Samouków, którzy jako pierwsi dowiadują się o nowych treściach na blogu, a prześlę je na Twój e-mail.
Zostaw komentarz