आज आपण चौथ्या JRU मॉड्यूलवर अंतिम प्रकल्प करू. ते काय असेल? चला वेगवेगळ्या तंत्रज्ञानासह कार्य करण्याचा प्रयत्न करूया: MySQL, हायबरनेट, रेडिस, डॉकर. आता अधिक विषय.

कार्य: आमच्याकडे स्कीमा (देश-शहर, देशानुसार भाषा) सह रिलेशनल MySQL डेटाबेस आहे. आणि शहराची वारंवार विनंती आहे, जी मंदावली आहे. आम्ही एक उपाय शोधून काढला - वारंवार विनंती केलेला सर्व डेटा Redis वर हलवण्यासाठी (की-व्हॅल्यू प्रकाराच्या मेमरी स्टोरेजमध्ये).

आणि आम्हाला MySQL मध्ये संग्रहित केलेल्या सर्व डेटाची आवश्यकता नाही, परंतु फील्डच्या निवडलेल्या संचाची आवश्यकता आहे. हा प्रकल्प ट्यूटोरियलच्या स्वरूपात असेल. म्हणजेच येथे आपण समस्या मांडू आणि त्वरित सोडवू.

तर, आम्हाला कोणत्या सॉफ्टवेअरची आवश्यकता असेल ते सुरू करूया:

  1. IDEA अल्टिमेट (ज्याची चावी संपली - रोमनला स्लॅकमध्ये लिहा)
  2. वर्कबेंच (किंवा MySQL साठी इतर कोणताही क्लायंट)
  3. डॉकर
  4. redis-insight - पर्यायी

आमची कृती योजना:

  1. डॉकर सेट करा (मी हे ट्युटोरियलमध्ये करणार नाही, कारण प्रत्येक ओएसची स्वतःची वैशिष्ट्ये असतील आणि "विंडोजवर डॉकर कसे स्थापित करावे" यासारख्या प्रश्नांची इंटरनेटवर बरीच उत्तरे आहेत), सर्वकाही कार्य करते हे तपासा.
  2. डॉकर कंटेनर म्हणून MySQL सर्व्हर चालवा.
  3. डंप विस्तृत करा .
  4. Idea मध्ये एक प्रकल्प तयार करा, maven अवलंबित्व जोडा.
  5. लेयर डोमेन बनवा.
  6. MySQL वरून सर्व डेटा मिळविण्यासाठी पद्धत लिहा.
  7. डेटा ट्रान्सफॉर्मेशन पद्धत लिहा (रेडीसमध्ये आम्ही फक्त वारंवार विनंती केलेला डेटा लिहू).
  8. रेडिस सर्व्हर डॉकर कंटेनर म्हणून चालवा.
  9. Redis वर डेटा लिहा.
  10. पर्यायी: रेडिस-इनसाइट स्थापित करा, रेडिसमध्ये संचयित केलेला डेटा पहा.
  11. Redis कडून डेटा मिळविण्याची पद्धत लिहा.
  12. MySQL वरून डेटा मिळविण्यासाठी पद्धत लिहा.
  13. MySQL आणि Redis कडून समान डेटा मिळविण्याच्या गतीची तुलना करा.

डॉकर सेटअप

डॉकर हे ऍप्लिकेशन्स विकसित करण्यासाठी, वितरित करण्यासाठी आणि ऑपरेट करण्यासाठी खुले व्यासपीठ आहे. आम्ही याचा वापर स्थानिक मशीनवर रेडिस स्थापित आणि कॉन्फिगर करण्यासाठी नाही तर तयार प्रतिमा वापरण्यासाठी करू. तुम्ही डॉकरबद्दल इथे अधिक वाचू शकता किंवा ते येथे पाहू शकता . आपण डॉकरशी परिचित नसल्यास, मी फक्त दुसरा दुवा पाहण्याची शिफारस करतो.

तुमच्याकडे डॉकर स्थापित आणि कॉन्फिगर केले असल्याची खात्री करण्यासाठी, कमांड चालवा:docker -v

सर्वकाही ठीक असल्यास, तुम्हाला डॉकर आवृत्ती दिसेल

डॉकर कंटेनर म्हणून MySQL सर्व्हर चालवा

MySQL आणि Redis वरून डेटा परत करण्याच्या वेळेची तुलना करण्यात सक्षम होण्यासाठी, आम्ही डॉकरमध्ये MySQL देखील वापरू. PowerShell मध्ये (किंवा तुम्ही Windows वापरत नसल्यास दुसरे कन्सोल टर्मिनल), कमांड चालवा:

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

