Jason
Jason

Reputation: 4130

Difficulty Using Spring in a JAR With Hibernate

I am attempting to split out the Hibernate DAO and Model Object layer from an existing application so they can be used across multiple applications. Unfortunately, I'm not having much success: a NoSuchBeanDefinitionException is thrown when trying to get the SessionFactory from the Application Context.

All of DAO classes extend a class called GenericDaoHibernate2. Each DAO extends this, and passes a Class in the constructor. Pretty standard Generic DAO stuff.

I figured this would be the logical place to set the session factory as well (there are ALOT of DAO classes). So, in the constructor class, I did this:

public GenericDaoHibernate2(final Class<T> persistentClass) {
    ctx = new ClassPathXmlApplicationContext("META-INF/applicationContext-dao.xml");
    this.sessionFactory = (SessionFactory) ctx.getBean(SessionFactory.class);
    log.debug("Value of app context: " + ctx.toString());
    log.debug("Value of sessionFactory: " + sessionFactory);
    this.persistentClass = persistentClass;
}

Unfortunately, this blew up with the previously mentioned exception:

Caused By: org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [org.hibernate.SessionFactory] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:924)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:793)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:707)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:551)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
Truncated. see log file for complete stacktrace

I've also attempted this with the app context file just in the classpath, setting the value when declaring the variable, etc, etc.

I'm guessing what is happing is that as part of the Maven build, the jar isn't referencing the libraries on the classpath, but I really don't know...

UPDATE: Stupid, stupid me... forgot to show the application context file.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-3.1.xsd
       http://www.springframework.org/schema/jee
       http://www.springframework.org/schema/jee/spring-jee-3.1.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd"
    default-lazy-init="true">

    <tx:annotation-driven transaction-manager="transactionManager" />
    <context:component-scan base-package="org.jason.dao.hibernate" />


    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
        <property name="url" value="jdbc:oracle:thin:@//192.168.1.1/db01" />
        <property name="username" value="USER" />
        <property name="password" value="PASSWORD" />
    </bean>

    <!-- Hibernate SessionFactory -->
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
                <prop key="hibername.format_sql">true</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.jdbc.use_get_generated_keys">true</prop>
                <prop key="hibernate.cglib.use_reflection_optimizer">true</prop>
                <prop key="hibernate.default_catalog">CATALOG</prop>
                <prop key="hibernate.cache.use_second_level_cache">true</prop>
                <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
                </prop>
            </props>
        </property>
        <property name="packagesToScan">
            <list>
                <value>org.jason.model</value>
            </list>
        </property>
    </bean>
</beans>

Another update: Was asked for a sample DAO. The interface is a "standard" generic interface, accepting the generic paramters T and PK, just like the Impl. The following one doesn't have any specific methods other than what it inherits from GenericDaoHibernate2.

@Repository("AreaOfPreferenceDAO")
public class HibernateAreaOfPreferenceDAO extends GenericDaoHibernate2<AreaOfPreference, AreaOfPreferenceCompositeId> implements AreaOfPreferenceDAO {

   public HibernateAreaOfPreferenceDAO()
   {
      super(AreaOfPreference.class);
   }
}

Upvotes: 1

Views: 1127

Answers (3)

Remi Morin
Remi Morin

Reputation: 292

Had the same problem, unable to make injection work.

My problem was "sequence". Hibernate xml must be invoked first.

dispatcher-servlet.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.1.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">

<!-- init hibernate first -->
<import resource="classpath:HibernateContext.xml"/>

<bean id="viewResolver"
    class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/pages/" />
    <property name="suffix" value=".jsp" />
</bean>
<mvc:annotation-driven />
<context:component-scan base-package="com.xxx.yyy" />

content of hibernateContext.xml:

<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:tx="http://www.springframework.org/schema/tx"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">

<bean id="propertyConfigurer"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <list>
            <value>classpath:database.properties</value>
        </list>
    </property>
</bean>

<bean id="dataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${jdbc.driver}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.user}" />
    <property name="password" value="${jdbc.password}" />
</bean>

<bean id="sessionFactory"
    class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="packagesToScan" value="com.xxx.yyy.model" />
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
            <prop key="hibernate.show_sql">true</prop>
        </props>

    </property>
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>

<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

<!--  bean id="transactionManager" class=" org.springframework.transaction.jta.JtaTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean-->

Upvotes: 0

Eugen
Eugen

Reputation: 8783

In order to wire in the SessionFactory into your custom generic DAO, you can go ahead and simply use @Autowire, as long as the overall Spring context is defining the SessionFactory bean. To define the bean:

<bean id="sessionFactory" class=
    "org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
   <property name="dataSource" ref="dataSource" />
   <property name="packagesToScan" value="org.rest" />

   <property name="hibernateProperties">
      ...
   </property>
</bean>
<bean id="dataSource" class=
    "org.springframework.jdbc.datasource.DriverManagerDataSource">
   <property name="driverClassName" value="${driverClassName}" />
   <property name="url" value="${url}" />
   <property name="username" value="restUser" />
   <property name="password" value="restmy5ql" />
</bean>

And to wire, simply:

@Autowired
SessionFactory sessionFactory;

The right place to bootstrap the context is not in the constructor of your DAO; if you're working with a web application, you can go with the traditional approach:

<servlet>
   <servlet-name>dispatcher</servlet-name>
   <servlet-class>
     org.springframework.web.servlet.DispatcherServlet
   </servlet-class>
   <init-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>/WEB-INF/spring/dispatcher-config.xml</param-value>
   </init-param>
   <load-on-startup>1</load-on-startup>
 </servlet>

 <servlet-mapping>
   <servlet-name>dispatcher</servlet-name>
   <url-pattern>/</url-pattern>
 </servlet-mapping>

Since this is not a web application, then the context cannot be bootstrapped in web.xml; however, the bootstrapping still needs to be external - a main class would simply need to create the XmlWebApplicationContext and configure it.

Hope this helps.

Upvotes: 1

jeff
jeff

Reputation: 4333

The fact that you are creating a new application context in each DAO object may be getting you into trouble.

ctx = new ClassPathXmlApplicationContext("META-INF/applicationContext-dao.xml");

This is kind of circular if you think about it. You call the context, which does a component scan for DAO's, then the DAO instantiates a new context, which does a component scan for DAO's ...

I would just autowire the sessionfactory directly as someone else had mentioned.

@Repository("AreaOfPreferenceDAO")
public class HibernateAreaOfPreferenceDAO extends GenericDaoHibernate2<AreaOfPreference, AreaOfPreferenceCompositeId> implements AreaOfPreferenceDAO {

   @Autowired
   public HibernateAreaOfPreferenceDAO(SessionFactory sessionFactory)
   {
      super(sessionFactory, AreaOfPreference.class);
   }
}

No component should need to construct a new application context.

Upvotes: 1

Related Questions