Статьи

Arquillian с NetBeans, встроенным GlassFish, JPA и источником данных MySQL

Это, давайте назовем это случайным постом. Я смотрел на транзакционных наблюдателей CDI и играл со GlassFish, чтобы выполнить некоторые интеграционные тесты. Но удивительно, что это не сработало, и я все еще выясняю, где именно проблемы при использовании простого встроенного GlassFish для этого.

Тем временем я перешел на Arquillian . После того, как я посмотрел Arquillian 1.0.0.Alpha4 немного более подробно в прошлом году, пришло время посмотреть, что ребята готовили к финалу 1.0.0. Некоторые вещи немного изменились, и я был удивлен, обнаружив в сети только некоторые базовые примеры. Это было главной причиной, по которой я начал писать этот пост. Чтобы иметь более полный пример всех трех технологий, работающих вместе.

Начиная
Прежде всего, получите свежую копию последней версии NetBeans 7.1 . Он вышел несколько недель назад и выглядит действительно хорошо. Если хотите, вы можете скачать копию версии Java EE, которая поставляется с последней предустановленной версией GlassFish OSS Edition 3.1.1. Установите его и запустите. Также соберите копию MySQL Community Edition и установите ее. Вернувшись в NetBeans, настройте новое веб-приложение Maven с помощью нового мастера проектов. Давайте назовем его «simpleweb» и скажем, что он будет работать на GF 3.1.1 (если вы этого еще не сделали, вы должны создать экземпляр Services> Server> GlassFish ранее или сделать это во время настройки проекта).

Реализуйте свое приложение
Даже если это не тестирование, мне нравится использовать этот подход, потому что я все еще верю, что это наиболее распространенный случай, который вы обнаруживаете в своих проектах. У вас есть много вещей, готовых к запуску, и вы ищете какие-то автоматизированные интеграционные тесты. Итак, давайте предположим, что у вас есть очень простая сущность, мы называем ее «com.mycompany.simpleweb.entities.AuditLog». Вы можете создать его с помощью мастера создания сущностей NetBeans. Третий шаг при создании сущности — это настройка провайдера и базы данных. Определите новый источник данных и назовите его «jdbc / auditlog». В качестве спецификации соединения используйте драйвер MySQL J, и я предполагаю, что у вас есть запущенная база данных (предположим, это называется audlog). Проверьте соединение и завершите работу мастера.

Если вы используете мастера, вы получаете бесплатные подарки. Помимо того, что у вас теперь есть объект AuditLog в дереве исходных текстов, вы также найдете файл META-INF / persistence.xml в вашем src / main / resources и glassfish-resources.xml в src / main / setup. Это нужно позже, имейте это в виду. Добавьте некоторые дополнительные свойства вашей сущности. А пока я добавляю «String account». И не забудьте определить поле Version «Timestamp timestamp». И также приятно иметь маленький именованный запрос, чтобы получить список всех AuditLogs

1
2
3
4
5
@NamedQuery(name = "findAllAuditLogs",
query = "SELECT OBJECT (e) FROM AuditLog e")
  
@Version
private Timestamp timestamp;

Если вы используете мастер, обязательно проверьте ваш pom.xml. Мастер добавляет некоторые зависимости eclipselink в предоставляемую область, поэтому здесь это не должно иметь большого значения. Далее следует добавить EJB com.mycompany.simpleweb.service.AuditRepositoryService. Это должно отвечать за все операции CRUD на объекте AuditLog.
Добавьте код для вставки AuditLog:

1
2
3
4
5
6
7
8
@PersistenceContext
private EntityManager em;
  
  public void writeLog(String account) {
        AuditLog log = new AuditLog();
        log.setAccount(account);
        em.persist(log);
    }

И еще немного кода, чтобы узнать общее количество записей в вашей таблице:

1
2
3
4
5
public int findAll() {
        TypedQuery<AuditLog> query = em.createNamedQuery("AuditLog.findAllAuditLogs", AuditLog.class);
        return query.getResultList().size();
  
    }

Это все на данный момент.

Добавление основных тестовых зависимостей
Далее мы собираемся добавить некоторые очень простые тестовые зависимости. Откройте файл вашего проекта pom.xml и добавьте следующие разделы для Arquillian в ваш проект:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<repository>
        <id>JBoss</id>
        <name>JBoss Repository</name>
    </repository>
