I dag skal vi lave det afsluttende projekt på det fjerde JRU-modul. Hvad bliver det? Lad os prøve at arbejde med forskellige teknologier: MySQL, Hibernate, Redis, Docker. Nu mere emne.

Opgave: vi har en relationel MySQL-database med et skema (land-by, sprog efter land). Og der er en hyppig anmodning fra byen, som bremser. Vi fandt på en løsning - at flytte alle de data, der efterspørges hyppigt til Redis (i hukommelseslager af typen nøgleværdi).

Og vi behøver ikke alle de data, der er gemt i MySQL, men kun et udvalgt sæt felter. Projektet vil være i form af en vejledning. Det vil sige, her vil vi rejse problemet og straks løse det.

Så lad os starte med hvilken software vi skal bruge:

  1. IDEA Ultimate (der løb tør for nøglen - skriv til Roman in the Slack)
  2. Workbench (eller enhver anden klient til MySQL)
  3. Docker
  4. redis-insight - valgfrit

Vores handlingsplan:

  1. Konfigurer docker (jeg vil ikke gøre dette i selvstudiet, fordi hvert OS vil have sine egne karakteristika, og der er mange svar på internettet på spørgsmål som "hvordan man installerer docker på Windows"), tjek at alt fungerer.
  2. Kør MySQL-serveren som en docker-container.
  3. Udvid dump .
  4. Opret et projekt i Idea, tilføj maven afhængigheder.
  5. Lav et lagdomæne.
  6. Skriv en metode til at hente alle data fra MySQL.
  7. Skriv en datatransformationsmetode (i Redis vil vi kun skrive de data, der efterspørges hyppigt).
  8. Kør Redis-serveren som en docker-container.
  9. Skriv data til Redis.
  10. Valgfrit: installer redis-insight, se på de data, der er gemt i Redis.
  11. Skriv en metode til at hente data fra Redis.
  12. Skriv en metode til at hente data fra MySQL.
  13. Sammenlign hastigheden for at få de samme data fra MySQL og Redis.

Docker opsætning

Docker er en åben platform til udvikling, levering og drift af applikationer. Vi vil bruge det til ikke at installere og konfigurere Redis på den lokale maskine, men for at bruge et færdiglavet billede. Du kan læse mere om docker her eller se det her . Hvis du ikke er bekendt med docker, anbefaler jeg, at du kun kigger på det andet link.

For at sikre, at du har docker installeret og konfigureret, skal du køre kommandoen:docker -v

Hvis alt er OK, vil du se docker-versionen

Kør MySQL-serveren som docker-container

For at kunne sammenligne tidspunktet for returnering af data fra MySQL og Redis, vil vi også bruge MySQL i dockeren. I PowerShell (eller en anden konsolterminal, hvis du ikke bruger Windows), skal du køre kommandoen:

docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root --restart unless-stopped -v mysql:/var/lib/mysql mysql:8 

Overvej, hvad vi gør med denne kommando:

  • docker run– start (og download, hvis det endnu ikke er blevet downloadet til den lokale maskine) billedet. Som følge af lanceringen får vi en kørende container.
  • --name mysql- Indstil navnet på mysql-beholderen.
  • -d- et flag, der siger, at containeren skal fortsætte med at fungere, selvom du lukker terminalvinduet, hvorfra denne container blev startet.
  • -p 3306:3306- angiver porte. Før kolon - porten på den lokale maskine, efter kolon - porten i containeren.
  • -e MYSQL_ROOT_PASSWORD=root– videregivelse af miljøvariablen MYSQL_ROOT_PASSWORD med værdien root til containeren. Flag specifikt til mysql/-billedet
  • --restart unless-stopped- indstilling af adfærdspolitikken (om beholderen skal genstartes, når den er lukket). Medmindre-stoppet-værdien betyder altid at genstarte, undtagen når beholderen blev stoppet /
  • -v mysql:/var/lib/mysql – tilføje volumen (billede til lagring af information).
  • mysql:8 – navnet på billedet og dets version.

