今天我們將完成第四個 JRU 模塊的最終項目。會是什麼?讓我們嘗試使用不同的技術:MySQL、Hibernate、Redis、Docker。現在更多的主題。
任務:我們有一個帶有模式的關係型 MySQL 數據庫(國家-城市,按國家/地區劃分的語言)。而且有頻繁的城市請求,速度變慢。我們想出了一個解決方案——將所有被頻繁請求的數據移動到 Redis 中(以 key-value 類型的內存存儲)。
而且我們不需要存儲在 MySQL 中的所有數據,而只需要一組選定的字段。該項目將採用教程的形式。也就是在這裡提出問題,馬上解決。
那麼,讓我們從我們需要的軟件開始:
- IDEA Ultimate(誰用完了鑰匙 - 在 Slack 中寫信給 Roman)
- Workbench(或任何其他 MySQL 客戶端)
- 碼頭工人
- redis-insight - 可選
我們的行動計劃:
- 設置docker(我不會在教程中這樣做,因為每個操作系統都會有自己的特點,網上有很多關於“如何在windows上安裝docker”等問題的答案),檢查一切是否正常。
- 將 MySQL 服務器作為 docker 容器運行。
- 展開轉儲。
- 在Idea中創建一個項目,添加maven依賴。
- 製作圖層域。
- 編寫一個方法從 MySQL 中獲取所有數據。
- 編寫一個數據轉換方法(在 Redis 中,我們將只編寫經常請求的數據)。
- 將 Redis 服務器作為 docker 容器運行。
- 向 Redis 寫入數據。
- 可選:安裝redis-insight,查看Redis存儲的數據。
- 編寫一個從 Redis 獲取數據的方法。
- 編寫一個從 MySQL 獲取數據的方法。
- 比較從 MySQL 和 Redis 獲取相同數據的速度。
碼頭工人設置
Docker 是一個用於開發、交付和運行應用程序的開放平台。我們將使用它不是為了在本地機器上安裝和配置 Redis,而是為了使用現成的圖像。您可以在此處閱讀有關 docker 的更多信息或在此處查看。如果您不熟悉 docker,我建議您只看第二個鏈接。
為確保已安裝和配置 docker,請運行以下命令:docker -v
如果一切正常,你會看到docker版本

將 MySQL 服務器作為 docker 容器運行
為了能夠比較MySQL和Redis返回數據的時間,我們也會在docker中使用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
– 將值為 root 的環境變量 MYSQL_ROOT_PASSWORD 傳遞給容器。特定於 mysql/ 圖像的標誌--restart unless-stopped
- 設置行為策略(容器是否應該在關閉時重新啟動)。unless-stopped 值意味著總是重新啟動,除非容器被停止/-v mysql:/var/lib/mysql
– 添加卷(用於存儲信息的圖像)。mysql:8
– 圖像的名稱及其版本。
在終端執行命令後,docker會下載鏡像的所有層並啟動容器:

重要說明:如果您在本地計算機上將 MySQL 作為服務安裝並且正在運行,則需要在啟動命令中指定不同的端口,或者停止該正在運行的服務。

展開轉儲
要擴展轉儲,您需要從 Workbench 創建一個到數據庫的新連接,您可以在其中指定參數。我使用了默認端口(3306),沒有更改用戶名(默認為root),並為root用戶(root)設置了密碼。

在 Workbench 中,執行Data Import/Restore
並選擇Import from Self Contained File
。指定將轉儲下載為文件的位置。您不需要事先創建架構 - 它的創建包含在轉儲文件中。成功導入後,您將擁有一個包含三個表的世界模式:
- city 是一張城市表。
- 國家 - 國家表。
- 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
是與 Redis 一起工作的可用 Java 客戶端之一。
jackson-databind
– 使用 ObjectMapper 的依賴項(轉換數據以存儲在 Redis 中(字符串類型的鍵值))。
同樣在資源文件夾(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
製作圖層域
創建包 com.codegym.domain
我在實體上映射表時方便使用Idea中的表結構,所以我們在Idea中添加一個數據庫連接。


我建議按以下順序創建實體:
- 國家
- 城市
- 國家語言
您最好自己執行映射。
國家類別代碼:
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
這是本模塊第二稿中沒有的鏈接。默認情況下,Hibernate 在請求國家/地區實體時不會提取此集合的值。但是由於我們需要從關係數據庫中減去所有的值進行緩存,所以FetchType.EAGER
.
三是城域。溝通@OneToOne
- 就像一切都是熟悉和可以理解的。但是,如果我們查看數據庫中的外鍵結構,我們會看到國家(國家)與首都(城市)有聯繫,城市(城市)與國家(國家)有聯繫。存在循環關係。
我們不會對此做任何事情,但是當我們到達“編寫一個從 MySQL 獲取所有數據的方法”項時,讓我們看看 Hibernate 執行了哪些查詢,查看它們的數量,並記住這項。Hibernate
市級代碼:
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
}
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
}
寫一個方法從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.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 個類導入到 Main 中。仍然缺少兩種方法:
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();
}
現在我們可以第一次在調試中運行我們的應用程序,看看它是如何工作的(或不工作——是的,它經常發生)。

