Статьи

Кодирование значения ключа в Java

логотипРуководство по
Ujo двухъядерного модулю
Ujorm рамок

Вступление

Ujorm — это библиотека для разработки Java-приложений, построенная на архитектуре ключ-значение объектов домена, которые получают доступ к своим атрибутам, используя только объекты, называемые ключами. Ключ состоит из неизменного объекта статического , который является частью класса домена и предоставляет несколько услуг. Ключ в Ujorm никогда не содержит бизнес-данных — в отличие от аналогичной среды, Joda Beans , автора Стивена Колебурна, где атрибуты содержатся в типе объекта Property .

Архитектура объектов Ujo была описана в исходной документации , в этой статье демонстрируются некоторые новые или интересные способы использования Ujo на коротких примерах, основанных на модуле ujo-core и, следовательно, не связанных с запросами к базе данных. Основной модуль может быть загружен в ваш проект , используя зависимость Maven:

        <dependency>
            <groupId>org.ujorm</groupId>
            <artifactId>ujo-core</artifactId>
            <version>1.45</version>
        </dependency>

Примеры кода основаны на двух сущностях Employee и Company типа Ujo, в то время как каждый из них содержит ( сгенерированные ) методы установки и получения, так что объект соответствует спецификации JavaBeans .

ключ-значение-модель

Запись и чтение значений

Здесь мы можем пропустить описание атрибутов чтения и записи в JavaBeans и перейти непосредственно к чтению и записи значений с использованием API-интерфейса Ujorm.

        Employee person = new Employee();

        // Write:
        person.set(ID, 7L);
        person.set(NAME, "Pavel");
        person.set(WAGE, 20.00);
        person.set(COMPANY, new Company());

        // Read:
        Long id = person.get(ID);
        String name = person.get(NAME);
        Double wage = person.get(WAGE);
        Company company = person.get(COMPANY);

        assert id == 7L;
        assert name == "Pavel";
        assert wage == 20.00;
        assert company != null;

Чтобы сократить код в приведенных выше примерах, необходимые статические ключи используемых объектов Ujo были импортированы в заголовок класса с помощью записи:

        import static org.Company.CITY;
        import static org.Employee.*;

Приведенный выше пример объясняет, как использовать ключ для доступа к атрибутам связанного объекта, однако теперь не похоже, что ключи будут иметь какие-либо преимущества по сравнению с POJO. Я постараюсь убедить вас в преимуществах этого решения в следующих разделах.

Восстановление значений по умолчанию

Каждый ключ содержит значение по умолчанию, которое можно получить с помощью метода Key.getDefault (). Что более интересно, так это чтение атрибута со значением NULL, поскольку ключ автоматически заменяет атрибуты NULL значением по умолчанию. Если значение по умолчанию равно NULL, замена незначительна. Поэтому для восстановления всех значений по умолчанию достаточно назначить каждому атрибуту значение NULL следующим образом:

        Employee employee = getEmployee();

        for (Key key : employee.readKeys()) {
            employee.set(key, null);
        }

        assert employee.getWage() == 0.0
               : "Default value is zero";
        assert employee.getWage() == WAGE.getDefault()
               : "Check the default value";

Другая версия предыдущего примера восстанавливает значения по умолчанию только для числовых атрибутов:

        Employee employee = getEmployee();

        for (Key key : employee.readKeys()) {
            if (key.isTypeOf(Number.class)) {
                employee.set(key, null);
            }
        }

Метод Ujo.readKeys () возвращает все прямые ключи текущего класса, включая родительские классы, поэтому восстановление значений по умолчанию повлияет на все родительские атрибуты, если они есть. Для сравнения типа значения ключа подходящим методом является Key.isTypeOf (Class).

Мелкая копия объекта

Поскольку мы знаем, как получить список всех прямых ключей от объекта Ujo (описание составных ключей будет следовать), мы можем легко изменить цикл тела, чтобы создать поверхностную копию исходного объекта:

        Employee source = getEmployee();
        Employee target = source.getClass().newInstance();

        for (Key<Ujo,?> key : source.readKeys()) {
            key.copy(source, target);
        }

        assert source.getId() == target.getId()

Проверка атрибутов при записи