Efter at have udført kommandoen i terminalen, vil dockeren downloade alle lagene i billedet og starte containeren:

Vigtig bemærkning: Hvis du har MySQL installeret som en tjeneste på din lokale computer, og den kører, skal du angive en anden port i startkommandoen eller stoppe denne kørende tjeneste.

Udvid dump

For at udvide dumpet skal du oprette en ny forbindelse til databasen fra Workbench, hvor du angiver parametrene. Jeg brugte standardporten (3306), ændrede ikke brugernavnet (root som standard) og indstillede adgangskoden til root-brugeren (root).

I Workbench skal du gøre Data Import/Restoreog vælge Import from Self Contained File. Angiv, hvor du downloadede dumpet som en fil . Du behøver ikke oprette skemaet på forhånd - dets oprettelse er inkluderet i dumpfilen. Efter en vellykket import vil du have et verdensskema med tre tabeller:

  1. by er en tabel over byer.
  2. land - land tabel.
  3. country_language - en tabel, der angiver, hvor stor en procentdel af befolkningen i landet, der taler et bestemt sprog.

Da vi brugte en volumen, da vi startede containeren, efter at have stoppet og endda slettet mysql-containeren og genudført startkommandoen ( docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root --restart unless-stopped -v mysql:/var/lib/mysql mysql:8), vil der ikke være behov for at implementere dumpet igen - det er allerede implementeret i volumen.

Opret projekt i Idé, tilføj maven-afhængigheder

Du ved allerede, hvordan du opretter et projekt i Idéen - det er det nemmeste punkt i dagens projekt.

Tilføj afhængigheder til pom-filen:


<dependencies> 
   <dependency> 
      <groupId>mysql</groupId> 
      <artifactId>mysql-connector-java</artifactId> 
      <version>8.0.30</version> 
   </dependency> 
 
   <dependency> 
      <groupId>org.hibernate</groupId> 
      <artifactId>hibernate-core-jakarta</artifactId> 
      <version>5.6.14.Final</version> 
   </dependency> 
 
   <dependency> 
      <groupId>p6spy</groupId> 
      <artifactId>p6spy</artifactId> 
      <version>3.9.1</version> 
   </dependency> 
    
   <dependency> 
      <groupId>io.lettuce</groupId> 
      <artifactId>lettuce-core</artifactId> 
      <version>6.2.2.RELEASE</version> 
   </dependency> 
 
   <dependency> 
      <groupId>com.fasterxml.jackson.core</groupId> 
      <artifactId>jackson-databind</artifactId> 
      <version>2.14.0</version> 
   </dependency> 
</dependencies> 

De første tre afhængigheder har længe været bekendt for dig.

lettuce-coreer en af ​​de tilgængelige Java-klienter til at arbejde med Redis.

jackson-databind– afhængighed for at bruge ObjectMapper (til at transformere data til lagring i Redis (nøgleværdi af typen String)).

Tilføj også spy.properties i ressourcemappen (src/main/resources) for at se anmodningerne med parametre, som Hibernate udfører. Filens indhold:

driverlist=com.mysql.cj.jdbc.Driver 
dateformat=yyyy-MM-dd hh:mm:ss a 
appender=com.p6spy.engine.spy.appender.StdoutLogger 
logMessageFormat=com.p6spy.engine.spy.appender.MultiLineFormat 

Lav et lagdomæne

Opret pakke com.codegym.domain

Det er praktisk for mig, når jeg kortlægger tabeller på en enhed, at bruge tabelstrukturen i Idéen, så lad os tilføje en databaseforbindelse i Idéen.

Jeg foreslår at oprette enheder i denne rækkefølge:

  • Land
  • By
  • Landesprog

Det er ønskeligt, at du selv udfører kortlægningen.

Landeklassekode:

package com.codegym.domain;

import jakarta.persistence.*;

import java.math.BigDecimal;
import java.util.Set;

