Упрощение доступа к данным с помощью MySQL и Jakarta Data
Как спецификация Jakarta Data упрощает персистентность.

Многие приложения, особенно в корпоративной сфере, хранят данные или каким-то образом обращаются к ним. Реляционные базы данных до сих пор чаще всего используются для сохранения данных, несмотря на то, что им в противовес стоят такие базы данных, как NoSQL. В этой статье рассматриваются некоторые концепции доступа к данным, а также то, как новая спецификация Jakarta Data упрощает доступ к данным для разработчиков приложений.

Персистентность данных

Начнем обсуждение с определения самой концепций персистентности. Если вы уже знакомы с этими понятиями, то можете пропустить эту часть и сразу перейти к следующему разделу с примерами кода.

CRUD. Наиболее распространенными операциями, используемыми в приложениях, сохраняющих данные, являются операции создания, чтения, обновления и удаления (CRUD). Операции CRUD обычно ассоциируются с реляционными базами данных, но могут быть применены к любым механизмам сохранения данных. Написание кода для этих операций обычно представляет собой повторяющуюся работу, состоящую в основном из шаблонного кода.

ORM. Объектно-реляционное отображение (ORM), как следует из названия, занимается отображением объектов объектно-ориентированного языка на данные в реляционной базе данных. Существует множество ORM-фреймворков, помогающих разработчикам решать эту задачу. Jakarta Persistence, ранее называвшаяся JPA, — это спецификация, стандартизирующая управление персистентностью и объектно-реляционным отображением для Java-приложений.

Паттерн "Репозиторий". Существует несколько паттернов и стратегий, таких как Data Access Object (DAO), Repository, Active Record и другие, которые обычно используются для структурирования кода, связанного с CRUD-операциями. В этой статье будем использовать паттерн Repository.

Смысл паттерна Repository (о котором можно прочитать в книге Мартина Фаулера "Patterns of Enterprise Application Architecture") заключается в том, чтобы не выносить специфику персистентности за пределы доменной модели приложения. Репозитории — это классы, которые инкапсулируют логику доступа к данным, тем самым отделяя механизм персистентности от доменной модели.

Repository стал популярным и широко используется благодаря таким технологиям, как Spring Data; и не секрет, что именно Spring Data послужила источником вдохновения для Jakarta Data.

Jakarta Data. Jakarta Data — это новая спецификация, которую предлагается включить в Jakarta EE 11, выпуск которой запланирован на первую половину 2024 года. Реализуя паттерн Repository, Jakarta Data упрощает доступ к данным и уменьшает количество необходимого шаблонного кода. Разработчикам достаточно определить интерфейс, представляющий хранилище, и сущность, представляющую таблицу базы данных. Реализация Jakarta Data будет обеспечивать фактическую реализацию репозитория.

Простой пример с MySQL

Рассмотрим на очень простом примере как Jakarta Data упрощает персистентность для разработчиков, избавляя их от необходимости использовать шаблонный код. В примере используются следующие технологии

MySQL 8.0.34

Open Liberty 23.0.0.9-beta

Так же должны быть установлены Apache Maven и JDK. Данный код был проверен на Java 20, но может работать и на других версиях.

В примере кода, в качестве среды выполнения используется Open Liberty. Однако при появлении другой реализации вы сможете заменить Open Liberty на другую реализацию без изменения кода.

Шаг 1. Убедитесь, что установлены Apache Maven и JDK. Вы должны увидеть что-то вроде следующего:
Статью в оригинале можно прочитать тут

$ mvn --version
Apache Maven 3.8.2 (ea98e05a04480131370aa0c110b8c54cf726c06f)
Maven home: /home/ivar/.sdkman/candidates/maven/current
Java version: 20.0.1, vendor: Eclipse Adoptium, runtime: /home/ivar/.sdkman/candidates/java/20.0.1-tem
Default locale: en_US, platform encoding: UTF-8
Шаг 2. Установите и настройте MySQL, загрузив его непосредственно с сайта https://www.mysql.com/ или используя ваш любимый менеджер пакетов. Ниже приведен пример, как это сделать, если вы используете Ubuntu.

$ sudo apt-get install mysql-server
Шаг 3. Войдите в MySQL Shell.

sudo mysql -u root
Шаг 4. Создайте базу данных и пользователя.

mysql> create database dukes_data;
mysql> use dukes_data;
mysql> create user 'duke'@'localhost' identified by 'duke';
mysql> grant all privileges on dukes_data to 'duke'@'localhost';
Шаг 5. Получите код из этого репозитория GitHub, а затем скомпилируйте и запустите его с помощью Maven.
Теперь приложение готово к тестированию.

