Ngayon ay gagawin natin ang huling proyekto sa ikaapat na JRU module. Ano ito? Subukan nating gumamit ng iba't ibang teknolohiya: MySQL, Hibernate, Redis, Docker. Ngayon mas maraming paksa.

Gawain: mayroon kaming relational MySQL database na may schema (bansa-lungsod, wika ayon sa bansa). At mayroong isang madalas na kahilingan ng lungsod, na bumabagal. Nakagawa kami ng solusyon - upang ilipat ang lahat ng data na madalas na hinihiling sa Redis (sa memory storage ng key-value type).

At hindi namin kailangan ang lahat ng data na nakaimbak sa MySQL, ngunit isang napiling hanay ng mga patlang lamang. Ang proyekto ay magiging sa anyo ng isang tutorial. Ibig sabihin, dito natin itataas ang problema at agad itong lutasin.

Kaya, magsimula tayo sa kung anong software ang kakailanganin natin:

  1. IDEA Ultimate (na naubusan ng susi - sumulat kay Roman sa Slack)
  2. Workbench (o anumang iba pang kliyente para sa MySQL)
  3. Docker
  4. redis-insight - opsyonal

Ang aming plano sa pagkilos:

  1. I-set up ang docker (hindi ko gagawin ito sa tutorial, dahil ang bawat OS ay magkakaroon ng sarili nitong mga katangian at maraming mga sagot sa Internet sa mga tanong tulad ng "paano mag-install ng docker sa mga bintana"), tingnan kung gumagana ang lahat.
  2. Patakbuhin ang MySQL server bilang isang docker container.
  3. Palawakin ang dump .
  4. Lumikha ng isang proyekto sa Ideya, magdagdag ng maven dependencies.
  5. Gumawa ng layer domain.
  6. Sumulat ng isang paraan upang makuha ang lahat ng data mula sa MySQL.
  7. Sumulat ng paraan ng pagbabagong-anyo ng data (sa Redis isusulat lang namin ang data na madalas na hinihiling).
  8. Patakbuhin ang Redis server bilang lalagyan ng docker.
  9. Sumulat ng data sa Redis.
  10. Opsyonal: i-install ang redis-insight, tingnan ang data na nakaimbak sa Redis.
  11. Sumulat ng isang paraan para sa pagkuha ng data mula sa Redis.
  12. Sumulat ng isang paraan para sa pagkuha ng data mula sa MySQL.
  13. Ihambing ang bilis ng pagkuha ng parehong data mula sa MySQL at Redis.

Pag-setup ng docker

Ang Docker ay isang bukas na platform para sa pagbuo, paghahatid at pagpapatakbo ng mga application. Gagamitin namin ito upang hindi mai-install at i-configure ang Redis sa lokal na makina, ngunit upang gumamit ng isang yari na imahe. Maaari kang magbasa nang higit pa tungkol sa docker dito o tingnan ito dito . Kung hindi ka pamilyar sa docker, inirerekumenda kong tingnan lamang ang pangalawang link.

Upang matiyak na na-install at na-configure mo ang docker, patakbuhin ang command:docker -v

Kung OK ang lahat, makikita mo ang bersyon ng docker

Patakbuhin ang MySQL server bilang lalagyan ng docker

Upang maihambing ang oras ng pagbabalik ng data mula sa MySQL at Redis, gagamitin din namin ang MySQL sa docker. Sa PowerShell (o ibang console terminal kung hindi ka gumagamit ng Windows), patakbuhin ang command:

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

Isaalang-alang kung ano ang ginagawa namin sa utos na ito:

  • docker run– paglulunsad (at pag-download, kung hindi pa ito nai-download sa lokal na makina) ng imahe. Bilang resulta ng paglulunsad, nakakakuha kami ng tumatakbong lalagyan.
  • --name mysql- itakda ang pangalan ng mysql container.
  • -d- isang flag na nagsasabing dapat na patuloy na gumana ang container, kahit na isara mo ang terminal window kung saan inilunsad ang container na ito.
  • -p 3306:3306- tumutukoy sa mga port. Bago ang colon - ang port sa lokal na makina, pagkatapos ng colon - ang port sa lalagyan.
  • -e MYSQL_ROOT_PASSWORD=root– pagpasa sa environment variable na MYSQL_ROOT_PASSWORD na may value root sa container. I-flag na partikular sa mysql/ image
  • --restart unless-stopped- pagtatakda ng patakaran sa pag-uugali (kung dapat i-restart ang lalagyan kapag isinara). Ang ibig sabihin ng unless-stop na value ay palaging i-restart, maliban kung ang lalagyan ay itinigil /
  • -v mysql:/var/lib/mysql - magdagdag ng lakas ng tunog (larawan para sa pag-iimbak ng impormasyon).
  • mysql:8 – ang pangalan ng larawan at ang bersyon nito.