@Entity
@Table(schema = "world", name = "country")
public class Country {
    @Id
    @Column(name = "id")
    private Integer id;

    private String code;

    @Column(name = "code_2")
    private String alternativeCode;

    private String name;

    @Column(name = "continent")
    @Enumerated(EnumType.ORDINAL)
    private Continent continent;

    private String region;

    @Column(name = "surface_area")
    private BigDecimal surfaceArea;

    @Column(name = "indep_year")
    private Short independenceYear;

    private Integer population;

    @Column(name = "life_expectancy")
    private BigDecimal lifeExpectancy;

    @Column(name = "gnp")
    private BigDecimal GNP;

    @Column(name = "gnpo_id")
    private BigDecimal GNPOId;

    @Column(name = "local_name")
    private String localName;

    @Column(name = "government_form")
    private String governmentForm;

    @Column(name = "head_of_state")
    private String headOfState;

    @OneToOne
    @JoinColumn(name = "capital")
    private City city;

    @OneToMany(fetch = FetchType.EAGER)
    @JoinColumn(name = "country_id")
    private Set<CountryLanguage> languages;


    //Getters and Setters omitted

}

Der er 3 interessante punkter i koden.

Den første er Continent enam , som er gemt i databasen som ordensværdier. I landetabellens struktur kan du i kommentarerne til kontinentfeltet se, hvilken talværdi der svarer til hvilket kontinent.

package com.codegym.domain;

public enum Continent {
    ASIA,
    EUROPE,
    NORTH_AMERICA,
    AFRICA,
    OCEANIA,
    ANTARCTICA,
    SOUTH_AMERICA
}

Det andet punkt er et sæt af enhederCountryLanguage . Her er et link @OneToMany, der ikke var i det andet udkast til dette modul. Som standard trækker Hibernate ikke værdien af ​​dette sæt, når der anmodes om en landeenhed. Men da vi skal trække alle værdier fra relationsdatabasen til caching, er FetchType.EAGER.

Den tredje er bymarken . Kommunikation @OneToOne- som om alt er velkendt og forståeligt. Men hvis vi ser på den udenlandske nøglestruktur i databasen, ser vi, at landet (landet) har et link til hovedstaden (byen), og byen (byen) har et link til landet (landet). Der er en cyklisk sammenhæng.

Vi vil ikke gøre noget med dette endnu, men når vi kommer til "Skriv en metode til at hente alle data fra MySQL", lad os se, hvilke forespørgsler Hibernate udfører, se på deres nummer og huske dette element. Dvaletilstand

Byklassekode:

package com.codegym.domain;

import jakarta.persistence.*;

@Entity
@Table(schema = "world", name = "city")
public class City {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "country_id")
    private Country country;

    private String district;

    private Integer population;


    //Getters and Setters omitted

}

CountryLanguage klassekode:

package com.codegym.domain;

import jakarta.persistence.*;
import org.hibernate.annotations.Type;

import java.math.BigDecimal;

@Entity
@Table(schema = "world", name = "country_language")
public class CountryLanguage {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

    @ManyToOne
    @JoinColumn(name = "country_id")
    private Country country;

    private String language;

    @Column(name = "is_official", columnDefinition = "BIT")
    @Type(type = "org.hibernate.type.NumericBooleanType")
    private Boolean isOfficial;

    private BigDecimal percentage;


    //Getters and Setters omitted
}

Skriv en metode til at hente alle data fra MySQL

I hovedklassen erklærer vi felterne:

private final SessionFactory sessionFactory;
private final RedisClient redisClient;

private final ObjectMapper mapper;

private final CityDAO cityDAO;
private final CountryDAO countryDAO;

og initialiser dem i konstruktøren af ​​Main-klassen:

public Main() {
    sessionFactory = prepareRelationalDb();
    cityDAO = new CityDAO(sessionFactory);
    countryDAO = new CountryDAO(sessionFactory);

    redisClient = prepareRedisClient();
    mapper = new ObjectMapper();
}

Som du kan se, er der ikke nok metoder og klasser - lad os skrive dem.