<dependencymanagement>    
       
    <dependencies>
        <dependency>
            <groupid>org.jboss.arquillian</groupid>
            <artifactid>arquillian-bom</artifactid>
            <version>1.0.0.Final-SNAPSHOT</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencymanagement>
<dependencies>
      
    <dependency>
        <groupid>org.jboss.arquillian.container</groupid>
        <artifactid>arquillian-glassfish-embedded-3.1</artifactid>
        <version>1.0.0.Final-SNAPSHOT</version>
        <scope>test</scope>
    </dependency>
    <dependency>
      
        <groupid>org.jboss.arquillian.junit</groupid>
        <artifactid>arquillian-junit-container</artifactid>
        <scope>test</scope>
    </dependency>
</dependencies>

Кроме того, вам также нужна встроенная зависимость Glassfish.

1
2
3
4
5
6
<dependency>
    <groupid>org.glassfish.extras</groupid>
    <artifactid>glassfish-embedded-all</artifactid>
    <version>3.1</version>
    <scope>test</scope>
</dependency>

Нам также нужен драйвер MySQL J:

1
2
3
4
5
6
<dependency>
    <groupid>mysql</groupid>
    <artifactid>mysql-connector-java</artifactid>
    <version>5.1.18</version>
    <scope>test</scope>
</dependency>

Конфигурирование Arquillian
После того, как у нас есть все необходимые зависимости, нам нужно дополнительно настроить Arquillian. Это делается с помощью arquillian.xml, который должен быть помещен в папку src / test / resources (вам может потребоваться создать его вне NetBeans раньше) и должен выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
11
    <engine>
        <property name="deploymentExportPath">target/arquillian</property>
    </engine>
  
    <container default="true" qualifier="glassfish">
        <configuration>
            <property name="sunResourcesXml">src/main/setup/glassfish-resources.xml</property>
        </configuration>
    </container>
</arquillian>

Параметр engine указывает Arquillian поместить упакованную версию вашего тестового архива в папку target / arquillian. Это очень полезно для поиска проблем. Спецификатор контейнера указывает testrunner на glassfish-resources.xml, который был создан мастером создания сущностей. Все сделано. Единственное, что я хотел бы предложить, это сделать копию файла persistence.xml и поместить его в папку test / resources, переименованную в нечто вроде test-persistence.xml. Я считаю, что лучше всего иметь возможность настроить свой тестовый JPA немного по-другому, чем продуктивный. Чтобы сделать простой пример, мы хотели бы увидеть еще несколько записей журнала во время тестов, поэтому скопированная версия должна дополнительно содержать необходимые параметры. Мне также нравится менять стратегию генерации таблиц для тестирования на drop-and-create-tables:

1
2
3
<property name="eclipselink.ddl-generation" value="drop-and-create-tables" />
<property name="eclipselink.logging.level.sql" value="FINEST" />
<property name="eclipselink.logging.parameters" value="true" />

Давайте посмотрим на тесты.

Добавить тестовый пример
Давайте добавим тест. Это легко сделать с помощью NetBeans: щелкните правой кнопкой мыши свой EJB и выберите «Инструменты> Создать тесты JUnit». Выберите JUnit 4.x и примите предложение по имени «com.mycompany.simpleweb.service.AuditRepositoryServiceTest». Теперь у вашего проекта есть новая папка «Test Packages». Как видите, тест на ошибку. NetBeans предполагает, что вы хотите сделать тест на основе встроенного EJBContainer. Хорошая догадка, но мы бы хотели добавить сюда немного аркиллианца. Удалите импорт EJBContainer и разденьте класс до этого:

1
2
3
@RunWith(Arquillian.class)
public class AuditRepositoryServiceTest {
}

Теперь пришло время определить архив развертывания для теста с помощью ShrinkWrap. Архив развертывания для теста определяется статическим методом, аннотированным аннотацией @Deployment от Arquillian.