Pagkatapos isagawa ang utos sa terminal, ida-download ng docker ang lahat ng mga layer ng imahe at sisimulan ang lalagyan:

Mahalagang tala: kung mayroon kang MySQL na naka-install bilang isang serbisyo sa iyong lokal na computer at ito ay tumatakbo, kailangan mong tukuyin ang ibang port sa start command, o ihinto ang tumatakbong serbisyong ito.

Palawakin ang dump

Upang palawakin ang dump, kailangan mong lumikha ng bagong koneksyon sa database mula sa Workbench, kung saan tinukoy mo ang mga parameter. Ginamit ko ang default na port (3306), hindi binago ang username (root bilang default), at itinakda ang password para sa root user (root).

Sa Workbench, gawin Data Import/Restoreat piliin ang Import from Self Contained File. Tukuyin kung saan mo na-download ang dump bilang isang file . Hindi mo kailangang likhain muna ang schema - ang paggawa nito ay kasama sa dump file. Pagkatapos ng matagumpay na pag-import, magkakaroon ka ng world schema na may tatlong talahanayan:

  1. ang lungsod ay isang talahanayan ng mga lungsod.
  2. bansa - talahanayan ng bansa.
  3. country_language - isang talahanayan na nagsasaad kung ilang porsyento ng populasyon sa bansa ang nagsasalita ng isang partikular na wika.

Dahil gumamit kami ng volume kapag sinimulan ang lalagyan, pagkatapos ihinto at kahit na tanggalin ang mysql container at muling isagawa ang start command ( docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root --restart unless-stopped -v mysql:/var/lib/mysql mysql:8), hindi na kailangang i-deploy muli ang dump - ito ay naka-deploy na sa volume.

Lumikha ng proyekto sa Ideya, magdagdag ng maven dependencies

Alam mo na kung paano lumikha ng isang proyekto sa Ideya - ito ang pinakamadaling punto sa proyekto ngayon.

Magdagdag ng mga dependency sa pom file:


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

Ang unang tatlong dependency ay matagal nang pamilyar sa iyo.

lettuce-coreay isa sa mga magagamit na kliyente ng Java para sa pagtatrabaho sa Redis.

jackson-databind– dependency para sa paggamit ng ObjectMapper (upang baguhin ang data para sa imbakan sa Redis (key-value ng uri ng String)).

Gayundin sa folder ng mapagkukunan (src/main/resources) magdagdag ng spy.properties upang tingnan ang mga kahilingan na may mga parameter na isinasagawa ng Hibernate. Mga nilalaman ng file:

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 

Gumawa ng layer domain

Lumikha ng package com.codegym.domain

Maginhawa para sa akin kapag nagmamapa ng mga talahanayan sa isang entity upang gamitin ang istraktura ng talahanayan sa Ideya, kaya magdagdag tayo ng koneksyon sa database sa Ideya.

Iminumungkahi ko ang paglikha ng mga entity sa ganitong pagkakasunud-sunod:

  • Bansa
  • lungsod
  • Wika ng Bansa

Ito ay kanais-nais na ikaw mismo ang magsagawa ng pagmamapa.

Code ng klase ng bansa:

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

}

Mayroong 3 kawili-wiling puntos sa code.

Ang una ay ang Continent enam , na nakaimbak sa database bilang mga ordinal na halaga. Sa istraktura ng talahanayan ng bansa, sa mga komento sa field ng kontinente, makikita mo kung aling numerical value ang tumutugma sa kung aling kontinente.

package com.codegym.domain;

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

Ang pangalawang punto ay isang set ng mga entityCountryLanguage . Narito ang isang link @OneToManyna wala sa pangalawang draft ng modyul na ito. Bilang default, hindi kukunin ng Hibernate ang halaga ng set na ito kapag humihiling ng entity ng bansa. Ngunit dahil kailangan nating ibawas ang lahat ng mga halaga mula sa relational database para sa pag-cache, ang FetchType.EAGER.

