CodeGym/Java kurs/All lectures for NO purposes/Relatert prosjekt: SQL, JDBC og Hibernate

Relatert prosjekt: SQL, JDBC og Hibernate

Tilgjengelig

I dag skal vi gjøre sluttprosjektet på den fjerde JRU-modulen. Hva skal det være? La oss prøve å jobbe med forskjellige teknologier: MySQL, Hibernate, Redis, Docker. Nå mer emne.

Oppgave: vi har en relasjonell MySQL-database med et skjema (land-by, språk etter land). Og det er en hyppig forespørsel fra byen, som bremser ned. Vi kom opp med en løsning - å flytte all data som etterspørres ofte til Redis (i minnelagring av nøkkelverdi-typen).

Og vi trenger ikke alle dataene som er lagret i MySQL, men bare et utvalgt sett med felt. Prosjektet vil være i form av en veiledning. Det vil si at her vil vi ta opp problemet og umiddelbart løse det.

Så la oss starte med hvilken programvare vi trenger:

  1. IDEA Ultimate (som gikk tom for nøkkelen - skriv til Roman in the Slack)
  2. Workbench (eller en annen klient for MySQL)
  3. Docker
  4. redis-insight - valgfritt

Vår handlingsplan:

  1. Sett opp docker (jeg vil ikke gjøre dette i opplæringen, fordi hvert OS vil ha sine egne egenskaper og det er mange svar på Internett på spørsmål som "hvordan installere docker på Windows"), sjekk at alt fungerer.
  2. Kjør MySQL-server som en docker-beholder.
  3. Utvid dump .
  4. Lag et prosjekt i Idea, legg til maven avhengigheter.
  5. Lag et lagdomene.
  6. Skriv en metode for å hente all data fra MySQL.
  7. Skriv en datatransformasjonsmetode (i Redis vil vi bare skrive data som etterspørres ofte).
  8. Kjør Redis-serveren som en docker-beholder.
  9. Skriv data til Redis.
  10. Valgfritt: installer redis-insight, se på dataene som er lagret i Redis.
  11. Skriv en metode for å hente data fra Redis.
  12. Skriv en metode for å hente data fra MySQL.
  13. Sammenlign hastigheten på å få de samme dataene fra MySQL og Redis.

Docker-oppsett

Docker er en åpen plattform for utvikling, levering og drift av applikasjoner. Vi vil bruke det for ikke å installere og konfigurere Redis på den lokale maskinen, men for å bruke et ferdiglagd bilde. Du kan lese mer om docker her eller se det her . Hvis du ikke er kjent med docker, anbefaler jeg å se på bare den andre lenken.

For å sikre at du har installert og konfigurert docker, kjør kommandoen:docker -v

Hvis alt er OK, vil du se docker-versjonen

Kjør MySQL-server som docker-beholder

For å kunne sammenligne tidspunktet for retur av data fra MySQL og Redis, vil vi også bruke MySQL i docker. I PowerShell (eller en annen konsollterminal hvis du ikke bruker Windows), kjør kommandoen:

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

Tenk på hva vi gjør med denne kommandoen:

  • docker run– starte (og laste ned, hvis det ennå ikke er lastet ned til den lokale maskinen) bildet. Som følge av lanseringen får vi en løpende container.
  • --name mysql- angi navnet på mysql-beholderen.
  • -d- et flagg som sier at containeren skal fortsette å fungere, selv om du lukker terminalvinduet som denne containeren ble lansert fra.
  • -p 3306:3306- spesifiserer porter. Før kolon - porten på den lokale maskinen, etter kolon - porten i containeren.
  • -e MYSQL_ROOT_PASSWORD=root– sende miljøvariabelen MYSQL_ROOT_PASSWORD med verdienroten til beholderen. Flagg spesifikt for mysql/bildet
  • --restart unless-stopped- angi atferdspolicyen (om beholderen skal startes på nytt når den er lukket). Unless-stopped-verdien betyr å alltid starte på nytt, bortsett fra når beholderen ble stoppet /
  • -v mysql:/var/lib/mysql – legg til volum (bilde for lagring av informasjon).
  • mysql:8 – navnet på bildet og versjonen.

