Статьи

JAXB — Перспектива новичка, часть 1

Я знаю, о чем многие из вас уже думают, так что давайте разберемся с этим: «JAXB? Как в XML? Да ладно, все классные ребята используют JSON.

Дискуссия «XML против JSON» и многие аргументы, способствующие этому, довольно хорошо документированы; Я не буду тратить много времени, перефразируя их здесь. Я полагаю, что каждый формат имеет свое применение, но даже если вы находитесь в лагере «никогда не было XML», вы все равно можете захотеть читать, поскольку наблюдения и методы, которые я обсуждаю, должны в равной степени применяться к связыванию данных JSON с Джексоном (или аналогичные инструменты).

В части 1 я описываю простой шаблон использования, который связывает возможности привязки данных JAXB с JPA. Конечно, взаимодействие между ними не всегда так просто, поэтому во второй части я расскажу, как решить некоторые из сложностей, с которыми вы можете столкнуться.

Проблема

В моем текущем проекте мы создаем набор приложений Java для управления подготовкой материалов в процессе производства. Мы решили создать «извне», чтобы облегчить демонстрацию, обращенную к пользователю, после любой итерации. Итак, в первой итерации мы создали несколько экранов с жестко закодированными фиктивными данными; затем с каждой последующей итерацией мы добавляли больше инфраструктуры и логики за экранами.

Чтобы сделать ранние демонстрации более интерактивными, мы решили создать «тестовую консоль» для центрального приложения. Человек, печатающий команды на консоли, может имитировать поведение «чистых, но реализованных» частей системы. Стоимость сборки консоли невелика благодаря таким инструментам, как Antlr 4, которые упрощают анализ команд, и мы видим долгосрочную ценность использования консоли для тестирования и диагностики.

Мы достигли точки, когда поведение системы должно зависеть от данных из другого приложения. «Другое приложение», которое отвечает за создание и поддержку этих данных, не было написано и не будет в течение некоторого времени, поэтому нам нужен был способ загрузки образцов данных через консоль.

Параметры

По сути, нашей задачей было создание (или использование) загрузчика данных. Мы остановились на XML как вероятном формате файла, а затем просмотрели список инструментов, с которыми наша команда обычно знакома.

DBUnit имеет возможности загрузки данных (предназначен для настройки повторяющихся условий тестирования). Он поддерживает две разные XML-схемы («плоская» и «полная»), каждая из которых явно ориентирована на таблицу. Он также предусматривает переменные подстановки, чтобы мы могли создавать файлы шаблонов и позволять вводу консоли устанавливать окончательные значения.

Я придерживаюсь некоторых оговорок относительно использования инструмента модульного тестирования таким образом, но из стрелок в колчане команды это может быть наиболее подходящим вариантом. Что бы там ни было, моя первая попытка применить его не увенчалась успехом (оказывается, я искал не ту часть API DBUnit), что заставило меня задуматься немного дальше.

У нас уже был способ — а именно Hibernate — поместить данные в нашу базу данных; поэтому, когда я сформулировал проблему в терминах «как создавать экземпляры сущностей из XML-документов», JAXB стал очевидным соперником. Я был рад обнаружить, что Java поставляется с реализацией JAXB, поэтому я решил попробовать его.

Перспектива новичка

Никогда не используя JAXB, я начал с небольшого исследования. Большая часть материала, который я нашел, касалась генерации классов Java из схемы XML. Это не удивительно — это большая часть того, что может сделать инструмент — но в моем случае я хотел связать данные с моими существующими классами доменов, отображаемых в Hibernate. И это приводит к чему-то, что может быть немного более удивительным: некоторые из наиболее полных учебных пособий, которые я нашел, не ожидали такого использования. Я думаю, что это хорошая демонстрация того, как ваши начальные предположения об инструменте могут сформировать то, как вы думаете о нем и как вы его используете.

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

Без сомнения, есть случаи, когда это именно то, что необходимо, но инструмент не ограничивается только этим подходом. Если вместо этого вы начнете сравнивать JAXB с Hibernate — как средство загрузки данных из внешнего источника в объекты вашего домена — тогда естественно спросить: «Почему я не могу использовать один набор объектов домена для обоих?» По крайней мере, иногда, с небольшой осторожностью, вы можете.

Простой случай

В этих примерах я буду использовать JAXB API напрямую. Нам нужно всего лишь сделать несколько простых звонков, чтобы выполнить нашу задачу, так что это достаточно просто. Стоит отметить, что Spring также предлагает интеграцию с JAXB, и особенно если вы используете Spring во всем приложении, подход к конфигурации, который он предлагает, может быть предпочтительным.

Предположим, у вас есть таблица СОТРУДНИКОВ. Каждый сотрудник имеет уникальный числовой идентификатор и имя. Если вы используете аннотации для данных сопоставления ORM, у вас может быть такой класс домена:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
@Entity
@Table(name=”EMPLOYEE”)
public class Employee {
    @Id
    @Column(name=”EMPLOYEE_ID”)
    private Integer employeeId;
  