Ang pangatlo ay ang larangan ng lungsod . Komunikasyon @OneToOne- tulad ng lahat ay pamilyar at naiintindihan. Ngunit, kung titingnan natin ang foreign key structure sa database, makikita natin na ang bansa (bansa) ay may link sa kabisera (city), at ang lungsod (city) ay may link sa bansa (country). Mayroong isang paikot na relasyon.

Wala pa kaming gagawin dito, ngunit kapag nakarating na kami sa item na "Sumulat ng paraan para sa pagkuha ng lahat ng data mula sa MySQL," tingnan natin kung anong mga query ang isinasagawa ng Hibernate, tingnan ang kanilang numero, at tandaan ang item na ito.

Code ng klase ng lungsod:

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 ng klase ng 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
}

Sumulat ng isang paraan upang makuha ang lahat ng data mula sa MySQL

Sa Pangunahing klase, ipinapahayag namin ang mga patlang:

private final SessionFactory sessionFactory;
private final RedisClient redisClient;

private final ObjectMapper mapper;

private final CityDAO cityDAO;
private final CountryDAO countryDAO;

at simulan ang mga ito sa tagabuo ng Pangunahing klase:

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

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

Tulad ng nakikita mo, walang sapat na mga pamamaraan at klase - isulat natin ang mga ito.

Magdeklara ng package com.codegym.dao at magdagdag ng 2 klase dito:

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

Ngayon ay maaari mong i-import ang 2 klase na ito sa Main. Nawawala pa rin ang dalawang pamamaraan:

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

Hindi pa namin naabot ang labanos, kaya ang pagpapatupad ng pagsisimula ng kliyente ng labanos ay mananatiling usbong sa ngayon:

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

Sa wakas, maaari tayong magsulat ng isang paraan kung saan hinuhugot natin ang lahat ng mga lungsod:

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

Ang tampok na pagpapatupad ay tulad na nakakakuha kami ng 500 lungsod bawat isa. Ito ay kinakailangan dahil may mga paghihigpit sa dami ng ipinadalang data. Oo, sa aming kaso, hindi kami makakarating sa kanila, dahil. mayroon kaming kabuuang 4079 na lungsod sa database. Ngunit sa mga aplikasyon ng produksyon, kapag kailangan mong makakuha ng maraming data, madalas na ginagamit ang pamamaraang ito.

At ang pagpapatupad ng pangunahing pamamaraan:

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

Ngayon ay maaari na naming patakbuhin ang aming application sa pag-debug sa unang pagkakataon at makita kung paano ito gumagana (o hindi gumagana - oo, madalas itong nangyayari).

Ang mga lungsod ay nakakakuha. Ang bawat lungsod ay nakakakuha ng isang bansa, kung hindi pa ito nabawasan sa database para sa isa pang lungsod. Halos kalkulahin natin kung gaano karaming mga query ang ipapadala ng Hibernate sa database:

  • 1 kahilingan para malaman ang kabuuang bilang ng mga lungsod (kailangan na umulit sa mahigit 500 lungsod para malaman kung kailan titigil).
  • 4079 / 500 = 9 na kahilingan (listahan ng mga lungsod).
  • Ang bawat lungsod ay nakakakuha ng isang bansa, kung hindi ito nabawas nang mas maaga. Dahil mayroong 239 na bansa sa database, magbibigay ito sa amin ng 239 na query.

Total 249 requests. At sinabi rin namin na kasama ang bansa ay agad kaming makakatanggap ng isang set ng mga wika, kung hindi ay magkakaroon ng kadiliman sa pangkalahatan. Pero marami pa rin, kaya medyo sabunutan natin ang ugali. Magsimula tayo sa mga pagmuni-muni: ano ang gagawin, kung saan tatakbo? Pero seryoso - bakit ang daming request. Kung titingnan mo ang log ng kahilingan, nakikita namin na ang bawat bansa ay hinihiling nang hiwalay, kaya ang unang simpleng solusyon: sabay nating hilingin ang lahat ng mga bansa, dahil alam natin nang maaga na kakailanganin natin ang lahat ng ito sa transaksyong ito.

