W tym celu dodajemy do naszego pliku pom.xml następującą zależność:
<dependency>
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
Dzięki temu będziemy mogli korzystać z JPA w naszym projekcie. Czym jest JPA? w skrócie jest to uniwersalny system do korzystania z baz danych. Pod spodem kryją się jego konkretne implementacje jak chociażby Hibernate czy OpenJPA.
*Spring korzysta z Hibernate
Oto jak wygląda sekcja <dependencies> w naszym pliku pom.xml:
<dependencies>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
Ok, dodaliśmy nasze JPA do projektu, teraz należy utworzyć nasze pierwsze repozytorium, które posłuży nam do zarządzania danymi. W tym celu tworzymy nowy interfejs LottoResultRepository w pakiecie com.example.repository:
package com.example.repository;
import org.springframework.data.repository.Repository;
public interface LottoResultRepository extends CrudRepository<LottoResultModel, Long> {
}
Utwórzmy zatem klasę o nazwie LottoResultModel, w pakiecie com.example.model:
O następującej treści:
package com.example.model; import java.util.Collection; import java.util.Date; import javax.persistence.CollectionTable; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class LottoResultModel { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private Date lotteryDrawingDate; @ElementCollection(fetch = FetchType.EAGER) private Collection<Integer> numbers; public long getId() { return id; } public void setId(long id) { this.id = id; } public Collection<Integer> getIntegers() { return numbers; } public void setIntegers(Collection<Integer> integers) { this.numbers = integers; } public Date getLotteryDrawingDate() { return lotteryDrawingDate; } public void setLotteryDrawingDate(Date lotteryDrawingDate) { this.lotteryDrawingDate = lotteryDrawingDate; } @Override public String toString() { return "LottoResultModel [id=" + id + ", lotteryDrawingDate=" + lotteryDrawingDate + ", integers=" + numbers + "]"; } }
Jak widzimy, korzystamy tutaj z 4 adnotacji:
@Entity
Służy do zaznaczenia klasy jako należącej do mapowania w naszej bazie. Jedynie klasy opatrzone w tę adnotację mogą być przechowywane w bazie danych.
@Id @GeneratedValue(strategy = GenerationType.AUTO)
Tutaj określamy która zmienna będzie ID naszego rekordu w bazie danych, i wskazujemy że jej wartość ma zostać wygenerowana automatycznie.
@ElementCollection(fetch = FetchType.EAGER)
Oznaczamy elementy występujące jako kolekcja, czyli obiekty mogące występować w liczbie mnogiej. Najprostsza analogia to tablica. Ustawienie FetchType.EAGER powoduje że pobierając z bazy danych obiekt LottoResultModel od razu pobierzemy przypisane do niego wylosowane numery. Jest to operacja kosztowne, z tego względu że liczby te przechowywane są w innej tabeli i muszą zostać połączone z aktualnym model. Zazwyczaj będziemy korzystać z LAZY zamiast EAGER, ale żeby uprościć proces tworzenia aplikacji skorzystamy z wiązania EAGER.
Oprócz tego nasz obiekt nie różni się od innego obiektu w Javie. Przechowywać będzie w nim informacje odnośnie daty i wylosowanych liczb.
Aby jednak móc działać na tych danych, musimy je skądś pobrać. Możemy to uczynić z strony http://www.mbnet.com.pl/wyniki.htm, gdzie przechowywane są dane archiwalne wszystkich losować lotto od jego początku. Cofamy się więc do początku naszej aplikacji czyli klasy MyFirstSpringBootAppApplication tam przekształcamy kod do takiej postaci:
package com.example; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @SpringBootApplication public class MyFirstSpringBootAppApplication{ public static void main(String[] args) { SpringApplication.run(MyFirstSpringBootAppApplication.class, args); } @Bean public CommandLineRunner prepareDatabase() { return (args) -> { //nasza logika }; } }
Dzięki temu, przy każdym uruchomieniu aplikacji, będzie wywoływana metoda prepareDatabase. Warto zaznaczyć że przy wywoływaniu tej metody działają już wszystkie funkcję Springa takie jak DI czy nasze Repozytoria/Serwisy/Componenty oraz możemy w prosty sposób odczytać argumenty przekazane na starcie aplikacji. Jeśli podany zapis:
return (args) -> { //nasza logika };
wydaje się nam dziwny, to na razie wystarczy nam wiedzieć że użyto tutaj lambdy dostępnej w Javie 8. I jest to skrócony zapis funkcji przyjmującej nasze argumenty o typie CommandLineRunner.
Logika przygotowania bazy danych:
Aby pobrać plik z sieci, wykorzystamy bibliotekę Apache Common IO, która udostępnia zestaw ciekawych operacji na plikach. Między innymi kopiowanie danych z URL do pliku.
A więc, dodajemy do naszego pom.xml następującą zależność:
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-io</artifactId> <version>1.3.2</version> </dependency>
Dodatkowo pobierzemy bibliotekę Joda-time do operacji na czasie:
<dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> </dependency>
Wygląd sekcji <dependencies>:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-io</artifactId> <version>1.3.2</version> </dependency>
<dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> </dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
A oto cała logika przygotowania bazy danych:
@Bean public CommandLineRunner prepareDatabase(LottoResultRepository lottoRepository) { return (args) -> { File urlFile = File.createTempFile("lotto", "url"); URL url = new URL("http://www.mbnet.com.pl/dl.txt"); FileUtils.copyURLToFile(url, urlFile); String fileText = FileUtils.readFileToString(urlFile); String[] linesText = fileText.split(System.lineSeparator()); DateTimeFormatter formatter = DateTimeFormat.forPattern("dd.MM.yyyy"); for(int x = 0; x < linesText.length; x++){ String[] dataText = linesText[x].split(" "); LottoResultModel lottoResultModel = new LottoResultModel(); //Parsowanie daty DateTime dt = formatter.parseDateTime(dataText[1]); lottoResultModel.setLotteryDrawingDate(dt.toDate()); //Parsowanie liczb Set<Integer> lotteryNumbers = new LinkedHashSet(); String[] numbers = dataText[2].split(","); for(int i = 0; i < numbers.length; i++){ lotteryNumbers.add(Integer.parseInt(numbers[i])); } lottoResultModel.setIntegers(lotteryNumbers); lottoRepository.save(lottoResultModel); } System.out.println("Ilość zapisanych pozycji: " + lottoRepository.count()); }; }
Pozostało jeszcze dodanie biblioteki z naszą bazą. W tym wypadku korzystać będziemy z bazy danych H2, która umożliwia tworzenie bazy bezpośrednio w pamięci operacyjnej lub na plikach w systemie gdzie została odpalona nasza aplikacja. Możemy także wykorzystać dowolną inną wspieraną przez Springa.
Dodajemy do pom.xml:
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency>
Nasz kod przygotowuje nam już naszą bazę danych i wypełnia ją liczbami z losowań dużego lotka od początku jego historii. W kolejnej części umożliwimy wyświetlenie tych danych za pomocą REST.