Rozliczenie pojazdu REST JDBC

Rozliczenie pojazdu REST, JDBC, MySQL. Zaczynamy drugi projekt z wykorzystaniem wcześniej zbudowanej bazy danych. Jest to prostsza wersja komunikacji z bazą danych wbudowana w Spring Boot. Zaletą jest prostota budowy zapytań, jednak wadą jest fakt, że nie da się jej zastosować do bardziej wymagających zapytań. Jednak w naszym prostym projekcie zupełnie wystarczy. Tak jak w przypadku pierwszego projektu wykorzystamy wzorzec projektowy REST API i stworzymy aplikację pozwalające na komunikację klient – server. Tak samo, jak w artykule Rozliczenie pojazdu REST MyBatis nie będzie to w pełni działająca aplikacja. Pozwoli to porównać, jakie należy wprowadzić zmiany w zależności od zastosowanego sposobu komunikacji z bazą danych. Część front end powstanie w innym artykule.

Rozliczenie pojazdu REST JDBC – Tworzymy projekt

Zacznijmy od stworzenia nowego projektu. 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 REST JDBC – Budowa projektu

Konfiguracja projektu

W pierwszej kolejności będziemy dodawać odpowiednie biblioteki, dzięki którym w naszym projekcie będzie można korzystać z REST, JDBC 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</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.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

Pierwsze dwie sekcje dependencji informuje Spring, że ta aplikacja ma być aplikacją webową, czyli po jej uruchomieniu ma ciągle nasłuchiwać w oczekiwaniu na żądania. Trzecią to sterownik bazy danych MySQL. Czwarta dodaje bibliotekę JDBC. Jak porównamy wcześniejszy artykuł, zmiana jest tylko w ostatnim zapisie.

Kolejnym krokiem jest skonfigurowanie bazy 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=8091

Jak widać, korzystamy z bazy danych stworzonych przy okazji wcześniejszego artykułu, jednak w tym przypadku nie ma to znaczenia.

Teraz zaczynamy wykorzystywać JDBC.

W przypadku JDBC tworzymy tylko klasę TransportDB w schemacie db. W klasie będziemy przechowywać wszystkie metody wraz z zapytaniami.

package pl.lomza.programowanie.rozliczenie_pojazdu_rest_jdbc.db;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import pl.lomza.programowanie.rozliczenie_pojazdu_rest_jdbc.common.Ride;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TransportDB {
    private final static Logger log= LoggerFactory.getLogger(TransportDB.class);
    private final NamedParameterJdbcTemplate template;
    private final BeanPropertyRowMapper<Ride> rideMapper = BeanPropertyRowMapper.newInstance(Ride.class);

    public TransportDB(NamedParameterJdbcTemplate template) {
        this.template = template;
    }


    public List<Ride> getRides(long vehicleId) {
        log.info("List rides");
        return template.query(
                "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 ",
                new MapSqlParameterSource("vehicleId", vehicleId), rideMapper);
    }

    public Ride getRide(long vehicleId,  long rideId) {
        log.info("Object ride");
        Map<String, String> map = new HashMap<>();
        map.put("vehicleId", Long.toString(vehicleId));
        map.put("rideId", Long.toString(rideId));
        return template.queryForObject(
                "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",
                map, rideMapper);
    }
}

Uwaga: W razie problemów z bazą danych należy sprawdzić w każdym zapytaniu, czy na końcu linii jest pusty znak. Jeżeli go zabraknie, zapytanie się nie wykona.

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

package pl.lomza.programowanie.rozliczenie_pojazdu_rest_jdbc.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import pl.lomza.programowanie.rozliczenie_pojazdu_rest_jdbc.common.Ride;
import pl.lomza.programowanie.rozliczenie_pojazdu_rest_jdbc.db.TransportDB;

import java.util.List;

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

    public TransportController(TransportDB transportDB) {
        this.transportDB = transportDB;
    }

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

Jednak jest tu w porównaniu do artykułu z Rozliczenie pojazdu REST MyBatis mała zmiana. Wcześniej przy czytywaniu interfejsu TransportDAO używaliśmy @Autowired. Tym razem jednak robimy to przez konstruktor. Obie metody są dobre, ale ogólnie preferowana jest metoda przez konstruktor. Jest to związane przede wszystkim z testowaniem aplikacji. Dlatego teraz właśnie takie podejście zaprezentowałem.

Pokażemy jeszcze, jak wygląda klasa Ride:

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 mianowicie główny plik projektu trochę się zmienił, więc go prezentujemy.

package pl.lomza.programowanie.rozliczenie_pojazdu_rest_jdbc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import pl.lomza.programowanie.rozliczenie_pojazdu_rest_jdbc.db.TransportDB;
import javax.sql.DataSource;

@SpringBootApplication
public class RozliczeniePojazduRestJdbcApplication {
	public static void main(String[] args) {
		SpringApplication.run(RozliczeniePojazduRestJdbcApplication.class, args);
	}
	@Bean
	public TransportDB transportDB(NamedParameterJdbcTemplate template){
		return new TransportDB(template);
	}
	@Bean
	public PlatformTransactionManager transactionManager(DataSource dataSource)
	{
		return new DataSourceTransactionManager(dataSource);
	}
}

Tak wygląda cała aplikacja z wykorzystaniem JDBC. 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_pojazdu_rest_jdbc

To jednak nie koniec. W następnym artykule pokaże jak te samo zadanie wykonać przy wykorzystaniu JPA. Zapraszam do kolejnego artykułu.