Spring TransactionProxyFactoryBean не загружает дао для обслуживания

У меня возникли проблемы с настройкой Spring с Hibernate в рамках GWT. Я довольно новичок в GWT. У меня настроен и загружается контекст приложения без ошибок вывода, но моя основная проблема на данный момент заключается в том, что для реализации моего сервисного уровня (PobaseServiceImpl) требуется DAO, который я настроил в контексте приложения, но он не обертывает DAO. Естественно, мой RPC пытается вызвать методы dao, что приводит к исключению NullPointerException. pobaseDao не устанавливается TransactionProxyFactoryBean, когда я его инициализирую.

Подводя итог: DAO должен быть создан (то есть настроен в) Spring, как и остальные мои сервисы. Затем внедрить в сервисы через Spring. Затем с помощью DAO оберните его прокси-сервером транзакции Spring (org.springframework.transaction.interceptor.TransactionProxyFactoryBean) и предоставьте ему Hibernate SessionFactory (org.springframework.orm.hibernate4.LocalSessionFactoryBean). Но по какой-то причине он не устанавливает дао

Итак, мой контекст приложения загружается через ServletContextListener. Вот мой контекст приложения:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

  <!--
    Spring Framework application context definition for the POBASE Website.
  -->

<beans>

  <!-- Configurer that replaces ${...} placeholders with values from a properties file -->
  <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
      <list>
         <value>classpath:/pobase.properties</value>
         <value>file:${user.home}/pobase.properties</value>
      </list>
    </property>
    <property name="ignoreResourceNotFound" value="no"/>
  </bean>

  <!-- Hibernate Data Source -->
  <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${pobase.database.driver}" />
    <property name="url" value="${pobase.database.url}" />
    <property name="username" value="${pobase.database.user}" />
    <property name="password" value="${pobase.database.password}" />
  </bean>

  <!-- Hibernate SessionFactory -->
  <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="dataSource">
      <ref bean="dataSource" />
    </property>
    <property name="packagesToScan" value="nz.co.doltech.pobase.client.entity"/>
    <property name="hibernateProperties">
      <props>
        <prop key="hibernate.dialect">${pobase.hibernate.dialect}</prop>
        <prop key="hibernate.show_sql">${pobase.hibernate.show_sql}</prop>
        <prop key="javax.persistence.validation.mode">none</prop>
      </props>
    </property>
  </bean>

  <!-- Transaction manager for a single Hibernate SessionFactory (alternative to JTA) -->
  <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
  </bean>

  <!-- Default transaction proxy, defining the transactional behaviour for
    a typical Dao configuration -->
  <bean id="baseDaoTransactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
    abstract="true">
    <property name="transactionManager" ref="transactionManager" />
    <property name="transactionAttributes">
      <value>*=PROPAGATION_MANDATORY</value>
    </property>
  </bean>

  <!-- Default transaction proxy, defining the transactional behaviour for
    a typical Service configuration -->
  <bean id="baseServiceTransactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
    abstract="true">
    <property name="transactionManager" ref="transactionManager" />
    <property name="transactionAttributes">
      <value>*=PROPAGATION_REQUIRED</value>
    </property>
  </bean>

  <!-- ========================= BUSINESS OBJECT DEFINITIONS ========================= -->

  <bean id="pobaseDao" parent="baseDaoTransactionProxy">
    <property name="target" ref="pobaseDaoTarget" />
  </bean>
  <bean id="pobaseDaoTarget" class="nz.co.doltech.pobase.server.dao.PobaseHibernateDao">
    <property name="sessionFactory" ref="sessionFactory" />
  </bean>

  <bean id="pobaseService" parent="baseServiceTransactionProxy">
    <property name="target" ref="pobaseServiceTarget" />
  </bean>
  <bean id="pobaseServiceTarget" class="nz.co.doltech.pobase.server.service.PobaseServiceImpl">
    <property name="pobaseDao" ref="pobaseDao" />
    <!-- property name="accessControlService" ref="accessControlService" />
    <property name="lookupService" ref="lookupService"/>
    <property name="notificationService" ref="notificationService"/ -->
  </bean>

</beans>

и вот моя реализация сервлета RPC:

package nz.co.doltech.pobase.server.service;

/**
 * Extends RSS and implements the PobaseService
 * @author Ben
 */
