-
Notifications
You must be signed in to change notification settings - Fork 27
Abdulkhanov mod4 typical project #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b7ab8d0
8441136
dd2e5d4
114579c
f71cb47
77f6751
78291f5
db2c812
03b1497
cda4b54
c984c60
f237f13
c27367e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| version: '3.8' | ||
|
|
||
| services: | ||
| mysql: | ||
| image: mysql:8 | ||
| container_name: mysql_hibernate_final | ||
| restart: unless-stopped | ||
| ports: | ||
| - "3306:3306" | ||
| environment: | ||
| MYSQL_ROOT_PASSWORD: root | ||
| volumes: | ||
| - mysql_data:/var/lib/mysql | ||
|
|
||
| redis-stack: | ||
| image: redis/redis-stack:latest | ||
| container_name: redis-stack | ||
| restart: unless-stopped | ||
| ports: | ||
| - "6379:6379" | ||
| - "8001:8001" | ||
| volumes: | ||
| - redis_data:/data | ||
|
|
||
| volumes: | ||
| mysql_data: | ||
| name: v_mysql_hibernate | ||
| redis_data: | ||
| name: v_redis_data |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,94 +1,56 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| <modelVersion>4.0.0</modelVersion> | ||
|
|
||
| <groupId>com.javarush.khmelov</groupId> | ||
| <artifactId>project-ledzeppelin</artifactId> | ||
| <groupId>com.javarush</groupId> | ||
| <artifactId>module4</artifactId> | ||
| <version>1.0-SNAPSHOT</version> | ||
| <name>ProjectLedzeppelin</name> | ||
| <packaging>war</packaging> | ||
|
|
||
| <properties> | ||
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
| <maven.compiler.target>21</maven.compiler.target> | ||
| <maven.compiler.source>21</maven.compiler.source> | ||
| <junit.version>5.10.2</junit.version> | ||
| <maven.compiler.target>21</maven.compiler.target> | ||
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
| </properties> | ||
|
|
||
| <parent> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-starter-parent</artifactId> | ||
| <version>3.3.5</version> | ||
| </parent> | ||
|
|
||
| <dependencyManagement> | ||
| <dependencies> | ||
| <dependency> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-dependencies</artifactId> | ||
| <type>pom</type> | ||
| <scope>import</scope> | ||
| <version>3.3.5</version> | ||
| </dependency> | ||
| </dependencies> | ||
| </dependencyManagement> | ||
|
|
||
| <dependencies> | ||
| <dependency> | ||
| <groupId>jakarta.servlet</groupId> | ||
| <artifactId>jakarta.servlet-api</artifactId> | ||
| <scope>provided</scope> | ||
| <groupId>mysql</groupId> | ||
| <artifactId>mysql-connector-java</artifactId> | ||
| <version>8.0.30</version> | ||
| </dependency> | ||
|
|
||
| <dependency> | ||
| <groupId>jakarta.servlet.jsp.jstl</groupId> | ||
| <artifactId>jakarta.servlet.jsp.jstl-api</artifactId> | ||
| <groupId>org.hibernate</groupId> | ||
| <artifactId>hibernate-core-jakarta</artifactId> | ||
| <version>5.6.14.Final</version> | ||
| </dependency> | ||
|
|
||
| <dependency> | ||
| <groupId>org.glassfish.web</groupId> | ||
| <artifactId>jakarta.servlet.jsp.jstl</artifactId> | ||
| <groupId>p6spy</groupId> | ||
| <artifactId>p6spy</artifactId> | ||
| <version>3.9.1</version> | ||
| </dependency> | ||
|
|
||
| <dependency> | ||
| <groupId>org.projectlombok</groupId> | ||
| <artifactId>lombok</artifactId> | ||
| <scope>provided</scope> | ||
| <groupId>io.lettuce</groupId> | ||
| <artifactId>lettuce-core</artifactId> | ||
| <version>6.2.2.RELEASE</version> | ||
| </dependency> | ||
|
|
||
| <dependency> | ||
| <groupId>org.junit.jupiter</groupId> | ||
| <artifactId>junit-jupiter-api</artifactId> | ||
| <scope>test</scope> | ||
| <groupId>com.fasterxml.jackson.core</groupId> | ||
| <artifactId>jackson-databind</artifactId> | ||
| <version>2.14.0</version> | ||
| </dependency> | ||
|
|
||
| <dependency> | ||
| <groupId>org.junit.jupiter</groupId> | ||
| <artifactId>junit-jupiter-engine</artifactId> | ||
| <scope>test</scope> | ||
| <groupId>org.projectlombok</groupId> | ||
| <artifactId>lombok</artifactId> | ||
| <version>1.18.36</version> | ||
| <scope>provided</scope> | ||
| </dependency> | ||
|
|
||
| </dependencies> | ||
|
|
||
| <build> | ||
| <plugins> | ||
| <plugin> | ||
| <groupId>org.apache.maven.plugins</groupId> | ||
| <artifactId>maven-war-plugin</artifactId> | ||
| <version>3.4.0</version> | ||
| </plugin> | ||
| <plugin> | ||
| <groupId>org.apache.maven.plugins</groupId> | ||
| <artifactId>maven-compiler-plugin</artifactId> | ||
| <configuration> | ||
| <annotationProcessorPaths> | ||
| <path> | ||
| <groupId>org.projectlombok</groupId> | ||
| <artifactId>lombok</artifactId> | ||
| <version>1.18.34</version> | ||
| </path> | ||
| </annotationProcessorPaths> | ||
| </configuration> | ||
| </plugin> | ||
| </plugins> | ||
|
|
||
| </build> | ||
| </project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,194 @@ | ||
| package com.javarush; | ||
|
|
||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import com.javarush.dao.CityDAO; | ||
| import com.javarush.dao.CountryDAO; | ||
| import com.javarush.domain.City; | ||
| import com.javarush.domain.Country; | ||
| import com.javarush.domain.CountryLanguage; | ||
| import com.javarush.redis.CityCountry; | ||
| import com.javarush.redis.Language; | ||
| import io.lettuce.core.RedisClient; | ||
| import io.lettuce.core.RedisURI; | ||
| import io.lettuce.core.api.StatefulRedisConnection; | ||
| import io.lettuce.core.api.sync.RedisStringCommands; | ||
| import org.hibernate.Session; | ||
| import org.hibernate.SessionFactory; | ||
| import org.hibernate.cfg.Configuration; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Класс Configuration для настройки Hibernate считается устаревшим. В новых проектах рекомендуется использовать ServiceRegistry и MetadataSources. |
||
| import org.hibernate.cfg.Environment; | ||
|
|
||
| import java.util.ArrayList; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Импорт java.util.ArrayList не используется. Это захламляет секцию импортов и затрудняет чтение кода. [INFO] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Импорт java.util.ArrayList не используется. Это захламляет секцию импортов и затрудняет чтение кода. |
||
| import java.util.List; | ||
| import java.util.Properties; | ||
| import java.util.Set; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import static java.util.Objects.nonNull; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Статический импорт Objects.nonNull не используется в представленном фрагменте кода. Неиспользуемые импорты следует удалять для поддержания чистоты проекта. [INFO] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Статический импорт Objects.nonNull не используется в представленном фрагменте кода. Неиспользуемые импорты следует удалять для поддержания чистоты проекта. |
||
|
|
||
| public class Main { | ||
|
|
||
| private final SessionFactory sessionFactory; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Поля sessionFactory и redisClient должны закрываться (close()) при завершении работы приложения. В коде не предусмотрен механизм закрытия ресурсов (Graceful Shutdown). [ERROR] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Поля sessionFactory и redisClient должны закрываться (close()) при завершении работы приложения. В коде не предусмотрен механизм закрытия ресурсов (Graceful Shutdown). |
||
| private final RedisClient redisClient; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Инициализация тяжеловесного RedisClient в конструкторе затрудняет создание объекта Main. Такую логику лучше вынести в отдельный метод инициализации или жизненного цикла. [WARNING] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Инициализация тяжеловесного RedisClient в конструкторе затрудняет создание объекта Main. Такую логику лучше вынести в отдельный метод инициализации или жизненного цикла. |
||
|
|
||
| private final ObjectMapper mapper; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Поле mapper следует пометить модификатором final, так как оно инициализируется один раз в конструкторе и не изменяется в дальнейшем. [INFO] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Поле mapper следует пометить модификатором final, так как оно инициализируется один раз в конструкторе и не изменяется в дальнейшем. |
||
|
|
||
| private final CityDAO cityDAO; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Для улучшения читаемости в Java 21 при инициализации локальных переменных внутри методов (например, в main или методах подготовки) следует чаще использовать ключевое слово var. [INFO] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Для улучшения читаемости в Java 21 при инициализации локальных переменных внутри методов (например, в main или методах подготовки) следует чаще использовать ключевое слово var. |
||
| private final CountryDAO countryDAO; | ||
|
|
||
| public Main() { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Конструктор содержит сложную логику инициализации ресурсов (БД, Redis). Согласно принципам SOLID, конструкторы должны заниматься только присваиванием зависимостей. [WARNING] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Конструктор содержит сложную логику инициализации ресурсов (БД, Redis). Согласно принципам SOLID, конструкторы должны заниматься только присваиванием зависимостей. |
||
| sessionFactory = prepareRelationalDb(); | ||
| cityDAO = new CityDAO(sessionFactory); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ручное создание экземпляров DAO (new CityDAO) создает жесткую связанность. Рекомендуется внедрять зависимости через конструктор, что упростит модульное тестирование. [WARNING] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ручное создание экземпляров DAO (new CityDAO) создает жесткую связанность. Рекомендуется внедрять зависимости через конструктор, что упростит модульное тестирование. |
||
| countryDAO = new CountryDAO(sessionFactory); | ||
|
|
||
| redisClient = prepareRedisClient(); | ||
| mapper = new ObjectMapper(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Создание нового экземпляра ObjectMapper в каждом экземпляре Main неэффективно. Этот объект потокобезопасен и должен использоваться как синглтон. [WARNING] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Создание нового экземпляра ObjectMapper в каждом экземпляре Main неэффективно. Этот объект потокобезопасен и должен использоваться как синглтон. |
||
| } | ||
|
|
||
|
|
||
| public static void main(String[] args) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Метод main содержит логику создания приложения. Рекомендуется разделять запуск приложения и выполнение бизнес-задач (например, через метод run()). [WARNING] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Метод main содержит логику создания приложения. Рекомендуется разделять запуск приложения и выполнение бизнес-задач (например, через метод run()). |
||
| Main main = new Main(); | ||
| List<City> allCities = main.fetchData(main); | ||
| List<CityCountry> preparedData = main.transformData(allCities); | ||
| main.pushToRedis(preparedData); | ||
|
|
||
| main.sessionFactory.getCurrentSession().close(); | ||
|
|
||
| List<Integer> ids = List.of(50, 1984, 42, 1, 2026, 49, 1337, 666, 15, 244); | ||
|
|
||
| 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(); | ||
| } | ||
|
|
||
| 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(); | ||
| } | ||
| } | ||
|
|
||
| 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(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| 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(); | ||
| } | ||
| } | ||
|
|
||
| } | ||
| } | ||
|
|
||
| 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; | ||
| } | ||
|
|
||
| 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.setIsOfficial(cl.getIsOfficial()); | ||
| language.setPercentage(cl.getPercentage()); | ||
| return language; | ||
| }).collect(Collectors.toSet()); | ||
| res.setLanguages(languages); | ||
|
|
||
| return res; | ||
| }).collect(Collectors.toList()); | ||
| } | ||
|
|
||
| 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(); | ||
| List<Country> countries = main.countryDAO.getAll(); | ||
| 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; | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Класс Configuration для настройки Hibernate считается устаревшим. В новых проектах рекомендуется использовать ServiceRegistry и MetadataSources. [INFO]