আজ আমরা চতুর্থ JRU মডিউলের চূড়ান্ত প্রকল্পটি করব। এটা কি হবে? আসুন বিভিন্ন প্রযুক্তির সাথে কাজ করার চেষ্টা করি: মাইএসকিউএল, হাইবারনেট, রেডিস, ডকার। এখন আরো বিষয়।
কাজ: আমাদের একটি স্কিমা সহ একটি সম্পর্কযুক্ত MySQL ডাটাবেস রয়েছে (দেশ-শহর, দেশ অনুসারে ভাষা)। এবং শহরের একটি ঘন ঘন অনুরোধ, যা ধীর হয়. আমরা একটি সমাধান নিয়ে এসেছি - প্রায়শই অনুরোধ করা সমস্ত ডেটা রেডিসে (কী-মানের ধরণের মেমরি স্টোরেজে) সরানোর জন্য।
এবং আমাদের MySQL-এ সংরক্ষিত সমস্ত ডেটার প্রয়োজন নেই, তবে শুধুমাত্র একটি নির্বাচিত ক্ষেত্রগুলির সেট। প্রকল্পটি একটি টিউটোরিয়াল আকারে হবে। অর্থাৎ, এখানে আমরা সমস্যাটি উত্থাপন করব এবং অবিলম্বে এটি সমাধান করব।
তো, চলুন শুরু করা যাক আমাদের কি কি সফটওয়্যার লাগবে:
- IDEA আলটিমেট (যার চাবি ফুরিয়ে গেছে - রোমানকে স্ল্যাকে লিখুন)
- ওয়ার্কবেঞ্চ (বা MySQL এর জন্য অন্য কোন ক্লায়েন্ট)
- ডকার
- redis- অন্তর্দৃষ্টি - ঐচ্ছিক
আমাদের কর্ম পরিকল্পনা:
- ডকার সেট আপ করুন (আমি টিউটোরিয়ালে এটি করব না, কারণ প্রতিটি ওএসের নিজস্ব বৈশিষ্ট্য থাকবে এবং "উইন্ডোজে ডকার কীভাবে ইনস্টল করবেন" এর মতো প্রশ্নের ইন্টারনেটে প্রচুর উত্তর রয়েছে), সবকিছু কাজ করে কিনা তা পরীক্ষা করে দেখুন।
- ডকার কন্টেইনার হিসাবে মাইএসকিউএল সার্ভার চালান।
- ডাম্প প্রসারিত করুন ।
- আইডিয়াতে একটি প্রকল্প তৈরি করুন, ম্যাভেন নির্ভরতা যোগ করুন।
- লেয়ার ডোমেইন তৈরি করুন।
- MySQL থেকে সমস্ত ডেটা পেতে একটি পদ্ধতি লিখুন।
- একটি ডেটা ট্রান্সফরমেশন পদ্ধতি লিখুন (রেডিসে আমরা কেবলমাত্র সেই ডেটাই লিখব যা ঘন ঘন অনুরোধ করা হয়)।
- একটি ডকার ধারক হিসাবে Redis সার্ভার চালান.
- রেডিসে ডেটা লিখুন।
- ঐচ্ছিক: রেডিস-ইনসাইট ইনস্টল করুন, রেডিসে সংরক্ষিত ডেটা দেখুন।
- রেডিস থেকে ডেটা পাওয়ার জন্য একটি পদ্ধতি লিখ।
- MySQL থেকে ডেটা পাওয়ার জন্য একটি পদ্ধতি লিখুন।
- মাইএসকিউএল এবং রেডিস থেকে একই ডেটা পাওয়ার গতির তুলনা করুন।
ডকার সেটআপ
ডকার অ্যাপ্লিকেশনগুলি বিকাশ, বিতরণ এবং পরিচালনার জন্য একটি উন্মুক্ত প্ল্যাটফর্ম। আমরা স্থানীয় মেশিনে রেডিস ইনস্টল এবং কনফিগার করার জন্য এটি ব্যবহার করব না, তবে একটি রেডিমেড চিত্র ব্যবহার করব। আপনি এখানে ডকার সম্পর্কে আরও পড়তে পারেন বা এটি এখানে দেখতে পারেন । আপনি যদি ডকারের সাথে পরিচিত না হন তবে আমি কেবল দ্বিতীয় লিঙ্কটি দেখার পরামর্শ দিই।
আপনি ডকার ইনস্টল এবং কনফিগার করেছেন তা নিশ্চিত করতে, কমান্ডটি চালান:docker -v
সবকিছু ঠিক থাকলে, আপনি ডকার সংস্করণ দেখতে পাবেন
![](https://cdn.codegym.cc/images/article/1bc6d1a9-3231-461f-a31c-46ce4a487c04/512.jpeg)
ডকার কন্টেইনার হিসাবে মাইএসকিউএল সার্ভার চালান
মাইএসকিউএল এবং রেডিস থেকে ডেটা ফেরত দেওয়ার সময় তুলনা করতে সক্ষম হওয়ার জন্য, আমরা ডকারে মাইএসকিউএলও ব্যবহার করব। PowerShell এ (বা অন্য কনসোল টার্মিনাল যদি আপনি উইন্ডোজ ব্যবহার না করেন), কমান্ডটি চালান:
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
- মাইএসকিউএল কন্টেইনারের নাম সেট করুন।-d
- একটি পতাকা যা বলে যে কন্টেইনারটি কাজ চালিয়ে যাওয়া উচিত, এমনকি যদি আপনি টার্মিনাল উইন্ডোটি বন্ধ করে দেন যেটি থেকে এই ধারকটি চালু করা হয়েছিল।-p 3306:3306
- পোর্ট নির্দিষ্ট করে। কোলনের আগে - লোকাল মেশিনে পোর্ট, কোলনের পরে - কন্টেইনারে পোর্ট।-e MYSQL_ROOT_PASSWORD=root
– এনভায়রনমেন্ট ভেরিয়েবল MYSQL_ROOT_PASSWORD মান রুট সহ কন্টেইনারে পাস করা। mysql/ চিত্রের জন্য নির্দিষ্ট পতাকা--restart unless-stopped
- আচরণ নীতি সেট করা (বন্ধ হয়ে গেলে ধারকটি পুনরায় চালু করা উচিত কিনা)। বন্ধ না হওয়া মানের মানে সর্বদা পুনঃসূচনা করা, ধারক বন্ধ করা ছাড়া /-v mysql:/var/lib/mysql
- ভলিউম যোগ করুন (তথ্য সংরক্ষণের জন্য ছবি)।mysql:8
- ছবির নাম এবং এর সংস্করণ।
টার্মিনালে কমান্ডটি কার্যকর করার পরে, ডকার চিত্রের সমস্ত স্তর ডাউনলোড করবে এবং ধারকটি শুরু করবে:
![](https://cdn.codegym.cc/images/article/2d4880df-a10e-4553-9354-b854018f53c7/1024.jpeg)
গুরুত্বপূর্ণ দ্রষ্টব্য: যদি আপনার স্থানীয় কম্পিউটারে MySQL একটি পরিষেবা হিসাবে ইনস্টল করা থাকে এবং এটি চলমান থাকে, তাহলে আপনাকে স্টার্ট কমান্ডে একটি ভিন্ন পোর্ট উল্লেখ করতে হবে, অথবা এই চলমান পরিষেবাটি বন্ধ করতে হবে।
![](https://cdn.codegym.cc/images/article/5354d6b9-187d-4514-b504-e157b1ef003a/1024.jpeg)
ডাম্প প্রসারিত করুন
ডাম্পটি প্রসারিত করতে, আপনাকে ওয়ার্কবেঞ্চ থেকে ডাটাবেসের সাথে একটি নতুন সংযোগ তৈরি করতে হবে, যেখানে আপনি পরামিতিগুলি নির্দিষ্ট করবেন। আমি ডিফল্ট পোর্ট (3306) ব্যবহার করেছি, ব্যবহারকারীর নাম পরিবর্তন করিনি (ডিফল্টরূপে রুট), এবং রুট ব্যবহারকারীর (রুট) জন্য পাসওয়ার্ড সেট করেছি।
![](https://cdn.codegym.cc/images/article/7e1b4972-af30-4aa7-9212-c99ff69920f2/1024.jpeg)
ওয়ার্কবেঞ্চে, করুন Data Import/Restore
এবং নির্বাচন করুন Import from Self Contained File
। আপনি ফাইল হিসাবে ডাম্পটি কোথায় ডাউনলোড করেছেন তা নির্দিষ্ট করুন । আপনাকে আগে স্কিমা তৈরি করতে হবে না - এটির তৈরি ডাম্প ফাইলে অন্তর্ভুক্ত করা হয়েছে। একটি সফল আমদানির পরে, আপনার তিনটি টেবিল সহ একটি বিশ্ব স্কিমা থাকবে:
- শহর হল শহরের একটি টেবিল।
- দেশ - দেশের টেবিল।
- country_language - একটি সারণী যা নির্দেশ করে যে দেশের কত শতাংশ জনসংখ্যা একটি নির্দিষ্ট ভাষায় কথা বলে।
![](https://cdn.codegym.cc/images/article/697339c6-8214-483a-9283-6696ad2ecf7d/512.jpeg)
যেহেতু আমরা কন্টেইনার শুরু করার সময় একটি ভলিউম ব্যবহার করেছি, তাই mysql কন্টেইনার বন্ধ করার পরে এমনকি মুছে ফেলার পরে এবং স্টার্ট কমান্ড ( docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root --restart unless-stopped -v mysql:/var/lib/mysql mysql:8
) পুনরায় কার্যকর করার পরে, আবার ডাম্প স্থাপন করার প্রয়োজন হবে না - এটি ইতিমধ্যে ভলিউমে স্থাপন করা হয়েছে।
আইডিয়াতে প্রকল্প তৈরি করুন, ম্যাভেন নির্ভরতা যোগ করুন
আপনি ইতিমধ্যে আইডিয়াতে একটি প্রকল্প তৈরি করতে জানেন - এটি আজকের প্রকল্পের সবচেয়ে সহজ পয়েন্ট।
![](https://cdn.codegym.cc/images/article/db14c218-aea0-4108-9a23-0ce172271cf0/1024.jpeg)
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
রেডিসের সাথে কাজ করার জন্য উপলব্ধ জাভা ক্লায়েন্টগুলির মধ্যে একটি।
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 প্যাকেজ তৈরি করুন
আইডিয়াতে টেবিল স্ট্রাকচার ব্যবহার করার জন্য একটি এন্টিটিতে টেবিল ম্যাপ করার সময় এটি আমার পক্ষে সুবিধাজনক, তাই আইডিয়াতে একটি ডাটাবেস সংযোগ যোগ করা যাক।
![](https://cdn.codegym.cc/images/article/7c7320ea-4dd5-4eff-b757-503a401565fb/800.jpeg)
![](https://cdn.codegym.cc/images/article/91b9ed51-e724-4cb9-900c-56e06d499c43/1024.jpeg)
আমি এই ক্রমে সত্তা তৈরি করার পরামর্শ দিই:
- দেশ
- শহর
- দেশের ভাষা
এটা বাঞ্ছনীয় যে আপনি নিজেই ম্যাপিং সঞ্চালন.
দেশের ক্লাস কোড:
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টি আকর্ষণীয় পয়েন্ট রয়েছে।
প্রথমটি হল মহাদেশ এনাম , যা ডাটাবেসে অর্ডিনাল মান হিসাবে সংরক্ষণ করা হয়। দেশের টেবিলের কাঠামোতে, মহাদেশ ক্ষেত্রের মন্তব্যে, আপনি দেখতে পারেন কোন সংখ্যাগত মান কোন মহাদেশের সাথে মিলে যায়।
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();
}
এখন আমরা প্রথমবারের জন্য ডিবাগে আমাদের অ্যাপ্লিকেশন চালাতে পারি এবং দেখতে পারি কিভাবে এটি কাজ করে (বা কাজ করে না - হ্যাঁ, এটি প্রায়শই ঘটে)।
![](https://cdn.codegym.cc/images/article/016f2ea1-822d-43eb-a572-39286c2c4830/800.jpeg)
শহরগুলো পাচ্ছে। প্রতিটি শহর একটি দেশ পায়, যদি এটি পূর্বে অন্য শহরের জন্য ডাটাবেস থেকে বিয়োগ না করা হয়। হাইবারনেট ডাটাবেসে কতগুলি প্রশ্ন পাঠাবে তা মোটামুটিভাবে গণনা করা যাক:
- মোট শহরের সংখ্যা খুঁজে বের করার জন্য 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 সত্তা তৈরি করি এবং সিটি , দেশ , কান্ট্রি ল্যাঙ্গুয়েজ থেকে ডেটা দিয়ে এটি পূরণ করি ।
একটি ডকার ধারক হিসাবে Redis সার্ভার চালান
এখানে 2টি বিকল্প রয়েছে। আপনি যদি ঐচ্ছিক পদক্ষেপটি করেন "রিডিস-ইনসাইট ইনস্টল করুন, রেডিসে সংরক্ষিত ডেটা দেখুন", তাহলে কমান্ডটি আপনার জন্য:
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
এবং আপনি এই মত কিছু দেখতে পাবেন:
![](https://cdn.codegym.cc/images/article/5c44653a-fa4e-4e4c-ac41-c4e5ed26ebf6/1024.jpeg)
আপনার যদি কিছু কমান্ড খোঁজার প্রয়োজন হয়, আপনি হয় টার্মিনালে সহায়তা দেখতে পারেন (ডকার সহায়তা) বা গুগল "কিভাবে ..." (উদাহরণস্বরূপ, ডকার কীভাবে চলমান কন্টেইনার সরাতে হয়)।
এবং আমরা মূল কনস্ট্রাক্টরে মূলা ক্লায়েন্টের প্রাথমিককরণকেও বলেছি, কিন্তু পদ্ধতিটি নিজেই বাস্তবায়ন করিনি। বাস্তবায়ন যোগ করুন:
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 যোগ করা হয়েছিল যাতে লঞ্চ লগে আপনি দেখতে পারেন যে সবকিছু ঠিক আছে এবং মূলা ক্লায়েন্টের মাধ্যমে সংযোগটি ত্রুটি ছাড়াই পাস হয়েছে।
রেডিসে ডেটা লিখুন
মূল পদ্ধতিতে একটি কল যোগ করুন
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-insight ডাউনলোড করুন এবং এটি ইনস্টল করুন। শুরু করার পরে, এটি অবিলম্বে ডকার পাত্রে আমাদের মূলার উদাহরণ দেখায়:
![](https://cdn.codegym.cc/images/article/15311a8f-a8af-4a6d-862b-cb10ca70d2ef/800.jpeg)
আপনি লগ ইন করলে, আমরা সমস্ত কীগুলির একটি তালিকা দেখতে পাব:
![](https://cdn.codegym.cc/images/article/97477080-6693-421a-81da-742753a3de5e/800.jpeg)
এবং এটিতে কী ডেটা সংরক্ষণ করা হয়েছে তা দেখতে আপনি যে কোনও কীতে যেতে পারেন:
![](https://cdn.codegym.cc/images/article/7f0836ac-09ba-4c0a-a759-2f85b4c29947/800.jpeg)
রেডিস থেকে ডেটা পাওয়ার জন্য একটি পদ্ধতি লিখ
পরীক্ষার জন্য, আমরা নিম্নলিখিত পরীক্ষা ব্যবহার করি: আমরা 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 থেকে ডেটা পেতে একটি পদ্ধতি লিখুন
সিটিডিএও ক্লাসে , একটি পদ্ধতি যুক্ত করুন 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();
}
পূর্ববর্তী অনুচ্ছেদের সাথে সাদৃশ্য অনুসারে, আসুন মাইএসকিউএল-এর জন্য মূল ক্লাসে একটি অনুরূপ পদ্ধতি যুক্ত করি:
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
এবং তার পরেই আমাদের অ্যাপ্লিকেশনটি কার্যকর করুন।
এখানে আমার পরীক্ষার ফলাফল আছে:
![](https://cdn.codegym.cc/images/article/51a91b8a-d86c-433b-ab4a-085c73a0c814/800.jpeg)
মোট, আমরা "ব্রেক ঘন ঘন" অনুরোধের প্রতিক্রিয়ার কার্যক্ষমতা দেড় গুণ বৃদ্ধি অর্জন করেছি। এবং এটি এই বিষয়টিকে বিবেচনায় নেওয়া হচ্ছে যে পরীক্ষার সময় আমরা অবজেক্টম্যাপারের মাধ্যমে দ্রুততম ডিসিরিয়ালাইজেশন ব্যবহার করিনি । আপনি যদি এটিকে GSON-এ পরিবর্তন করেন, সম্ভবত, আপনি একটু বেশি সময় "জিততে" পারেন৷
এই মুহুর্তে, আমি একজন প্রোগ্রামার এবং সময় সম্পর্কে একটি কৌতুক মনে করি: কীভাবে আপনার কোড লিখতে এবং অপ্টিমাইজ করতে হয় সে সম্পর্কে পড়ুন এবং চিন্তা করুন।
GO TO FULL VERSION