@SuppressWarnings("serial")
public class PobaseServiceImpl extends RemoteServiceServlet implements PobaseService {

    @SuppressWarnings("unused")
    private static final Logger logger = Logger.getLogger(PobaseServiceImpl.class.getName());

    private PobaseDao pobaseDao;
    private final HashMap<Integer, PobaseEntity> pobaseEntities = new HashMap<Integer, PobaseEntity>();

    private void fetchExternPobase()
    {
        pobaseEntities.clear();
        List<PobaseEntity> pobaseList = pobaseDao.getAllPobase();
        for (int i = 0; i < pobaseList.size(); i++)
        {
            PobaseEntity en = pobaseList.get(i);
            if(en != null) {
                pobaseEntities.put(en.getId(), en);
            }
        }
    }

    public void setPobaseDao(PobaseDao dao)
    {
        this.pobaseDao = dao;
    }

    public PobaseDao getPobaseDao()
    {
        return this.pobaseDao;
    }

    public PobaseData addLocalPobase(PobaseData pobase)
    {
        PobaseEntity entity = new PobaseEntity();
        entity.mirrorObjectData(pobase);

        pobase.setId(pobaseEntities.size());
        pobaseEntities.put(pobase.getId(), entity); 

        return entity.getDataObject();
    }

    public PobaseData updateLocalPobase(PobaseData pobase)
    {
        PobaseEntity entity = new PobaseEntity();
        entity.mirrorObjectData(pobase);

        pobaseEntities.remove(entity.getId());
        pobaseEntities.put(entity.getId(), entity);

        return entity.getDataObject();
    }

    public Boolean deleteLocalPobase(int id)
    {
        pobaseEntities.remove(id);
        return true;
    }

    public ArrayList<PobaseData> deleteLocalPobases(ArrayList<Integer> ids)
    {
        for (int i = 0; i < ids.size(); ++i) {
            deleteLocalPobase(ids.get(i));
        }

        return getLocalPobaseData();
    }

    public ArrayList<PobaseData> getLocalPobaseData()
    {
        ArrayList<PobaseData> pobaseList = new ArrayList<PobaseData>();
        Iterator<Integer> it = pobaseEntities.keySet().iterator();
        while(it.hasNext())
        {
            PobaseData pobase = pobaseEntities.get(it.next()).getDataObject();
            pobaseList.add(pobase);
        }
        return pobaseList;
    }

    public PobaseData getLocalPobase(int id)
    {
        return pobaseEntities.get(id).getDataObject();
    }

    public ArrayList<PobaseData> resyncExternPobase()
    {
        fetchExternPobase();
        return getLocalPobaseData();
    }

}

Также вот лог запуска веб-приложения:

ServletContextListener started
Nov 12, 2012 8:20:33 PM nz.co.doltech.pobase.SpringInitialiser initSpringContext
INFO: Creating new Spring context. Configs are [/nz/co/doltech/pobase/appcontext.xml]
Nov 12, 2012 8:20:33 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@8423321: startup date [Mon Nov 12 20:20:33 NZDT 2012]; root of context hierarchy
Nov 12, 2012 8:20:33 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [nz/co/doltech/pobase/appcontext.xml]
Nov 12, 2012 8:20:33 PM org.springframework.core.io.support.PropertiesLoaderSupport loadProperties
INFO: Loading properties file from class path resource [pobase.properties]
Nov 12, 2012 8:20:33 PM org.springframework.core.io.support.PropertiesLoaderSupport loadProperties
INFO: Loading properties file from URL [file:/home/ben/pobase.properties]
Nov 12, 2012 8:20:33 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@4c56666d: defining beans [org.springframework.beans.factory.config.PropertyPlaceholderConfigurer#0,dataSource,sessionFactory,transactionManager,baseDaoTransactionProxy,baseServiceTransactionProxy,pobaseDao,pobaseDaoTarget,pobaseService,pobaseServiceTarget]; root of factory hierarchy
Nov 12, 2012 8:20:33 PM org.springframework.jdbc.datasource.DriverManagerDataSource setDriverClassName
INFO: Loaded JDBC driver: org.postgresql.Driver
Nov 12, 2012 8:20:33 PM org.hibernate.annotations.common.Version <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.1.Final}
Nov 12, 2012 8:20:33 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.1.7.Final}
Nov 12, 2012 8:20:33 PM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
Nov 12, 2012 8:20:33 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Nov 12, 2012 8:20:34 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect
Nov 12, 2012 8:20:34 PM org.hibernate.engine.jdbc.internal.LobCreatorBuilder useContextualLobCreation
INFO: HHH000424: Disabling contextual LOB creation as createClob() method threw error : java.lang.reflect.InvocationTargetException
Nov 12, 2012 8:20:34 PM org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService
INFO: HHH000399: Using default transaction strategy (direct JDBC transactions)
Nov 12, 2012 8:20:34 PM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
INFO: HHH000397: Using ASTQueryTranslatorFactory
Nov 12, 2012 8:20:34 PM org.springframework.orm.hibernate4.HibernateTransactionManager afterPropertiesSet
INFO: Using DataSource [org.springframework.jdbc.datasource.DriverManagerDataSource@55acc0d4] of Hibernate SessionFactory for HibernateTransactionManager
Starting Jetty on port 8888

