CodeGym/Cours Java/All lectures for FR purposes/Projet connexe : SQL, JDBC et Hibernate

Projet connexe : SQL, JDBC et Hibernate

Disponible

Aujourd'hui, nous allons faire le projet final sur le quatrième module JRU. Qu'est-ce qu'il serait? Essayons de travailler avec différentes technologies : MySQL, Hibernate, Redis, Docker. Maintenant plus de sujet.

Tâche : nous avons une base de données relationnelle MySQL avec un schéma (pays-ville, langue par pays). Et il y a une demande fréquente de la ville, qui ralentit. Nous avons trouvé une solution - déplacer toutes les données fréquemment demandées vers Redis (dans un stockage en mémoire de type clé-valeur).

Et nous n'avons pas besoin de toutes les données stockées dans MySQL, mais seulement d'un ensemble sélectionné de champs. Le projet se présentera sous la forme d'un tutoriel. Autrement dit, ici, nous allons soulever le problème et le résoudre immédiatement.

Alors, commençons par le logiciel dont nous aurons besoin :

  1. IDEA Ultimate (qui n'a plus de clé - écrivez à Roman in the Slack)
  2. Workbench (ou tout autre client pour MySQL)
  3. Docker
  4. redis-insight - facultatif

Notre plan d'actions :

  1. Configurez docker (je ne le ferai pas dans le tutoriel, car chaque OS aura ses propres caractéristiques et il y a beaucoup de réponses sur Internet à des questions comme "comment installer docker sur windows"), vérifiez que tout fonctionne.
  2. Exécutez le serveur MySQL en tant que conteneur Docker.
  3. Développez le vidage .
  4. Créez un projet dans Idea, ajoutez des dépendances maven.
  5. Créer un domaine de couche.
  6. Écrivez une méthode pour obtenir toutes les données de MySQL.
  7. Écrivez une méthode de transformation de données (dans Redis, nous n'écrirons que les données fréquemment demandées).
  8. Exécutez le serveur Redis en tant que conteneur Docker.
  9. Écrire des données sur Redis.
  10. Facultatif : installez redis-insight, examinez les données stockées dans Redis.
  11. Écrivez une méthode pour obtenir des données de Redis.
  12. Écrivez une méthode pour obtenir des données de MySQL.
  13. Comparez la vitesse d'obtention des mêmes données depuis MySQL et Redis.

Configuration Docker

Docker est une plate-forme ouverte pour le développement, la livraison et l'exploitation d'applications. Nous l'utiliserons pour ne pas installer et configurer Redis sur la machine locale, mais pour utiliser une image toute faite. Vous pouvez en savoir plus sur docker ici ou le voir ici . Si vous n'êtes pas familier avec docker, je vous recommande de regarder uniquement le deuxième lien.

Pour vous assurer que docker est installé et configuré, exécutez la commande :docker -v

Si tout est OK, vous verrez la version docker

Exécuter le serveur MySQL en tant que conteneur Docker

Afin de pouvoir comparer le temps de retour des données de MySQL et Redis, nous utiliserons également MySQL dans le docker. Dans PowerShell (ou un autre terminal de console si vous n'utilisez pas Windows), exécutez la commande :

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

Considérez ce que nous faisons avec cette commande :

  • docker run– lancer (et télécharger, si elle n'a pas encore été téléchargée sur la machine locale) l'image. À la suite du lancement, nous obtenons un conteneur en cours d'exécution.
  • --name mysql- définir le nom du conteneur mysql.
  • -d- un drapeau indiquant que le conteneur doit continuer à fonctionner, même si vous fermez la fenêtre du terminal à partir de laquelle ce conteneur a été lancé.
  • -p 3306:3306- spécifie les ports. Avant les deux-points - le port sur la machine locale, après les deux-points - le port dans le conteneur.
  • -e MYSQL_ROOT_PASSWORD=root– en passant la variable d'environnement MYSQL_ROOT_PASSWORD avec la valeur root au conteneur. Flag spécifique à l'image mysql/
  • --restart unless-stopped- définir la politique de comportement (si le conteneur doit être redémarré lorsqu'il est fermé). La valeur à moins d'être arrêté signifie qu'il faut toujours redémarrer, sauf lorsque le conteneur a été arrêté /
  • -v mysql:/var/lib/mysql – ajouter du volume (image pour stocker des informations).
  • mysql:8 – le nom de l'image et sa version.

Après avoir exécuté la commande dans le terminal, le docker téléchargera toutes les couches de l'image et démarrera le conteneur :

Remarque importante : si MySQL est installé en tant que service sur votre ordinateur local et qu'il est en cours d'exécution, vous devez spécifier un port différent dans la commande de démarrage ou arrêter ce service en cours d'exécution.

Développer le vidage

Pour développer le vidage, vous devez créer une nouvelle connexion à la base de données à partir de Workbench, où vous spécifiez les paramètres. J'ai utilisé le port par défaut (3306), je n'ai pas changé le nom d'utilisateur (root par défaut) et défini le mot de passe pour l'utilisateur root (root).

Dans Workbench, faites Data Import/Restoreet sélectionnez Import from Self Contained File. Spécifiez où vous avez téléchargé le vidage sous forme de fichier . Vous n'avez pas besoin de créer le schéma au préalable - sa création est incluse dans le fichier de vidage. Après une importation réussie, vous aurez un schéma mondial avec trois tables :

  1. city ​​est un tableau des villes.
  2. pays - tableau des pays.
  3. country_language - une table qui indique quel pourcentage de la population du pays parle une langue particulière.

Puisque nous avons utilisé un volume lors du démarrage du conteneur, après avoir arrêté et même supprimé le conteneur mysql et ré-exécuté la commande start ( docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root --restart unless-stopped -v mysql:/var/lib/mysql mysql:8), il n'y aura pas besoin de déployer à nouveau le dump - il est déjà déployé dans le volume.

Créer un projet dans Idea, ajouter des dépendances maven

Vous savez déjà comment créer un projet dans l'idée - c'est le point le plus simple du projet d'aujourd'hui.

Ajoutez des dépendances au fichier pom :


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

Les trois premières dépendances vous sont familières depuis longtemps.

lettuce-coreest l'un des clients Java disponibles pour travailler avec Redis.

jackson-databind– dépendance pour l'utilisation d'ObjectMapper (pour transformer les données pour le stockage dans Redis (clé-valeur de type String)).

Toujours dans le dossier des ressources (src/main/resources), ajoutez spy.properties pour afficher les requêtes avec les paramètres exécutés par Hibernate. Contenu du fichier :

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 

Créer un domaine de couche

Créer le package com.codegym.domain

Il est pratique pour moi lors du mappage de tables sur une entité d'utiliser la structure de table dans l'idée, alors ajoutons une connexion à la base de données dans l'idée.

Je suggère de créer des entités dans cet ordre :

  • Pays
  • Ville
  • La langue du pays

Il est souhaitable que vous effectuiez vous-même le mappage.

Code de classe de pays :

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

}

Il y a 3 points intéressants dans le code.

Le premier est le Continent enam , qui est stocké dans la base de données sous forme de valeurs ordinales. Dans la structure du tableau des pays, dans les commentaires du champ continent, vous pouvez voir quelle valeur numérique correspond à quel continent.

package com.codegym.domain;

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

Le deuxième point est un ensemble d'entitésCountryLanguage . Voici un lien @OneToManyqui n'était pas dans la deuxième version de ce module. Par défaut, Hibernate ne tirera pas la valeur de cet ensemble lors de la demande d'une entité de pays. Mais comme nous devons soustraire toutes les valeurs de la base de données relationnelle pour la mise en cache, le fichier FetchType.EAGER.

Le troisième est le champ de la ville . Communication @OneToOne- comme tout est familier et compréhensible. Mais, si nous regardons la structure de la clé étrangère dans la base de données, nous voyons que le pays (pays) a un lien vers la capitale (ville) et la ville (ville) a un lien vers le pays (pays). Il existe une relation cyclique.

Nous ne ferons rien avec cela pour le moment, mais lorsque nous arriverons à l'élément "Écrire une méthode pour obtenir toutes les données de MySQL", voyons quelles requêtes Hibernate exécute, regardons leur nombre et rappelons-nous cet élément.

Code de classe de ville :

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

}

Code de classe CountryLanguage :

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
}

Écrire une méthode pour obtenir toutes les données de MySQL

Dans la classe Main, nous déclarons les champs :

private final SessionFactory sessionFactory;
private final RedisClient redisClient;

private final ObjectMapper mapper;

private final CityDAO cityDAO;
private final CountryDAO countryDAO;

et initialisez-les dans le constructeur de la classe Main :

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

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

Comme vous pouvez le voir, il n'y a pas assez de méthodes et de classes - écrivons-les.

Déclarez un package com.codegym.dao et ajoutez-y 2 classes :

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

Vous pouvez maintenant importer ces 2 classes dans Main. Manque encore deux méthodes :

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

Nous n'avons pas encore atteint le radis, donc la mise en œuvre de l'initialisation du client radis restera pour l'instant un stub :

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

Enfin, nous pouvons écrire une méthode dans laquelle nous extrayons toutes les villes :

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

La fonctionnalité de mise en œuvre est telle que nous obtenons 500 villes chacune. Cela est nécessaire car il existe des restrictions sur la quantité de données transmises. Oui, dans notre cas, nous n'y parviendrons pas, car. nous avons un total de 4079 villes dans la base de données. Mais dans les applications de production, lorsque vous avez besoin d'obtenir beaucoup de données, cette technique est souvent utilisée.

Et la mise en œuvre de la méthode principale :

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

Maintenant, nous pouvons exécuter notre application en débogage pour la première fois et voir comment cela fonctionne (ou ne fonctionne pas - oui, cela arrive souvent).

Les villes gagnent. Chaque ville obtient un pays, s'il n'a pas été précédemment soustrait de la base de données pour une autre ville. Calculons approximativement le nombre de requêtes qu'Hibernate enverra à la base de données :

  • 1 requête pour connaître le nombre total de villes (nécessaire pour parcourir plus de 500 villes afin de savoir quand s'arrêter).
  • 4079 / 500 = 9 requêtes (liste des villes).
  • Chaque ville obtient un pays, s'il n'a pas été soustrait plus tôt. Puisqu'il y a 239 pays dans la base de données, cela nous donnera 239 requêtes.

Total 249 requests. Et nous avons également dit qu'avec le pays, nous recevrons immédiatement un ensemble de langues, sinon il y aurait des ténèbres en général. Mais c'est encore beaucoup, alors modifions un peu le comportement. Commençons par les réflexions : que faire, où courir ? Mais sérieusement - pourquoi y a-t-il tant de demandes. Si vous regardez le journal des demandes, nous voyons que chaque pays est demandé séparément, donc la première solution simple : demandons tous les pays ensemble, car nous savons à l'avance que nous aurons besoin de tous dans cette transaction.

Dans la méthode fetchData(), immédiatement après le début de la transaction, ajoutez la ligne suivante :

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

Nous comptons les demandes :

  • 1 - obtenir tous les pays
  • 239 - requête pour chaque pays de sa capitale
  • 1 - demande du nombre de villes
  • 9 - demande de listes de villes

Total 250. L'idée est bonne, mais ça n'a pas marché. Le problème est que le pays a un lien avec la capitale (ville) @OneToOne. Et un tel lien est chargé immédiatement par défaut ( FetchType.EAGER). Disons FetchType.LAZY, parce que de toute façon, nous chargerons toutes les villes plus tard dans la même transaction.

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

Les capitales ne sont plus demandées séparément, mais le nombre de demandes n'a pas changé. Désormais, pour chaque pays, la liste CountryLanguage est demandée par une requête distincte . C'est-à-dire qu'il y a des progrès et que nous allons dans la bonne direction. Si vous vous en souvenez, les cours suggéraient la solution "join fetch" afin de demander une entité avec des données dépendantes dans une requête en ajoutant une jointure supplémentaire à la requête. Dans CountryDAO , réécrivez la requête HQL dans la méthode getAll()pour :

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

Lancement. Nous regardons le journal, comptons les requêtes :

  • 1 - tous les pays avec des langues
  • 1 - nombre de villes
  • 9 - listes de villes.

Total 11- nous avons réussi)) Si vous avez non seulement lu tout ce texte, mais également essayé de l'exécuter après chaque étape de réglage de l'application, vous devriez même noter visuellement l'accélération de l'ensemble de l'application plusieurs fois.

Écrire une méthode de transformation de données

Créons un package com.codegym.redisdans lequel nous ajoutons 2 classes : CityCountry (données sur la ville et le pays dans lequel se situe cette ville) et Language (données sur la langue). Voici tous les champs qui sont souvent demandés « par tâche » dans la « demande de freinage ».

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
}