Etter å ha utført kommandoen i terminalen, vil docker laste ned alle lagene i bildet og starte beholderen:

Viktig merknad: hvis du har MySQL installert som en tjeneste på din lokale datamaskin og den kjører, må du spesifisere en annen port i startkommandoen, eller stoppe denne kjørende tjenesten.

Utvid dump

For å utvide dumpen må du opprette en ny tilkobling til databasen fra Workbench, hvor du spesifiserer parameterne. Jeg brukte standardporten (3306), endret ikke brukernavnet (root som standard), og satte passordet for root-brukeren (root).

I Workbench, gjør Data Import/Restoreog velg Import from Self Contained File. Angi hvor du lastet ned dumpen som en fil . Du trenger ikke å lage skjemaet på forhånd - opprettelsen av det er inkludert i dumpfilen. Etter en vellykket import vil du ha et verdensskjema med tre tabeller:

  1. by er en tabell over byer.
  2. land - land tabell.
  3. country_language - en tabell som angir hvor stor prosentandel av befolkningen i landet som snakker et bestemt språk.

Siden vi brukte et volum når vi startet beholderen, etter å ha stoppet og til og med slettet mysql-beholderen og utført startkommandoen på nytt ( docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root --restart unless-stopped -v mysql:/var/lib/mysql mysql:8), vil det ikke være nødvendig å distribuere dumpen igjen - den er allerede distribuert i volumet.

Opprett prosjekt i Idea, legg til maven-avhengigheter

Du vet allerede hvordan du lager et prosjekt i Ideen - dette er det enkleste punktet i dagens prosjekt.

Legg til avhengigheter 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 tre første avhengighetene har lenge vært kjent for deg.

lettuce-coreer en av de tilgjengelige Java-klientene for å jobbe med Redis.

jackson-databind– avhengighet for bruk av ObjectMapper (for å transformere data for lagring i Redis (nøkkelverdi av typen String)).

Legg også til spy.properties i ressursmappen (src/main/resources) for å se forespørslene med parametere som Hibernate kjører. Filinnhold:

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 

Lag et lagdomene

Opprett pakken com.codegym.domain

Det er praktisk for meg når jeg kartlegger tabeller på en enhet å bruke tabellstrukturen i ideen, så la oss legge til en databaseforbindelse i ideen.

Jeg foreslår at du oppretter enheter i denne rekkefølgen:

  • Land
  • By
  • CountryLanguage

Det er ønskelig at du utfører kartleggingen selv.

Landsklassekode:

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

}

Det er 3 interessante punkter i koden.

Den første er Continent enam , som er lagret i databasen som ordensverdier. I strukturen til landtabellen, i kommentarene til kontinentfeltet, kan du se hvilken tallverdi som tilsvarer hvilket kontinent.

package com.codegym.domain;

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

Det andre punktet er et sett med enheterCountryLanguage . Her er en lenke @OneToManysom ikke var i det andre utkastet til denne modulen. Som standard vil Hibernate ikke trekke verdien av dette settet når du ber om en landenhet. Men siden vi trenger å trekke alle verdier fra relasjonsdatabasen for caching, vil FetchType.EAGER.

Den tredje er byfeltet . Kommunikasjon @OneToOne- som om alt er kjent og forståelig. Men hvis vi ser på fremmednøkkelstrukturen i databasen, ser vi at landet (landet) har en kobling til hovedstaden (byen), og byen (byen) har en kobling til landet (landet). Det er en syklisk sammenheng.

Vi vil ikke gjøre noe med dette ennå, men når vi kommer til "Skriv en metode for å hente alle data fra MySQL", la oss se hvilke spørringer Hibernate utfører, se på nummeret deres og husk dette elementet. Hibernate

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 for å hente all data fra MySQL

I hovedklassen erklærer vi feltene:

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 til hovedklassen:

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

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

Som du kan se, er det ikke nok metoder og klasser - la oss skrive dem.

Erklær en pakke com.codegym.dao og legg til 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());
    }
}

Nå kan du importere disse 2 klassene til Main. Fortsatt mangler 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 ennå ikke nådd reddik, så implementeringen av initialiseringen av reddikklienten vil forbli en stump for nå:

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

