Здесь мы используем H2 в нашей тестовой среде, Derby в разработке и Postgres в производстве. Это 2014 год, и у Java EE все еще нет приличной спецификации конфигурации … Так как же мы изменим источники данных в Java EE в зависимости от нашей среды? Есть несколько возможностей (из внешних файлов свойств, фильтрации ресурсов Maven, профилей Maven с другой конфигурацией или трюков JNDI), но этот пост будет сосредоточен только на одном: переключение источника данных с альтернативами и стереотипами CDI .
Случай использования
Допустим, у нас есть веб-приложение Java EE (приложение CRUD для объекта Speaker, использующего JSF, EJB, JPA и CDI), работающее на JBoss, и нам нужно легко переключать источники данных: источник данных H2 в памяти в нашей тестовой среде, Derby DataSource для развития и Postgres DataSource в производстве. Каждый источник данных имеет уникальное имя JNDI и указывает на базу данных (в памяти или на сервере). Затем, с небольшой настройкой XML (да, Java EE по-прежнему использует XML, а не JSon; o), мы хотим переключить источники данных . Вот как это выглядит на хорошей диаграмме:
Обратите внимание на @DevDatabase и @ProdDatabase на диаграмме. Позже вы увидите, что эти аннотации, по сути, являются стереотипами CDI, и они будут очень полезны для решения нашего варианта использования.
Конфигурация источника данных
В Java EE немного болезненно то, как вы создаете и настраиваете новые источники данных. Благодаря аннотации @DataSourceDefinition, добавленной в Java EE 6, теперь мы можем создавать новые источники данных, используя аннотацию в нашем коде . Я не буду делать это здесь, так как я предпочитаю использовать CLI администратора JBoss. Следующие команды создадут источник данных для H2, Derby и Postgres (проверьте сценарий jboss-setup.cli, потому что вам нужно создать драйверы JDBC Derby и Postgres ), и результат можно увидеть в консоли JBoss:
/subsystem=datasources/data-source=H2DS:add(driver-name=h2, user-name=sa, password=sa, connection-url="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE", jndi-name=java:/global/datasources/H2DS, enabled=true) /subsystem=datasources/data-source=DerbyDS:add(driver-name=derby, user-name=app, password=app, connection-url="jdbc:derby://localhost:1527/sample;create=true", jndi-name=java:/global/datasources/DerbyDS, enabled=true) /subsystem=datasources/data-source=PostgresDS:add(driver-name=postgres, user-name=postgres, password=postgres, connection-url="jdbc:postgresql://localhost:5432/postgres", jndi-name=java:/global/datasources/PostgresDS, enabled=true)
Теперь, когда наш JBoss знает эти три источника данных, давайте настроим их в нашем файле persistence.xml.
JPA Персистентные Единицы
В файле persistence.xml у нас может быть несколько единиц постоянства. Таким образом, идея состоит в том, чтобы создать три постоянных модуля, каждый из которых указывает на отдельный источник данных. Итак, мы будем иметь:
- alternative-test-pu источник тестовых данных, указывающий на H2 в памяти в java: / global / datasources / H2DS
- alternative-dev-pu источник данных разработки, указывающий на Derby на java: / global / datasources / DerbyDS
- alternative-prod-pu источник производственных данных, указывающий на java: / global / datasources / PostgresDS
Вот как это выглядит в файле persistence.xml (для ясности я пропустил несколько свойств, вы можете проверить здесь весь файл ):
persistence.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.1" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="alternative-test-pu" transaction-type="JTA"> <jta-data-source>java:/global/datasources/H2DS</jta-data-source> <exclude-unlisted-classes>false</exclude-unlisted-classes> <properties> <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/> <property name="javax.persistence.sql-load-script-source" value="insert.sql"/> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.transaction.flush_before_completion" value="true"/> <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/> </properties> </persistence-unit> <persistence-unit name="alternative-dev-pu" transaction-type="JTA"> <jta-data-source>java:/global/datasources/DerbyDS</jta-data-source> <exclude-unlisted-classes>false</exclude-unlisted-classes> <properties> <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/> <property name="javax.persistence.sql-load-script-source" value="insert.sql"/> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.transaction.flush_before_completion" value="true"/> <property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect"/> </properties> </persistence-unit> <persistence-unit name="alternative-prod-pu" transaction-type="JTA"> <jta-data-source>java:/global/datasources/PostgresDS</jta-data-source> <exclude-unlisted-classes>false</exclude-unlisted-classes> <properties> <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/> <property name="javax.persistence.sql-load-script-source" value="insert.sql"/> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.transaction.flush_before_completion" value="true"/> <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/> </properties> </persistence-unit> </persistence>
Такая конфигурация может быть необычной в реальном проекте. Вы можете предпочесть иметь несколько файловperistence.xml, каждый из которых объявляет один постоянный модуль, и разрешать профилю Maven переключаться между файлами. Но это не цель этого поста.
Внедрение EntityManager
Как мы вводим EntityManager в Java EE 7? Итак, мы берем любой управляемый компонент, добавляем аннотацию @Transactional, чтобы получить управляемую транзакционную демаркацию, и аннотация @PersistenceContext выполнит свою работу: с учетом единицы сохраняемости (здесь alternative-test-pu) контейнер будет внедрять EntityManager. В нашем приложении SpeakerBean является ответственным за выполнение CRUD-операций над сущностью Speaker , поэтому мы внедряем EntityManager;
SpeakerBean
@Named @Transactional @ConversationScoped public class SpeakerBean implements Serializable { @PersistenceContext(unitName = "alternative-test-pu") private EntityManager entityManager; ...
Но, как вы можете видеть из этого кода, здесь нет способа переключения между единицами постоянства, и, следовательно, нет способа переключения источников данных. Идея состоит в том, чтобы внедрить EntityManager с помощью аннотации CDI @Inject:
SpeakerBean
@Named @Transactional @ConversationScoped public class SpeakerBean implements Serializable { @Inject private EntityManager entityManager; ...
Теперь нам нужно создать EntityManager, чтобы иметь возможность его внедрять.
Создание трех EntityManager
Как вы, возможно, уже знаете, единственный способ внедрить EntityManager в Java EE — это использовать аннотацию @PersistenceContext или создать ее с помощью CDI (спасибо производителям ). Возьмите любой POJO, добавьте любой атрибут или метод, аннотируйте его @Produces , и результат будет внедрен CDI. Таким образом, приведенный ниже класс создаст наших трех EntityManager, каждый из которых указывает на свой постоянный модуль:
DatabaseProducer
public class DatabaseProducer { @Produces @PersistenceContext(unitName = "alternative-test-pu") private EntityManager entityManagerTest; @Produces @PersistenceContext(unitName = "alternative-dev-pu") private EntityManager entityManagerDev; @Produces @PersistenceContext(unitName = "alternative-prod-pu") private EntityManager entityManagerProd; }
Этот код не будет развернут, потому что развертывание неоднозначно. Если вы не квалифицируете продюсера, компонент или точку внедрения, он автоматически получает квалификатор @Default . Итак, здесь три атрибута EntityManager имеют одинаковый спецификатор @Default, а CDI не знает, как сделать разницу между собой. Способ разделить их — создать разные классификаторы или стереотипы. Помните наш вариант использования: возможность переключения с одного источника данных на другой. Стереотипы — это то, что мы хотим. Так что нам действительно нужен следующий код (обратите внимание на аннотацию @Alternative и оба стереотипа @DevDatabase и @ProdDatabase ):
DatabaseProducer
public class DatabaseProducer { @Produces @PersistenceContext(unitName = "alternative-test-pu") private EntityManager entityManagerTest; @Produces @Alternative @DevDatabase @PersistenceContext(unitName = "alternative-dev-pu") private EntityManager entityManagerDev; @Produces @Alternative @ProdDatabase @PersistenceContext(unitName = "alternative-prod-pu") private EntityManager entityManagerProd; }
Способ чтения этого кода: « По умолчанию создайте тест EntityManager. Если альтернатива @DevDatabase активирована, создайте EntityManager для разработки. Если альтернатива @ProdDatabase активирована, создайте производственный EntityManager «.
Использование CDI стереотипов и альтернатив
В CDI стереотип инкапсулирует различные свойства, включая область видимости, привязки перехватчиков, квалификаторы, альтернативы и т. Д., В единый пакет многократного использования. С точки зрения кода, это просто аннотация Java с аннотацией @Stereotype . Ниже представлен стереотип @DevDatabase:
DevDatabase
@Stereotype @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) @Documented public @interface DevDatabase { }
Код будет аналогичным для стереотипа @ProdDatabase. Теперь у нас есть один стереотип для базы данных разработки, один для производственной базы данных и ни одного для тестовой базы данных (так как он используется по умолчанию).
С другой стороны, альтернативы — это бины, реализация которых специфична для конкретного клиентского модуля или сценария развертывания. Это именно то, что мы хотим: в зависимости от нашего сценария развертывания (тестирование, разработка, производство) нам нужен другой EntityManager. Итак, давайте объединим стереотипы и альтернативы, чтобы получить то, что мы хотим.
Создание трех EntityManager со стереотипами и альтернативами
Стереотип может указывать, что компонент (или производитель), к которому он применяется, является @Al альтернативой . Альтернативный стереотип позволяет нам классифицировать бины по сценарию развертывания. Итак, в наших стереотипах @DevDatabase и @ ProdDatabase нам нужно добавить квалификатор @Alternative :
DevDatabase
@Stereotype @Alternative @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) @Documented public @interface DevDatabase { }
Благодаря этому стереотипу с альтернативой мы можем теперь переключать источник данных в файлах beans.xml . Если файл пуст, альтернатива не активирована, поэтому будет применяться значение по умолчанию и будет использоваться тест EntityManager, указывающий на H2. Вместо этого, если мы хотим переключиться на производственную базу данных Postgres, нам нужно активировать альтернативу следующим образом:
beans.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bean-discovery-mode="all" version="1.1" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"> <alternatives> <stereotype>org.agoncal.sample.cdi.alternatives.database.cdi.ProdDatabase</stereotype> </alternatives> </beans>
Вывод
В этом посте я показал вам, как использовать CDI для переключения источников данных. CDI поставляется с мощной реализацией шаблона проектирования моста во имя альтернатив . Это позволяет нам менять одну реализацию другой, в нашем случае, с одного EntityManager на другой. Будучи строго типизированным, единственный способ добиться этого в CDI — это использование стереотипов (CDI ненавидит внедрение с именами String) и создание соответствующего EntityManager, благодаря Producers andbeans.xml.
Кстати, спасибо JBoss Forge за то, что выкинули веб-приложение с помощью нескольких команд .
Итак, загрузите код (или разветвите его на GitHub), попробуйте и дайте мне обратную связь.