Dans la méthode principale, après avoir obtenu toutes les villes, ajoutez la ligne

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

Et implémentez cette méthode:

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

Je pense que cette méthode est explicite : nous créons simplement une entité CityCountry et la remplissons avec les données de City , Country , CountryLanguage .

Exécutez le serveur Redis en tant que conteneur Docker

Il y a 2 options ici. Si vous effectuez l'étape facultative "installer redis-insight, regardez les données stockées dans Redis", alors la commande est pour vous :

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

Si vous décidez de sauter cette étape, alors simplement :

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

La différence est que dans la première option, le port 8001 est transmis à la machine locale, à laquelle vous pouvez vous connecter avec un client externe pour voir ce qui est stocké à l'intérieur. Et j'avais l'habitude de donner des noms significatifs, donc, redis-stackou redis.

Après le lancement, vous pouvez voir la liste des conteneurs en cours d'exécution. Pour ce faire, exécutez la commande :

docker container ls 

Et vous verrez quelque chose comme ceci :

Si vous avez besoin de trouver une commande, vous pouvez soit consulter l'aide dans le terminal (docker help) ou google "how to ..." (par exemple, docker comment supprimer le conteneur en cours d'exécution).

Et nous avons également appelé l'initialisation du client radis dans le constructeur principal, mais n'avons pas implémenté la méthode elle-même. Ajouter l'implémentation :

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 a été ajouté à des fins éducatives afin que dans le journal de lancement, vous puissiez voir que tout va bien et que la connexion via le client radis s'est déroulée sans erreur.