城市越來越多。每個城市都會得到一個國家,如果它之前沒有從另一個城市的數據庫中減去的話。讓我們粗略地計算一下 Hibernate 將向數據庫發送多少查詢:
- 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;
資本不再單獨請求,但請求的數量沒有改變。現在,對於每個國家/地區,CountryLanguage列表由單獨的查詢請求。也就是有進步,我們在朝著正確的方向前進。如果您還記得的話,講座建議使用“連接獲取”解決方案,以便通過向請求添加額外的連接來在一個請求中請求具有依賴數據的實體。在CountryDAO中,將方法中的 HQL 查詢重寫getAll()
為:
"select c from Country c join fetch c.languages"
發射。我們查看日誌,統計請求數:
- 1 - 所有有語言的國家
- 1 - 城市數量
- 9 - 城市列表。
Total 11
- 我們成功了))如果您不僅閱讀了所有這些文本,而且在調整應用程序的每一步後都嘗試運行它,您甚至應該多次直觀地註意到整個應用程序的加速。
寫一個數據轉換方法
讓我們創建一個包com.codegym.redis
,在其中添加 2 個類:CityCountry(關於城市和該城市所在國家/地區的數據)和Language(關於語言的數據)。這裡列出了“制動請求”中“按任務”經常請求的所有字段。
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實體並用來自City、Country、CountryLanguage的數據填充它。
將 Redis 服務器作為 docker 容器運行
這裡有 2 個選項。如果您執行可選步驟“安裝 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構造函數中調用了蘿蔔客戶端的初始化,但是並沒有實現方法本身。添加實現:
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 方法的調用
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類型的每個對象寫入蘿蔔。由於蘿蔔是一個String key-value store ,key(城市id)被轉換為字符串。並且該值也是字符串,但是使用JSON 格式的ObjectMapper 。
它仍然運行並檢查日誌中是否沒有錯誤。一切正常。
安裝redis-insight,查看Redis中存儲的數據(可選)
從鏈接下載 redis-insight 並安裝它。啟動後,它立即在 docker 容器中顯示我們的蘿蔔實例:

如果您登錄,我們將看到所有密鑰的列表:

您可以轉到任意鍵以查看其上存儲了哪些數據:

寫一個從Redis獲取數據的方法
為了進行測試,我們使用以下測試:我們獲得 10 條 CityCountry 記錄。每個都有一個單獨的請求,但在一個連接中。
蘿蔔的數據可以通過我們的蘿蔔客戶端獲取。為此,讓我們編寫一個方法來獲取要獲取的 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,我們得到一個 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();
}
類比上一段,我們在Main類中添加一個類似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獲取相同數據的速度
這裡我馬上給出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);
// 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 的數據只被讀取,所以它不能在我們的應用程序啟動之間重新啟動。在 Redis 中,它們被寫入。
雖然當您嘗試為同一個鍵添加副本時,數據只會被更新,但我建議您在終端中的應用程序啟動之間運行命令來停止容器docker stop redis-stack
並刪除容器docker rm redis-stack
。之後,再次舉起裝有蘿蔔的容器docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest
,然後才執行我們的應用程序。
這是我的測試結果:

總的來說,我們實現了對“頻繁剎車”請求的響應性能提升一倍半。這是考慮到這樣一個事實,即在測試中我們沒有通過ObjectMapper使用最快的反序列化。如果將其更改為 GSON,很可能您可以“贏得”更多時間。
這一刻,我想起了一個關於一個程序員和時間的笑話:閱讀並思考如何編寫和優化你的代碼。
GO TO FULL VERSION