या आदेशासह आम्ही काय करत आहोत याचा विचार करा:

  • docker run- प्रतिमा लॉन्च करणे (आणि डाउनलोड करणे, जर ती अद्याप स्थानिक मशीनवर डाउनलोड केली गेली नसेल तर). प्रक्षेपणाच्या परिणामी, आम्हाला एक चालू कंटेनर मिळतो.
  • --name mysql- mysql कंटेनरचे नाव सेट करा.
  • -d- एक ध्वज जो म्हणतो की कंटेनरने कार्य करणे सुरू ठेवावे, जरी आपण टर्मिनल विंडो बंद केली तरीही ज्यामधून हा कंटेनर लॉन्च केला गेला होता.
  • -p 3306:3306- पोर्ट निर्दिष्ट करते. कोलनच्या आधी - स्थानिक मशीनवरील पोर्ट, कोलन नंतर - कंटेनरमधील पोर्ट.
  • -e MYSQL_ROOT_PASSWORD=root– MYSQL_ROOT_PASSWORD पर्यावरण व्हेरिएबल व्हॅल्यू रूटसह कंटेनरमध्ये पास करणे. mysql/ प्रतिमेसाठी विशिष्ट ध्वजांकित करा
  • --restart unless-stopped- वर्तन धोरण सेट करणे (बंद केल्यावर कंटेनर रीस्टार्ट करावा की नाही). न थांबवलेले मूल्य म्हणजे कंटेनर थांबवल्याशिवाय /
  • -v mysql:/var/lib/mysql - व्हॉल्यूम जोडा (माहिती संचयित करण्यासाठी प्रतिमा).
  • mysql:8 - प्रतिमेचे नाव आणि त्याची आवृत्ती.

टर्मिनलमध्ये कमांड कार्यान्वित केल्यानंतर, डॉकर इमेजचे सर्व स्तर डाउनलोड करेल आणि कंटेनर सुरू करेल:

महत्त्वाची सूचना: जर तुम्ही तुमच्या स्थानिक संगणकावर MySQL सेवा म्हणून स्थापित केली असेल आणि ती चालू असेल, तर तुम्हाला स्टार्ट कमांडमध्ये वेगळे पोर्ट नमूद करावे लागेल किंवा ही चालू सेवा थांबवावी लागेल.

डंप विस्तृत करा

डंपचा विस्तार करण्यासाठी, आपल्याला वर्कबेंचमधून डेटाबेसमध्ये नवीन कनेक्शन तयार करण्याची आवश्यकता आहे, जिथे आपण पॅरामीटर्स निर्दिष्ट करता. मी डीफॉल्ट पोर्ट (3306) वापरले, वापरकर्तानाव बदलले नाही (रूट बाय डीफॉल्ट), आणि रूट वापरकर्त्यासाठी (रूट) पासवर्ड सेट केला.

वर्कबेंचमध्ये, करा Data Import/Restoreआणि निवडा Import from Self Contained File. फाइल म्हणून तुम्ही डंप कुठे डाउनलोड केला ते निर्दिष्ट करा . आपल्याला आधी स्कीमा तयार करण्याची आवश्यकता नाही - त्याची निर्मिती डंप फाइलमध्ये समाविष्ट आहे. यशस्वी आयात केल्यानंतर, तुमच्याकडे तीन सारण्यांसह जागतिक स्कीमा असेल:

  1. शहर हे शहरांचे सारणी आहे.
  2. देश - देश सारणी.
  3. देश_भाषा - एक सारणी जे दर्शवते की देशातील किती टक्के लोकसंख्या विशिष्ट भाषा बोलते.

कंटेनर सुरू करताना आम्ही व्हॉल्यूम वापरला असल्याने, mysql कंटेनर थांबवल्यानंतर आणि हटवल्यानंतर आणि स्टार्ट कमांड ( docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root --restart unless-stopped -v mysql:/var/lib/mysql mysql:8) पुन्हा कार्यान्वित केल्यानंतर, डंप पुन्हा उपयोजित करण्याची आवश्यकता नाही - ते आधीच व्हॉल्यूममध्ये तैनात केले आहे.

Idea मध्ये प्रकल्प तयार करा, maven अवलंबित्व जोडा

आयडियामध्ये प्रकल्प कसा तयार करायचा हे तुम्हाला आधीच माहित आहे - आजच्या प्रकल्पातील हा सर्वात सोपा मुद्दा आहे.

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> 

