Статьи

Сервер приложений Spring3 + JPA2 + Java EE6 = Путаница в конфигурации

Весна – это здорово, JavaEE6 – это здорово, и последние серверы приложений JavaEE6 также хороши. Эта статья не посвящена Spring Vs JavaEE6, но мой опыт переноса приложения Spring3 + JPA2 (Hibernate) на сервер приложений JBoss AS-7.1.

Мое требование к приложению очень простое: разработка нескольких веб-сервисов на основе SOAP с использованием Spring3.1 и JPA2 (Hibernate) и размещение их на JBoss AS 7.1.

Поэтому я начал создавать многомодульный проект maven с одним jar-модулем, содержащим реализации сервисов с использованием Spring & JPA, и другим war-модулем, который представляет эти сервисы как веб-сервисы на основе SOAP. Но ключевой частью является то, что сервисам необходимо взаимодействовать с несколькими базами данных для некоторых методов сервиса.

Мне известна поддержка интеграции JPA2 из Spring без persistence.xml и классного атрибута packagesToScan, который немного облегчает жизнь. Я настроил 2 источника данных, 2 LocalContainerEntityManagerFactoryBeans, зарегистрировал 2 JpaTransactionManager и включил поддержку управления транзакциями на основе аннотаций.

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<tx:annotation-driven transaction-manager='txnManager1'/>
<tx:annotation-driven transaction-manager='txnManager2'/>
 
<bean class='org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor'/>
<bean class='org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor'/><!-- This will throw error because it found multiple EntityManagerFactory beans -->
 
<bean id='txnManager1'
  class='org.springframework.orm.jpa.JpaTransactionManager'
        p:entityManagerFactory-ref='emf1'/>
    
   <bean id='txnManager2'
  class='org.springframework.orm.jpa.JpaTransactionManager'
        p:entityManagerFactory-ref='emf2'/>        
       
   <bean id='emf1' class='org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean'>
       <property name='persistenceUnitName' value='Sivalabs1PU'></property>       
       <property name='dataSource' ref='dataSource1'></property>
       <property name='jpaVendorAdapter'>
        <bean id='jpaAdapter' class='org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter'
           p:showSql='${hibernate.show_sql}'/>
       </property>
       <property name='jpaProperties'>
        <props>
         <prop key='hibernate.dialect'>${hibernate.dialect}</prop>
         <prop key='hibernate.hbm2ddl.auto'>${hibernate.hbm2ddl.auto}</prop>
        </props>
       </property>
       <property name='packagesToScan' value='com.sivalabs.springdemo.entities'></property>
       <property name='loadTimeWeaver'>
         <bean class='org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver'/>
       </property>
        
   </bean>
    
   <bean id='emf2' class='org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean'>
       <property name='persistenceUnitName' value='Sivalabs2PU'></property>
       <property name='dataSource' ref='dataSource2'></property>
       <property name='jpaVendorAdapter'>
        <bean id='jpaAdapter' class='org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter'
           p:showSql='${hibernate.show_sql}'/>
       </property>
       <property name='jpaProperties'>
        <props>
         <prop key='hibernate.dialect'>${hibernate.dialect}</prop>
         <prop key='hibernate.hbm2ddl.auto'>${hibernate.hbm2ddl.auto}</prop>
        </props>
       </property>
       <property name='packagesToScan' value='com.sivalabs.springdemo.entities'></property>
       <property name='loadTimeWeaver'>
         <bean class='org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver'/>
       </property>
        
   </bean>
 
<bean id='dataSource1' class='org.apache.commons.dbcp.BasicDataSource'>
 <property name='driverClassName' value='${node1.jdbc.driverClassName}'></property>
 <property name='url' value='${node1.jdbc.url}'></property>
 <property name='username' value='${node1.jdbc.username}'></property>
 <property name='password' value='${node1.jdbc.password}'></property>
</bean>
 
<bean id='dataSource2' class='org.apache.commons.dbcp.BasicDataSource'>
 <property name='driverClassName' value='${node2.jdbc.driverClassName}'></property>
 <property name='url' value='${node2.jdbc.url}'></property>
 <property name='username' value='${node2.jdbc.username}'></property>
 <property name='password' value='${node2.jdbc.password}'></property>
</bean>

После этого я понял, что для связывания Entitymanager с правильным PersistenceUnit мне нужно дать persistenceUnitName для LocalContainerEntityManagerFactoryBean.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
<bean class='org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor'>
 <property name='persistenceUnits' >
     <map>
       <entry key='unit1' value='Sivalabs1PU'/>
       <entry key='unit2' value='Sivalabs2PU'/>
     </map>
 </property>
</bean>
 
<bean id='emf1' class='org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean'>
       <property name='persistenceUnitName' value='Sivalabs1PU'></property>
       <property name='dataSource' ref='dataSource1'></property>
       ....
 ....       
   </bean>
    
   <bean id='emf2' class='org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean'>
       <property name='persistenceUnitName' value='Sivalabs2PU'></property>
       <property name='dataSource' ref='dataSource2'></property>
       ....
 ....       
   </bean>