Til slutt kan vi skrive en metode der vi trekker ut alle byene:

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;
    }
}

Implementeringsfunksjonen er slik at vi får 500 byer hver. Dette er nødvendig fordi det er begrensninger på mengden overførte data. Ja, i vårt tilfelle kommer vi ikke til dem, fordi. vi har totalt 4079 byer i databasen. Men i produksjonsapplikasjoner, når du trenger å få mye data, brukes denne teknikken ofte.

Og implementeringen av hovedmetoden:

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

Nå kan vi kjøre applikasjonen vår i debug for første gang og se hvordan den fungerer (eller ikke fungerer - ja, det skjer ofte).

Byer får. Hver by får et land, hvis det ikke tidligere har blitt trukket fra databasen for en annen by. La oss grovt beregne hvor mange søk Hibernate vil sende til databasen:

  • 1 forespørsel for å finne ut det totale antallet byer (nødvendig for å iterere over 500 byer for å vite når du skal stoppe).
  • 4079 / 500 = 9 forespørsler (liste over byer).
  • Hver by får et land, hvis det ikke er trukket fra tidligere. Siden det er 239 land i databasen, vil dette gi oss 239 søk.

Total 249 requests. Og vi sa også at sammen med landet ville vi umiddelbart motta et sett med språk, ellers ville det bli mørke generelt. Men det er fortsatt mye, så la oss justere oppførselen litt. La oss starte med refleksjoner: hva skal man gjøre, hvor skal man løpe? Men seriøst - hvorfor er det så mange forespørsler. Hvis du ser på forespørselsloggen, ser vi at hvert land blir forespurt separat, så den første enkle løsningen: la oss be om alle landene sammen, fordi vi vet på forhånd at vi vil trenge dem alle i denne transaksjonen.

I fetchData()-metoden, umiddelbart etter starten av transaksjonen, legg til følgende linje:

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

Vi teller forespørsler:

  • 1 - få alle land
  • 239 - spørring for hvert land i hovedstaden
  • 1 - forespørsel om antall byer
  • 9 - forespørsel om lister over byer

Total 250. Tanken er god, men den fungerte ikke. Problemet er at landet har en forbindelse med hovedstaden (byen) @OneToOne. Og en slik lenke lastes umiddelbart som standard ( FetchType.EAGER). La oss si FetchType.LAZY, fordi uansett, vi vil laste inn alle byene senere i samme transaksjon.

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

Store bokstaver er ikke lenger forespurt separat, men antallet forespørsler er ikke endret. Nå, for hvert land, blir listen CountryLanguage forespurt av en separat spørring . Det vil si at det er fremgang, og vi går i riktig retning. Hvis du husker det, foreslo forelesningene «join appetch» -løsningen for å be om en enhet med avhengige data i én forespørsel ved å legge til en ekstra sammenføyning til forespørselen. I CountryDAO , omskriv HQL-spørringen i metoden getAll()til:

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

Lansering. Vi ser på loggen, teller forespørsler:

  • 1 - alle land med språk
  • 1 - antall byer
  • 9 - lister over byer.

Total 11- vi lyktes)) Hvis du ikke bare leste all denne teksten, men også prøvde å kjøre den etter hvert trinn med å justere applikasjonen, bør du til og med visuelt merke akselerasjonen av hele applikasjonen flere ganger.

Skriv en datatransformasjonsmetode

La oss lage en pakke com.codegym.redisder vi legger til 2 klasser: CityCountry (data om byen og landet der denne byen ligger) og Language (data om språket). Her er alle feltene som ofte etterspørres "ved oppgave" i "bremseforespørsel".

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, etter å ha fått alle byene, legg til linjen

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

Og implementer denne metoden:

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 metoden er selvforklarende: vi oppretter bare en CityCountry- enhet og fyller den med data fra City , Country , CountryLanguage .

Kjør Redis-server som en docker-beholder

Det er 2 alternativer her. Hvis du gjør det valgfrie trinnet "installer redis-insight, se på dataene som er lagret i Redis", så er kommandoen for deg:

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