Erklær en pakke com.codegym.dao og tilføj 2 klasser til den:

package com.codegym.dao;

import com.codegym.domain.Country;
import org.hibernate.SessionFactory;
import org.hibernate.query.Query;

import java.util.List;

public class CountryDAO {
    private final SessionFactory sessionFactory;

    public CountryDAO(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public List<Country> getAll() {
        Query<Country> query = sessionFactory.getCurrentSession().createQuery("select c from Country c", Country.class);
        return query.list();
    }
}
package com.codegym.dao;

import com.codegym.domain.City;
import org.hibernate.SessionFactory;
import org.hibernate.query.Query;

import java.util.List;

public class CityDAO {
    private final SessionFactory sessionFactory;

    public CityDAO(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public List<City> getItems(int offset, int limit) {
        Query<City> query = sessionFactory.getCurrentSession().createQuery("select c from City c", City.class);
        query.setFirstResult(offset);
        query.setMaxResults(limit);
        return query.list();
    }

    public int getTotalCount() {
        Query<Long> query = sessionFactory.getCurrentSession().createQuery("select count(c) from City c", Long.class);
        return Math.toIntExact(query.uniqueResult());
    }
}

Nu kan du importere disse 2 klasser til Main. Der mangler stadig to metoder:

private SessionFactory prepareRelationalDb() {
    final SessionFactory sessionFactory;
    Properties properties = new Properties();
    properties.put(Environment.DIALECT, "org.hibernate.dialect.MySQL8Dialect");
    properties.put(Environment.DRIVER, "com.p6spy.engine.spy.P6SpyDriver");
    properties.put(Environment.URL, "jdbc:p6spy:mysql://localhost:3306/world");
    properties.put(Environment.USER, "root");
    properties.put(Environment.PASS, "root");
    properties.put(Environment.CURRENT_SESSION_CONTEXT_CLASS, "thread");
    properties.put(Environment.HBM2DDL_AUTO, "validate");
    properties.put(Environment.STATEMENT_BATCH_SIZE, "100");

    sessionFactory = new Configuration()
            .addAnnotatedClass(City.class)
            .addAnnotatedClass(Country.class)
            .addAnnotatedClass(CountryLanguage.class)
            .addProperties(properties)
            .buildSessionFactory();
    return sessionFactory;
}

Vi har endnu ikke nået radisen, så implementeringen af ​​initialiseringen af ​​radiseklienten vil forblive en stump indtil videre:

private void shutdown() {
    if (nonNull(sessionFactory)) {
        sessionFactory.close();
    }
    if (nonNull(redisClient)) {
        redisClient.shutdown();
    }
}

Til sidst kan vi skrive en metode, hvor vi trækker alle byerne ud:

private List<City> fetchData(Main main) {
    try (Session session = main.sessionFactory.getCurrentSession()) {
        List<City> allCities = new ArrayList<>();
        session.beginTransaction();

        int totalCount = main.cityDAO.getTotalCount();
        int step = 500;
        for (int i = 0; i < totalCount; i += step) {
            allCities.addAll(main.cityDAO.getItems(i, step));
        }
        session.getTransaction().commit();
        return allCities;
    }
}

Implementeringsfunktionen er sådan, at vi får 500 byer hver. Dette er nødvendigt, fordi der er begrænsninger på mængden af ​​transmitterede data. Ja, i vores tilfælde kommer vi ikke til dem, fordi. vi har i alt 4079 byer i databasen. Men i produktionsapplikationer, når du har brug for at få en masse data, bruges denne teknik ofte.

Og implementeringen af ​​hovedmetoden:

public static void main(String[] args) {
    Main main = new Main();
    List<City> allCities = main.fetchData(main);
    main.shutdown();
}

Nu kan vi køre vores applikation i debug for første gang og se, hvordan det virker (eller ikke virker - ja, det sker ofte).

Byer får. Hver by får et land, hvis det ikke tidligere er blevet trukket fra databasen for en anden by. Lad os groft beregne, hvor mange forespørgsler Hibernate sender til databasen:

  • 1 anmodning om at finde ud af det samlede antal byer (nødvendig for at gentage over 500 byer for at vide, hvornår man skal stoppe).
  • 4079 / 500 = 9 anmodninger (liste over byer).
  • Hver by får et land, hvis det ikke er blevet trukket fra tidligere. Da der er 239 lande i databasen, vil dette give os 239 forespørgsler.

Total 249 requests. Og vi sagde også, at vi sammen med landet straks ville modtage et sæt sprog, ellers ville der være mørke generelt. Men det er stadig meget, så lad os justere adfærden lidt. Lad os starte med refleksioner: hvad skal man gøre, hvor skal man løbe? Men seriøst - hvorfor er der så mange forespørgsler. Hvis du ser på anmodningsloggen, ser vi, at hvert land anmodes separat, så den første enkle løsning: lad os anmode om alle landene sammen, fordi vi på forhånd ved, at vi skal bruge dem alle i denne transaktion.

I metoden fetchData() skal du umiddelbart efter starten af ​​transaktionen tilføje følgende linje:

List<Country> countries = main.countryDAO.getAll(); 

Vi tæller anmodninger:

  • 1 - få alle lande
  • 239 - forespørgsel for hvert land i dets hovedstad
  • 1 - anmodning om antal byer
  • 9 - anmodning om lister over byer

Total 250. Ideen er god, men den virkede ikke. Problemet er, at landet har en forbindelse med hovedstaden (byen) @OneToOne. Og sådan et link indlæses som standard med det samme ( FetchType.EAGER). Lad os sige FetchType.LAZY, fordi alligevel indlæser vi alle byerne senere i den samme transaktion.

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "capital")
private City city;

Der anmodes ikke længere om store bogstaver separat, men antallet af anmodninger er ikke ændret. Nu, for hvert land, anmodes CountryLanguage- listen om af en separat forespørgsel . Det vil sige, at der er fremskridt, og vi bevæger os i den rigtige retning. Hvis du husker det, foreslog forelæsningerne "join fetch" -løsningen for at anmode om en enhed med afhængige data i én anmodning ved at tilføje en ekstra join til anmodningen. I CountryDAO skal du omskrive HQL-forespørgslen i metoden getAll()til:

"select c from Country c join fetch c.languages" 

Lancering. Vi ser på loggen, tæller anmodninger:

  • 1 - alle lande med sprog
  • 1 - antal byer
  • 9 - lister over byer.

Total 11- det lykkedes)) Hvis du ikke kun læste al denne tekst, men også prøvede at køre den efter hvert trin i tuning af applikationen, bør du endda visuelt bemærke accelerationen af ​​hele applikationen flere gange.

Skriv en datatransformationsmetode

Lad os oprette en pakke, com.codegym.redishvori vi tilføjer 2 klasser: CityCountry (data om byen og det land, hvor denne by ligger) og Language (data om sproget). Her er alle de felter, der ofte efterspørges "efter opgave" i "bremseanmodning".

package com.codegym.redis;

import com.codegym.domain.Continent;

import java.math.BigDecimal;
import java.util.Set;

public class CityCountry {
    private Integer id;

    private String name;

    private String district;

    private Integer population;

    private String countryCode;

    private String alternativeCountryCode;

    private String countryName;

    private Continent continent;

    private String countryRegion;

    private BigDecimal countrySurfaceArea;

    private Integer countryPopulation;

    private Set<Language> languages;

    //Getters and Setters omitted
}
package com.codegym.redis;

import java.math.BigDecimal;

public class Language {
    private String language;
    private Boolean isOfficial;
    private BigDecimal percentage;

    //Getters and Setters omitted
}

I hovedmetoden, efter at have fået alle byerne, skal du tilføje linjen

List<CityCountry>> preparedData = main.transformData(allCities); 

Og implementer denne metode:

private List<CityCountry> transformData(List<City> cities) {
    return cities.stream().map(city -> {
        CityCountry res = new CityCountry();
        res.setId(city.getId());
        res.setName(city.getName());
        res.setPopulation(city.getPopulation());
        res.setDistrict(city.getDistrict());

        Country country = city.getCountry();
        res.setAlternativeCountryCode(country.getAlternativeCode());
        res.setContinent(country.getContinent());
        res.setCountryCode(country.getCode());
        res.setCountryName(country.getName());
        res.setCountryPopulation(country.getPopulation());
        res.setCountryRegion(country.getRegion());
        res.setCountrySurfaceArea(country.getSurfaceArea());
        Set<CountryLanguage> countryLanguages = country.getLanguages();
        Set<Language> languages = countryLanguages.stream().map(cl -> {
            Language language = new Language();
            language.setLanguage(cl.getLanguage());
            language.setOfficial(cl.getOfficial());
            language.setPercentage(cl.getPercentage());
            return language;
        }).collect(Collectors.toSet());
        res.setLanguages(languages);

        return res;
    }).collect(Collectors.toList());
}

Jeg tror, ​​denne metode er selvforklarende: vi opretter bare en CityCountry- enhed og udfylder den med data fra City , Country , CountryLanguage .

Kør Redis-serveren som en docker-container

Der er 2 muligheder her. Hvis du udfører det valgfrie trin "installer redis-insight, se på de data, der er gemt i Redis", så er kommandoen noget for dig:

docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest 

Hvis du beslutter dig for at springe dette trin over, så bare:

docker run -d --name redis -p 6379:6379 redis:latest 

Forskellen er, at i den første mulighed videresendes port 8001 til den lokale maskine, hvortil du kan oprette forbindelse med en ekstern klient for at se, hvad der er gemt inde. Og jeg plejede derfor at give meningsfulde navne redis-stackeller redis.

Efter lanceringen kan du se listen over kørende containere. For at gøre dette skal du køre kommandoen:

docker container ls 

Og du vil se noget som dette:

Hvis du skal finde en kommando, kan du enten se på hjælpen i terminalen (docker-hjælp) eller google "how to ..." (f.eks. docker how to remove running container).

Og vi kaldte også initialiseringen af ​​radiseklienten i hovedkonstruktøren, men implementerede ikke selve metoden. Tilføj implementering:

private RedisClient prepareRedisClient() {
    RedisClient redisClient = RedisClient.create(RedisURI.create("localhost", 6379));
    try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
        System.out.println("\nConnected to Redis\n");
    }
    return redisClient;
}

sout blev tilføjet til undervisningsformål, så man i lanceringsloggen kan se, at alt er OK, og forbindelsen gennem radiseklienten forløb uden fejl.

Skriv data til Redis

Tilføj et opkald til hovedmetoden

main.pushToRedis(preparedData); 

Med denne metode implementering:

private void pushToRedis(List<CityCountry> data) {
    try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
        RedisStringCommands<String, String> sync = connection.sync();
        for (CityCountry cityCountry : data) {
            try {
                sync.set(String.valueOf(cityCountry.getId()), mapper.writeValueAsString(cityCountry));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }

    }
}

Her åbnes en synkron forbindelse med radiseklienten, og sekventielt skrives hvert objekt af typen CityCountry til radisen. Da radisen er et String -nøgleværdilager , konverteres nøglen (by-id) til en streng. Og værdien er også til strengen, men ved hjælp af ObjectMapper i JSON-format.

Det er tilbage at køre og kontrollere, at der ikke er fejl i loggen. Alt fungerede.

Installer redis-insight, se på de data, der er gemt i Redis (valgfrit)

Download redis-insight fra linket og installer det. Efter start viser den straks vores radiseforekomst i docker-beholderen:

Hvis du logger ind, vil vi se en liste over alle nøgler:

Og du kan gå til en hvilken som helst nøgle for at se, hvilke data der er gemt på den:

Skriv en metode til at hente data fra Redis

