Ma a negyedik JRU modulon végezzük el az utolsó projektet. Mi lesz az? Próbáljunk meg különböző technológiákkal dolgozni: MySQL, Hibernate, Redis, Docker. Most több téma.

Feladat: van egy relációs MySQL adatbázisunk sémával (ország-város, nyelv országonként). És gyakori kérés a város részéről, ami lelassul. Találtunk egy megoldást - hogy az összes gyakran kért adatot áthelyezzük a Redisbe (a kulcsérték típusú memóriatárolóba).

És nem minden MySQL-ben tárolt adatra van szükségünk, hanem csak egy kiválasztott mezőkészletre. A projekt oktatóanyag formájában valósul meg. Vagyis itt felvetjük a problémát és azonnal megoldjuk.

Tehát kezdjük azzal, hogy milyen szoftverre lesz szükségünk:

  1. IDEA Ultimate (akinek elfogyott a kulcs – írjon Roman in the Slack-nek)
  2. Workbench (vagy bármely más MySQL-kliens)
  3. Dokkmunkás
  4. redis-insight – nem kötelező

Akciótervünk:

  1. Állítsa be a dokkolót (ezt nem fogom megtenni az oktatóanyagban, mert minden operációs rendszernek megvannak a sajátosságai, és az interneten sok válasz található az olyan kérdésekre, mint a „docker telepítése a Windows rendszeren”), ellenőrizze, hogy minden működik-e.
  2. Futtassa a MySQL-kiszolgálót docker-tárolóként.
  3. Nyomtatvány kibontása .
  4. Hozzon létre egy projektet az Idea alkalmazásban, adjon hozzá hatalmas függőségeket.
  5. Legyen réteg domain.
  6. Írjon egy módszert az összes adat lekéréséhez a MySQL-ből.
  7. Írjon adatátalakítási metódust (a Redisben csak a gyakran kért adatokat írjuk ki).
  8. Futtassa a Redis-kiszolgálót docker-tárolóként.
  9. Írjon adatokat Redisnek.
  10. Nem kötelező: telepítse a redis-insight-ot, nézze meg a Redisben tárolt adatokat.
  11. Írjon egy módszert az adatok Redisből való lekérésére.
  12. Írjon egy módszert az adatok MySQL-ből való lekérésére.
  13. Hasonlítsa össze ugyanazon adatok MySQL-ből és Redisből való beszerzésének sebességét.

Docker beállítása

A Docker egy nyílt platform alkalmazások fejlesztésére, szállítására és üzemeltetésére. Arra fogjuk használni, hogy ne a helyi gépen telepítsük és konfiguráljuk a Redist, hanem egy kész képfájlt használjunk. A dockerről bővebben itt olvashat, vagy itt tekintheti meg . Ha nem ismeri a dockert, azt javaslom, hogy nézze meg a második hivatkozást.

Ha meg szeretné győződni arról, hogy a docker telepítve és konfigurálva van, futtassa a parancsot:docker -v

Ha minden rendben van, látni fogja a docker verziót

Futtassa a MySQL-kiszolgálót docker-tárolóként

Annak érdekében, hogy összehasonlíthassuk a MySQL és a Redis adatainak visszaküldésének idejét, a MySQL-t is használjuk a dockerben. A PowerShellben (vagy egy másik konzolterminálban, ha nem Windows-t használ), futtassa a parancsot:

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

Fontolja meg, mit csinálunk ezzel a paranccsal:

  • docker run– a kép elindítása (és letöltése, ha még nem lett letöltve a helyi gépre). Az indítás eredményeként egy futó konténert kapunk.
  • --name mysql- állítsa be a mysql tároló nevét.
  • -d- egy zászló, amely azt mondja, hogy a tárolónak továbbra is működnie kell, még akkor is, ha bezárja azt a terminálablakot, amelyből a tároló elindult.
  • -p 3306:3306- megadja a portokat. A kettőspont előtt - a port a helyi gépen, a kettőspont után - a port a tárolóban.
  • -e MYSQL_ROOT_PASSWORD=root– a MYSQL_ROOT_PASSWORD környezeti változó gyökér értékű átadása a tárolónak. Jelölje meg a mysql/ image-et
  • --restart unless-stopped- a viselkedési szabályzat beállítása (az, hogy a tárolót újra kell-e indítani bezáráskor). Azless-stopped érték azt jelenti, hogy mindig újra kell indulni, kivéve ha a tároló leállt /
  • -v mysql:/var/lib/mysql – kötet hozzáadása (információ tárolására szolgáló kép).
  • mysql:8 – a kép neve és változata.

A parancs végrehajtása után a terminálban a docker letölti a kép összes rétegét, és elindítja a tárolót:

Fontos megjegyzés: ha a MySQL szolgáltatásként telepítve van a helyi számítógépen, és az fut, meg kell adnia egy másik portot a start parancsban, vagy le kell állítania ezt a futó szolgáltatást.

A kirakás kiterjesztése

A dump kibontásához új kapcsolatot kell létrehozni az adatbázissal a Workbenchből, ahol meg kell adni a paramétereket. Az alapértelmezett portot (3306) használtam, nem változtattam meg a felhasználónevet (alapértelmezés szerint root), és beállítottam a root felhasználó jelszavát (root).

A Workbenchben tegye meg, Data Import/Restoreés válassza a lehetőséget Import from Self Contained File. Adja meg, honnan töltötte le a kiíratást fájlként . A sémát nem kell előzetesen létrehoznia – a létrehozása benne van a dump fájlban. Sikeres importálás után egy világsémája lesz három táblával:

  1. A város a városok táblázata.
  2. ország - ország táblázat.
  3. country_language - egy táblázat, amely megmutatja, hogy az ország lakosságának hány százaléka beszél egy adott nyelvet.

Mivel a tároló indításakor kötetet használtunk, a mysql konténer leállítása, sőt törlése, valamint a start parancs ( ) újbóli végrehajtása után docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root --restart unless-stopped -v mysql:/var/lib/mysql mysql:8nem kell újra telepíteni a dump-ot - az már telepítve van a kötetben.

Hozzon létre projektet az Idea alkalmazásban, adjon hozzá hatalmas függőségeket

Már tudja, hogyan kell projektet létrehozni az Ötletben – ez a mai projekt legegyszerűbb pontja.

Adjon hozzá függőséget a pom fájlhoz:


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

Az első három függőség már régóta ismerős számodra.

lettuce-coreaz egyik elérhető Java kliens a Redis-szel való együttműködéshez.

jackson-databind– függőség az ObjectMapper használatához (adatok átalakítása a Redisben való tároláshoz (String típusú kulcsérték)).

Szintén az erőforrások mappában (src/main/resources) adja hozzá a spy.properties fájlt a Hibernate által végrehajtott paraméterekkel rendelkező kérések megtekintéséhez. Fájl tartalma:

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 

Legyen réteg domain

Hozzon létre com.codegym.domain csomagot

Számomra kényelmes egy entitás tábláinak leképezésekor az ötlet táblaszerkezetét használni, ezért adjunk hozzá egy adatbázis-kapcsolatot az ötlethez.

Javaslom az entitások létrehozását a következő sorrendben:

  • Ország
  • város
  • OrszágNyelv

Kívánatos, hogy a feltérképezést saját maga végezze el.

Ország osztály kódja:

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

}

3 érdekes pont van a kódban.

Az első a Continent enam , amely az adatbázisban sorszámként tárolódik. Az országtáblázat szerkezetében a kontinens mezőhöz fűzött megjegyzésekben láthatja, hogy melyik kontinensnek melyik számérték felel meg.

package com.codegym.domain;

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

A második pont az entitások halmazaCountryLanguage . Itt van egy link @OneToMany, amely nem volt a modul második vázlatában. Alapértelmezés szerint a Hibernate nem kéri le ennek a készletnek az értékét, amikor ország entitást kér. De mivel a gyorsítótárazáshoz ki kell vonnunk az összes értéket a relációs adatbázisból, a FetchType.EAGER.

A harmadik a város mezője . Kommunikáció @OneToOne- mintha minden ismerős és érthető. De ha megnézzük az adatbázisban az idegen kulcs szerkezetét, akkor azt látjuk, hogy az országnak (országnak) van linkje a fővároshoz (városhoz), a városnak (városnak) pedig az országhoz (országhoz). Ciklikus kapcsolat van.

Ezzel még nem fogunk mit kezdeni, de amikor eljutunk a „Módszer írása az összes adat megszerzéséhez a MySQL-ből” elemhez, nézzük meg, milyen lekérdezéseket hajt végre a Hibernate, nézzük meg a számukat, és emlékezzünk erre az elemre.

Városi osztálykód:

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

}

OrszágLanguage osztálykód:

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
}

Írjon egy módszert az összes adat lekéréséhez a MySQL-ből

A Fő osztályban deklaráljuk a következő mezőket:

private final SessionFactory sessionFactory;
private final RedisClient redisClient;

private final ObjectMapper mapper;

private final CityDAO cityDAO;
private final CountryDAO countryDAO;

és inicializálja őket a Main osztály konstruktorában:

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

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

Mint látható, nincs elég metódus és osztály - írjuk meg őket.

Deklaráljon egy com.codegym.dao csomagot, és adjon hozzá 2 osztályt:

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