У кого-нибудь есть идеи, почему он не работает? Некоторые характеристики здесь:

  • Весна 3.2.0
  • Спящий режим4
  • GWT SDK 2.4.0

Ценю любую помощь, которую я могу получить!

Привет, Бен


person Ben Dol    schedule 12.11.2012    source источник
comment
пожалуйста, добавьте трассировку стека, и, кроме того, вы уверены, что хотите загружать данные и хранить их в своей службе?   -  person Francisco Spaeth    schedule 12.11.2012
comment
Есть ли более традиционный способ хранения данных? Я новичок в GWT, поэтому не уверен на 100%, каковы лучшие практики. (Добавит трассировку стека в основной пост) Исключение null связано с тем, что pobaseDao вызывается перед установкой. Но, насколько я понимаю, прокси-сервер транзакции и сервисный компонент, настроенный в appcontext.xml, должны позаботиться об этом?   -  person Ben Dol    schedule 12.11.2012
comment
Ну, это зависит от вашего подхода, но хранение данных в реализации службы разумно, когда вы хотите выполнить что-то вроде кеша, иначе это не тот путь.   -  person Francisco Spaeth    schedule 12.11.2012
comment
Ок, буду иметь в виду, спасибо. Это приложение является тестовым приложением для накопления моих знаний в GWT с помощью Spring и Hibernate :)   -  person Ben Dol    schedule 12.11.2012
comment
Вместо ‹property name=pobaseDao ref=pobaseDao /› попробуйте ‹property name=pobaseDao ref=pobaseDaoTarget /› и посмотрите, что получится.   -  person Sajan Chandran    schedule 12.11.2012
comment
@SajanChandran спасибо за ответ, к сожалению, это не сработало. Я считаю, что, возможно, мне не хватает чего-то общего с реализацией службы. Сообщение, которое я пометил как ответ, является лучшим ответом на данный момент, потому что это один из способов сделать это, но, основываясь на том, что мне сказали, есть еще один способ подключить его, не переопределяя метод инициализации RemoteServiceServlet и т. д.   -  person Ben Dol    schedule 13.11.2012


Ответы (1)


Ваш класс PobaseServiceImpl не является потокобезопасным. В Java любой удаленно вызываемый сервис (и, конечно, то же самое относится и к синглтонам в контейнере) должен быть готов к тому, что его методы будут вызываться из разных потоков, и один и тот же метод может выполняться одновременно в разных потоках.

Весь класс нуждается в детальной проверке.

Прежде всего, вы должны использовать ConcurrentHashMap вместо HashMap. И все методы должны быть тщательно переписаны — например, метод, возвращающий ArrayList (public ArrayList<PobaseData> resyncExternPobase()), должен создавать защитную копию данных.

В качестве альтернативы вам следует рассмотреть возможность синхронизации общедоступных методов ваших методов службы, однако это заставит методы выполняться последовательно и снизит производительность, если ожидается, что служба будет активно использоваться из разных потоки.

Для справки рассмотрите возможность чтения Брайана Гетца Параллелизм Java на практике, глава 5. Строительные блоки и особенно 5.1 Синхронизированные коллекции и 5.2. Параллельные коллекции

обновление: ваш PobaseServiceImpl кажется сервлетом, экземпляр которого создается контейнером сервлетов — для заполнения его полей компонентами Spring вы должны использовать служебные методы Spring — например WebApplicationContextUtils - в Интернете есть много примеров, например http://pgt.de/2009/07/17/non-invasive-gwt-and-spring-integration-reloaded/