1
2
3
4
5
6
7
8
@Deployment
    public static JavaArchive createTestArchive() {
        return ShrinkWrap.create(JavaArchive.class, "test.jar").addPackage(AuditLog.class.getPackage()).addPackage(AuditRepositoryService.class.getPackage()).addAsManifestResource(
                new ByteArrayAsset("<beans>".getBytes()),
                ArchivePaths.create("beans.xml")).addAsManifestResource(
                "test-persistence.xml",
                ArchivePaths.create("persistence.xml"));
    }

После добавления пакетов, которые должны содержаться, добавляется пустой файл beans.xml (которого должно быть достаточно на данный момент) и добавляется test-persistence.xml в качестве ресурса манифеста с именем persistence.xml. Отлично. И последнее, что нужно определить самому тесту.

01
02
03
04
05
06
07
08
09
10
@EJB
AuditRepositoryService repository;
  
 @Test
    public void insertLog() throws Exception {
        repository.writeLog("Markus");
        int numberOfLogs = repository.findAll();
        Assert.assertEquals(1, numberOfLogs);
  
    }

Мы вставляем простой тестовый объект и получаем счет обратно из базы данных, которая проверяется с помощью assertEquals. Это все. Запустите ваши тесты. (Щелкните правой кнопкой мыши класс AuditRepositoryServiceTest и выберите «Тестовый файл (Strg + F6)».

Изучение того, что происходит
Окно вывода показывает Std.out стартового GlassFish. Если вы дополнительно изучите вывод, то увидите, что пул соединений JDBC и ресурс JDBC созданы:
ИНФОРМАЦИЯ: команда add-resources результат: PlainTextActionReporterSUCCESSDescription: add-resources AdminCommandnull
Пул соединений JDBC mysql_auditlog_rootPool создан успешно.
Ресурс JDBC jdbc / auditlog создан успешно.
и «тестовое» приложение было развернуто:
ИНФОРМАЦИЯ: WEB0671: загрузка приложения [test] в [/ test]
17.01.2012 10:12:39 org.glassfish.deployment.admin.DeployCommand выполнить
ИНФОРМАЦИЯ: тест был успешно развернут за 6,461 миллисекунды.

Сканирование выходных данных действительно указывает на некоторые вещи EclipseLink, но дополнительное ведение журнала sql, похоже, не действует. Это связано с тем, что EclipseLink необходимо знать, на какой регистратор указывать выходные данные. Обычно вывод журнала перенаправляется на регистратор сервера, который обнаруживается автоматически. До сих пор мы не занимались настройкой ведения журнала и просто полагались на то, что по умолчанию используется для ведения журнала Java. Итак, давайте добавим немного конфигурации регистрации. Поместите пустой файл logging.properties в src / test / resources и добавьте в него несколько простых строк:

Обработчики = java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.ConsoleHandler.level = УТОНЧЕННАЯ
добавьте плагин maven sure-fire в раздел build вашего pom.xml:

01
02
03
04
05
06
07
08
09
10
11
12
13
<plugin>
                <groupid>org.apache.maven.plugins</groupid>
                <artifactid>maven-surefire-plugin</artifactid>
                <version>2.11</version>
                <configuration>
                    <includes>
                        <include>com/mycompany/simpleweb/service/*</include>
                    </includes>
                    <systempropertyvariables>
                        <java.util.logging.config.file>${basedir}/src/test/resources/logging.properties</java.util.logging.config.file>
                    </systempropertyvariables>
                </configuration>
            </plugin>

Если вы теперь выполните команду clean and build, вы увидите, что требуемый вывод журнала отображается в выводе сборки NetBeans.
FEIN: ВСТАВИТЬ В AUDITLOG (ID, ACCOUNT, TIMESTAMP) ЗНАЧЕНИЯ (?,?,?)
bind => [1, Маркус, 2012-01-17 11: 02: 54.0]
Фактически, если вы используете Strg + F6, вы все равно видите только сообщения уровня INFO. Чтобы это исправить, вам нужно изменить настройки действия запуска NetBeans. Щелкните правой кнопкой мыши ваш проект и выберите «Свойства». Выберите «Действия» и «Тестовый файл». Добавьте следующее в качестве новой строки в области «Set Properties»:

java.util.logging.config.file = SRC / тест / ресурсы / logging.properties

Теперь вы также видите желаемый вывод на уровне журнала с одним тестовым прогоном. Вот и все. Вы можете скачать полный пример проекта Maven (в виде ZIP-файла) с указанными классами и немного поиграть. Повеселись!

Даже если вышеприведенный пост был задуман как более сложная демонстрация, оказалось, что я пропустил большую проблему с настройкой. Все работает хорошо до того момента, когда вы начинаете внедрять расширенные функции для ваших сущностей. Чтобы назвать только несколько: ленивая загрузка, отслеживание изменений, выборка групп и так далее. Провайдеры JPA любят называть это «улучшением», и это чаще всего называют «ткачеством». Ткачество — это метод манипулирования байт-кодом скомпилированных классов Java. Поставщик персистентности EclipseLink JPA использует ткачество для улучшения сущностей JPA для упомянутых вещей и для внутренней оптимизации. Плетение может выполняться либо динамически во время выполнения, когда загружаются сущности, либо статически во время компиляции путем последующей обработки файлов .class сущности. Динамическое переплетение в основном рекомендуется, так как оно легко настраивается и не требует каких-либо изменений в процессе сборки проекта. Возможно, вы видели более точный вывод журнала из EclipseLink, например:

[…] — Начальный класс обработки преобразователя класса ткача [com / mycompany / simpleweb / entity / AuditLog].
[…] — Сохранение персистентности (PersistenceEntity) [com / mycompany / simpleweb / entity / AuditLog].
[…] — Отслеживание изменений в сети (ChangeTracker) [com / mycompany / simpleweb / entity / AuditLog].
[…] — Weaved lazy (косвенное указание ValueHolder) [com / mycompany / simpleweb / entity / AuditLog].
[…] — Сплетенные группы извлечения (FetchGroupTracker) [com / mycompany / simpleweb / entity / AuditLog].
[…] — Конечный класс обработки преобразователя класса ткача [com / mycompany / simpleweb / entity / AuditLog].

Проблема с Arquillian и Embedded GlassFish
Представьте, что вы взяли пример из вчерашнего поста в блоге и изменили свойство простой учетной записи String на что-то вроде этого:

1
2
@ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
   private Person person;

Это как раз один из упомянутых случаев, когда вашему JPA-провайдеру необходимо выполнить некоторые улучшения в ваших файлах классов перед выполнением. Без изменения проекта это приведет к некоторым очень неприятным исключениям:

Описание исключения: возникла исключительная ситуация NullPointerException при доступе к несуществующему сотканному _vh_ методу [_persistence_get_person_vh]. Класс не был правильно сплетен — для развертываний EE проверьте порядок модулей в дескрипторе развертывания application.xml и убедитесь, что модуль, содержащий модуль персистентности, опережает любой другой модуль, который его использует.
[…]
Внутреннее исключение: java.lang.NoSuchMethodException: com.mycompany.simpleweb.entities.AuditLog._persistence_get_person_vh ()
Отображение: org.eclipse.persistence.mappings.ManyToOneMapping [person]
Дескриптор: RelationalDescriptor (com.mycompany.simpleweb.entities.AuditLog -> [DatabaseTable (AUDITLOG)])
в org.eclipse.persistence.exceptions.DescriptorException.noSuchMethodWhileInitializingAttributesInMethodAccessor (DescriptorException.java:1170)
в org.eclipse.persistence.internal.descriptors.MethodAttributeAccessor.initializeAttributes (MethodAttributeAccessor.java:200)
[…]

показывая, что чего-то не хватает. И этот недостающий метод вводится в процессе ткачества. Если вы декомпилируете сотканную сущность, вы можете увидеть, на что жалуется поставщик JPA. Вот как должен выглядеть ваш расширенный класс сущностей. И это только один из усовершенствованных методов, которые процесс ткачества внедряет в ваш код.

01
02
03
04
05
06
07
08
09
10
11
public WeavedAttributeValueHolderInterface _persistence_get_person_vh()
  {
      _persistence_initialize_person_vh();
      if(_persistence_person_vh.isCoordinatedWithProperty() || _persistence_person_vh.isNewlyWeavedValueHolder())
      {
          Person person1 = _persistence_get_person();
          if(person1 != _persistence_person_vh.getValue())
              _persistence_set_person(person1);
      }
      return _persistence_person_vh;
  }

Динамическое и статическое плетение
Очевидно, что динамическое плетение по умолчанию не работает с описанной настройкой. Почему? Ткачество — испорченный ребенок. Это работает только тогда, когда классы сущностей, которые нужно сплести, существуют только в загрузчике классов приложения. Комбинация встроенных GlassFish, Arquillian и maven sure-fire-plugin немного смешивает это, и конец истории в том, что ни одна из ваших сущностей вообще не улучшена. Сравните это хорошее обсуждение для более подробного объяснения. Если динамическое колебание не работает, мы должны использовать запасной вариант, называемый статическим переплетением. Статические средства: постобработка сущностей во время сборки. Имея проект Maven под рукой, это звучит как довольно легкая работа. Давайте посмотрим на что-то вроде этого. Первое, что вы, вероятно, найдете, это StaticWeaveAntTask . Вторым может быть плагин Крэйга eclipselink-staticweave-maven-plugin .

Давайте начнем с StaticWeaveAntTask. Вы должны будете использовать maven-antrunner-plugin, чтобы представить это. Скопируйте классы слева направо и сделайте невероятные споры, чтобы добиться нужного уровня. Лейрд Нельсон проделал большую работу по созданию примера конфигурации для всех трех крупных поставщиков JPA (EclipseLink, OpenJPA, Hibernate), и вы могли бы попробовать. Подробное объяснение происходящего можно найти в его блоге . Спасибо Laird за указатели! Не поймите меня неправильно: это правильный подход, но он мне просто не нравится. Главным образом потому, что это создает огромную сложность для сборки и слишком большого количества проектов, в которых нет необходимых навыков для управления даже обычными проектами maven, это просто не решение для меня. Я попробовал плагин статического плетения, созданный Крейгом Дэй.

Добавление статического плетения в simpleweb
Итак, давайте откроем pom.xml из проекта вчерашнего дня и представим новый плагин:

01
02
03
04
05
06
07
08
09
10
11
12
13
<plugin>
    <artifactId>eclipselink-staticweave-maven-plugin</artifactId>
    <groupId>au.com.alderaan</groupId>
    <version>1.0.1</version>
         <executions>
            <execution>
                 <goals>
                   <goal>weave</goal>
                 </goals>
                 <phase>process-classes</phase>
            </execution>
          </executions>
     </plugin>

Готово. Теперь ваши классы переплетены, и если вы введете некоторую регистрацию через конфигурацию плагина, вы действительно сможете увидеть, что происходит с вашими классами сущностей. Плагин доступен через repo1.maven.org. Единственная проблема, с которой я столкнулся, заключается в том, что введенная зависимость от EclipseLink 2.2.0 недоступна (или, конечно, недоступна) через тот же репозиторий, поэтому вам, вероятно, потребуется создать ее для себя с правильными репозиториями и зависимостями. Вы можете получить исходный код через кодовую страницу Google плагина .

Не забудьте добавить свойство weaving в ваш test-persistance.xml:

1
<property name="eclipselink.weaving" value="static" />

[ОБНОВЛЕНИЕ: 19.01.2012]
Крейг выпустил новую версию плагина 1.0.2, которая решает проблемы с зависимостью EclipseLink. Теперь вы можете просто включить необходимую версию EclipseLink в качестве зависимости от плагина. Также необходим правильный EclipseLink Maven репозиторий. Полный пример с настроенным уровнем журнала выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<repository>
            <id>eclipselink</id>
            <name>Repository hosting the eclipselink artifacts</name>
        </repository>
  
[...]
  <plugin>
                <artifactId>eclipselink-staticweave-maven-plugin</artifactId>
                <groupId>au.com.alderaan</groupId>
                <version>1.0.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>weave</goal>
                        </goals>
                        <phase>process-classes</phase>
                        <configuration>
                            <logLevel>ALL</logLevel>
                        </configuration>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>org.eclipse.persistence</groupId>
                        <artifactId>eclipselink</artifactId>
                        <version>2.3.1</version>
                    </dependency>
                </dependencies>
            </plugin>

Ссылки: Arquillian с NetBeans, встроенный GlassFish, JPA и источник данных MySQL и Arquillian с NetBeans, встроенный GlassFish, JPA и источник данных MySQL — часть II от нашего партнера JCG Маркуса Эйзела (Markus Eisele) из блога Enterprise Software Development с Java .