Rozliczenie pojazdu REST MyBatis

Rozliczenie pojazdu REST, MyBatis, MySQL. Zaczynamy pierwszy projekt z wykorzystaniem wcześniej zbudowanej bazy danych. Wykorzystamy do tego wzorzec projektowy REST API i stworzymy aplikację pozwalające na komunikację klient – server. Nie będzie to oczywiście w pełni działająca aplikacja, ponieważ zajmiemy się tylko back endem naszej aplikacji. Część front end powstanie w innym artykule. Takie podejście pozwoli nam na porównanie różnych sposobów tworzenia aplikacji, gdyż to będzie cykl artykułów tworzących tę samą funkcjonalność w zupełnie różny sposób.

Rozliczenie pojazdu REST MyBatis – Tworzymy projekt

Zacznijmy od stworzenia nowego projektu. Ja na co dzień pracuje z IntelliJ IDEA i mógłbym pokazać jak to zrobić przy wykorzystaniu tego narzędzia. Jednak mam świadomość, że część osób może wykorzystywać inne IDE, dlatego wykonam to w taki sposób, aby każdy mógł z tego skorzystać.

Na początku wejdę na stronę https://start.spring.io/, gdzie utworzę nasz projekt. W moim przypadku będzie on wyglądał następująco.

Rozliczenie pojazdu generowanie projektu REST MyBatis

Projekt możesz pobrać z mojego repozytorium, które jest pod adresem.

Rozliczenie pojazdu REST MyBatis – Budowa projektu

Konfiguracja projektu

Więc w pierwszej kolejności będzie dodanie odpowiednich bibliotek, dzięki którym w naszym projekcie będzie można korzystać z REST, MyBatis oraz MySQL. Przechodzimy do pliku pom.xml i dodajemy do sekcji dependences następujące elementy.

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.2</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>

Pierwsza dependencja informuje Spring, że ta aplikacja ma być aplikacją webową, czy po jej uruchomieniu ma ciągle nasłuchiwać w oczekiwaniu na żądania. Drugą i trzecią uruchamia komunikację z bazą danych. Czwarta dodaje bibliotekę MyBatis, a ostatnia jest to system do testowania czy nasza aplikacja działa.

Jednak w obecnie chwili nasza aplikacja jeszcze nie zadziała. Należy najpierw skonfigurować bazę danych. W tym celu edytujemy plik application.property i dodajemy odpowiednie linijki.

spring.datasource.url=jdbc:mysql://localhost:3306/transportMyBatis?serverTimezone=UTC
spring.datasource.username=transportMyBatis
spring.datasource.password=transportMyBatis

logging.file=log/transportMyBatis.log
server.port=8090

Teraz po uruchomieniu serwer zacznie działać bez problemu, ale nic tak naprawdę nie będzie się działo. Nie mamy jeszcze gotowej żadnej usługi, czym się teraz zajmiemy.

Controller

Pierwszym elementem, jaki się zajmiemy to kontroler. Przy okazji jego utworzenie przetestujemy czy nasz web serwis na pewno działa. Więc po utworzeniu pakietu controller utworzymy plik Javy TransportController.

@RestController
public class TransportController {

    @GetMapping("/")
    public String getTest() {
        return "Działa";
    }
}

Gdy uruchomimy nasz serwer i wpiszemy w przeglądarkę http://localhost:8090/, naszym oczom ukarze się napis Działa. Jeżeli masz taki sam efekt, czyli do tej pory wszystko jest ok i możemy przejść dalej. W innym przypadku musisz jeszcze raz wszystko sprawdzić.

Teraz zaczynamy wykorzystywać MyBatis.

W pierwszej kolejności należy poprawić plik pom, aby można było użyć MyBatis. Dlatego teraz ten plik będzie wyglądał następująco:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.7.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>pl.lomza.programowanie</groupId>
	<artifactId>rozliczenie_projektu_rest_mybatis</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>rozliczenie_projektu_rest_mybatis</name>
	<description>Rozliczenie projektu - web serwis REST MyBatis</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.ibatis</groupId>
			<artifactId>ibatis-core</artifactId>
			<version>3.0</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