Most már importálhatja ezt a 2 osztályt a Main-ba. Még mindig hiányzik két módszer:

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

A retekhez még nem érkeztünk el, így a retek kliens inicializálásának megvalósítása egyelőre csonk marad:

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

Végül írhatunk egy módszert, amelyben kihúzzuk az összes várost:

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

A megvalósítási funkció olyan, hogy egyenként 500 várost kapunk. Erre azért van szükség, mert a továbbított adatok mennyiségét korlátozzák. Igen, a mi esetünkben nem jutunk el hozzájuk, mert. összesen 4079 város van az adatbázisban. De az éles alkalmazásokban, amikor sok adatot kell beszerezni, gyakran használják ezt a technikát.

És a fő módszer végrehajtása:

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

Most először futtathatjuk az alkalmazásunkat debug-ban, és megnézhetjük, hogyan működik (vagy nem működik - igen, gyakran előfordul).

A városok egyre. Minden város kap egy országot, ha előzőleg nem vonták ki egy másik város adatbázisából. Számítsuk ki nagyjából, hogy a Hibernate hány lekérdezést küld az adatbázisnak:

  • 1 kérés a városok teljes számának kiderítésére (több mint 500 város iterálásához szükséges, hogy megtudjuk, mikor kell megállni).
  • 4079 / 500 = 9 kérés (városok listája).
  • Minden város kap egy országot, ha korábban nem vonták le. Mivel 239 ország van az adatbázisban, így 239 lekérdezést kapunk.

Total 249 requests. És azt is mondtuk, hogy az országgal együtt azonnal megkapjuk a nyelveket, különben általában sötétség lesz. De ez még mindig sok, úgyhogy finomítsunk egy kicsit a viselkedésen. Kezdjük elmélkedésekkel: mit tegyünk, hová futjunk? De komolyan - miért van annyi kérés. Ha megnézzük a kérésnaplót, azt látjuk, hogy minden országot külön kérnek, így az első egyszerű megoldás: kérjük le az összes országot együtt, mert előre tudjuk, hogy ebben a tranzakcióban mindegyikre szükségünk lesz.

A fetchData() metódusban közvetlenül a tranzakció kezdete után adja hozzá a következő sort:

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

A kéréseket számoljuk:

  • 1 - kap minden országot
  • 239 - lekérdezés a főváros minden országához
  • 1 - kérés a városok számára
  • 9 - városi listák kérése

Total 250. Az ötlet jó, de nem működött. A probléma az, hogy az országnak kapcsolata van a fővárossal (várossal) @OneToOne. És egy ilyen hivatkozás alapértelmezés szerint azonnal betöltődik ( FetchType.EAGER). Tegyük fel FetchType.LAZY, mert mindenesetre az összes várost később betöltjük ugyanabban a tranzakcióban.

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

A tőkéket már nem kérik külön, de a kérések száma nem változott. Mostantól minden ország esetében külön lekérdezés kéri a CountryLanguage listát . Vagyis van előrelépés, és jó irányba haladunk. Ha emlékszel, az előadások a „join fetch” megoldást javasolták annak érdekében, hogy egy függő adatokkal rendelkező entitást egyetlen kérelemben lekérhessen egy további csatlakozás hozzáadásával. A CountryDAO- ban írja át a HQL-lekérdezést a metódusban a getAll()következőre:

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

Dob. Megnézzük a naplót, megszámoljuk a kéréseket:

  • 1 – minden nyelvű ország
  • 1 - városok száma
  • 9 - városok listája.

Total 11- sikerült)) Ha nemcsak elolvasta ezt a szöveget, hanem az alkalmazás hangolásának minden lépése után megpróbálta futtatni, akkor többször is meg kell jegyeznie vizuálisan a teljes alkalmazás gyorsulását.

Írjon adatátalakítási metódust!

Hozzunk létre egy csomagot, com.codegym.redisamelyben 2 osztályt adunk hozzá: CityCountry (adatok a városról és az országról, amelyben ez a város található) és Language (adatok a nyelvről). Itt vannak az összes olyan mező, amelyet gyakran „feladat szerint” kérnek a „fékezési kérelemben”.

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
}

A fő módszerben, miután megkapta az összes várost, adja hozzá a sort

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

És hajtsa végre ezt a módszert:

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

Szerintem ez a módszer magától értetődő: csak létrehozunk egy CityCountry entitást, és kitöltjük a City , Country , CountryLanguage adatokkal .

Futtassa a Redis-kiszolgálót docker-tárolóként

Itt 2 lehetőség van. Ha elvégzi a „redis-insight telepítése, a Redisben tárolt adatok megtekintése” opciót, akkor a parancs az Ön számára készült:

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