Валидаторы — это неизменяемые объекты, которые можно комбинировать с помощью операторов И / ИЛИ и которые могут быть вставлены в ключ при его создании. Если ключ получает валидатор один раз, он будет постоянно проверять каждое значение во время ввода в объект Ujo навсегда.

        Key<Employee, String> NAME = keyFactory.newKey(length(7));

Ключ NAME теперь имеет валидатор для проверки имени любого сотрудника длиной до 7 символов. В следующем примере проверяется валидатор на имя, которое соответствует ограничению, и имя, которое является слишком длинным.

        final String correctName = "1234567";
        final String wrongName = "12345678";

        Employee employee = new Employee();
        employee.set(NAME, correctName);

        try {
            employee.set(NAME, wrongName);
        } catch (ValidationException e) {
            String expected
                = "Text length for Employee.name must be between 0 and 7, "
                + "but the input length is: 8";
            assert expected.equals(e.getMessage());
        }

        assert employee.getName() == correctName;

API валидаторов предлагает поддержку локализованных сообщений об ошибках с использованием шаблонов .

        final ValidationException exception = getException();

        String template = "The name can be up to ${MAX} characters long, not ${LENGTH}.";
        String expected = "The name can be up to 7 characters long, not 8.";
        String result = exception.getError().getMessage(template);

        assert expected.equals(result);

Каждый объект типа ValidationError имеет один шаблон по умолчанию для ведения журнала. Другие примеры использования валидаторов доступны здесь .

Я рассматриваю возможность использования аннотаций из JSR 303 для определения ключа в качестве альтернативы прямой вставке ключей с использованием кода Java — в одной из следующих версий Ujorm.

Композитные ключи

Два логически связанных ключа могут быть объединены с помощью ключа. и (ключ) метод, где результатом является экземпляр составного ключа типа CompositeKey, который также является типом исходного интерфейсного ключа. Из-за этого составные ключи могут использоваться так же, как и прямые ключи — включая чтение и запись значений объектов домена или соединение с другими составными ключами. Работа с составными ключами также дает нам несколько новых интересных функций при чтении и записи значений:

  • Если вы читаете какой-либо атрибут объекта с отсутствующими отношениями , результат всегда равен NULL, поэтому нет исключения NullPointerException , что типично для цепочки геттеров JavaBeans. Это правило не применяется к объекту по умолчанию.
  • Если вы записываете новое значение в атрибут неопределенного отношения, составной ключ автоматически создает отсутствующие связанные объекты во всех случаях. Если по какой-то причине мы недовольны таким поведением, мы можем использовать CompositeKey. Метод setValue (Ujo, value, createRelations) с последним параметром, равным false .

Пример использования:

        Key<Employee,String> companyNameKey = Employee.COMPANY.add(Company.NAME);

        Employee employee = new Employee();
        String companyName = employee.get(companyNameKey); // !1
        assert companyName == null;

        employee.set(companyNameKey, "Prague"); // !2
        companyName = employee.get(companyNameKey);

        assert employee.getCompany() != null;
        assert companyName == "Prague";

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

Если вас интересует поведение составных ключей, вы можете изучить другие примеры в этом TestCase .

Критерий как модель состояния

Используя ключи, значения и операторы, можно описать логическое состояние объекта Ujo, для которого должен использоваться класс Criterion. Объекты типа Criterion являются неизменяемыми сериализуемыми экземплярами, которые могут быть объединены в двоичное дерево с помощью AND / OR. Объект Criterion также используется в модуле ORM в качестве шаблона для построения фраз WHERE в командах SQL, однако этот объект является полностью автономным и может использоваться даже вне ORM — например, для проверки объектов.

        Criterion<Employee> validator = Employee.WAGE.whereGt(100.0);

        try {
            validator.validate(getEmployee()
            , "Minimal WAGE is: %f."
            , validator.getRightNode());
        } catch (IllegalArgumentException e) {
            assert e.getMessage() != null;
        }

В этом примере мы используем критерий, чтобы проверить, что зарплата сотрудника выше 100 (единиц).

Значение критерия остается в его способности передавать описание условий другим модулям или системам, которые затем могут предоставить индивидуальное решение для реализации.

Критерий фильтрации коллекций