पहिले तीन अवलंबित्व तुम्हाला फार पूर्वीपासून परिचित आहेत.

lettuce-coreRedis सह काम करण्यासाठी उपलब्ध Java क्लायंटपैकी एक आहे.

jackson-databind- ऑब्जेक्टमॅपर वापरण्यासाठी अवलंबित्व (रेडीसमधील स्टोरेजसाठी डेटा बदलण्यासाठी (टाईप स्ट्रिंगचे की-व्हॅल्यू)).

तसेच रिसोर्स फोल्डरमध्ये (src/main/resources) हायबरनेट कार्यान्वित केलेल्या पॅरामीटर्ससह विनंत्या पाहण्यासाठी spy.properties जोडा. फाइल सामग्री:

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 

लेयर डोमेन बनवा

com.codegym.domain पॅकेज तयार करा

आयडियामध्ये टेबल स्ट्रक्चर वापरणे एखाद्या घटकावर टेबल मॅप करताना माझ्यासाठी सोयीचे आहे, म्हणून आयडियामध्ये डेटाबेस कनेक्शन जोडू या.

मी या क्रमाने संस्था तयार करण्याचा सल्ला देतो:

  • देश
  • शहर
  • देशभाषा

आपण स्वतः मॅपिंग करणे इष्ट आहे.

देश वर्ग कोड:

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 मनोरंजक मुद्दे आहेत.

पहिले आहे Continent enam , जे डेटाबेसमध्ये ऑर्डिनल व्हॅल्यू म्हणून साठवले जाते. देशाच्या सारणीच्या संरचनेत, खंड फील्डवरील टिप्पण्यांमध्ये, कोणते संख्यात्मक मूल्य कोणत्या खंडाशी संबंधित आहे ते आपण पाहू शकता.

package com.codegym.domain;

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

दुसरा मुद्दा घटकांचा संच आहेCountryLanguage . येथे एक लिंक आहे @OneToManyजी या मॉड्यूलच्या दुसऱ्या मसुद्यात नव्हती. डीफॉल्टनुसार, देशाच्या घटकाची विनंती करताना हायबरनेट या सेटचे मूल्य खेचणार नाही. परंतु आपल्याला कॅशिंगसाठी रिलेशनल डेटाबेसमधून सर्व मूल्ये वजा करणे आवश्यक असल्याने, FetchType.EAGER.

तिसरे म्हणजे शहराचे मैदान . संप्रेषण @OneToOne- जसे सर्वकाही परिचित आणि समजण्यासारखे आहे. परंतु, जर आपण डेटाबेसमधील परदेशी की रचना पाहिली तर आपण पाहतो की देश (देश) राजधानी (शहर) शी लिंक आहे आणि शहर (शहर) देशाशी (देश) लिंक आहे. एक चक्रीय संबंध आहे.

आम्ही अद्याप यासह काहीही करणार नाही, परंतु जेव्हा आम्ही "MySQL वरून सर्व डेटा मिळविण्यासाठी एक पद्धत लिहा" आयटमवर पोहोचतो, तेव्हा हायबरनेट कोणत्या क्वेरी कार्यान्वित करते ते पाहू, त्यांचा क्रमांक पाहू आणि हा आयटम लक्षात ठेवा. हायबरनेट करा

शहर वर्ग कोड:

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

}

देशभाषा वर्ग कोड:

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
}

MySQL वरून सर्व डेटा मिळविण्यासाठी पद्धत लिहा

मुख्य वर्गात, आम्ही फील्ड घोषित करतो:

private final SessionFactory sessionFactory;
private final RedisClient redisClient;

private final ObjectMapper mapper;

private final CityDAO cityDAO;
private final CountryDAO countryDAO;

आणि त्यांना मुख्य वर्गाच्या कन्स्ट्रक्टरमध्ये प्रारंभ करा:

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

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

जसे आपण पाहू शकता, तेथे पुरेशा पद्धती आणि वर्ग नाहीत - चला ते लिहूया.

com.codegym.dao पॅकेज घोषित करा आणि त्यात 2 वर्ग जोडा:

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

आता तुम्ही हे 2 वर्ग मुख्य मध्ये आयात करू शकता. अद्याप दोन पद्धती गहाळ आहेत:

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

आम्ही अद्याप मुळा पर्यंत पोहोचलो नाही, त्यामुळे मुळा क्लायंटच्या आरंभीची अंमलबजावणी आत्तासाठी अपूर्ण राहील:

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

