Имрӯз мо лоиҳаи ниҳоиро дар модулҳои JRU иҷро хоҳем кард. Дар бораи чӣ мешавад сухан? Мо кӯшиш мекунем, ки бо технологияҳои гуногун кор кунем: MySQL, Hibernate, Redis, Docker. Ҳоло бахшияторӣ бештар.
Вазифа: мо дорои БД релясионӣ MySQL бо схема (кишвар-шаҳр, забон тибқи кишвар) ҳастем. Ва як дархости зуд-зуд дар бораи шаҳр вуҷуд дорад, ки он суст мешавад. Мо роҳи ҳалли онро фикр кардем - ҳамаи маълумотҳое, ки зуд-зуд дархоста мешаванд, ба Redis (in memory storage типа ключ-значение) баровардан лозим аст.
Ва ба мо лозим нест, ҳама маълумоте, ки дар MySQL нигоҳ дошта мешаванд, балки танҳо маҷмӯи интихобшудаи майдонҳо. Лоиҳа таълимӣ хоҳад буд. Яъне, мо мушкилотро бардошта, фавран ҳал менамоем.
Ҳамин тавр, биёед аз он шурӯъ кунем, ки чӣ гуна нармафзори ба мо лозим аст:
- IDEA Ultimate (агар калид дошта бошад ва тамом шуда бошад - дар Slack нависед ба Роман)
- Workbench (ё ҳар як мизоҷи дигари MySQL)
- Docker
- redis-insight - ихтиёрӣ
Нақшаи мо:
- Насби Docker (дар таълимӣ бо ин кор намекунам, зеро барои ҳар як ОС хусусиятҳои худ вуҷуд доранд ва дар Интернет ҷавобҳои зиёд ба саволҳои ба монанди "how to install docker on windows" вуҷуд дорад), тафтиши ҳамаи он ки ҳама кор мекунад.
- Иқдоми MySQL сервер ҳамчун контейнер Docker.
- Дамбе насб кардан дамб.
- Лоиҳаи IDEA эҷод кунед, вобастагиҳои maven илова намоед.
- Сатҳи domain созед.
- Усули гирифтаи тамоми маълумотро аз MySQL нависед.
- Усули трансформатсияи маълумотҳоро нависед (время гузаранд дар Redis танҳо он маълумотҳое, ки зуд-зуд дархоста мешаванд).
- Иқдоми Redis сервер ҳамчун контейнер Docker.
- Маълумотҳоро дар Redis сабт кунед.
- Ихтиёрӣ: redis-insight насб кунед, ба нигоҳдоштаҳо дар Redis нигаред.
- Усули гирифтани маълумотҳоро аз Redis нависед.
- Усули гирифтани маълумотҳоро аз MySQL нависед.
- Суръати гирифтани маълумоти якхела аз MySQL ва Redis-ро муқоиса кунед.
Насби Docker
Docker – платформаи кушода барои таҳия, таҳвил ва иҷрои барномаҳо мебошад. Мо онро истифода мебарем, то ки Redis-ро дар компютери локалии мо насб ва танзим накунем, балки истифодаи image омодаи он. Дар бораи Docker бештар хонед дар ин ҷо ё тамошо кунед ин ҷо. Агар бо Docker ошно набошед, ман тавсия медиҳам, ки албатта ба навори дуюм нигаред.
Барои боварӣ ҳосил кардан ки Docker дар шумост насб ва танзим шудааст, фармонро иҷро кунед: docker -v
Агар ҳама ОК бошад, шумо версияи Docker-ро хоҳед дид