Til test bruger vi følgende test: vi får 10 CityCountry-poster. Hver med en separat anmodning, men i én forbindelse.

Data fra radise kan fås gennem vores radiseklient. For at gøre dette, lad os skrive en metode, der kræver en liste over id'er at få.

private void testRedisData(List<Integer> ids) {
    try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
        RedisStringCommands<String, String> sync = connection.sync();
        for (Integer id : ids) {
            String value = sync.get(String.valueOf(id));
            try {
                mapper.readValue(value, CityCountry.class);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
    }
}

Implementeringen, synes jeg, er intuitiv: vi åbner en synkron forbindelse, og for hvert id får vi en JSON String , som vi konverterer til objektet af typen CityCountry, vi har brug for .

Skriv en metode til at hente data fra MySQL

Tilføj en metode i CityDAO-getById(Integer id) klassen, hvor vi får byen sammen med landet:

public City getById(Integer id) {
    Query<City> query = sessionFactory.getCurrentSession().createQuery("select c from City c join fetch c.country where c.id = :ID", City.class);
    query.setParameter("ID", id);
    return query.getSingleResult();
}

I analogi med det foregående afsnit, lad os tilføje en lignende metode til MySQL til Main-klassen:

private void testMysqlData(List<Integer> ids) {
    try (Session session = sessionFactory.getCurrentSession()) {
        session.beginTransaction();
        for (Integer id : ids) {
            City city = cityDAO.getById(id);
            Set<CountryLanguage> languages = city.getCountry().getLanguages();
        }
        session.getTransaction().commit();
    }
}

Af funktionerne, for at være sikker på at få det fulde objekt (uden proxy-stubber), anmoder vi eksplicit om en liste over sprog fra landet.

Sammenlign hastigheden for at få de samme data fra MySQL og Redis

Her vil jeg straks give koden for hovedmetoden, og resultatet, der er opnået på min lokale computer.

public static void main(String[] args) {
    Main main = new Main();
    List<City> allCities = main.fetchData(main);
    List<CityCountry> preparedData = main.transformData(allCities);
    main.pushToRedis(preparedData);

    // close the current session in order to make a query to the database for sure, and not to pull data from the cache
    main.sessionFactory.getCurrentSession().close();

    //choose random 10 id cities
    //since we did not handle invalid situations, use the existing id in the database
    List<Integer> ids = List.of(3, 2545, 123, 4, 189, 89, 3458, 1189, 10, 102);

    long startRedis = System.currentTimeMillis();
    main.testRedisData(ids);
    long stopRedis = System.currentTimeMillis();

    long startMysql = System.currentTimeMillis();
    main.testMysqlData(ids);
    long stopMysql = System.currentTimeMillis();

    System.out.printf("%s:\t%d ms\n", "Redis", (stopRedis - startRedis));
    System.out.printf("%s:\t%d ms\n", "MySQL", (stopMysql - startMysql));

    main.shutdown();
}

Ved test er der en funktion - data fra MySQL læses kun, så det kan ikke genstartes mellem lancering af vores applikation. Og i Redis er de skrevet.

Selvom når du forsøger at tilføje en dublet for den samme nøgle, vil dataene simpelthen blive opdateret, men jeg vil anbefale, at du kører kommandoerne for at stoppe containeren docker stop redis-stackog slette containeren mellem applikationslanceringer i terminalen docker rm redis-stack. Hæv derefter beholderen med radisen igen docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latestog udfør først vores ansøgning.

Her er mine testresultater:

I alt har vi opnået en stigning i ydeevnen af ​​svaret på anmodningen om "bremsning hyppige" med halvanden gang. Og dette er under hensyntagen til det faktum, at vi i test ikke brugte den hurtigste deserialisering gennem ObjectMapper . Hvis du ændrer det til GSON, kan du højst sandsynligt "vinde" lidt mere tid.

I dette øjeblik husker jeg en vittighed om en programmør og tid: læs og tænk over, hvordan du skriver og optimerer din kode.