शेवटी, आम्ही एक पद्धत लिहू शकतो ज्यामध्ये आम्ही सर्व शहरे बाहेर काढतो:

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

अंमलबजावणी वैशिष्ट्य असे आहे की आम्हाला प्रत्येकी 500 शहरे मिळतात. हे आवश्यक आहे कारण प्रसारित डेटाच्या प्रमाणात निर्बंध आहेत. होय, आमच्या बाबतीत, आम्ही त्यांच्याकडे जाणार नाही, कारण. आमच्या डेटाबेसमध्ये एकूण 4079 शहरे आहेत. परंतु प्रॉडक्शन ऍप्लिकेशन्समध्ये, जेव्हा आपल्याला भरपूर डेटा मिळण्याची आवश्यकता असते तेव्हा हे तंत्र अनेकदा वापरले जाते.

आणि मुख्य पद्धतीची अंमलबजावणी:

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

आता आम्ही आमचा अनुप्रयोग प्रथमच डीबगमध्ये चालवू शकतो आणि ते कसे कार्य करते ते पाहू शकतो (किंवा कार्य करत नाही - होय, असे बरेचदा घडते).

शहरे मिळत आहेत. प्रत्येक शहराला एक देश मिळतो, जर तो पूर्वी दुसऱ्या शहराच्या डेटाबेसमधून वजा केला गेला नसेल. हायबरनेट डेटाबेसला किती क्वेरी पाठवेल याची अंदाजे गणना करूया:

  • शहरांची एकूण संख्या शोधण्यासाठी 1 विनंती (केव्हा थांबायचे हे जाणून घेण्यासाठी 500 हून अधिक शहरांची पुनरावृत्ती करणे आवश्यक आहे).
  • 4079 / 500 = 9 विनंत्या (शहरांची यादी).
  • आधी वजा न केल्यास प्रत्येक शहराला एक देश मिळतो. डेटाबेसमध्ये 239 देश असल्याने, हे आम्हाला 239 प्रश्न देईल.

Total 249 requests. आणि आम्ही असेही म्हटले की देशासह आम्हाला त्वरित भाषांचा संच मिळेल, अन्यथा सर्वसाधारणपणे अंधार होईल. पण अजून खूप आहे, म्हणून वर्तनात थोडा बदल करूया. चला प्रतिबिंबांसह प्रारंभ करूया: काय करावे, कुठे धावायचे? पण गंभीरपणे - इतक्या विनंत्या का आहेत. तुम्ही विनंती लॉग पाहिल्यास, आम्ही पाहतो की प्रत्येक देशाला स्वतंत्रपणे विनंती केली आहे, म्हणून पहिला सोपा उपाय: चला सर्व देशांना एकत्र विनंती करूया, कारण आम्हाला आधीच माहित आहे की आम्हाला या व्यवहारात त्या सर्वांची आवश्यकता असेल.

fetchData() पद्धतीमध्ये, व्यवहार सुरू झाल्यानंतर लगेच, खालील ओळ जोडा:

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

आम्ही विनंत्या मोजतो:

  • 1 - सर्व देश मिळवा
  • 239 - त्याच्या राजधानीच्या प्रत्येक देशासाठी क्वेरी
  • 1 - शहरांच्या संख्येसाठी विनंती
  • 9 - शहरांच्या यादीसाठी विनंती

Total 250. कल्पना चांगली आहे, पण ती चालली नाही. समस्या अशी आहे की देशाचा राजधानीशी (शहर) संबंध आहे @OneToOne. आणि अशी लिंक डीफॉल्टनुसार लगेच लोड केली जाते ( FetchType.EAGER). चला FetchType.LAZY, कारण तरीही, आम्ही सर्व शहरे नंतर त्याच व्यवहारात लोड करू.

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

कॅपिटलची यापुढे स्वतंत्रपणे विनंती केली जात नाही, परंतु विनंत्यांची संख्या बदललेली नाही. आता, प्रत्येक देशासाठी, देशभाषा सूची वेगळ्या क्वेरीद्वारे विनंती केली जाते . म्हणजेच प्रगती आहे आणि आपण योग्य दिशेने वाटचाल करत आहोत. जर तुम्हाला आठवत असेल, तर लेक्चर्समध्ये "जॉइन फेच" सोल्यूशन सुचवले आहे जेणेकरून एका विनंतीमध्ये अवलंबून डेटा असलेल्या घटकाची विनंती करण्यासाठी विनंतीमध्ये अतिरिक्त सामील होण्यासाठी जोडले जाईल. CountryDAO मध्ये , HQL क्वेरी या पद्धतीमध्ये पुन्हा लिहा getAll():

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