Kolejnym elementem jest utworzenie interfejs TransportDAO, w którym będziemy przechowywać wszystkie metody. Jednak teraz stworzymy tylko dwie do pobierania przejazdów. Pozostałe powstaną w kolejnym artykule.

package pl.lomza.programowanie.rozliczenie_projektu_rest_mybatis.mappers;
...
@Component
public interface TransportDAO {
    List<Ride> getRides(@Param("vehicleId") long vehicleId, @Param("personId") long personId);
    Ride getRide(@Param("vehicleId") long vehicleId, @Param("rideId") long rideId);
}

W przypadku MyBatis jest również możliwość użycia adnotacji na tym etapie i zapisu zapytań w tych adnotacjach. Jednak ja uważam, że jest to nie wygodne, dlatego użyje do zapisu zapytań pliku XML.

Zwróć uwagę, gdzie umieszczamy nasz pakiet. Gdy stworzymy nasz XML, musimy go umieścić dokładnie na tej samej ścieżce.

Jeżeli korzystasz z IDE, to pakiety XML są umieszczane w taki sposób: pl.lomza.programowanie.rozliczenie_pojazdu_rest_mybatis.mappers. Jednak tak to jest wyświetlane. W rzeczywistości powinno to wyglądać tak, sprawdź to poza IDE: pl/lomza/programowanie/rozliczenie_pojazdu_rest_mybatis/mappers. Więc, gdy po uruchomieniu system mówi, że nie widzi TransportDAO. W pierwszej kolejności sprawdź, czy ścieżka pliku XML jest prawidłowa. Może to oszczędzić wiele czasu i nerwów. Więc czas na utworzenie XML.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="pl.lomza.programowanie.rozliczenie_projektu_rest_mybatis.mappers.TransportDAO">
    <select id="getRides" resultType="pl.lomza.programowanie.rozliczenie_projektu_rest_mybatis.common.Ride">
        <![CDATA[
        SELECT r.id, r.rideDate, r.counterBefore, r.counterAfter, r.km,
        r.whence, r.`where`, r.rideKind, r.fuelCondition, r.fuelAdd, r.fuelAfter,
        SUM(s.fuel) as fuel, f.price, SUM(s.fuel*f.price) as currency, r.fuelNorm
        FROM transportMyBatis.Ride r
        LEFT JOIN transportMyBatis.Settlement  s on r.id = s.idRide
        LEFT JOIN transportMyBatis.Refuel f on f.id = s.idRefuel
        WHERE r.idVehicle = #{vehicleId}
        GROUP BY r.id, r.whence, r.`where`, r.rideKind, f.price
        ORDER BY r.id
        ]]>
    </select>
    <select id="getRide" resultType="pl.lomza.programowanie.rozliczenie_projektu_rest_mybatis.common.Ride">
        <![CDATA[
        SELECT r.id, r.rideDate, r.counterBefore, r.counterAfter, r.km,
        r.whence, r.`where`, r.rideKind, r.fuelCondition, r.fuelAdd, r.fuelAfter,
        SUM(s.fuel) as fuel, f.price, SUM(s.fuel*f.price) as currency, r.fuelNorm
        FROM transportMyBatis.Ride r
        LEFT JOIN transportMyBatis.Settlement  s on r.id = s.idRide
        LEFT JOIN transportMyBatis.Refuel f on f.id = s.idRefuel
        WHERE r.idVehicle = #{vehicleId} and r.id = #{rideId}
        GROUP BY r.id, r.whence, r.`where`, r.rideKind, f.price
        ORDER BY r.id
        ]]>
    </select>
</mapper>

To wystarczy do komunikacji z bazą danych. Teraz musimy podłączyć to do naszego kontrolera. Robimy to w następujący sposób:

@RestController
public class TransportController {
    private final static Logger log = LoggerFactory.getLogger(TransportController.class);

    @Autowired
    private TransportDAO transportDAO;

    @GetMapping("/")
    public String getTest() {
        return "Działa";
    }