Запустить MySQL сервер как докер-контейнер
Барои ин ки вақтро барои гирифтани маълумотҳо аз MySQL ва Redis муқоиса кунем, MySQL-ро низ дар Docker истифода мебарем. Дар 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
– ташвиқи вақте, ки мегӯяд, ки контейнер бояд идома диҳад, ҳатто агар термиaliro ки контейнер аз он оғоз шуда буд, пӯшонад.-p 3306:3306
– портҳоро нишон медиҳад. Пеш аз дӯкони порт дар компютери локалӣ, пас аз дӯкони порт дар контейнер.-e MYSQL_ROOT_PASSWORD=root
– интиқоли давлатии муҳити MYSQL_ROOT_PASSWORD бо арзиши root дар контейнер. Аломат хос барои тасвири mysql/--restart unless-stopped
– барасмиятдароришавии рафтори сиёсӣ (оё контейнер бояд ҳангоми пӯшида шудан тасдиқ шавад). Арзиши unless-stopped ҳамаи вақт тасдиқ мекунад, ба истиснои ҳолат вақте контейнер исто шуд /-v mysql:/var/lib/mysql
– илова кардани volume (тасвир барои нигаҳдории маълумот).mysql:8
– номи тасвир ва версияи он.
Пас аз иҷрои фармон дар терминал, Docker ҳамаи қабатҳои тасвирро загрузи намуда, контейнерро оғоз мекунад:

Эзоҳи муҳим: агар дар компютери локалии шумо MySQL ҳамчун хидмат насб шуда бошад ва он кор мекунад – дар фармони оғоз дигар портро нишон додан лозим аст ё ин хидматро исто кардан лозим аст.

Насб кардани дамб
Барои насби дамб бояд аз Workbench конекшн нав ба БД созед, ки дар он параметрҳоро қайд кунед. Ман порти стандартии (3306) истифода кардам, номи корбарро тағйир надодам (root аз рӯи хоста) ва паролро барои корбари реша (root) гузоштам.

Дар Workbench Data Import/Restore мекунем ва Import from Self Contained File интихоб мекунем. Ҳамчун файл нишонаи дамбе, ки шумо ба куҷо дохил мекунед, нишон диҳед дамб. Схемаро пешакӣ эҷод кардан лозим нест – эҷоди он ба файл дамб дохил карда мешавад. Пас аз воридоти муваффақият, шумо хоҳед дошт схема world бо се ҷадвалҳо:
- city – ин ҷадвали шаҳрҳо.
- country – ҷадвали кишварҳо.
- country_language – ҷадвале, ки дар он нишон диҳад, ки кадом фоизи аҳолӣ дар кишвар бо забони муайян гап мезананд.

Азбаски ҳангоми оғоз кардани контейнер мо вольюмро истифода бурдем, пас аз истгоҳ ва ҳатто хориҷ кардани контейнер 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 илова намоед
Чӣ тавр бо кор дар Idea лоиҳа созед, шумо аллакай медонед – ин бахш дар лоиҳаи имрӯзаи мо осонтарин аст.

Ба файли 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-core
– яке аз мизоҷони Java барои кор бо Redis мебошад.
jackson-databind
– вобастагии барои истифодаи ObjectMapper (барои тағйир додани маълумотҳо барои нигоҳдорӣ дар Redis (ключ-значение типа String)).
Ҳамчунин ба папкаи захираҳо (src/main/resources) файли spy.properties илова кунед барои намоиши дархостҳо бо параметрҳо, ки Hibernate иҷро мекунад. Мазмуни файл:
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
Сатҳи domain созед
Пакети com.javarush.domain созед
Ба ман бо мапинги ҷадвалиҳо ба энтити кор кардан дар Идея осон аст, бинобар ин мо пайвастшавии БД-ро дар Идея илова мекунем.