Hvis du bestemmer deg for å hoppe over dette trinnet, så bare:

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

Forskjellen er at i det første alternativet blir port 8001 videresendt til den lokale maskinen, som du kan koble til med en ekstern klient for å se hva som er lagret inni. Og jeg pleide å gi meningsfulle navn, derfor, redis-stackeller redis.

Etter lansering kan du se listen over kjørende containere. For å gjøre dette, kjør kommandoen:

docker container ls 

Og du vil se noe slikt:

Hvis du trenger å finne en kommando, kan du enten se på hjelpen i terminalen (docker-hjelp) eller google "how to ..." (for eksempel docker how to remove running container).

Og vi kalte også initialiseringen av reddikklienten i hovedkonstruktøren, men implementerte ikke selve metoden. Legg til 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 ble lagt til for pedagogiske formål slik at du i lanseringsloggen kan se at alt er OK og forbindelsen gjennom reddikklienten gikk uten feil.

Skriv data til Redis

Legg til et anrop til hovedmetoden

main.pushToRedis(preparedData); 

Med denne metodeimplementeringen:

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 åpnes en synkron forbindelse med reddikklienten, og sekvensielt skrives hvert objekt av typen CityCountry til reddiken. Siden reddiken er et streng nøkkelverdilager , konverteres nøkkelen (by-ID) til en streng. Og verdien er også til strengen, men bruker ObjectMapper i JSON-format.

Det gjenstår å kjøre og sjekke at det ikke er noen feil i loggen. Alt fungerte.

Installer redis-insight, se på dataene som er lagret i Redis (valgfritt)

Last ned redis-insight fra lenken og installer den. Etter oppstart viser den umiddelbart vår reddikforekomst i docker-beholderen:

Hvis du logger inn, vil vi se en liste over alle nøkler:

Og du kan gå til hvilken som helst tast for å se hvilke data som er lagret på den:

Skriv en metode for å hente data fra Redis

For testing bruker vi følgende test: vi får 10 CityCountry-poster. Hver med en separat forespørsel, men i en forbindelse.

Data fra reddik kan fås gjennom vår reddikklient. For å gjøre dette, la oss skrive en metode som krever en liste over ID-er.

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 tror jeg er intuitiv: vi åpner en synkron tilkobling, og for hver id får vi en JSON-streng , som vi konverterer til objektet av typen CityCountry vi trenger .

Skriv en metode for å hente data fra MySQL

I CityDAO- klassen legger du til en metode getById(Integer id)der 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 forrige avsnitt, la oss legge til en lignende metode for 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();
    }
}

Av funksjonene, for å være sikker på å få hele objektet (uten proxy-stubber), ber vi eksplisitt om en liste over språk fra landet.

Sammenlign hastigheten på å få de samme dataene fra MySQL og Redis

Her vil jeg umiddelbart gi koden til hovedmetoden, og resultatet som er oppnådd på min lokale datamaskin.

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 testing er det en funksjon - data fra MySQL leses kun, så det kan ikke startes på nytt mellom lanseringer av applikasjonen vår. Og i Redis er de skrevet.

Selv om når du prøver å legge til et duplikat for samme nøkkel, vil dataene ganske enkelt bli oppdatert, men jeg vil anbefale at du kjører kommandoene for å stoppe beholderen docker stop redis-stackog slette beholderen mellom applikasjonsstarter i terminalen docker rm redis-stack. Hev deretter beholderen med reddiken igjen docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latestog utfør søknaden vår først etter det.

Her er mine testresultater:

Totalt har vi oppnådd en økning i ytelsen til responsen på "bremsing hyppig"-forespørselen med en og en halv gang. Og dette tar hensyn til det faktum at i testing brukte vi ikke den raskeste deserialiseringen gjennom ObjectMapper . Hvis du endrer den til GSON, kan du mest sannsynlig "vinne" litt mer tid.

I dette øyeblikket husker jeg en vits om en programmerer og tid: les og tenk på hvordan du skriver og optimerer koden din.

Kommentarer
  • Populær
  • Ny
  • Gammel
Du må være pålogget for å legge igjen en kommentar
Denne siden har ingen kommentarer ennå