    @GetMapping("vehicle/{vehicleId}/rides")
    public List<Ride> getRides(@PathVariable("vehicleId") long vehicleId) {
        log.info("Wyświetlamy listę przejazdów");
        return transportDAO.getRides(vehicleId);
    }
    @GetMapping("vehicle/{vehicleId}/ride/{rideId}")
    public Ride getRide(@PathVariable("vehicleId") long vehicleId, @PathVariable("rideId") long rideId) {
        log.info("Wyświetlamy przejazd");
        return transportDAO.getRide(vehicleId, rideId);
    }
}

Jak pewnie zauważyłeś za każdym razem zwracamy klasę Ride. W jednej metodzie jako listę obiektów a w drugim jako obiekt. Klasa ta wygląda następująco:

public class Ride {
    private long id;
    private LocalDate rideDate;
    private int counterBefore;
    private int counterAfter;
    private int km;
    private String whence;
    private String where;
    private String rideKind;
    private double fuelCondition;
    private double fuelAdd;
    private double fuelAfter;
    private double fuel;
    private double price;
    private double currency;
    private double fuelNorm;

    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public LocalDate getRideDate() {
        return rideDate;
    }
    public void setRideDate(LocalDate rideDate) {
        this.rideDate = rideDate;
    }
    public int getCounterBefore() {
        return counterBefore;
    }
    public void setCounterBefore(int counterBefore) {
        this.counterBefore = counterBefore;
    }
    public int getCounterAfter() {
        return counterAfter;
    }
    public void setCounterAfter(int counterAfter) {
        this.counterAfter = counterAfter;
    }
    public int getKm() {
        return km;
    }
    public void setKm(int km) {
        this.km = km;
    }
    public String getWhence() {
        return whence;
    }
    public void setWhence(String whence) {
        this.whence = whence;
    }
    public String getWhere() {
        return where;
    }
    public void setWhere(String where) {
        this.where = where;
    }
    public String getRideKind() {
        return rideKind;
    }
    public void setRideKind(String rideKind) {
        this.rideKind = rideKind;
    }
    public double getFuelCondition() {
        return fuelCondition;
    }
    public void setFuelCondition(double fuelCondition) {
        this.fuelCondition = fuelCondition;
    }
    public double getFuelAdd() {
        return fuelAdd;
    }
    public void setFuelAdd(double fuelAdd) {
        this.fuelAdd = fuelAdd;
    }
    public double getFuelAfter() {
        return fuelAfter;
    }
    public void setFuelAfter(double fuelAfter) {
        this.fuelAfter = fuelAfter;
    }
    public double getFuel() {
        return fuel;
    }
    public void setFuel(double fuel) {
        this.fuel = fuel;
    }
    public double getPrice() {
        return price;
    }
    public void setPrice(double price) {
        this.price = price;
    }
    public double getCurrency() {
        return currency;
    }
    public void setCurrency(double currency) {
        this.currency = currency;
    }
    public double getFuelNorm() {
        return fuelNorm;
    }
    public void setFuelNorm(double fuelNorm) {
        this.fuelNorm = fuelNorm;
    }
}

Jeszcze jeden element od pokazania mianowićie główny plik projektu trochę się zmienił więc go prezentujemy.

@SpringBootApplication
@EnableCaching
@EnableTransactionManagement
@MapperScan(value = "pl.lomza.programowanie.rozliczenie_projektu_rest_mybatis.mappers")
public class RozliczenieProjektuRestMybatisApplication {

	public static void main(String[] args) {
		SpringApplication.run(RozliczenieProjektuRestMybatisApplication.class, args);
	}
}

Taka konfiguracja jest nie zbędna, aby wszystko działało prawidłowo. Pozostało nam wszystko przetestować i powinno działać. Jeżeli jednak nie działa, warto podejrzeć, jak ja to zrobiłem. Zapraszam na mojego GitHub pod adresem: https://github.com/grzegorzkossa/rozliczenie_projektu_rest_mybatis.

To jednak nie koniec. Zrobiliśmy tylko odczyt, a nie zajęliśmy się w ogóle zapisu. Pozostało nam jeszcze zabezpieczyć nasze rozwiązanie, ale to już w kolejnym artykule.