    @Column(name=”FIRST_NAME”)
    private String firstName;
  
    @Column(name=”LAST_NAME”)
    private String lastName;
  
    // … getters and setters …
};

Теперь мы хотим позволить пользователю предоставить файл данных Employee.xml. Предположим, что у нас нет конкретной XML-схемы, которой мы должны соответствовать, мы могли бы также увидеть, как будет обрабатываться класс JAXB по умолчанию. Итак, начнем с минимальных шагов по «маршализации» экземпляра Employee в XML-документ. Если мы довольны тем, как выглядит итоговый документ, мы поменяем код демаршаллинга; если нет, мы можем посмотреть на настройку отображения.

Сначала нам нужен экземпляр JAXBContext, настроенный для работы с нашими классами домена.

1
JAXBContext jaxb = JAXBContext.newInstance(Employee.class);

Кроме того, вместо передачи объекта (ов) класса в newInstance (), мы могли бы передать имя (я) пакета (ов), содержащего классы, при условии, что каждый пакет содержит файл jaxb.index, в котором перечислены классы для использования или класс ObjectFactory с методами для создания экземпляров классов домена (и / или JAXBElements, которые обертывают их). Этот подход может быть предпочтительным, если вам необходимо сопоставление XML для большого числа не связанных между собой классов домена.

JAXBContext имеет методы для создания маршаллеров (которые создают документы XML для представления объектов) и демаршаллеров (которые создают экземпляры объектов и инициализируют их из данных в документах XML). Мы можем проверить отображение по умолчанию для нашего класса Employee следующим образом:

1
2
3
4
5
6
7
8
Employee employee = new Employee();
    employee.setEmployeeId(37);
    employee.setFirstName(“Dave”);
    employee.setLastName(“Lister”);
 
    Marshaller marshaller = jaxb.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(employee, System.out);

(Вызов setProperty () не является строго необходимым, но делает вывод гораздо более понятным для человека.) Если мы попробуем запустить этот код, мы получим исключение, сообщающее нам, что мы не определили корневой элемент. Чтобы это исправить, мы добавляем аннотацию @XmlRootElement в наш класс Employee.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@XmlRootElement
@Entity
@Table(name=”EMPLOYEE”)
public class Employee {
    @Id
    @Column(name=”EMPLOYEE_ID”)
    private Integer employeeId;
  
    @Column(name=”FIRST_NAME”)
    private String firstName;
  
    @Column(name=”LAST_NAME”)
    private String lastName;
  
    // … getters and setters …
};

По умолчанию маршаллер отображает каждое свойство общедоступного компонента (пара геттер / установщик) и каждое открытое поле; так что если у нашего класса Employee есть получатели и установщики, которые вы ожидаете, то наш вывод должен выглядеть примерно так:

1
2
3
4
5
6
<?xml version=”1.0” encoding=”UTF-8” standalone=”yes”?>
<employee>
    <employeeId>37</employeeId>
    <firstName>Dave</firstName>
    <lastName>Lister</lastName>
</employee>

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

JAXB, к счастью, не знает аннотации JPA, и Hibernate (или любой другой поставщик JPA, которого вы можете использовать) игнорирует аннотации JAXB, поэтому теперь мы можем загружать данные из файлов XML в нашу базу данных, просто попросив JAXB разархивировать данные из файлов и передача полученных объектов поставщику JPA. Код демаршаллинга будет выглядеть так:

1
2
3
4
JAXBContext jaxb = JAXBContext.newInstance(Employee.class);
Unmarshaller unmarshaller = jaxb.createUnmarshaller();
File xmlFile = /* … */;
Employee employee = unmarshaller.unmarshal(xmlFile);

По умолчанию, если элемент, представляющий одно из свойств компонента, отсутствует в XML, это свойство просто не устанавливается; так, например, если наше отображение JPA включает автоматическую генерацию employeeId, то элемент <employee> должен содержать только <firstName> и <lastName>.

Добро…

По идее, это все. (Дополнительный кредит, если вы знаете разницу между теорией и практикой.) Для начала достаточно пары аннотаций и, возможно, дюжины строк кода. В качестве дополнительного преимущества вы можете видеть взаимосвязи между всеми представлениями ваших данных (XML, база данных и объект Java) в одном аннотированном файле .java.

Не так хорошо …

Приведенный выше пример прост и может охватывать значительное количество основных вариантов использования; но большинство реальных моделей данных включают такие вещи, как отношения «один ко многим» и составные ключи, которые добавляют складки, которые вы можете или не можете предвидеть. Во второй части (намечено на 25 августа 2014 г.) я расскажу о некоторых возникших сложностях и расскажу о достаточно простых вариантах решения каждой из них.

Ссылка: JAXB — Перспектива новичка, часть 1 от нашего партнера JCG Марка Адельсбергера в блоге