Sa paraan ng fetchData(), kaagad pagkatapos ng pagsisimula ng transaksyon, idagdag ang sumusunod na linya:

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

Nagbibilang kami ng mga kahilingan:

  • 1 - makuha ang lahat ng mga bansa
  • 239 - query para sa bawat bansa ng kabisera nito
  • 1 - kahilingan para sa bilang ng mga lungsod
  • 9 - kahilingan para sa mga listahan ng mga lungsod

Total 250. Maganda ang ideya, ngunit hindi ito gumana. Ang problema ay ang bansa ay may koneksyon sa kabisera (lungsod) @OneToOne. At ang naturang link ay na-load kaagad bilang default ( FetchType.EAGER). Ilagay natin FetchType.LAZY, dahil anyway, ilo-load namin ang lahat ng lungsod mamaya sa parehong transaksyon.

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

Ang mga kapital ay hindi na hinihiling nang hiwalay, ngunit ang bilang ng mga kahilingan ay hindi nagbago. Ngayon, para sa bawat bansa, ang listahan ng CountryLanguage ay hinihiling ng isang hiwalay na query . Ibig sabihin, may pag-unlad, at tayo ay gumagalaw sa tamang direksyon. Kung naaalala mo, iminungkahi ng mga lecture ang "join fetch" na solusyon upang humiling ng entity na may nakadependeng data sa isang kahilingan sa pamamagitan ng pagdaragdag ng karagdagang pagsali sa kahilingan. Sa CountryDAO , muling isulat ang HQL query sa pamamaraan getAll()upang:

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

Ilunsad. Tinitingnan namin ang log, bilang ng mga kahilingan:

  • 1 - lahat ng mga bansa na may mga wika
  • 1 - bilang ng mga lungsod
  • 9 - mga listahan ng mga lungsod.

Total 11- nagtagumpay kami)) Kung hindi mo lamang nabasa ang lahat ng tekstong ito, ngunit sinubukan mo ring patakbuhin ito pagkatapos ng bawat hakbang ng pag-tune ng application, dapat mo ring biswal na tandaan ang pagbilis ng buong application nang maraming beses.

Sumulat ng paraan ng pagbabago ng data

Gumawa tayo ng package com.codegym.rediskung saan nagdaragdag tayo ng 2 klase: CityCountry (data sa lungsod at bansa kung saan matatagpuan ang lungsod na ito) at Language (data sa wika). Narito ang lahat ng mga patlang na madalas na hinihiling "sa pamamagitan ng gawain" sa "paghiling ng pagpepreno".

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
}

Sa pangunahing pamamaraan, pagkatapos makuha ang lahat ng mga lungsod, idagdag ang linya

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

At ipatupad ang pamamaraang ito:

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

Sa tingin ko, ang pamamaraang ito ay nagpapaliwanag sa sarili: gumagawa lang kami ng isang CityCountry entity at pinupunan ito ng data mula sa City , Country , CountryLanguage .

Patakbuhin ang Redis server bilang isang docker container

Mayroong 2 pagpipilian dito. Kung gagawin mo ang opsyonal na hakbang na "i-install ang redis-insight, tingnan ang data na nakaimbak sa Redis", kung gayon ang utos ay para sa iyo:

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

Kung magpasya kang laktawan ang hakbang na ito, pagkatapos ay:

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

Ang pagkakaiba ay sa unang opsyon, ang port 8001 ay ipinapasa sa lokal na makina, kung saan maaari kang kumonekta sa isang panlabas na kliyente upang makita kung ano ang nakaimbak sa loob. At dati akong nagbibigay ng mga makabuluhang pangalan, samakatuwid, redis-stacko redis.

Pagkatapos ng paglunsad, makikita mo ang listahan ng mga tumatakbong container. Upang gawin ito, patakbuhin ang command:

docker container ls 

At may makikita kang ganito:

Kung kailangan mong maghanap ng ilang command, maaari mong tingnan ang tulong sa terminal (tulong sa docker) o sa google "kung paano ..." (halimbawa, docker kung paano alisin ang tumatakbong lalagyan).

At tinawag din namin ang pagsisimula ng radish client sa Main constructor, ngunit hindi ipinatupad ang pamamaraan mismo. Magdagdag ng pagpapatupad:

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