Ман пешниҳод менамоям, ки энтитиҳоро дар чунин тартиб эҷод кунед:
- Country
- City
- CountryLanguage
Мақсад ба мапингом мустақилона ҷавоб додан аст.
Коди класи Country:
package com.javarush.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
}
Дар код се нуқтаи ҷолиб мавҷуданд.
Аввалин – энами Continent, ки дар БД дар худи арзишҳои ordinal нигоҳ дошта мешавад. Дар структураи ҷадвали country дар шарҳи майдони continent метавонед бубинед, ки кадом арзиши шумораи ба кадом континент мувофиқ аст.
package com.javarush.domain;
public enum Continent {
ASIA,
EUROPE,
NORTH_AMERICA,
AFRICA,
OCEANIA,
ANTARCTICA,
SOUTH_AMERICA
}
Дуюмин нуқта - ин сет аз CountryLanguage. Ин ҷо алоқаи @OneToMany мебошад, ки дар лоиҳаи дуюми ҳамин модул вуҷуд надошт. Табиӣ Hibernate арзиши ин сетро ҳангоми дархости энтити кишвар набарорад. Аммо азбаски мо бояд ҳамаи арзишҳоро аз БД релясионӣ барои кеш сабт кунем, дар ин ҷо параметри FetchType.EAGER
илова карда шудааст.
Сеюм - майдони city. Алоқаи @OneToOne – ба монанди ҳамеша ба назар меояд. Аммо агар ба структураи foreign key дар БД нигаред, мебинем, ки дар кишвар (country) алоқа ба пойтахт (city) вуҷуд дорад, ва дар шаҳр (city) алоқа ба кишвар (country). Ягон алоқаи даврӣ барпо шудааст.
Ҳанӯз бо ин чиз кор намекунем, аммо вақте ки ба нуқтаи «Навиштани усули гирифтани ҳамаи маълумотҳо аз MySQL» расидем, мебинем, ки кадом дархостҳо Hibernate иҷро мекунад, кадом миқдор, ва ин нуқтаро ёд мекунем.Hibernate
Коди класи City:
package com.javarush.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:
package com.javarush.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
Дар класси Main майдонҳоро эълон мекунем:
private final SessionFactory sessionFactory;
private final RedisClient redisClient;
private final ObjectMapper mapper;
private final CityDAO cityDAO;
private final CountryDAO countryDAO;
ва онҳо дар конструктори класи Main инициализ мешаванд:
public Main() {
sessionFactory = prepareRelationalDb();
cityDAO = new CityDAO(sessionFactory);
countryDAO = new CountryDAO(sessionFactory);
redisClient = prepareRedisClient();
mapper = new ObjectMapper();
}
Ҳамон тавр, ки мебинед, методҳо ва классҳои нокомро нарафтем – биёед онҳоро нависем.
Пакети com.javarush.dao созед ва дар он ду класс илова намоед:
package com.javarush.dao;
import com.javarush.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.javarush.dao;
import com.javarush.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());
}
}
Акнун дар Main ин ду классро import кардан мумкин аст. Ҳанӯз ду метод нокофӣ аст:
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;
}
Ҳанӯз ба Redis нарасидаем, бинобар ин имплементацияи инициализатсияи муштарии Redis дар замима мемонад:
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 шаҳр дорем. Аммо дар барномаҳои истеҳсолӣ, вақте ки лозим аст маълумоти зиёд ба даст оварда шавад, чунин усул аксар вақт истифода мешавад.
Амали иҷрои методи main:
public static void main(String[] args) {
Main main = new Main();
List<City> allCities = main.fetchData(main);
main.shutdown();
}
Ҳоло мо метавонем бори аввал барномаро дар дебаг озмоиш кунем ва бибинем, ки он чӣ гуна кор мекунад (ё накор мекунад – ҳа, чунин ҳолатҳо ҳастанд).