$ mvn liberty:run
Шаг 6. Доступны три конечные точки.

List all greetings (GET)
Search for a greeting by greeter name (GET)
Add a greeting (POST)
Вот как они работают.

Чтобы перечислить все приветствия, используйте следующее:

http://localhost:9080/dukes-data/api/greetings/

Ожидаемый ответ будет выглядеть так, поскольку данных пока нет:

[]

Чтобы найти приветствие Дюка, выполните следующее:

http://localhost:9080/dukes-data/api/greetings/duke

Аналогично, поскольку данных еще нет, вы получите следующий ответ.

duke not found

Чтобы добавить приветствие Дюка:

$ echo -n '{"message":"Hello from Duke", "name":"Duke"}' | http post :9080/dukes-data/api/greetings
Перечислим все приветствия:

http://localhost:9080/dukes-data/api/greetings/

Ожидаем следующий ответ:

[
  {
    id: 1,
    message: "Hello from Duke",
    name: "Duke"
  }
]
Наконец, чтобы снова найти приветствие Дюка, используйте:

http://localhost:9080/dukes-data/api/greetings/duke

Ожидаемый ответ:

Hello from Duke
Код примера

Приложение состоит из четырех классов: GreetingApplication, GreetingResource, Greeting и GreetingRepository.

GreetingApplication настраивает REST-приложение Jakarta. В данном случае требуется только путь к приложению.

package dukes.data;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

@ApplicationPath("/api")
public class GreetingApplication extends Application {
}
GreetingResource предоставляет три метода API для получения всех приветствий, получения одного приветствия и добавления приветствия.

package dukes.data;

import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

import java.util.List;

@Path("/greetings")
public class GreetingResource {

    @Inject
    private GreetingRepository greetingRepository;

    @GET
    @Path("/{name}")
    @Produces(MediaType.TEXT_PLAIN)
    public String findOne(@PathParam("name") String name) {

       return greetingRepository.findByNameIgnoreCase(name)
               .map(Greeting::getMessage)
               .orElse(name + " not found");
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Greeting> findAll() {

        return greetingRepository.findAll()
                .toList();
    }

    @POST()
    @Consumes(MediaType.APPLICATION_JSON)
    public Response addGreeting(Greeting greeting) {

        Greeting saved = greetingRepository.save(greeting);
        return Response.ok("Created greeting: " + greeting.getId()).build();
    }
}
Класс Greeting определяет сущность, которая сохраняется в базе данных. Это сущность Jakarta Persistence с тремя полями. Аннотация @Entity идентифицирует его как сущность Jakarta Persistence, а аннотации @Id и @GeneratedValue определяют первичный ключ и способ его генерации. В остальном это обычный Java-объект.

package dukes.data;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Greeting {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;
    private String message;

    // constructor/getters/setters
}
Репозиторий GreetingRepository — это то самое интересное место. Это простой интерфейс, расширяющий CrudRepository и аннотированный @Repository. Этой информации достаточно для того, чтобы реализация Jakarta Data сгенерировала методы для всех CRUD-операций, а также несколько других удобных методов, таких как count, existsById и различные finders.

package dukes.data;

import jakarta.data.repository.CrudRepository;
import jakarta.data.repository.Repository;

import java.util.Optional;

@Repository
public interface GreetingRepository extends CrudRepository<Greeting, Long> {

    Optional<Greeting> findByNameIgnoreCase(String name);
}
Единственный метод, определенный разработчиком, — findByNameIgnoreCase. Как следует из названия, этот метод ищет в базе данных строки с заданным именем. В Jakarta Data будет сгенерирован метод, который делает именно это.

Заключение

Jakarta Data — очень интересное дополнение к Jakarta EE. Оно повышает производительность труда разработчиков и качество кода, избавляя их от написания гипотетически подверженного ошибкам шаблонного кода. Когда работа над Jakarta Data будет завершена, она будет доступна во всех продуктах, совместимых с платформой Jakarta EE 11.

Jakarta Data и Jakarta Persistence совершенно не зависят от базового механизма персистентности, поэтому можно использовать любую реляционную базу данных. В данном примере использовалась MySQL. Существует несколько причин, по которым MySQL долгое время была и остается одной из самых популярных баз данных. Например, она свободно распространяется с открытым исходным кодом на широком спектре платформ, ее легко установить и начать работу с ней, а документация и ресурсы доступны в Интернете. А при необходимости можно приобрести и платные варианты.

Итого, Jakarta Data содержит исчерпывающий язык и аннотации для создания методов запросов с широкими возможностями сортировки.