Следующий пример представляет собой другую версию предыдущего примера и используется для фильтрации коллекции сотрудников, чья компания базируется в Праге. В примере показано, что составные ключи могут входить в критерий:

        List<Employee> employees = COMPANY.add(CITY)
            .whereEq("Prague")
            .evaluate(getEmployees());

        for (Employee employee : employees) {
            System.out.println(employee);
        }

        assert employees.size() == 4;

Полезно знать, что значение критерия также может быть ключевым:

        List<Employee> employees = COMPANY.add(CITY)
            .whereEq(Employee.NAME)
            .evaluate(getEmployees());

        assert employee.size() == 1; 

Второй пример фильтрует всех сотрудников, чье имя соответствует названию города (места) их компании. Кстати, я согласен со всеми, кто считает, что это не совсем типичный пример :).

Сортировка коллекций

Ujorm предоставляет полезный класс для сортировки коллекций, который называется UjoComparator . Нет необходимости реализовывать новый класс для большой группы требований (в отличие от коллекций JavaBeans, хотя Java 8 принесла упрощение), просто предоставьте методу фабрики требуемый список ключей вместе с информацией о направлении сортировки, см. Key.descending (). Метод всегда создает новый экземпляр Key, помеченный по убыванию .

        List<Employee> employees = UjoComparator.of
            ( COMPANY.add(CITY)
            , NAME.descending())
            .sort(getEmployees());

        for (Employee employee : employees) {
            System.out.println(employee);
        }

Метод downnding () фактически создает новый составной ключ с одним членом, который предоставляет информацию о сортировке по убыванию. Моделирование смещения вниз также используется в модуле ORM.

Сериализация ключей

Каждый прямой ключ в ClassLoader имеет свой уникальный экземпляр, например элемент перечисляемого типа Enum . Чтобы сериализовать ключи, вы должны вставить их в набор ключей, представленный классом KeyRing . Эти цепочки ключей не новы для нас, потому что метод Ujo.readKeys (), использованный выше, возвращает (в реализации по умолчанию) этот тип.

        final KeyRing<Employee> keyRing1, keyRing2;
        keyRing1 = KeyRing.of(Employee.ID, Employee.COMPANY.add(Company.NAME));
        keyRing2 = service.serialize(keyRing1);

        assert keyRing1 != keyRing2 : "Different instances";
        assert keyRing1.get(0) == keyRing2.get(0) : "The same direct keys";
        assert keyRing1.get(1).equals(keyRing2.get(1)) : "The equal composite keys";
        assert new Employee().readKeys() instanceof KeyRing : "readKeys() returns the KeyRing";

Импорт из формата CSV

В последнем примере описывается, как импортировать текст из CSV (Comma Separated Value), который предлагает возможность импортировать атрибуты также с использованием составных ключей, так что вы можете импортировать атрибуты реляционных сущностей. Описание столбцов CSV определяется списком ключей (импортированного) объекта Ujo. Содержимое текстового CSV-файла с заголовком:

        id;name;companyId
        1;Pavel;10
        2;Petr;30
        3;Kamil;50

Java-код для импорта файла CSV:

        Scanner scanner = new Scanner(getClass().getResourceAsStream("employee.csv"), "utf-8");

        UjoManagerCSV<Employee> manager = UjoManagerCSV.of
            ( Employee.ID
            , Employee.NAME
            , Employee.COMPANY.add(Company.ID));

        List<Employee> employes = manager.loadCSV(scanner, this);

        assert employes.size() == 3;
        assert employes.get(0).getId().equals(1L);
        assert employes.get(0).getName().equals("Pavel");
        assert employes.get(0).getCompany().getId().equals(10L);

Если вместо списка ключей мы используем класс типа Ujo, содержимое файла CSV импортируется всеми прямыми ключами класса. Порядок статических ключей Ujo гарантирован в структуре Ujorm и такой же, как порядок статического поля, указанного в классе домена, в отличие от поля неопределенного порядка JavaBeans.

Вывод

Модуль ujo-core фреймворка Ujorm предоставляет различные другие инструменты для общего использования, другой проблемой является использование объектов Ujo в ORM. Для большего количества примеров, в том числе кода ORM, есть мотивирующее приложение Demo Hotels . Возможно, некоторые части приложения нуждаются в специальных описаниях, возможно, это будет сделано в отдельной статье в другой раз. Все примеры моделей включены в проект Ujorm, а основной класс доступен здесь .