Затем в моем компоненте Service Bean EntityManager и менеджеры транзакций склеиваются следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class AdminUserService implements UserService
{
 @PersistenceContext(unitName='Sivalabs1PU')
 private EntityManager sivalabs1EM;
 @PersistenceContext(unitName='Sivalabs2PU')
 private EntityManager sivalabs2EM;
  
 @Override
 @Transactional('txnManager1')
 public List<User> getAllUsersFromSivalabs1DB() {
  return sivalabs1EM.createQuery('from User', User.class).getResultList();
 }
 
 @Override
 @Transactional('txnManager2')
 public List<User> getAllUsersFromSivalabs2DB() {
  return sivalabs2EM.createQuery('from User', User.class).getResultList();
 }
  
}
1
 

С этой настройкой теперь я получил Исключение, говорящее «Не найден блок персистентности с именем Sivalabs1PU». Затем, после некоторого поиска, я создал файл META-INF / persistence.xml следующим образом:

1
2
3
4
5
6
7
8
9
<persistence>
 
   <persistence-unit name='Sivalabs1PU' transaction-type='RESOURCE_LOCAL'>    
   </persistence-unit>
    
   <persistence-unit name='Sivalabs2PU'  transaction-type='RESOURCE_LOCAL'>    
   </persistence-unit>
    
</persistence>

Теперь ошибка имени модуля персистентности была устранена и получено другое исключение, говорящее «Пользователь не сопоставлен [от пользователя]». Класс User помечен @Entity и находится в пакетеcom.sivalabs.springdemo.entities ‘, который я настроил для атрибута ‘ packagesToScan ‘. Я не понял, почему не работает атрибут «packagesToScan», который работает нормально без файла persistence.xml. Поэтому я настроил классы сущностей в файле persistence.xml.

01
02
03
04
05
06
07
08
09
10
11
<persistence>
 
   <persistence-unit name='Sivalabs1PU' transaction-type='RESOURCE_LOCAL'>   
  <class>com.sivalabs.springdemo.entities.User</class>  
   </persistence-unit>
    
   <persistence-unit name='Sivalabs2PU'  transaction-type='RESOURCE_LOCAL'>   
  <class>com.sivalabs.springdemo.entities.User</class>
   </persistence-unit>
    
</persistence>

Наконец, когда я запустил свой тест JUnit, который вызывает методы AdminUserService, все выглядит хорошо и работает нормально. Затем я развернул файл war на сервере JBoss AS 7.1, после чего снова получил кучу ошибок. JBoss жалуется, что «Соединение не может быть нулевым, когда« hibernate.dialect »не установлен»…. ‘[PersistenceUnit: Sivalabs1PU] Невозможно построить EntityManagerFactory’.

Подумав пару минут, я понял, что сервер JBoss пытается сделать то, что он должен делать с правилами ‘Convention Over Configuration’. JBoss пытается создать EntityManagerFactory, потому что он нашел META-INF / persistence.xml в пути к классам. Но так как он не содержит подробностей соединения jdbc, выдается ошибка.

Опять же после некоторого поиска в Google я обнаружил, что мы можем переименовать файл persistence.xml во что-то другое (spring-persistence.xml) и подключить это новое имя к Spring следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<bean id='emf1' class='org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean'>
       <property name='persistenceUnitName' value='Sivalabs1PU'></property>
 <property name='persistenceXmlLocation' value='classpath:META-INF/spring-persistence.xml'/>
       <property name='dataSource' ref='dataSource1'></property>
       ....
 ....       
   </bean>
    
   <bean id='emf2' class='org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean'>
       <property name='persistenceUnitName' value='Sivalabs2PU'></property>
 <property name='persistenceXmlLocation' value='classpath:META-INF/spring-persistence.xml'/>
       <property name='dataSource' ref='dataSource2'></property>
       ....
 ....       
   </bean>

Наконец-то я получил это приложение, работающее на моем JBoss AS 7.1 успешно (все же я не знаю, сколько еще дыр там, которые я еще не нашел).

Но здесь я не понял несколько концепций Spring:
1. Когда я пытаюсь указать persistenceUnitName, почему Spring проверяет наличие этого имени в persistence.xml? В любом случае, файл persistence.xml не содержит ничего, кроме имени модуля!

2. Почему не работает механизм packagesToScan при использовании файла persistence.xml? Это весенний жук?

Кажется, все работает нормально, за исключением одной вещи, с улыбкой на лице, которая обычно появляется при работе с Spring и Tomcat 🙁

Мне очень нравится Spring Framework, и я использую его с 2006 года, и мне очень нравится писать Spring-код. Это не значит, что я не люблю CDI, EJB3, JAX-RS 🙂

В любом случае, со всеми вышеупомянутыми упражнениями я чувствую себя Spring3 + JPA2 + JavaEE6AppServer = Confusion Over Configuration, и это только мое мнение (среднего разработчика Java).

Снова скажем еще раз: Spring великолепен, JavaEE6 великолепен, и последние серверы приложений JavaEE6 также великолепны :-).

Ссылка: Spring3 + JPA2 + JavaEE6AppServer = Confusion Over Configuration от нашего партнера по JCG Шивы Редди из блога « Мои эксперименты по технологии» .