Шаҳрҳо дастрас мешаванд. Ба ҳар шаҳр лоиҳаи кишвар оварда мешавад, агар он аллакай аз БД барои шаҳрҳои дигар гирифта нашуда бошад. Ҳисоб мекунем, ки чанд запрос Hibernate ба БД мефиристад:
- 1 запрос барои маълумоти умумии шаҳрҳо (барои интераксияи даврӣ ба 500 шаҳр, то ки донем, ки кай бас кунем).
- 4079 / 500 = 9 запросҳо (рӯйхати шаҳрҳо).
- Ба ҳар як шаҳр кишвар оварда мешавад, агар он аллакай барои як шаҳрҳои дигар гирифта нашуда бошад. Азбаски дар БД 239 кишвар вуҷуд дорад, ин моро ба 239 запрос мерасонад.
Ҳамагӣ 249 запрос
. Ва ин ҳам мо гуфтем, ки ба кишвар фавран сет забонҳоро ба даст орем, ки агар не он гоҳ хеле бад буд. Аммо ин ҳанӯз бисёр аст, бинобар ин биёед каме бо коҳиши масъала рӯ ба рӯ шавем. Аз мулоҳизаҳо шурӯъ мекунем: чиро бояд кард, ба куҷо давид? Ва агар ҷиддӣ бошем – ки чаро ин қадар бисёр запросҳо. Агар ба лог запросҳо назар кунем, мебинем, ки ҳар як кишвар алоҳидаи омадааст, бинобар ин аввалин роҳи содда: биёед ҳамаи кишварҳоро якҷоя дархост кунем, зеро мо пешакӣ медонем, ки дар ин транзаксия ҳама ба мо лозим мешаванд.
Дар методи fetchData() фавран пас аз оғози транзаксия натиҷаҳоро илова кунед:
List<Country> countries = main.countryDAO.getAll();
Ҳисоб мекунем запросҳо:
- 1 – барои гирифтани ҳамаи кишварҳо
- 239 – барои ҳар як кишвар пойтахти онро оварда
- 1 – барои маълумоти умумии шаҳрҳо
- 9 – барои рӯйхати шаҳрҳо
Ҳамагӣ 250
. Идея хуб аст, аммо натиҷа надод. Проблема дар он аст, ки дар кишвар алоқа ба пойтахт (city) @OneToOne мебошад. Ва чунин алоқа тавассути FetchType.EAGER барқарор мешавад. Биёед онро ба FetchType.LAZY иҷро кунем, зеро мо дар ин ҳол ҳамеша ҳамаи шаҳрҳоро дар ин транзаксия мерӯем.
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "capital")
private City city;
Пойтахтҳо алоҳида насабро намегиранд, аммо шумораи запросҳо тағйир наёфт. Ҳоло барои ҳар як кишвар алоҳидаи рӯйхати CountryLanguage запрос мешавад. Яъне, дастоварди вуҷуд дорад ва мо ба самти дуруст ҳаракат мекунем. Агар ёд доред, дар лексияҳо ҳалли "join fetch" пешниҳод шуда буд, ки тавассути илова кардани join иловагӣ ба запрос алоқаи талабшуда бо маълумотҳои вобаста бо як запрос талаб кардан мумкин аст. Дар класс CountryDAO, HQL запросро дар методи getAll() ба ин ивази кунед:
"select c from Country c join fetch c.languages"
Иҷро намудан. Логро бинед, ҳисоб кунед запросҳо:
- 1 – ҳамаи кишварҳо бо забонҳо
- 1 – маълумоти умумии шаҳрҳо
- 9 – рӯйхати шаҳрҳо.
Ҳамагӣ 11
- мо онро тавонистем)) Агар шумо танҳо ин матнро хонед ва пас аз ҳар як қадами тюнинг кардани барнома анҷом додед, шумо бояд ҳатто визуалӣ бодиққаткунӣ ва суръати иҷрои тамоми барнома кайҳо афзоиш ёфта бошад.
Навиштани усули трансформатсияи маълумот
Пакети com.javarush.redis созед ва ду класс илова намоед: CityCountry (маълумоти дар бораи шаҳр ва дар бораи кишвар, ки ин шаҳр ҷойгир аст) ва Language (маълумоти дар бораи забон). Ҳамаи майдонҳо, ки "аз рӯи вазифа" зуд-зуд дар "запроси тормоздор" дархоста мешаванд, ин ҷо оварда шудаанд.
package com.javarush.redis;
import com.javarush.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.javarush.redis;
import java.math.BigDecimal;
public class Language {
private String language;
private Boolean isOfficial;
private BigDecimal percentage;
//Getters and Setters omitted
}
Дар методи main пас аз гирифтани ҳамаи шаҳрҳонатиҳо илова намоед:
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 сохта мешавад ва он бо маълумотҳои аз City, Country, ва CountryLanguage пур мешавад.
Иқдоми Redis сервер ҳамчун докер-контейнер
Ин ҷо ду вариант вуҷуд дорад. Агар шумо қарор гирифта бошед, ки ихтиёрии қадами "насби redis-insight, биниши маълумотҳо, ки дар 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
Шумо бо рӯйхати чизе ба монанди ин рӯ ба рӯ мешавед:

Агар ягон фармонро ёфтан лозим аст, шумо метавонед ё дар терминал ёрдамчии (docker help) бинед, ё ҷустуҷӯ намоед "how to …" (масалан, docker how to remove running container).
Ва мо аллакай конструктори Main-ро барои инититализатсияи муштарии Redis таъини карда будем, аммо худи методро иҷро накардаем. Илтироиро илова кунед:
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 бе хатогӣ гузашт.
Навиштани маълумотҳо ба Redis
Дар методи main фармони зеринро илова намоед:
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();
}
}
}
}
Дар ин ҷо пайвастшавии ҳамзамон бо муштарии Redis кушода мешавад ва ҳар як объекти навъи CityCountry ба Redis навишта мешавад. Азбаски Redis хранилище типа ключ-значение типа String мебошад, калид (id-и шаҳр) ба сатр табдил меёбад. Ва арзиш низ ба сатр, аммо бо истифодаи ObjectMapper ба формати JSON.
Мо кӯшиш мекунем, ки иҷро кунем ва надидани хатогиҳоро дар логҳо назорат кунем. Ҳама чиз иҷро шуд.
Насб кардани redis-insight ва биниши маълумотҳо, ки дар Redis нигоҳ дошта мешаванд (ихтиёрӣ)
Бо истифодаи пайванд redis-insight-ро зеркашӣ кунед ва насб кунед. Пас аз оғози он, ман инситени redis-и докер-контейнерро дарҳол мебинам:

Агар ворид шавед - рӯйхати ҳамаи калидҳоро мебинед:

Ва шумо метавонед ба ягон калид ворид шавед, то ки бубинед, ки кадом маълумотҳо барои он нигоҳ дошта мешаванд:

Нависед усули гирифтани маълумотҳо аз Redis
Барои санҷиш чунин санҷишро анҷом медиҳем: мо 10 сабти CityCountry мегирем. Ҳар яке бо дархости алоҳида, аммо дар як пайвастшавӣ.
Маълумотҳо аз Redis бо истифодаи муштарии Redis гирифта мешаванд. Барои ин, мо як метод менависем, ки рӯйхати ID-ҳоро, ки лозим аст гирифта шаванд, қабул мекунад.
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();
}
}
}
}
Иҷрои метод, умедворем, ки фаҳмидатар мебошад: пайвастшавии синхронӣ боз мешавад ва барои ҳар як id String 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 илова мекунем дар класи 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();
}
}
Аз хусусиятҳои, барои ба даст овардани объекти пурра (бе прокси-шаклҳо), ба рӯйхати забонҳо аз кишвар дархости равшан менависем.
Суръати гирифтани маълумоти якхела аз MySQL ва Redis муқоиса кунед
Дар ин ҷо ман кодиро барои методи main медиҳам ва натиҷаҳое, ки ба компютери локалии ман гирифта шудаанд.
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);
//ҳозируниро дарк мекунем, то дархост ба БД, ва на ба кэш, кардан лозим аст
main.sessionFactory.getCurrentSession().close();
//10 id-и шаҳрҳоиро тасодуфӣ интихоб мекунем
//азбаски мо ҳеҷ гуна коркарди ҳолатҳои ноқисро накардаем, истифодаи id-и мавҷуда дар БД
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 танҳо хонда мешаванд, бинобар ин онро бо анҷоми барнома дубора сар кардан мумкин нест. Аммо маълумотҳо дар Redis навишта мешаванд.
Гарчанде, ки ҳангоми кӯшиши илова кардани дубликат бо чунин калид, маълумотҳо танҳо нав карда мешаванд, ман тасвияи меъёре пешниҳод менамоям, ки дар терминал фармонҳои истодани контейнерро иҷро кунед docker stop redis-stack
ва хориҷ кардани контейнер docker rm redis-stack
. Пас аз ин, контейнер бо Redis дубора бардошта шавад docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest
ва танҳо пас аз ин барномаи мо ишора шавад.
Ин натиҷаҳои ман аз санҷиш мебошанд:

Ҳамин тавр, мо ба баландшавии коршиносии ҷавоб ба "запроси тормоздор зуд-зуд" дар як нисфи ворид шудем. Ва ин бо назардошти он аст, ки дар санҷиш мо десериализатсияи зудтарин бо ObjectMapper истифода бурдем. Агар онро бо GSON иваз кунем, гумон мекунам, ки каме вақти бештар ба даст оранд.
Дар айни лаҳза ман як дӯстдоштаи барноманавис ва вақтро худдорӣ намекунам: хонед ва фикр кунед, ки чӣ гуна коди худ ва оптимизатсияи онро нависед.
Маводи иловагӣ
Виртуализатсия ва Docker
Docker Compose
GO TO FULL VERSION