Ang sout ay idinagdag para sa mga layuning pang-edukasyon upang sa log ng paglulunsad ay makikita mo na ang lahat ay OK at ang koneksyon sa pamamagitan ng kliyente ng labanos ay pumasa nang walang mga pagkakamali.

Sumulat ng data sa Redis

Magdagdag ng tawag sa pangunahing paraan

main.pushToRedis(preparedData); 

Sa pagpapatupad ng pamamaraang ito:

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

    }
}

Dito, binuksan ang isang kasabay na koneksyon sa kliyente ng labanos, at sunud-sunod ang bawat bagay ng uri ng CityCountry ay isinusulat sa labanos. Dahil ang labanos ay isang String key-value store , ang key (city id) ay na-convert sa isang string. At ang halaga ay nasa string din, ngunit gumagamit ng ObjectMapper sa JSON na format.

Ito ay nananatiling tumakbo at suriin na walang mga error sa log. Lahat ay gumana.

I-install ang redis-insight, tingnan ang data na nakaimbak sa Redis (opsyonal)

I-download ang redis-insight mula sa link at i-install ito. Pagkatapos magsimula, agad nitong ipinapakita ang aming radish instance sa docker container:

Kung mag-log in ka, makikita namin ang isang listahan ng lahat ng mga susi:

At maaari kang pumunta sa anumang key upang makita kung anong data ang nakaimbak dito:

Sumulat ng isang paraan para sa pagkuha ng data mula sa Redis

Para sa pagsubok, ginagamit namin ang sumusunod na pagsubok: nakakakuha kami ng 10 talaan ng CityCountry. Ang bawat isa ay may hiwalay na kahilingan, ngunit sa isang koneksyon.

Maaaring makuha ang data mula sa labanos sa pamamagitan ng aming kliyente ng labanos. Para magawa ito, magsulat tayo ng paraan na nangangailangan ng listahan ng mga id na makukuha.

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

Ang pagpapatupad, sa tingin ko, ay intuitive: nagbubukas kami ng kasabay na koneksyon, at para sa bawat id ay nakakakuha kami ng JSON String , na kino-convert namin sa object ng uri ng CityCountry na kailangan namin .

Sumulat ng isang paraan upang makakuha ng data mula sa MySQL

Sa klase ng CityDAO , magdagdag ng paraan getById(Integer id)kung saan makukuha natin ang lungsod kasama ang bansa:

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

Sa pamamagitan ng pagkakatulad sa nakaraang talata, magdagdag tayo ng katulad na pamamaraan para sa MySQL sa Pangunahing klase:

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

Sa mga feature, para makasigurado na makuha ang buong bagay (nang walang proxy stub), tahasan kaming humihiling ng listahan ng mga wika mula sa bansa.

Ihambing ang bilis ng pagkuha ng parehong data mula sa MySQL at Redis

Dito ay agad kong ibibigay ang code ng pangunahing pamamaraan, at ang resulta na nakuha sa aking lokal na computer.

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

Kapag sinusubukan, mayroong isang tampok - ang data mula sa MySQL ay binabasa lamang, kaya hindi ito maaaring i-restart sa pagitan ng mga paglulunsad ng aming application. At sa Redis sila ay nakasulat.

Bagama't kapag sinubukan mong magdagdag ng duplicate para sa parehong key, maa-update lang ang data, inirerekumenda kong patakbuhin mo ang mga command upang ihinto ang lalagyan docker stop redis-stackat tanggalin ang lalagyan sa pagitan ng paglulunsad ng application sa terminal docker rm redis-stack. Pagkatapos nito, itaas muli ang lalagyan na may labanos docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latestat pagkatapos lamang na isagawa ang aming aplikasyon.

Narito ang aking mga resulta ng pagsusulit:

Sa kabuuan, nakamit namin ang isang pagtaas sa pagganap ng tugon sa kahilingan na "madalas na pagpepreno" ng isa at kalahating beses. At ito ay isinasaalang - alang ang katotohanan na sa pagsubok ay hindi namin ginamit ang pinakamabilis na deserialization sa pamamagitan ng ObjectMapper . Kung papalitan mo ito sa GSON, malamang, maaari kang "manalo" ng kaunti pang oras.

Sa sandaling ito, naaalala ko ang isang biro tungkol sa isang programmer at oras: basahin at isipin kung paano isulat at i-optimize ang iyong code.