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:
- IDEA Ultimate (akinek elfogyott a kulcs – írjon Roman in the Slack-nek)
- Workbench (vagy bármely más MySQL-kliens)
- Dokkmunkás
- redis-insight – nem kötelező
Akciótervünk:
- Á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.
- Futtassa a MySQL-kiszolgálót docker-tárolóként.
- Nyomtatvány kibontása .
- Hozzon létre egy projektet az Idea alkalmazásban, adjon hozzá hatalmas függőségeket.
- Legyen réteg domain.
- Írjon egy módszert az összes adat lekéréséhez a MySQL-ből.
- Írjon adatátalakítási metódust (a Redisben csak a gyakran kért adatokat írjuk ki).
- Futtassa a Redis-kiszolgálót docker-tárolóként.
- Írjon adatokat Redisnek.
- Nem kötelező: telepítse a redis-insight-ot, nézze meg a Redisben tárolt adatokat.
- Írjon egy módszert az adatok Redisből való lekérésére.
- Írjon egy módszert az adatok MySQL-ből való lekérésére.
- 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:
- A város a városok táblázata.
- ország - ország táblázat.
- 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:8
nem 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-core
az 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.redis
amelyben 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-stack
ill 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.
GO TO FULL VERSION