Vandaag zullen we het laatste project doen over de vierde JRU-module. Wat zal het zijn? Laten we proberen met verschillende technologieën te werken: MySQL, Hibernate, Redis, Docker. Nu meer onderwerp.
Taak: we hebben een relationele MySQL-database met een schema (land-stad, taal per land). En er is een frequent verzoek van de stad, die vertraagt. We hebben een oplossing bedacht: alle gegevens die vaak worden opgevraagd, verplaatsen naar Redis (in geheugenopslag van het type sleutel/waarde).
En we hebben niet alle gegevens nodig die in MySQL zijn opgeslagen, maar alleen een geselecteerde set velden. Het project heeft de vorm van een tutorial. Dat wil zeggen, hier zullen we het probleem aan de orde stellen en het onmiddellijk oplossen.
Dus laten we beginnen met welke software we nodig hebben:
- IDEA Ultimate (die geen sleutel meer had - schrijf naar Roman in the Slack)
- Workbench (of een andere client voor MySQL)
- Dokwerker
- redis-inzicht - optioneel
Ons actieplan:
- Stel docker in (ik zal dit niet doen in de tutorial, omdat elk besturingssysteem zijn eigen kenmerken zal hebben en er zijn veel antwoorden op internet op vragen als "hoe docker op Windows te installeren"), controleer of alles werkt.
- Voer de MySQL-server uit als een docker-container.
- Vouw dump uit .
- Maak een project in Idea, voeg maven-afhankelijkheden toe.
- Maak een laagdomein.
- Schrijf een methode om alle data uit MySQL te halen.
- Schrijf een methode voor gegevenstransformatie (in Redis schrijven we alleen de gegevens die vaak worden opgevraagd).
- Voer de Redis-server uit als een docker-container.
- Schrijf gegevens naar Redis.
- Optioneel: installeer redis-insight, bekijk de data opgeslagen in Redis.
- Schrijf een methode om gegevens uit Redis te halen.
- Schrijf een methode om gegevens uit MySQL te halen.
- Vergelijk de snelheid van het verkrijgen van dezelfde gegevens van MySQL en Redis.
Docker-installatie
Docker is een open platform voor het ontwikkelen, leveren en bedienen van applicaties. We zullen het gebruiken om Redis niet op de lokale machine te installeren en configureren, maar om een kant-en-klare afbeelding te gebruiken. Je kunt hier meer lezen over docker of het hier bekijken . Als u niet bekend bent met docker, raad ik u aan alleen naar de tweede link te kijken.
Voer de volgende opdracht uit om ervoor te zorgen dat Docker is geïnstalleerd en geconfigureerd:docker -v
Als alles in orde is, ziet u de docker-versie
![](https://cdn.codegym.cc/images/article/1bc6d1a9-3231-461f-a31c-46ce4a487c04/512.jpeg)
Voer MySQL-server uit als docker-container
Om de tijd van het retourneren van gegevens van MySQL en Redis te kunnen vergelijken, zullen we ook MySQL in de docker gebruiken. Voer in PowerShell (of een andere consoleterminal als u geen Windows gebruikt) de volgende opdracht uit:
docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root --restart unless-stopped -v mysql:/var/lib/mysql mysql:8
Overweeg wat we doen met deze opdracht:
docker run
– het starten (en downloaden, als het nog niet naar de lokale computer is gedownload) van de afbeelding. Als resultaat van de lancering krijgen we een lopende container.--name mysql
- stel de naam van de mysql-container in.-d
- een vlag die aangeeft dat de container moet blijven werken, zelfs als u het terminalvenster sluit van waaruit deze container is gelanceerd.-p 3306:3306
- specificeert poorten. Voor de dubbele punt - de poort op de lokale machine, na de dubbele punt - de poort in de container.-e MYSQL_ROOT_PASSWORD=root
– de omgevingsvariabele MYSQL_ROOT_PASSWORD met de waarde root doorgeven aan de container. Markeer specifiek voor de mysql/afbeelding--restart unless-stopped
- instellen van het gedragsbeleid (of de container opnieuw moet worden opgestart wanneer deze is gesloten). De waarde tenzij-gestopt betekent altijd opnieuw opstarten, behalve wanneer de container is gestopt /-v mysql:/var/lib/mysql
- voeg volume toe (afbeelding voor het opslaan van informatie).mysql:8
– de naam van de afbeelding en de versie ervan.
Na het uitvoeren van de opdracht in de terminal, downloadt de docker alle lagen van de afbeelding en start de container:
![](https://cdn.codegym.cc/images/article/2d4880df-a10e-4553-9354-b854018f53c7/1024.jpeg)
Belangrijke opmerking: als u MySQL als een service op uw lokale computer hebt geïnstalleerd en het draait, moet u een andere poort opgeven in de startopdracht of deze actieve service stoppen.
![](https://cdn.codegym.cc/images/article/5354d6b9-187d-4514-b504-e157b1ef003a/1024.jpeg)
Vouw dump uit
Om de dump uit te breiden, moet u een nieuwe verbinding met de database maken vanuit Workbench, waar u de parameters opgeeft. Ik gebruikte de standaardpoort (3306), veranderde de gebruikersnaam niet (standaard root) en stelde het wachtwoord in voor de rootgebruiker (root).
![](https://cdn.codegym.cc/images/article/7e1b4972-af30-4aa7-9212-c99ff69920f2/1024.jpeg)
In Workbench, doe Data Import/Restore
en selecteer Import from Self Contained File
. Geef op waar u de dump als bestand hebt gedownload . U hoeft het schema niet van tevoren te maken - het maken ervan is opgenomen in het dumpbestand. Na een succesvolle import heeft u een wereldschema met drie tabellen:
- stad is een tabel met steden.
- land - landentabel.
- country_language - een tabel die aangeeft welk percentage van de bevolking in het land een bepaalde taal spreekt.
![](https://cdn.codegym.cc/images/article/697339c6-8214-483a-9283-6696ad2ecf7d/512.jpeg)
Aangezien we een volume hebben gebruikt bij het starten van de container, is het na het stoppen en zelfs verwijderen van de mysql-container en het opnieuw uitvoeren van de startopdracht ( docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root --restart unless-stopped -v mysql:/var/lib/mysql mysql:8
) niet nodig om de dump opnieuw in te zetten - deze is al in het volume geïmplementeerd.
Maak een project in Idea, voeg maven-afhankelijkheden toe
Je weet al hoe je een project moet maken in het idee - dit is het gemakkelijkste punt in het project van vandaag.
![](https://cdn.codegym.cc/images/article/db14c218-aea0-4108-9a23-0ce172271cf0/1024.jpeg)
Voeg afhankelijkheden toe aan het pom-bestand:
<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 eerste drie afhankelijkheden zijn u al lang bekend.
lettuce-core
is een van de beschikbare Java-clients voor het werken met Redis.
jackson-databind
- afhankelijkheid voor het gebruik van ObjectMapper (om gegevens te transformeren voor opslag in Redis (sleutelwaarde van het type String)).
Voeg ook in de bronnenmap (src/main/resources) spy.properties toe om de verzoeken met parameters te bekijken die Hibernate uitvoert. Bestandsinhoud:
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
Maak een laagdomein
Maak pakket com.codegym.domain
Het is handig voor mij om bij het toewijzen van tabellen aan een entiteit de tabelstructuur in het idee te gebruiken, dus laten we een databaseverbinding toevoegen aan het idee.
![](https://cdn.codegym.cc/images/article/7c7320ea-4dd5-4eff-b757-503a401565fb/800.jpeg)
![](https://cdn.codegym.cc/images/article/91b9ed51-e724-4cb9-900c-56e06d499c43/1024.jpeg)
Ik stel voor om entiteiten in deze volgorde te maken:
- Land
- Stad
- Landstaal
Het is wenselijk dat u de mapping zelf uitvoert.
Land klassecode:
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
}
Er zijn 3 interessante punten in de code.
De eerste is de Continent enam , die als ordinale waarden in de database is opgeslagen. In de structuur van de landentabel, in het commentaarveld bij het veld continent, kunt u zien welke getalswaarde bij welk continent hoort.
package com.codegym.domain;
public enum Continent {
ASIA,
EUROPE,
NORTH_AMERICA,
AFRICA,
OCEANIA,
ANTARCTICA,
SOUTH_AMERICA
}
Het tweede punt is een reeks entiteitenCountryLanguage
. Hier is een link @OneToMany
die niet in de tweede versie van deze module stond. Standaard haalt Hibernate de waarde van deze set niet op bij het aanvragen van een landentiteit. Maar aangezien we alle waarden uit de relationele database moeten aftrekken voor caching, is de FetchType.EAGER
.
Het derde is het stadsveld . Communicatie @OneToOne
- alsof alles vertrouwd en begrijpelijk is. Maar als we kijken naar de externe sleutelstructuur in de database, zien we dat het land (land) een link heeft naar de hoofdstad (stad) en de stad (stad) een link heeft naar het land (land). Er is een cyclisch verband.
We zullen hier nog niets mee doen, maar als we bij het item "Schrijf een methode om alle gegevens uit MySQL te krijgen" komen, laten we eens kijken welke query's Hibernate uitvoert, naar hun nummer kijken en dit item onthouden.
Stadsklassecode:
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 klassecode:
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
}
Schrijf een methode om alle data uit MySQL te halen
In de klasse Main declareren we de velden:
private final SessionFactory sessionFactory;
private final RedisClient redisClient;
private final ObjectMapper mapper;
private final CityDAO cityDAO;
private final CountryDAO countryDAO;
en initialiseer ze in de constructor van de klasse Main:
public Main() {
sessionFactory = prepareRelationalDb();
cityDAO = new CityDAO(sessionFactory);
countryDAO = new CountryDAO(sessionFactory);
redisClient = prepareRedisClient();
mapper = new ObjectMapper();
}
Zoals je kunt zien, zijn er niet genoeg methoden en klassen - laten we ze schrijven.
Declareer een pakket com.codegym.dao en voeg er 2 klassen aan toe:
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 kunt u deze 2 klassen importeren in Main. Er ontbreken nog twee methoden:
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;
}
We zijn nog niet bij de radijs, dus de implementatie van de initialisatie van de radijsclient blijft voorlopig een stomp:
private void shutdown() {
if (nonNull(sessionFactory)) {
sessionFactory.close();
}
if (nonNull(redisClient)) {
redisClient.shutdown();
}
}
Ten slotte kunnen we een methode schrijven waarin we alle steden eruit halen:
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;
}
}
De implementatiefunctie is zodanig dat we elk 500 steden krijgen. Dit is nodig omdat er beperkingen zijn aan de hoeveelheid verzonden gegevens. Ja, in ons geval zullen we ze niet bereiken, omdat. we hebben in totaal 4079 steden in de database. Maar in productietoepassingen, wanneer u veel gegevens nodig heeft, wordt deze techniek vaak gebruikt.
En de implementatie van de belangrijkste methode:
public static void main(String[] args) {
Main main = new Main();
List<City> allCities = main.fetchData(main);
main.shutdown();
}
Nu kunnen we onze applicatie voor het eerst in foutopsporing uitvoeren en zien hoe het werkt (of niet werkt - ja, het gebeurt vaak).
![](https://cdn.codegym.cc/images/article/016f2ea1-822d-43eb-a572-39286c2c4830/800.jpeg)
Steden krijgen. Elke stad krijgt een land, als het niet eerder is afgetrokken uit de database voor een andere stad. Laten we ruwweg berekenen hoeveel vragen Hibernate naar de database zal sturen:
- 1 verzoek om het totale aantal steden te achterhalen (nodig om meer dan 500 steden te herhalen om te weten wanneer te stoppen).
- 4079 / 500 = 9 verzoeken (lijst met steden).
- Elke stad krijgt een land, als het niet eerder is afgetrokken. Aangezien er 239 landen in de database staan, levert dit ons 239 zoekopdrachten op.
Total 249 requests
. En we zeiden ook dat we samen met het land onmiddellijk een set talen zouden ontvangen, anders zou er in het algemeen duisternis zijn. Maar het is nog steeds veel, dus laten we het gedrag een beetje aanpassen. Laten we beginnen met reflecties: wat te doen, waar te rennen? Maar serieus - waarom zijn er zoveel verzoeken? Als je naar het aanvraaglogboek kijkt, zien we dat elk land afzonderlijk wordt aangevraagd, dus de eerste eenvoudige oplossing: laten we alle landen samen aanvragen, omdat we van tevoren weten dat we ze allemaal nodig zullen hebben in deze transactie.
Voeg in de methode fetchData() onmiddellijk na het begin van de transactie de volgende regel toe:
List<Country> countries = main.countryDAO.getAll();
We tellen aanvragen:
- 1 - krijg alle landen
- 239 - vraag voor elk land van zijn hoofdstad
- 1 - verzoek om het aantal steden
- 9 - verzoek om lijsten met steden
Total 250
. Het idee is goed, maar het werkte niet. Het probleem is dat het land een band heeft met de hoofdstad (stad) @OneToOne
. En zo'n link wordt standaard meteen geladen ( FetchType.EAGER
). Laten we zeggen FetchType.LAZY
, omdat hoe dan ook, we zullen alle steden later in dezelfde transactie laden.
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "capital")
private City city;
Hoofdsteden worden niet meer apart opgevraagd, maar het aantal aanvragen is niet veranderd. Nu wordt voor elk land de CountryLanguage- lijst opgevraagd door een aparte query . Dat wil zeggen, er is vooruitgang en we gaan in de goede richting. Als u het zich herinnert, stelden de lezingen de "join fetch" -oplossing voor om een entiteit met afhankelijke gegevens in één verzoek aan te vragen door een extra join aan het verzoek toe te voegen. Herschrijf in CountryDAOgetAll()
de HQL-query in de methode om:
"select c from Country c join fetch c.languages"
Launch. We kijken naar het logboek, tellen verzoeken:
- 1 - alle landen met talen
- 1 - aantal steden
- 9 - lijsten met steden.
Total 11
- het is gelukt)) Als je niet alleen al deze tekst leest, maar ook probeert uit te voeren na elke stap van het afstemmen van de applicatie, zou je zelfs de versnelling van de hele applicatie meerdere keren visueel moeten noteren.
Schrijf een methode voor gegevenstransformatie
Laten we een pakket maken com.codegym.redis
waarin we 2 klassen toevoegen: CityCountry (gegevens over de stad en het land waarin deze stad zich bevindt) en Language (gegevens over de taal). Hier zijn alle velden die vaak "per taak" worden opgevraagd in het "remverzoek".
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
}
Voeg in de hoofdmethode, nadat je alle steden hebt gekregen, de regel toe
List<CityCountry>> preparedData = main.transformData(allCities);
En implementeer deze methode:
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());
}
Ik denk dat deze methode voor zichzelf spreekt: we maken gewoon een CityCountry- entiteit en vullen deze met gegevens uit City , Country , CountryLanguage .
Voer de Redis-server uit als een docker-container
Er zijn hier 2 opties. Als u de optionele stap "installeer redis-insight, kijk naar de gegevens die zijn opgeslagen in Redis" uitvoert, dan is de opdracht voor u:
docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest
Als u besluit deze stap over te slaan, doet u het volgende:
docker run -d --name redis -p 6379:6379 redis:latest
Het verschil is dat in de eerste optie poort 8001 wordt doorgestuurd naar de lokale machine, waarmee u verbinding kunt maken met een externe client om te zien wat erin is opgeslagen. En daarom gaf ik betekenisvolle namen, redis-stack
of redis
.
Na de lancering ziet u de lijst met actieve containers. Voer hiervoor de opdracht uit:
docker container ls
En je zult zoiets als dit zien:
![](https://cdn.codegym.cc/images/article/5c44653a-fa4e-4e4c-ac41-c4e5ed26ebf6/1024.jpeg)
Als u een opdracht moet vinden, kunt u ofwel naar de help in de terminal kijken (docker help) of google "how to ..." (bijvoorbeeld docker how to remove running container).
En we hebben ook de initialisatie van de radijsclient genoemd in de hoofdconstructor, maar hebben de methode zelf niet geïmplementeerd. Implementatie toevoegen:
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 is toegevoegd voor educatieve doeleinden, zodat u in het startlogboek kunt zien dat alles in orde is en dat de verbinding via de radijsclient zonder fouten is verlopen.
Schrijf gegevens naar Redis
Voeg een aanroep toe aan de hoofdmethode
main.pushToRedis(preparedData);
Met deze methode implementatie:
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();
}
}
}
}
Hier wordt een synchrone verbinding met de radijsclient geopend en achtereenvolgens wordt elk object van het type CityCountry naar de radijs geschreven. Aangezien de radijs een String key-value store is , wordt de sleutel (city id) geconverteerd naar een string. En de waarde is ook voor de tekenreeks, maar met behulp van ObjectMapper in JSON-indeling.
Het blijft om uit te voeren en te controleren of er geen fouten in het logboek staan. Alles werkte.
Installeer redis-insight, bekijk de data opgeslagen in Redis (optioneel)
Download redis-insight via de link en installeer het. Na het starten toont het onmiddellijk onze radijsinstantie in de docker-container:
![](https://cdn.codegym.cc/images/article/15311a8f-a8af-4a6d-862b-cb10ca70d2ef/800.jpeg)
Als je inlogt, zien we een lijst met alle sleutels:
![](https://cdn.codegym.cc/images/article/97477080-6693-421a-81da-742753a3de5e/800.jpeg)
En u kunt naar elke toets gaan om te zien welke gegevens erop zijn opgeslagen:
![](https://cdn.codegym.cc/images/article/7f0836ac-09ba-4c0a-a759-2f85b4c29947/800.jpeg)
Schrijf een methode om gegevens uit Redis te halen
Voor het testen gebruiken we de volgende test: we krijgen 10 CityCountry records. Elk met een apart verzoek, maar in één verband.
Gegevens van radijs zijn te verkrijgen via onze radijsclient. Om dit te doen, gaan we een methode schrijven die een lijst met id's nodig heeft om te krijgen.
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();
}
}
}
}
De implementatie is, denk ik, intuïtief: we openen een synchrone verbinding en voor elke id krijgen we een JSON String , die we omzetten in het object van het CityCountry- type dat we nodig hebben .
Schrijf een methode om data uit MySQL te halen
Voeg in de klasse CityDAO een methode toe getById(Integer id)
waarin we de stad samen met het land krijgen:
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();
}
Laten we naar analogie van de vorige paragraaf een vergelijkbare methode voor MySQL toevoegen aan de klasse Main:
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();
}
}
Om er zeker van te zijn dat we het volledige object (zonder proxy-stubs) krijgen, vragen we expliciet om een lijst met talen van het land.
Vergelijk de snelheid van het verkrijgen van dezelfde gegevens van MySQL en Redis
Hier zal ik meteen de code van de hoofdmethode geven, en het resultaat dat op mijn lokale computer wordt verkregen.
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();
}
Tijdens het testen is er een functie - gegevens van MySQL worden alleen gelezen, dus het kan niet opnieuw worden gestart tussen lanceringen van onze applicatie. En in Redis zijn ze geschreven.
Hoewel wanneer u een duplicaat voor dezelfde sleutel probeert toe te voegen, de gegevens eenvoudig worden bijgewerkt, raad ik u aan de opdrachten uit te voeren om de container te stoppen docker stop redis-stack
en de container te verwijderen tussen het starten van de applicatie in de terminal docker rm redis-stack
. Breng daarna de container met de radijs weer omhoog docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest
en pas daarna onze applicatie uit.
Hier zijn mijn testresultaten:
![](https://cdn.codegym.cc/images/article/51a91b8a-d86c-433b-ab4a-085c73a0c814/800.jpeg)
In totaal hebben we de prestatie van de respons op het verzoek "frequent remmen" met anderhalf keer verhoogd. En dit houdt rekening met het feit dat we bij het testen niet de snelste deserialisatie via ObjectMapper hebben gebruikt . Als u dit wijzigt in GSON, kunt u hoogstwaarschijnlijk wat meer tijd "winnen".
Op dit moment herinner ik me een grap over een programmeur en tijd: lees en denk na over hoe je je code schrijft en optimaliseert.