Écrire des données sur Redis

Ajouter un appel à la méthode main

main.pushToRedis(preparedData); 

Avec cette implémentation de méthode :

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

    }
}

Ici, une connexion synchrone est ouverte avec le client radis, et séquentiellement chaque objet de type CityCountry est écrit sur le radis. Puisque le radis est un String key-value store , la clé (city id) est convertie en chaîne. Et la valeur est également à la chaîne, mais en utilisant ObjectMapper au format JSON.

Il reste à exécuter et à vérifier qu'il n'y a pas d'erreurs dans le journal. Tout a fonctionné.

Installez redis-insight, regardez les données stockées dans Redis (facultatif)

Téléchargez redis-insight à partir du lien et installez-le. Après le démarrage, il affiche immédiatement notre instance de radis dans le conteneur Docker :

Si vous vous connectez, nous verrons une liste de toutes les clés :

Et vous pouvez accéder à n'importe quelle clé pour voir quelles données y sont stockées :

Écrire une méthode pour obtenir des données de Redis

Pour les tests, nous utilisons le test suivant : nous obtenons 10 enregistrements CityCountry. Chacun avec une demande distincte, mais dans une connexion.

Les données du radis peuvent être obtenues via notre client radis. Pour ce faire, écrivons une méthode qui prend une liste d'identifiants à obtenir.

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