person Boris Treukhov    schedule 12.11.2012
comment
Спасибо за ответ, обязательно изучу то, что вы упомянули. Я новичок в этом типе разработки, поэтому изучение лучших практик в удаленных службах и т. д. действительно полезно. По сути, это тестовое приложение для меня, чтобы узнать больше о GWT и разработке больших веб-приложений. Так или иначе, то, что вы упомянули, вызывает мою проблему с оберткой DAO? - person Ben Dol; 12.11.2012
comment
@BenDol Сначала замените HashMap на ConcurrentHashMap, добавьте комментарий, если что-то изменилось. - person Boris Treukhov; 12.11.2012
comment
Никаких изменений в поведении. Я не очень хорошо сформулировал свою проблему в основном посте. В основном кажется, что Spring DAO не оборачивается, когда я инициализирую контекст приложения. Насколько я понимаю, в моем appcontext.xml я определил bean-компонент с идентификатором pobaseService, который указывает на целевой bean-компонент (pobaseServiceTarget), который имеет класс PobaseServiceImpl и свойство ‹property name=pobaseDao ref=pobaseDao /›, который должен обернуть DAO в услуга. Но я явно что-то упускаю, не слишком уверен, что. - person Ben Dol; 12.11.2012
comment
Вы пытались запустить приложение в режиме отладки и перейти к методу fetchExternPobase, чтобы найти фактическую строку, в которой возникает исключение? - person Boris Treukhov; 12.11.2012
comment
Это когда я вызываю pobaseDao, который неправильно инициализируется в службе. 'Список‹PobaseEntity› pobaseList = pobaseDao.getAllPobase();' - person Ben Dol; 12.11.2012
comment
@BenDol Но ссылка на Дао не пуста? - person Boris Treukhov; 12.11.2012
comment
Это ноль, вот в чем проблема. Spring контекст должен инициализировать DAO, как я настроил его таким образом в appcontext.xml. Если только я не сделал это неправильно. - person Ben Dol; 12.11.2012
comment
DAO должен быть создан (то есть настроен в) Spring, как и остальные мои сервисы. Затем внедрить в сервисы через Spring. Что я делаю для DAO, так это оборачиваю его в прокси-сервер транзакций Spring (org.springframework.transaction.interceptor.TransactionProxyFactoryBean) и даю ему Hibernate SessionFactory (org.springframework.orm.hibernate4.LocalSessionFactoryBean). Но по какой-то причине он не устанавливает дао. - person Ben Dol; 12.11.2012
comment
@BenDol У меня еще нет опыта работы с GWT, но RemoteServiceServlet экземпляры не инициализируются вне контейнера Spring, поскольку они являются внешними по отношению к Spring. mitosome.blogspot.ru/2011/01/ Также, если это сервлет - сервлеты должны быть прикреплены к контейнеру вручную, так как они инициализируются веб-сервером. - person Boris Treukhov; 12.11.2012
comment
Да, но я полагаю, что Spring инициализирует его в службе с помощью прокси-сервера. Разве я не слишком оптимистичен, думая, что это будет настолько автоматизировано? Возможно, я упустил что-то, связанное с аннотацией @autowired ... не уверен, что буду продолжать исследования, и, надеюсь, кто-то сможет пролить свет на это. - person Ben Dol; 12.11.2012
comment
В этом случае Spring просто создает еще один экземпляр сервлета (поскольку это класс java, объявленный в конфигурации контекста xml) - как изменится жизненный цикл веб-приложения? :) - person Boris Treukhov; 12.11.2012
comment
@BenDol здесь нет никакой магии - экземпляры сервлетов создаются контейнером сервлетов - для доступа к контексту корневого веб-приложения Spring оттуда вы должны использовать служебные классы Spring, например, как это сделано в миланском ответе: stackoverflow.com/questions/8933778/ — в сети есть несколько классов для упрощения интеграции GWT — которые просто заполняют поля сервлета, вызывая служебные методы, или предоставляют фасад для Spring с помощью метода getBean (beanName), который также реализуется через служебные классы Spring. - person Boris Treukhov; 12.11.2012
comment
Большое спасибо за помощь, теперь это имеет гораздо больше смысла. Наиболее ценится. - person Ben Dol; 13.11.2012