Ha úgy dönt, hogy kihagyja ezt a lépést, akkor egyszerűen:

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

A különbség az, hogy az első opciónál a 8001-es portot továbbítják a helyi gépre, amelyre külső klienssel csatlakozva megnézheti, mi van benne. És értelmes neveket szoktam adni ezért, redis-stackill redis.

Indítás után láthatja a futó tárolók listáját. Ehhez futtassa a parancsot:

docker container ls 

És valami ilyesmit fog látni:

Ha valamilyen parancsot kell találnia, akkor vagy nézze meg a terminál súgóját (docker súgó), vagy nézze meg a google "how to ..." címét (például docker hogyan távolítsa el a futó tárolót).

És meghívtuk a retek kliens inicializálását is a fő konstruktorban, de magát a metódust nem valósítottuk meg. Megvalósítás hozzáadása:

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

A sout oktatási céllal került hozzáadásra, hogy az indítási naplóban lássa, minden rendben van, és a kapcsolat a retek kliensen keresztül hiba nélkül ment.

Írjon adatokat Redisnek

Hívás hozzáadása a fő metódushoz

main.pushToRedis(preparedData); 

Ezzel a módszerrel a megvalósítás:

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

    }
}

Itt szinkron kapcsolat nyílik meg a retek klienssel, és sorrendben minden CityCountry típusú objektum a retekbe íródik. Mivel a retek egy karakterlánc -kulcsérték-tároló , a kulcs (városazonosító) karakterláncsá alakul. És az érték szintén a karakterlánchoz tartozik, de az ObjectMappert JSON formátumban használja.

Továbbra is futni kell, és ellenőrizni kell, hogy nincsenek-e hibák a naplóban. Minden működött.

Telepítse a redis-insight-ot, nézze meg a Redisben tárolt adatokat (opcionális)

Töltse le a redis-insight programot a hivatkozásról , és telepítse. Indítás után azonnal megmutatja a retek példányunkat a docker konténerben:

Ha bejelentkezik, látni fogjuk az összes kulcs listáját:

És bármelyik kulcsra léphet, hogy megnézze, milyen adatok vannak rajta tárolva:

Írjon egy módszert az adatok Redisből való lekérésére

A teszteléshez a következő tesztet használjuk: 10 CityCountry rekordot kapunk. Mindegyik külön kéréssel, de egy kapcsolatban.

A retek adatait retek kliensünkön keresztül szerezheti be. Ehhez írjunk egy metódust, amelyhez az azonosítók listája szükséges.

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

A megvalósítás szerintem intuitív: megnyitunk egy szinkron kapcsolatot, és minden azonosítóhoz kapunk egy JSON String-et , amit átalakítunk a szükséges CityCountry típusú objektummá .

Írjon egy módszert az adatok MySQL-ből való lekérésére

A CityDAO osztályban adjunk hozzá egy metódust getById(Integer id), amelyben megkapjuk a várost az országgal együtt:

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

Az előző bekezdéshez hasonlóan adjunk hozzá egy hasonló módszert a MySQL-hez a Main osztályhoz:

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

A szolgáltatások közül, hogy biztosan megkapjuk a teljes objektumot (proxy csonkok nélkül), kifejezetten kérünk egy nyelvlistát az országból.

Hasonlítsa össze ugyanazon adatok MySQL-ből és Redisből való beszerzésének sebességét

Itt azonnal megadom a fő módszer kódját és a helyi számítógépen kapott eredményt.

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

A tesztelés során van egy funkció - a MySQL-ből származó adatok csak beolvasásra kerülnek, így alkalmazásunk elindítása között nem lehet újraindítani. A Redisben pedig le vannak írva.

Ha ugyanannak a kulcsnak a másolatát próbálja hozzáadni, az adatok egyszerűen frissülnek, azt javaslom, hogy futtassa a parancsokat a tároló leállításához docker stop redis-stackés a tároló törléséhez a terminálban történő alkalmazásindítások között docker rm redis-stack. Ezt követően emeljük fel ismét a retekkel a tartályt, docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latestés csak ezután hajtsuk végre az alkalmazásunkat.

Íme a teszt eredményem:

Összességében másfélszeres növekedést értünk el a "gyakori fékezés" kérésre adott választeljesítményben. És ez figyelembe veszi azt a tényt, hogy a tesztelés során nem a leggyorsabb deserializációt használtuk az ObjectMapperen keresztül . Ha GSON-ra változtatja, valószínűleg még egy kis időt "nyerhet".

Ebben a pillanatban eszembe jut egy vicc a programozóról és az időről: olvass és gondolkozz azon, hogyan írd meg és optimalizáld a kódodat.