लाँच करा. आम्ही लॉग पाहतो, विनंत्या मोजतो:

  • 1 - भाषा असलेले सर्व देश
  • 1 - शहरांची संख्या
  • 9 - शहरांच्या याद्या.

Total 11- आम्ही यशस्वी झालो)) जर तुम्ही हा सर्व मजकूर केवळ वाचला नाही तर अनुप्रयोग ट्यूनिंगच्या प्रत्येक चरणानंतर तो चालवण्याचा प्रयत्न केला असेल, तर तुम्ही संपूर्ण अनुप्रयोगाचा प्रवेग अनेक वेळा दृष्यदृष्ट्या लक्षात घ्यावा.

डेटा ट्रान्सफॉर्मेशन पद्धत लिहा

चला एक पॅकेज तयार करू com.codegym.redisज्यामध्ये आम्ही 2 वर्ग जोडू: CityCountry (शहराचा डेटा आणि हे शहर ज्या देशामध्ये आहे) आणि भाषा (भाषेवरील डेटा). येथे "ब्रेकिंग विनंती" मध्ये "कार्यानुसार" विनंती केलेली सर्व फील्ड आहेत.

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
}

मुख्य पद्धतीमध्ये, सर्व शहरे मिळाल्यानंतर, ओळ जोडा

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

आणि ही पद्धत अंमलात आणा:

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

मला वाटते की ही पद्धत स्वयं-स्पष्टीकरणात्मक आहे: आम्ही फक्त एक CityCountry अस्तित्व तयार करतो आणि शहर , देश , देशभाषा मधील डेटाने भरतो .

डॉकर कंटेनर म्हणून रेडिस सर्व्हर चालवा

येथे 2 पर्याय आहेत. जर तुम्ही पर्यायी पायरी "redis-insight install करा, Redis मध्‍ये साठवलेला डेटा पहा", तर कमांड तुमच्यासाठी आहे:

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

तुम्ही ही पायरी वगळण्याचे ठरविल्यास, फक्त:

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

फरक असा आहे की पहिल्या पर्यायामध्ये, पोर्ट 8001 स्थानिक मशीनवर अग्रेषित केला जातो, ज्यामध्ये आपण आत काय संग्रहित केले आहे हे पाहण्यासाठी बाह्य क्लायंटशी कनेक्ट करू शकता. आणि मी अर्थपूर्ण नावे देत असे, म्हणून, redis-stackकिंवा redis.

लॉन्च केल्यानंतर, आपण चालू असलेल्या कंटेनरची सूची पाहू शकता. हे करण्यासाठी, कमांड चालवा:

docker container ls 

आणि तुम्हाला असे काहीतरी दिसेल:

तुम्हाला काही कमांड शोधण्याची आवश्यकता असल्यास, तुम्ही टर्मिनलमधील मदत पाहू शकता (डॉकर मदत) किंवा गुगल "कसे करावे ..." (उदाहरणार्थ, डॉकर कसे काढायचे कंटेनर).

आणि आम्ही मुख्य कन्स्ट्रक्टरमध्ये रॅडिश क्लायंटचे आरंभिकरण देखील म्हटले, परंतु पद्धत स्वतःच लागू केली नाही. अंमलबजावणी जोडा:

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 जोडले गेले जेणेकरून लॉन्च लॉगमध्ये आपण पाहू शकता की सर्व काही ठीक आहे आणि मुळा क्लायंटद्वारे कनेक्शन त्रुटीशिवाय पास झाले आहे.

Redis वर डेटा लिहा

मुख्य पद्धतीमध्ये कॉल जोडा

main.pushToRedis(preparedData); 

या पद्धतीच्या अंमलबजावणीसह:

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

    }
}

येथे, मुळा क्लायंटसह एक समकालिक कनेक्शन उघडले आहे, आणि क्रमाक्रमाने CityCountry प्रकारातील प्रत्येक ऑब्जेक्ट मुळा वर लिहिलेला आहे. मुळा स्ट्रिंग की-व्हॅल्यू स्टोअर असल्याने , की (शहर आयडी) स्ट्रिंगमध्ये रूपांतरित केली जाते. आणि मूल्य स्ट्रिंगसाठी देखील आहे, परंतु JSON फॉरमॅटमध्ये ObjectMapper वापरणे.

