{"id":25507422,"url":"https://github.com/coder-chekunkov/article-postgresql","last_synced_at":"2025-04-10T12:33:19.840Z","repository":{"id":189534522,"uuid":"637396609","full_name":"coder-chekunkov/article-postgreSQL","owner":"coder-chekunkov","description":"article. postgreSQL, spring framework and android for beginner developer.","archived":false,"fork":false,"pushed_at":"2023-05-08T10:48:19.000Z","size":13006,"stargazers_count":6,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-24T11:13:22.617Z","etag":null,"topics":["android","article","java","kotlin","postgresql","spring","sql"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/coder-chekunkov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-05-07T12:40:27.000Z","updated_at":"2024-05-24T12:01:26.000Z","dependencies_parsed_at":null,"dependency_job_id":"b88b9431-7573-4824-a2c8-80246dfbe236","html_url":"https://github.com/coder-chekunkov/article-postgreSQL","commit_stats":null,"previous_names":["coder-chekunkov/postgresql-article","coder-chekunkov/article-postgresql"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder-chekunkov%2Farticle-postgreSQL","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder-chekunkov%2Farticle-postgreSQL/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder-chekunkov%2Farticle-postgreSQL/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder-chekunkov%2Farticle-postgreSQL/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/coder-chekunkov","download_url":"https://codeload.github.com/coder-chekunkov/article-postgreSQL/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248217145,"owners_count":21066633,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","article","java","kotlin","postgresql","spring","sql"],"created_at":"2025-02-19T07:31:47.914Z","updated_at":"2025-04-10T12:33:19.824Z","avatar_url":"https://github.com/coder-chekunkov.png","language":"Kotlin","readme":"### :book: PostgreSQL, Spring Framework и Android для начинающего разработчика.\n\nДанная статья была написана [coder-chekunkov](https://github.com/coder-chekunkov) и [uttsvlad](https://github.com/uttsvlad) для начинающих разработчиков. Темы, которые будут затронуты в данной статье: **PostgreSQL**, **Spring Framework** и **Android**. \u003cbr/\u003e\nСтатья опубликована на информационном ресурсе \"Habr\" - [ссылка](https://habr.com/ru/articles/733918/).\n\n---\n\nЗдравствуй, дорогой читатель. Каждый разработчик, независимо от его специальности, сталкивался (или столкнётся во время своей профессиональной карьеры) с задачей, в которой необходимо разработать проект, имеющий базу данных, серверную часть и конечный продукт, взаимодействующий с пользователем. Данная статья поможет новичку разобраться с данной задачей.\n\nВ статье будут затронуты такие важные темы, как теория баз данных, реляционная база данных **PostgreSQL**, **Spring Framework** и **Android разработка**. Также будет рассмотрен базовый, не очень сложный пример, который поможет разобраться во всех этих темах и \"потрогать\" их руками.\n\nСтатья предназначена для начинающего разработчика, но имеющего базовые знания о разработке программного обеспечения и языках программирования Java и Kotlin.\n\nВсе материалы и исходный код можно найти [здесь](https://github.com/coder-chekunkov/PostgreSQL-Article).\n\n### Что должно получиться?\n\nВ конечном итоге должен получиться **маленький pet-проект**, с мобильным приложением, серверной частью и базой данных. \n\nОсновная тема приложения: интернет-магазин, который продаёт какой-то товар.\nУ конечного пользователя должна быть возможность зарегистрироваться и авторизоваться в интернет-магазине, редактировать свои персональные данные, выбирать и заказывать товар.\n\n\u003cp align=\"center\"\u003e\n \u003cimg alt=\"image_APP_001\" src=\"https://github.com/coder-chekunkov/PostgreSQL-Article/blob/main/app/app/src/main/res/drawable/image_APP_001.png\" width=\"220\"/\u003e\n \u003cimg alt=\"image_APP_002\" src=\"https://github.com/coder-chekunkov/PostgreSQL-Article/blob/main/app/app/src/main/res/drawable/image_APP_002.png\" width=\"220\"/\u003e\n \u003cimg alt=\"GIF\" src=\"https://github.com/coder-chekunkov/PostgreSQL-Article/blob/main/app/app/src/main/res/drawable/gif-APP-001.gif\" width=\"220\"/\u003e\n\u003c/p\u003e\n\n### Теория баз данных\n\nПеред тем как изучать PostgreSQL, Spring Framework и Android необходимо **разобраться с основными понятиями баз данных**.\n\n`База данных` - организованная коллекция данных, которая хранится в компьютерной системе и используется для эффективного хранения, управления и доступа к информации. База данных содержит структурированные данные, такие как числа, текст, изображения или другие типы информации, и обеспечивает методы для добавления, изменения, удаления и извлечения данных.\n\n`СУБД (Система Управления Базами Данных)` - программное обеспечение, предназначенное для создания, управления и обработки баз данных. СУБД предоставляет интерфейсы и функциональность, позволяющие пользователям определить структуру данных, добавлять, изменять, удалять и извлекать данные из базы данных, а также выполнять другие операции, связанные с обработкой данных. \n\n`Сущность базы данных` - объект или объектная модель, который представляет определенный тип данных или информацию, хранящуюся в базе данных. Сущность базы данных также может называться \"таблицей\" или \"классом\" в различных моделях баз данных.\n\n`Атрибут базы данных` - конкретная характеристика или свойство, которое определяет определенный аспект сущности базы данных, такой как сущность, таблица или класс. Атрибуты представляют собой конкретные данные, которые хранятся в базе данных.\n\n`ER-диаграмма` - модель данных, позволяющая описывать концептуальные схемы предметной области. ER-модель используется при высокоуровневом проектировании баз данных. С её помощью можно выделить ключевые сущности и обозначить связи, которые могут устанавливаться между этими сущностями.\n\n`Первичный ключ` - уникальный идентификатор, который однозначно идентифицирует каждую запись в таблице базы данных. Он используется для уникальной идентификации записей в таблице и обеспечивает уникальность данных в столбце или наборе столбцов.\n\n`Внешний ключ` - атрибут или набор атрибутов в таблице, который ссылается на первичный ключ (или уникальный ключ) в другой таблице. Он используется для установления связей между таблицами в реляционной базе данных. Внешний ключ служит для определения отношений между записями в разных таблицах и обеспечивает целостность данных.\n\n`Транзакция` - логическая операция или последовательность операций, выполняемых в базе данных, которые образуют единую и неделимую единицу работы. Транзакция может включать операции чтения, записи или модификации данных в базе данных. Главной особенностью транзакции является ее атомарность, то есть она либо выполняется полностью, либо не выполняется совсем.\n\n`Нормализация` - процесс организации данных в базе данных с целью устранения избыточности и аномалий данных, чтобы обеспечить эффективное хранение, манипуляцию и поддержку целостности данных.\n\n`Связь` - способ, с помощью которого данные в различных таблицах базы данных могут быть связаны и взаимодействовать друг с другом. Связи используются для определения отношений между таблицами и обеспечивают эффективную организацию и связность данных в базе данных.\n\nСуществует несколько типов связей в реляционных базах данных, таких как:\n\n\n| Связь | Пояснение | Пример |\n| -------- | -------- | -------- |\n| Один-к-одному (One-to-One)     |  Один объект в одной таблице связан с одним объектом в другой таблице.     | Таблица \"Сотрудники\" может быть связана с таблицей \"Паспортные данные\" с помощью уникального идентификатора сотрудника.     |\n| Один-ко-многим (One-to-Many)     | Один объект в одной таблице связан с несколькими объектами в другой таблице.    | Таблица \"Отделы\" может быть связана с таблицей \"Сотрудники\", где один отдел может иметь несколько сотрудников, используя общий идентификатор отдела.     |\n| Многие-ко-многим (Many-to-Many)    | Множество объектов в одной таблице связано с множеством объектов в другой таблице. Для реализации такой связи требуется дополнительная таблица-связь, которая содержит связи между объектами двух таблиц.| Таблица \"Студенты\" может быть связана с таблицей \"Курсы\" через таблицу-связь \"Расписание\", чтобы отслеживать связи между студентами и курсами.   |\n\n\nИсточники, которые могут **расширить кругозор** по теории баз данных можно найти [здесь](https://metanit.com/sql/), [здесь](https://habr.com/ru/articles/555760/) и [здесь](https://site-do.ru/db/db1.php).\n\n### PostgreSQL и создание базы данных\n\nТеперь следует поговорить о PostgreSQL.\n\n`PostgreSQL` - это свободная объектно-реляционная система управления базами данных (СУБД), которая является одной из наиболее мощных и распространенных СУБД в мире. \n\nPostgreSQL предоставляет множество возможностей, включая поддержку **SQL** (Structured Query Language), транзакции с поддержкой **ACID** (атомарность, согласованность, изолированность, долговечность), индексы, хранимые процедуры, триггеры и многое другое. Она также обладает **высокой степенью надежности**, масштабируемости и производительности, что делает ее популярным выбором для различных приложений, включая веб-сайты, системы управления контентом, финансовые приложения и т.д. \n\nВ первую очередь, при разработке базы данных, необходимо **составить ER-диаграмму**, которая наглядно отобразит будущую систему.\n\n\u003cp align=\"center\"\u003e\n    \u003cimg alt=\"image_DB_001\" src=\"https://github.com/coder-chekunkov/PostgreSQL-Article/blob/main/app/app/src/main/res/drawable/image_DB_001.png\" width=\"350\"/\u003e\n\u003c/p\u003e\n\n| Таблица | Пояснение | \n| -------- | -------- | \n|USER|Содержит данные пользователей, такие как ФИО, email и пароль, хранящийся в зашифрованном виде|\n| PRODUCT|Хранит данные продуктов: ссылка на изображение, название, цена|\n|CART|Сопоставляет пользователя с его корзиной и продуктами в ней. Вспомогательной является таблица CART_PRODUCTS|\n\nНиже приведен **SQL-скрипт, который создаёт базу данных**:\n\n```sql\ncreate table _user\n(\n    id          bigserial not null,\n    email       varchar(255),\n    first_name  varchar(255),\n    last_name   varchar(255),\n    middle_name varchar(255),\n    password    varchar(255),\n    primary key (id)\n)\n\ncreate table cart\n(\n    id      bigserial not null,\n    user_id bigint,\n    primary key (id)\n)\n\ncreate table cart_products\n(\n    cart_id     bigint not null,\n    products_id bigint not null\n)\n\ncreate table product\n(\n    id    bigserial not null,\n    image varchar(255),\n    name  varchar(255),\n    price float(53),\n    primary key (id)\n)\n\nalter table if exists cart_products\n    add constraint UK_3kg5kx19f8oy0lo76hdhc1uw1 unique (products_id)\n\nalter table if exists cart\n    add constraint FKil7wc86wc3xs4je2ghfenn854 foreign key (user_id) references _user\n\nalter table if exists cart_products\n    add constraint FKhyhnx21758m3wmbi4ps96m0yw foreign key (products_id) references product\n\nalter table if exists cart_products\n    add constraint FKnlhjc091rdu9k5c8u9xwp280w foreign key (cart_id) references cart\n```\n\nТакже привём SQL-скрипт, который **заполняет базу данных тестовыми значениями**:\n\n```sql\ninsert into product\nvalues (default,\n        'https://static.wikia.nocookie.net/fnaf-fanon-animatronics/images/4/40/%D0%91%D0%B0%D0%BD%D0%B0%D0%BD.png/revision/latest?cb=20190614113143\u0026path-prefix=ru',\n        'Banana', 179.90),\n       (default,\n        'https://m.dom-eda.com/uploads/images/catalog/item/86df51de21/c25c94fe96_1000.jpg',\n        'Apple', 199.90),\n       (default,\n        'https://media.vprok.ru/products/x700/tn/u6/uyt2yc2e337ofqf7vsfm4cdomdxwu6tn.jpeg',\n        'Strawberry', 250.00),\n       (default,\n        'https://foodcity.ru/storage/products/October2018/8No9uQ14ycYG7UluJEaM.jpg',\n        'Pineapple', 419.90),\n       (default,\n        'https://eden-g.com/assets/images/products/1645/gridjpgbigest/qiwi-m.jpg',\n        'Kiwi', 315.90),\n       (default,\n        'https://fruit-berries.ru/images/cms/thumbs/3a031f89ff2849f64a5233c90f77a8ac353f35bb/durian-800x1000_483_483_jpg_5_92.jpg',\n        'Durian', 899.00),\n       (default,\n        'https://ecomarket.ru/imgs/products/13647/mango-sochnoe---600-g-1.1632501465.JPG',\n        'Mango', 510.90)\n```\n\n### Spring Framework\n\n`Spring Framework` - это популярный фреймворк для разработки приложений на языке Java. Он предоставляет различные инструменты и функции, которые упрощают разработку приложений, такие как управление зависимостями, инверсия управления, а также возможность использования различных модулей для реализации конкретной функциональности.\n\n*Spring Framework* построен на основе паттерна **\"Inversion of Control\" (IoC)**, что позволяет сосредоточиться на разработке бизнес-логики приложения, а не заботиться о решении технических задач, связанных с управлением зависимостями и ресурсами. Кроме того, *Spring Framework* также поддерживает паттерн **\"Aspect Oriented Programming\" (AOP)**, который позволяет разделять общую функциональность на отдельные аспекты, чтобы упростить их использование и повторное использование.\n\n*Spring Framework* включает в себя **множество модулей**, каждый из которых предназначен для решения конкретных задач, таких как веб-приложения, работа с базами данных, интеграция с другими приложениями и т.д. Эти модули могут использоваться как самостоятельно, так и в комбинации с другими модулями, что делает Spring Framework очень гибким и масштабируемым решением для различных типов приложений.\n\nСоставим \"поэтапный план действий\", которому будем придерживаться для создания backend'a приложения:\n\n***1. Подключение зависимомстей.***\n\nВ качестве сборщика **следует использовать Gradle**, подробнее про работу с ним можно прочитать [здесь](https://spring.io/guides/gs/gradle/). \n\nНам необходимо **дабавить следующие модули** Spring: *Data JPA*, *Web* и *Security*. Также, необходимо **подключить драйвер базы данных** PostgreSQL. \n\n```groovy\ndependencies {\n    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'\n    implementation 'org.springframework.boot:spring-boot-starter-web'\n    implementation 'org.springframework.boot:spring-boot-starter-security'\n    implementation 'com.auth0:java-jwt:4.4.0'\n    compileOnly 'org.projectlombok:lombok'\n    runtimeOnly 'org.postgresql:postgresql'\n    annotationProcessor 'org.projectlombok:lombok'\n    testImplementation 'org.springframework.boot:spring-boot-starter-test'\n}\n```\n\n***2. Создание сущностей и репозиториев.***\n\nНеобходимо **создать 3 класса сущностей**: User, Product и Cart, пометим их аннотацией `@Entity`. Про механизм ORM, который использует *Spring Data JPA* подробнее можно прочитать [здесь](https://practicum.yandex.ru/blog/hibernate-java/).\n\n```java\n@Entity\n@Getter\n@Setter\n@AllArgsConstructor\n@NoArgsConstructor\n@Table(name = \"_user\")\npublic class User {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n    private String email;\n    private String password;\n    private String firstName;\n    private String lastName;\n    private String middleName;\n}\n```\n```java\n@Entity\n@Getter\n@Setter\n@AllArgsConstructor\n@NoArgsConstructor\npublic class Product {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n    private String name;\n    private Double price;\n    private String image;\n\n}\n```\n```java\n@Entity\n@Getter\n@Setter\n@AllArgsConstructor\n@NoArgsConstructor\npublic class Cart {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n    @OneToOne\n    private User user;\n    @OneToMany\n    private List\u003cProduct\u003e products;\n}\n```\nАналогичным образом **создадим репозитории**:\n```java\npublic interface CartRepository extends JpaRepository\u003cCart, Long\u003e {\n}\n```\n\n*Spring Data* позволяет разработчику не беспокоиться над реализацией метода, который решает типичные задачи манипуляций данными (чтение, добавление, обновление и удаление). Например, мы хотим создать метод, который будет искать корзину по пользователю, для этого достаточно описать сигнатуру в интерфейсе **без последующей реализации**, например: \n```java\npublic interface CartRepository extends JpaRepository\u003cCart, Long\u003e {\n    Cart findByUser(User user);\n}\n```\n\nДля более глубокого понимания как работает Spring Data - рекомендую посмотреть данный [источник](https://www.youtube.com/watch?v=nwM7A4TwU3M\u0026t=5s).\n\n***3. Создание контроллеров.***\n\n**Создадим контроллеры**, в которых происходит обработка входящих запросов. В качестве примера разберем UserController:\n```java\n@RestController\n@RequestMapping(\"/users\")\n@RequiredArgsConstructor\npublic class UserController {\n    private final UserService userService;\n\n    @GetMapping(\"/{id}\")\n    public ResponseEntity\u003cUser\u003e findUser(@PathVariable Long id) {\n        User user = userService.findUser(id);\n        return ResponseEntity.ok(user);\n    }\n\n    @PatchMapping(\"/edit/{id}\")\n    public ResponseEntity\u003cUser\u003e editUser(@PathVariable Long id, @RequestBody UserEditDTO userEditDTO) {\n        User user = userService.editUser(id, userEditDTO);\n        return ResponseEntity.ok(user);\n    }\n}\n```\nВ нем представлены 2 конечные точки: ***/users/{id}*** и ***/users/edit/{id}***.\nАннотация `@RestController` сообщает Spring, что ответы на запросы неоходимо возвращать в формате JSON. `@RequestMapping(\"/users\")` позволяет использовать один префикс для всех запросов. `@GetMapping` и `@PatchMapping` сообщают о том, какие HTTP методы исопльзовать для запросов: GET и PATCH соответсвенно.\n\n***4. Создание бизнес-логики.***\n```java\n@Service\n@RequiredArgsConstructor\npublic class UserService {\n    private final UserRepository userRepository;\n\n    public User findUser(Long id) {\n        Optional\u003cUser\u003e optionalUser = userRepository.findById(id);\n        return optionalUser.orElseThrow();\n    }\n\n    public User editUser(Long id, UserEditDTO userEditDTO) {\n        User user = userRepository.findById(id).orElseThrow();\n        user.setFirstName(userEditDTO.firstName());\n        user.setLastName(userEditDTO.lastName());\n        user.setMiddleName(userEditDTO.middleName());\n\n        return userRepository.save(user);\n    }\n}\n```\nВ сервисе присутсвует 2 метода с простой логикой: поиск пользователя по ID, в котором используется метод репозитория `findById(Long id)` с реализацией, поставляемой *Spring Data* по умолчанию, а также `editUser(Long id, UserEditDTO userEditDTO)` ([что такое DTO](https://habr.com/ru/articles/513072/)), который обновляет ФИО пользователя в базе данных с помощью метода `save(User user) UserRepository`.\n\n***5. Безопасность.***\n\nДля того, чтобы запросы к нашему приложению могли приходить только от аутентифицированных пользователей, **используем модуль** Spring Security. Первым делом **создадим и настроим файл конфигурации** SecurityConfig:\n```java\n@Configuration\n@EnableWebSecurity\n@EnableMethodSecurity\n@RequiredArgsConstructor\npublic class SecurityConfig {\n    private final JwtFilter jwtFilter;\n\n    @Bean\n    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n        http\n                .csrf().disable()\n                .authorizeHttpRequests().requestMatchers(\"/auth/**\").permitAll()\n                .anyRequest().hasRole(\"USER\")\n                .and().sessionManagement().disable()\n                .logout().logoutUrl(\"/logout\")\n                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);\n        http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);\n\n        return http.build();\n    }\n\n    @Bean\n    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)\n            throws Exception {\n        return authenticationConfiguration.getAuthenticationManager();\n    }\n\n    @Bean\n    public PasswordEncoder getPasswordEncoder() {\n        return new BCryptPasswordEncoder();\n    }\n}\n```\n**Особое внимание** в коде выше стоит уделить созданию бина `filterChain`, в нем описывается основная логика работы Spring Security. В качестве принципа, по которому мы создаем этот бин, выступает паттерн [chain of responsibility](https://metanit.com/sharp/patterns/3.7.php). Так, запрос поступающий нашему приложению поэтапно проходит валидацию через каждый метод, например: с помощью `.requestMatchers(\"/auth/**\").permitAll()` мы сообщаем Spring-у пропускать все запросы начинающиеся на ***/auth/*** без авторизации, нужно это для того, чтобы пользователи могли зарегистрироваться/авторизоваться в приложении. Строкой `.anyRequest().hasRole(\"USER\")` мы описываем, что ко всем остальным эндпоинтам имеют доступ только авторизованные пользователи с ролью \"USER\" (в соответсвии с логикой нашего приложения, она выдается всем при регистрации). Также, стоит обратить внимание на строку `http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);`, в ней мы добавляем фильтр проверки валидности [JWT](https://habr.com/ru/articles/340146/) токена.\nТема безопасности в Spring приложении достойна отдельного обсуждения, чтобы не перегружать статью, оставлю ссылку на изучение модуля [Security](https://www.baeldung.com/security-spring), [JWT-аутентификация в Spring приложении](https://www.toptal.com/spring/spring-security-tutorial). \n\n### Android-приложение\n\nПосле того, как была разработана и заполнена база данных, создан сервер - необходимо перейти к **созданию мобильного приложения**, с которым будет взаимодействовать конечный пользователь.\n\nВ данной статье не будут рассмотрены основы разработки приложения - верстка layot'ов, связь с классами и т.д. Основное внимание будет уделено библиотеке [Retrofit2](https://github.com/square/retrofit).\n\n**Важное уточнение!** В данном приложении используется небольшое \"ядро\" с архитектурой MVVM. Данное \"ядро\" используется для быстрого и качественного расширения приложения, а также для удобства разработки приложения с шаблоном проектирования MVVM. Более детально с \"ядром\" можно ознакомиться [здесь](https://github.com/coder-chekunkov/MVVM-architecture).\n\n\u003e MVVM (Model-View-ViewModel) - паттерн проектирования для разработки пользовательского интерфейса, который помогает разделить приложение на три отдельных компонента: Model, View и viewModel. Более подробно - [здесь](https://itsobes.ru/AndroidSobes/chto-takoe-mvvm/).\n\n`Retrofit2` - это библиотека для работы с сетевыми запросами в приложениях для Android. Она предоставляет удобный интерфейс для взаимодействия с API удаленных серверов с использованием HTTP-запросов.\n\nС помощью данной библиотеки можно **определить интерфейс** для взаимодействия с сервером и **использовать аннотации** для указания конечной точки URL, параметров запроса, тела запроса и типа запроса (например, GET, POST, PUT и т.д.). Также библиотека может **автоматически преобразовывать** ответы в Java/Kotlin-объекты с помощью специализированных конвертеров.\n\nБиблиотека упрощает процесс работы с сетевыми запросами и позволяет сосредоточиться на более важных аспектах разработки приложения. Retrofit2 обладает хорошей производительностью и является одной из наиболее популярных библиотек для работы с сетью в Android-разработке.\n\nВ первую очередь **необходимо импортировать** саму библиотеку и JSON-конвертер, который будет преобразовывать JSON-файлы в data-классы. Для этого в файле *build.gradle* уровня приложения, в тэге *dependencies*:\n\n```groovy\n    implementation 'com.squareup.retrofit2:retrofit:2.9.0'\n    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'\n```\n\nТакже добавим **другие зависимости**, которые потребуются в процессе разработки:\n\n```groovy\ndependencies {\n    ---\n    // !!! - Retrofit 2 \u0026 GSON-converter\n    implementation 'com.squareup.retrofit2:retrofit:2.9.0'\n    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'\n    // !!! - Glide\n    implementation 'com.github.bumptech.glide:glide:4.15.1'\n    // !!! - Fragment Ktx\n    implementation 'androidx.fragment:fragment-ktx:1.5.6'\n    // !!! - Kotlin Coroutines\n    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'\n    // !!! - JWT decode\n    implementation 'com.auth0.android:jwtdecode:2.0.2'\n    ---\n}\n```\n\nДля работы библиотеки **необходимы разрешения**, которые следует прописать в AndroidManifest:\n\n```xml\n    \u003cuses-permission android:name=\"android.permission.INTERNET\" /\u003e\n    \u003cuses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" /\u003e\n```\n\nТакже, важно отметить, что в Retrofit2 огромное количество задач **реализовываются с помощью аннотаций**, о которых будет рассказано ниже. Вот самые основные:\n\n* `@GET`, `@POST`, `@PUT`, `@DELETE`, `@PATCH` - используются для указания типа HTTP-запроса, который должен быть отправлен на сервер.\n* `@Body` - используется для передачи тела запроса. Тело запроса может быть представлено объектом модели или строкой.\n* `@Path` - используется для передачи динамических параметров запроса. Значение параметра будет извлечено из URL-адреса запроса.\n* `@Url` - используется для указания URL-адреса запроса. Эта аннотация может использоваться вместо параметра *baseURL*, который указывается в *Retrofit.Builder()*.\n* `@Header`, `@Headers` - используются для передачи заголовков запроса. *@Header* используется для передачи одного заголовка, а *@Headers* - для передачи нескольких заголовков.\n* `@JsonAdapter` - используется для указания пользовательского адаптера для сериализации и десериализации объектов в JSON.\n\nПосле того, как все организационные вопросы были рассмотрены, а задачи сделаны, следует перейти к связи приложения и сервера. **Начнем с авторизации**.\n\nВ первую очередь **создадим сущности** *AuthEntity* и *TokenEntity*. Первая сущность,  содержащая авторизационные данные, будет отправляться на сервер. Вторая сущность, содержащая два токена, будет приходить с сервера.\n\n```kotlin\ndata class AuthEntity(\n    @SerializedName(\"email\") var email: String,\n    @SerializedName(\"password\") var password: String\n)\n\ndata class TokenEntity(\n    @SerializedName(\"accessToken\") var accessToken: String? = null,\n    @SerializedName(\"refreshToken\") var refreshToken: String? = null\n)\n```\n\nДля реализации авторизации **создадим интерфейс** *UserAPI*, в котором, с помощью аннотаций, пропишем метод, который будет связываться с эндпоинтом \"auth/login\". Данный метод, в качестве тела запроса будет получать сущность AuthEntity, а возвращать - TokenEntity:\n\n```kotlin\ninterface UserAPI {\n\n    @POST(\"auth/login\")\n    suspend fun authorization(@Body body: AuthEntity): TokenEntity\n\n}\n```\n\nТеперь **создадим класс** *UserServerRepository*, который будет вызывать методы связи с сервером. Данный класс имеет один метод *signIn*, принимающий e-mail и пароль как авторизационные данные пользователя. \nВажно отметить - метод *signIn* будет **вызываться из корутины**, соответсвенно, данный метод помечен ключевым словом *suspend*. Также в данном методе переопределен Dispatcher для корректной работы приложения. Подробнее про корутины можно почитать [здесь](https://metanit.com/kotlin/tutorial/8.1.php) и [здесь](https://kotlinlang.org/docs/coroutines-overview.html).\n\n```kotlin\nclass UserServerRepository(retrofit: Retrofit) : UserRepository {\n\n    private val userAPI = retrofit.create(UserAPI::class.java)\n\n    override suspend fun signIn(email: String, password: String): TokenEntity {\n        return withContext(Dispatchers.IO) {\n            val authEntity = AuthEntity(email, password)\n\n            return@withContext userAPI.authorization(authEntity)\n        }\n    }\n\n}\n```\n\nВ *AuthViewModel* **создадим метод** *authorization*, который будет принимать e-mail и пароль от пользователя со стороны фрагмента. Данный метод запускает новую корутину и вызывает метод *signIn* из UserServerRepository. \nВ случае, если данные (два токена) будут **получены успешно**, то токены сохраняться в хранилище устройства, а после будет запущен основной экран.\n\n```kotlin\n    fun authorization(email: String, password: String) {\n        viewModelScope.launch() {\n            try {\n                val tokenEntity = userRepository.signIn(email, password)\n                tokenService.setTokens(tokenEntity)\n\n                launchMainScreen()\n            } catch (exception: Exception) {\n                Log.d(\"Auth Error\", exception.toString())\n            }\n        }\n    }\n```\n\nАналогично авторизации реализовывается регистрация. Пусть это будет **домашнем заданием**, ведь если вставлять каждый раз один и тот же код - статья бы стала слишком большой. Но, все же, если реализовать самому не получается - все исходники [здесь](https://github.com/coder-chekunkov/PostgreSQL-Article/tree/main/app).\n\nРассмотрим еще один запрос к серверу - получение персональных данных пользователя. В данном запросе необходимо **передать в параметре запроса** уникальный номер пользователя и провести авторизацию с помощью JWT. Создадим метод:\n\n```kotlin\n    @GET(\"users/{user_id}\")\n    suspend fun getUserPersonalData(\n        @Header(\"Authorization\") accessToken: String,\n        @Path(\"user_id\") userId: Long\n    ): PersonalDataEntity\n```\n\nВ параметрах данного метода находятся две переменные **с соответсвующими аннотациями**. Как уже было сказано, аннотация *@Header* используются для передачи заголовков запроса, в данном случае - авторизация. Аннотация *@Path* используется для передачи id пользователя в URL (*например, если у пользователя id = 7, то получаем путь: \".../users/7\"*).\n\nАналогично сущностям авторизации и регистрации **создадим сущность** с персональными данными пользователя:\n\n```kotlin\ndata class PersonalDataEntity(\n    @SerializedName(\"id\") var id: Long? = null,\n    @SerializedName(\"email\") var email: String? = null,\n    @SerializedName(\"firstName\") var firstName: String? = null,\n    @SerializedName(\"lastName\") var lastName: String? = null,\n    @SerializedName(\"middleName\") var middleName: String? = null\n) \n```\n\nИдентично регистрации и авторизации **получим данные о пользователе** из userRepository, в котором создадим метод *getPersonalData*, принимающий токен и уникальный номер:\n\n```kotlin\n    override suspend fun getPersonalData(accessToken: String, userId: Long): PersonalDataEntity {\n        return withContext(Dispatchers.IO) {\n            return@withContext userAPI.getUserPersonalData(\"Bearer $accessToken\", userId)\n        }\n    }\n```\n\n**Обратимся к этому методу** из *PersonalDataViewModel*. Функция во ViewModel создает новую корутину, в которой вытаскивает из хранилища устройства JWT, расшифровывает его (библиотека \"JWT-Decode\") и отправляет в репозиторий:\n\n```kotlin\n    private val _personalData = MutableStateFlow(PersonalDataEntity.emptyPersonalDataEntity)\n    val personalData: StateFlow\u003cPersonalDataEntity\u003e = _personalData\n\n    fun getPersonalData() {\n        viewModelScope.launch {\n            try {\n                val accessToken = tokenService.getAccessToken()!!\n                val jwt = JWT(accessToken)\n                val id = jwt.getClaim(\"id\").asString()?.toLong() ?: -1\n\n                val personalDataEntity = userRepository.getPersonalData(accessToken, id)\n                _personalData.value = personalDataEntity\n            } catch (exception: Exception) {\n                Log.d(\"PersonalData Error\", exception.toString())\n            }\n        }\n    }\n```\n\nЯ надеюсь, основная концепция работы библиотеки понятна. Далее в данной статье будут приводиться только сущности, которые приходят с сервера, API-методы и методы репозиториев. \n\n***Редактирование персональных данных пользователя.***\n\n```kotlin\n// Сущность для редактирования персональных данных пользователя:\ndata class EditPersonalDataEntity(\n    @SerializedName(\"firstName\") var firstName: String,\n    @SerializedName(\"lastName\") var lastName: String? = null,\n    @SerializedName(\"middleName\") var middleName: String? = null\n)\n```\n\n```kotlin\n    // API-метод, который делает запрос на редактирование:\n    @PATCH(\"users/edit/{user_id}\")\n    suspend fun editUserPersonalData(\n        @Header(\"Authorization\") accessToken: String,\n        @Path(\"user_id\") userId: Long,\n        @Body newUserData: EditPersonalDataEntity\n    ): PersonalDataEntity\n```\n\n```kotlin\n    // Метод UserServerRepository, который связывается с сервером:\n    override suspend fun editPersonalData(\n        accessToken: String, firstName: String, lastName: String?, middleName: String?, userId: Long\n    ): PersonalDataEntity {\n        return withContext(Dispatchers.IO) {\n            val newUserData = EditPersonalDataEntity(\n                firstName = firstName, lastName = lastName, middleName = middleName\n            )\n\n            return@withContext userAPI.editUserPersonalData(\n                accessToken = \"Bearer $accessToken\", userId = userId, newUserData = newUserData\n            )\n        }\n    }\n```\n\n***Получение всех существующих продуктов для вкладки \"Магазин.\"***\n\n```kotlin\n// Сущность одного продукта магазина:\ndata class ProductEntity(\n    @SerializedName(\"id\") var id: Long,\n    @SerializedName(\"name\") var name: String,\n    @SerializedName(\"price\") var price: Double,\n    @SerializedName(\"image\") var image: String\n)\n```\n\n```kotlin\n    // API-метод, который возвращает список объектов-продуктов:\n    @GET(\"products\")\n    suspend fun getAllProducts(@Header(\"Authorization\") accessToken: String): List\u003cProductEntity\u003e\n```\n\n```kotlin\n    // Метод ProductsServerRepository, который связывается с сервером:\n    override suspend fun getAllProducts(accessToken: String): List\u003cProductEntity\u003e {\n        return withContext(Dispatchers.IO) {\n            return@withContext productsAPI.getAllProducts(\"Bearer $accessToken\")\n        }\n    }\n```\n\n***Добавление нового продукта в корзину.***\n\n```kotlin\n// Сущность \"нового продукта\", добавленного в корзину:\ndata class NewProductEntity(\n    @SerializedName(\"userId\") var userId: Long,\n    @SerializedName(\"productId\") var productId: Long\n)\n```\n\n```kotlin\n    // API-метод, который добавляет новый продукт в корзину:\n    @POST(\"carts/add\")\n    suspend fun addNewProductInBasket(\n        @Header(\"Authorization\") accessToken: String,\n        @Body body: NewProductEntity\n    )\n\n```\n\n```kotlin\n    // Метод ProductsServerRepository, который связывается с сервером:\n    override suspend fun addNewProductInBasket(accessToken: String, userId: Long, productId: Long) {\n        withContext(Dispatchers.IO) {\n            productsAPI.addNewProductInBasket(\n                accessToken = \"Bearer $accessToken\",\n                body = NewProductEntity(\n                    userId = userId,\n                    productId = productId\n                )\n            )\n        }\n    }\n```\n\n### Заключение\n\nВ данной статье было затронуто огромное количество важных тем, с которыми должен быть знаком каждый разработчик, независимо от его специальности. \nЕстественно, это **далеко не все**, что позволяют делать PostgreSQL, Spring Framework, Retrofit2, но лишь основы. Если Вы хотите сильнее углубиться в эти темы, то изучите следующие источники, которые могут помочь Вам:\n* Metanit - Руководство по PostgreSQL - [ссылка](https://metanit.com/sql/postgresql/).\n* Что такое База Данных (БД) - [ссылка](https://habr.com/ru/articles/555760/).\n* Metanit - Программирование под Android - [ссылка](https://metanit.com/java/android/).\n* Retrofit 2 в Android - [ссылка](https://developer.alexanderklimov.ru/android/library/retrofit.php).\n* Spring | Learn - [ссылка](https://spring.io/learn).\n* Learn Spring Boot - [ссылка](https://www.baeldung.com/spring-boot).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoder-chekunkov%2Farticle-postgresql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcoder-chekunkov%2Farticle-postgresql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoder-chekunkov%2Farticle-postgresql/lists"}