L'implémentation, je pense, est intuitive : nous ouvrons une connexion synchrone, et pour chaque identifiant nous obtenons un JSON String , que nous convertissons en objet du type CityCountry dont nous avons besoin .

Écrire une méthode pour obtenir des données de MySQL

Dans la classe CityDAO , ajoutez une méthode getById(Integer id)dans laquelle nous obtiendrons la ville avec le pays :

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

Par analogie avec le paragraphe précédent, ajoutons une méthode similaire pour MySQL à la classe 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();
    }
}

Parmi les fonctionnalités, afin d'être sûr d'obtenir l'objet complet (sans les stubs proxy), nous demandons explicitement une liste des langues du pays.

Comparez la vitesse d'obtention des mêmes données depuis MySQL et Redis

Ici, je vais donner immédiatement le code de la méthode principale et le résultat obtenu sur mon ordinateur local.

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

Lors des tests, il existe une fonctionnalité - les données de MySQL sont uniquement lues, elles ne peuvent donc pas être redémarrées entre les lancements de notre application. Et dans Redis, ils sont écrits.

Bien que lorsque vous essayez d'ajouter un doublon pour la même clé, les données seront simplement mises à jour, je vous recommande d'exécuter les commandes pour arrêter le conteneur docker stop redis-stacket supprimer le conteneur entre les lancements d'application dans le terminal docker rm redis-stack. Après cela, soulevez à nouveau le récipient avec le radis docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latestet seulement après cela, exécutez notre application.

Voici mes résultats de test :

Au total, nous avons obtenu une augmentation des performances de la réponse à la demande "freinage fréquent" d'une fois et demie. Et cela tient compte du fait que lors des tests, nous n'avons pas utilisé la désérialisation la plus rapide via ObjectMapper . Si vous le changez en GSON, vous pouvez très probablement "gagner" un peu plus de temps.

À ce moment, je me souviens d'une blague sur un programmeur et le temps : lisez et réfléchissez à la façon d'écrire et d'optimiser votre code.

Commentaires
  • Populaires
  • Nouveau
  • Anciennes
Tu dois être connecté(e) pour laisser un commentaire
Cette page ne comporte pas encore de commentaires