लॉगमध्ये कोणत्याही त्रुटी नाहीत हे चालवणे आणि तपासणे बाकी आहे. सर्व काही काम केले.

रेडिस-इनसाइट स्थापित करा, रेडिसमध्ये संग्रहित डेटा पहा (पर्यायी)

लिंकवरून रेडिस-इनसाइट डाउनलोड करा आणि स्थापित करा. सुरू केल्यानंतर, ते ताबडतोब डॉकर कंटेनरमध्ये आमची मुळा दर्शवते:

तुम्ही लॉग इन केल्यास, आम्हाला सर्व की ची सूची दिसेल:

आणि त्यावर कोणता डेटा संग्रहित आहे हे पाहण्यासाठी तुम्ही कोणत्याही कीवर जाऊ शकता:

Redis कडून डेटा मिळविण्याची पद्धत लिहा

चाचणीसाठी, आम्ही खालील चाचणी वापरतो: आम्हाला 10 CityCountry रेकॉर्ड मिळतात. प्रत्येक स्वतंत्र विनंतीसह, परंतु एका कनेक्शनमध्ये.

आमच्या मुळा क्लायंटद्वारे मुळा पासून डेटा मिळवता येतो. हे करण्यासाठी, एक पद्धत लिहूया जी प्राप्त करण्यासाठी आयडीची यादी घेते.

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

अंमलबजावणी, मला वाटते, अंतर्ज्ञानी आहे: आम्ही एक समकालिक कनेक्शन उघडतो, आणि प्रत्येक आयडीसाठी आम्हाला एक JSON स्ट्रिंग मिळते, जी आम्हाला CityCountry प्रकाराच्या ऑब्जेक्टमध्ये रूपांतरित करते .

MySQL वरून डेटा मिळविण्यासाठी पद्धत लिहा

CityDAO वर्गात , एक पद्धत जोडा getById(Integer id)ज्यामध्ये आम्हाला देशासह शहर मिळेल:

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

मागील परिच्छेदाशी साधर्म्य साधून, मुख्य वर्गात MySQL साठी समान पद्धत जोडूया:

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

वैशिष्ट्यांपैकी, संपूर्ण ऑब्जेक्ट (प्रॉक्सी स्टबशिवाय) मिळविण्याची खात्री करण्यासाठी, आम्ही स्पष्टपणे देशातील भाषांच्या सूचीची विनंती करतो.

MySQL आणि Redis कडून समान डेटा मिळविण्याच्या गतीची तुलना करा

येथे मी ताबडतोब मुख्य पद्धतीचा कोड देईन आणि माझ्या स्थानिक संगणकावर प्राप्त होणारा परिणाम.

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

चाचणी करताना, एक वैशिष्ट्य आहे - MySQL कडील डेटा केवळ वाचला जातो, म्हणून तो आमच्या अनुप्रयोगाच्या लाँच दरम्यान रीस्टार्ट केला जाऊ शकत नाही. आणि रेडिसमध्ये ते लिहिलेले आहेत.

जरी तुम्ही त्याच कीसाठी डुप्लिकेट जोडण्याचा प्रयत्न करता तेव्हा, डेटा फक्त अद्यतनित केला जाईल, मी शिफारस करतो की तुम्ही कंटेनर थांबवण्यासाठी कमांड चालवा docker stop redis-stackआणि टर्मिनलमध्ये अॅप्लिकेशन लॉन्च दरम्यान कंटेनर हटवा docker rm redis-stack. त्यानंतर, मुळा सह कंटेनर पुन्हा वाढवा docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latestआणि त्यानंतरच आमचा अर्ज कार्यान्वित करा.

येथे माझे चाचणी परिणाम आहेत:

एकूण, आम्ही "ब्रेकिंग वारंवार" विनंतीला प्रतिसादाच्या कामगिरीमध्ये दीड पट वाढ केली आहे. आणि हे वस्तुस्थिती लक्षात घेत आहे की चाचणीमध्ये आम्ही ऑब्जेक्टमॅपरद्वारे सर्वात वेगवान डीसीरियलायझेशन वापरले नाही . तुम्ही ते GSON मध्ये बदलल्यास, बहुधा, तुम्ही आणखी थोडा वेळ "जिंकू" शकता.

या क्षणी, मला प्रोग्रामर आणि वेळेबद्दल एक विनोद आठवतो: आपला कोड कसा लिहायचा आणि ऑप्टिमाइझ कसा करायचा याबद्दल वाचा आणि विचार करा.