配置 Hibernate, Spring 和 (MyFaces) JSF

配置 Hibernate, Spring 和 (MyFaces) JSF

This document discusses best practices for configuring Spring, JSF (MyFaces), and Hibernate. Please feel free to provide comments, constructive criticisms and suggestions.

This document is related to Pagination using a h:dataGrid and Hibernate.
This document on MyFaces WIKI (although I don't agree with all of the conclusions) is a good starting point for the background information for this document Hibernate, MyFaces and OpenInSessionViewFilter.

Objective

This document discusses best practices for configuring Spring, JSF (MyFaces), and Hibernate.

web.xml configuration for JSF, Spring, Hibernate basic configuration

To configure Hibernate, Spring and JSF to work together you need to do the following:

  • Configure web.xml
    1. Install the Spring ContextLoaderListener into the war file deployment descriptor (web.xml)
      • Used to install the Spring ApplicationContext for the web application
      • Configure the contextConfigLocation context param with a comma delimited list of application context files
      • Often the application context files are split up by tier (web, managers, dao), etc.
    2. Install the Spring OpenSessionInViewFilter into the war file deployment descriptor (web.xml)
      • Sets up the Servlet filter that manages the Hibernate session
      • Associates a Hibernate session with the current thread assigned to the current request
      • Classes that use HibernateTemplate will access the same session by default
      • HibernateTemplate uses SessionFactoryUtils.getSession
      • SessionFactoryUtils.getSession uses existing Hibernate Session associated with the current thread
    3. Install the MyFaces StartupServletContextListener into the war file deployment descriptor (web.xml)
      • Used to initialize MyFaces
      • Configure the javax.faces.CONFIG_FILES context param with a comma delimited list of faces-context.xml files.
      • You can split faces-context.xml files by domain (faces-context-store-system.xml, faces-context-inventory.xml)
      • You can split the faces-context.xml files by logical configuration areas (faces-context-navigation.xml, faces-context-managed-beans.xml)
      • Or you can split the faces-context.xml file using both of the above
    4. Install the JSF FacesServlet into the war file deployment descriptor (web.xml)
      • Used to handle JSF bound requests
    5. If you are going to write filters, install the FilterToBeanProxy so your filters can be managed by Spring
      • Allows you to use Spring's dependency injection with Filters

Here is a sample web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.4" 
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee   http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

  <context-param>
    <param-name>javax.faces.CONFIG_FILES</param-name>
    <param-value>/WEB-INF/faces-config.xml</param-value>
  </context-param>
  <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
  </context-param>  
  <filter>
        <filter-name>Acegi Filter Chain Proxy</filter-name>
        <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
        <init-param>
            <param-name>targetClass</param-name>
            <param-value>net.sf.acegisecurity.util.FilterChainProxy</param-value>
        </init-param>
  </filter>
  <filter-mapping>
        <filter-name>Acegi Filter Chain Proxy</filter-name>
        <url-pattern>/*</url-pattern>
  </filter-mapping>   
  <filter>
	  <filter-name>sessionFilter</filter-name>
	  <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
  </filter>
	        
  <filter-mapping>
	  <filter-name>sessionFilter</filter-name>
	  <url-pattern>*.faces</url-pattern>
  </filter-mapping>
  
  <listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>


  <listener>
    <listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>
  </listener>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.faces</url-pattern>
  </servlet-mapping>

  <security-constraint>
        <display-name>Restrict access to JSP pages</display-name>
        <web-resource-collection>
            <web-resource-name>Restrict access to JSP pages</web-resource-name>
            <url-pattern>/process/*jsp</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <description>With no roles defined, no access granted</description>
        </auth-constraint>
  </security-constraint>

</web-app>
OpenInSessionViewFilter

Please read this blog entry OpenInSessionViewFilter for more details about the OpenInSessionViewFilter.

It should be noted that the OpenInSessionViewFilter will allow you to avoid the problem (to some extent) of lazy initialization errors. However, this is likely just an unwelcome band-aid for your web development efforts as your HQL queries should be returning exactly the object graph it needs for the particular use case. If your HQL queries return too many objects (in the object graph) it is inefficient use of database resources; conversly, if you HQL queries do not return enough objects (from the object graph) this can lead to the dreaded N+1 problem. Please read Hibernate performance tuning and Hibernate Tips for more details. With that said, you did get a lot of advantage by using the same session for the request with some caveats (noted in the API documents of OpenInSessionViewFilter). For one, objects loaded in the user transaction (request) are available to other DAO objects and do not have to be retrieved again from the database.

Configuring Spring for Hibernate/JSF/Spring combination
  • Configure Spring
    1. Include the non-web related applicationContext.xml
      • This allows easier integration testing
    2. Include the web only applicationContext file.
      • From the non-web related applicationContext.xml file, include context files for different tiers.
      • You can also seperate applicationContext files by any logical seperation.

Include the non-web related applicationContext.xml and the web related xml file.

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

<!--
 | The Spring application context for the web application.
 +-->

<!--
 | Original author: Rick Hightower
 | $Revision: #1 $
 +-->
<beans>
    <import resource="classpath:com/example/jsf/hibernate/example/resources/applicationContext.xml" />
    <import resource="webContext.xml" />
</beans>

Notice that the /WEB-INF/applicationContext.xml includes the applicationContext.xml file from the classpath.
This makes it easy to write JUnit integration tests that use ClassPathXMLApplicationContext.
The webContext.xml file has beans that are specific to the web tier, and can only run in the application server, like filters and such.

com.example.jsf.hibernate.example.resources.applicationContext.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
"http://www.springframework.org/dtd/spring-beans.dtd">

<!--
 | The main application context (please improve this description).
 +-->

<!--
 | $File$
 | $Change$ submitted by $Author$ at $DateTime$
 | Original author: Rick Hightower
 | $Revision$
 +-->
<beans>

    <import resource="applicationContextProperties.xml" />
    
    <import resource="daoContext.xml" />
    <import resource="managerContext.xml" />
    <import resource="serviceContext.xml" /> 
    ...
    ...

    
</beans>

The applicationContext.xml file that is on the classpath (for testing) imports an applicationContext file per tier (and any other logical seperation of configuration options that you should choose).

The applicationContextProperties.xml is used to setup configurable properties that will change often.

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

<!--
 | The main application context property configurer.
 +-->

<!--
 | Original author: Rick Hightower
 | $Revision: #2 $
 +-->
<beans>

    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:com/example/jsf/hibernate/example/resources/applicationContext.properties</value>
                <value>classpath:com/example/jsf/hibernate/example/resources/db.properties</value>
            </list>
        </property>
        <!-- Override properties in file with system properties -->
        <property name="systemPropertiesModeName">
            <value>SYSTEM_PROPERTIES_MODE_OVERRIDE</value>
        </property>
    </bean>

</beans>

The applicationContextProperties.xml imports properties files. The properties from these property files can be used as substitions for values in the application context files. This allows us to use the applicationContext files for developement, integration testing, QA, and production. We just need different properties files for each unique configuration. The build script would take care of bundling the correct versions of the properties files with the war file distribution.

The daoContext.xml files is used to setup the DAO tier of the application.

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

<!--
 | Spring configuration file for Data Access Objects (DAOs).
 +-->

<!--
 +-->
<beans>

    <!-- Configure the dataSource. 
         Notice that we are using properties defined in applicationContextProperties.xml, i.e.,
         ${db.connection.driver_class} uses a property called db.connection.driver_class defined 
         in db.properties file, which was imported by applicationContextProperties.xml.
    --> 
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName"><value>${db.connection.driver_class}</value></property>
        <property name="url"><value>${db.connection.url}</value></property>
        <property name="username"><value>${db.connection.username}</value></property>
        <property name="password"><value>${db.connection.password}</value></property>
    </bean>


    <!-- We may use this later on if we choose to use JTA transactions.
    <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>jdbc/example/dataSource</value>
        </property>
    </bean>
    -->

    <!--
      Setup Hibernate based on config file 
      classpath:com/example/jsf/hibernate/example/resources/hibernate.cfg.xml and properties
      defined by hibernateProperties.
    -->
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource"><ref local="dataSource"/></property>
        <property name="configLocation">
            <value>classpath:com/example/jsf/hibernate/example/resources/hibernate.cfg.xml</value>
        </property>
        <property name="hibernateProperties"><ref local="hibernateProperties"/></property>
    </bean>

    <!-- This configures default properties, which can overridden with the file specified by the location property -->
    <bean id="hibernateProperties" 
          class="org.springframework.beans.factory.config.PropertiesFactoryBean">
         <property name="properties">
            <props>
				<prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.cglib.use_reflection_optimizer">true</prop>
            </props>
        </property>
        <property name="location" value="classpath:com/example/jsf/hibernate/example/resources/hibernate.properties"/> 
    </bean>


    <!-- If we opt to use JTA transactions, we could replace this with the JTATransactionManager or
         configure Hibernante to work with JTATransactions and keep this as is. -->
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory">
            <ref local="sessionFactory"/>
        </property>
    </bean>

    <!-- You can setup a HibernateTemplate that configures Hibernate sessions in special ways.
         For example, you could set a different FlushMode than the default.
         NOTE THIS COMMENTED OUT AS THE DEFAULTS ARE ACCEPTABLE.
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
        <property name="sessionFactory">
            <ref bean="sessionFactory"/>
        </property>
        <property name="cacheQueries">
            <value>true</value>
        </property>
    </bean>
    -->

    <!-- This is the base definition for all Hibernate based DAOs -->
    <bean id="hibernateDaoSupport" 
          class="org.springframework.orm.hibernate3.support.HibernateDaoSupport"
          abstract="true">
        <property name="sessionFactory">
            <ref bean="sessionFactory"/>
        </property>
        <!-- You could use the configured Hibernate template or create
             a new mapping that uses a configured hibernate template. 
             HibernateDaoSupport creates a HibernateTempalte with the default settings.
             NOTE THIS IS COMMENTED OUT.
        <property name="hibernateTemplate">
            <ref bean="hibernateTemplate"/>
        </property>
        -->
    </bean>
    
    <!-- You can use Hibernate for 90% to 95% or so of database access.
        The JdbcTemplate is for when you can't use Hibernate. 
        Don't use JDBC directly! Use the JdbcTemplate as it handles JDBC cleanup.-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource">
            <ref local="dataSource"/>
        </property>
    </bean>

    <!--
     | DAO implementations. Note that this extends the base definition hibernateDaoSupport.
     +-->
    <bean id="rssDAO" 
          class="com.example.jsf.hibernate.example.dao.HibernateRssFeedDAO" 
          parent="hibernateDaoSupport"/>


    

</beans>

Please read the comments in the above file as they describe what each piece is doing. Notice that all DAO bean definitions (or most DAOs) will extend the hibernateDaoSupport definition. This means there will not be a lot of duplication. This is why the rssDAO definition is so short as it extends the hibernateDaoSupport definition.

The sessionFactory bean imports the hibernate.cfg file as follows:

hibernate.cfg.xml
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
          "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

<session-factory>
	<mapping resource="com/example/jsf/hibernate/example/model/RssFeed.hbm.xml" />
	<mapping resource="com/example/jsf/hibernate/example/model/Channel.hbm.xml" />
	<mapping resource="com/example/jsf/hibernate/example/model/Item.hbm.xml" />
</session-factory>

</hibernate-configuration>

It is a common practice to but the *.hbm.xml files that are related to a persistent class next to the class on the classpath.

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

<!--
 | Spring configuration file for Manager Objects
 +-->

<!--
 +-->
<beans>

    
    <!-- This is the base transaction proxy factory bean, all transactional managers 
         use this bean definition. -->
    <bean id="txProxyTemplate" abstract="true"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager">
            <ref bean="transactionManager"/>
        </property>
        <property name="transactionAttributes">
            <props>
                <prop key="save*">PROPAGATION_REQUIRED</prop>
                <prop key="store*">PROPAGATION_REQUIRED</prop>
                <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
                <prop key="read*">PROPAGATION_REQUIRED,readOnly</prop>
                <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>

        <property name="preInterceptors">
            <list>

            </list>
        </property>

        <property name="postInterceptors">
            <list>

            </list>
        </property>
    </bean>
    

    <!-- 
         You should only decorate services or manager beans.
    -->
    <bean id="rssManager" parent="txProxyTemplate">
    	<property name="proxyInterfaces">
    		<value>com.example.jsf.hibernate.example.manager.FeedManager</value>
    	</property>
        <property name="target">
           <bean class="com.example.jsf.hibernate.example.manager.FeedManagerImpl">
                <property name="rssDAO" ref="rssDAO"/> 
           </bean>
        </property>
    </bean>
    

</beans>

Please read the comments in the above file as they describe what each piece is doing. Notice that all transactional managers will extend the txProxyTemplate definition. This means there will not be a lot of duplication. This is why the rssManager definition is so short as it extends the txProxyTemplate definition. Notice that the manager implementation is defined with an Inner Bean (pronounced "Innar Baen" at the TSS convention :o)), and it takes a rssDAO object as its base.

As stated earlier, the DAOs use HibernateTemplate, which in turn uses SessionFactoryUtils.getSession, which in turn gets the Hibernate session that the OpenInSessionViewFilter associated with the current thread (by default).

Sample DAO

Here is the DAO object for reference:

HibernateRssFeedDAO.java
package com.example.jsf.hibernate.example.dao;

import java.sql.SQLException;
import java.util.List;

import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.example.jsf.hibernate.example.model.Channel;
import com.example.jsf.hibernate.example.model.RssFeed;

public class HibernateRssFeedDAO extends HibernateDaoSupport implements RssFeedDAO  {

	public void storeFeed(RssFeed feed) {
		this.getHibernateTemplate().save(feed);

	}

	public RssFeed readFeed(Integer id) {
		return (RssFeed) this.getHibernateTemplate().get(RssFeed.class, id);
	}

	public void addChannel(Channel channel) {
		this.getHibernateTemplate().save(channel);
	}

	public Channel readChannel(final String id) {
		
		return (Channel) DataAccessUtils
		          .requiredUniqueResult(getHibernateTemplate()
		            .findByNamedQueryAndNamedParam("readChannel", "channelId", id));
		
	}

	public Channel readChannelPopulated(final String id) {
		return (Channel) DataAccessUtils
        	.requiredUniqueResult(getHibernateTemplate()
        	  .findByNamedQueryAndNamedParam("readChannelPopulated", "channelId", id));
	}

	public void updateChannel(Channel channel) {
		this.getHibernateTemplate().update(channel);
	}

	public void updateFeed(RssFeed feed) {
		this.getHibernateTemplate().update(feed);
	}

	public RssFeed readFeedPopulated(final Integer id) {
		return (RssFeed) DataAccessUtils
    	.requiredUniqueResult(getHibernateTemplate()
    			.findByNamedQueryAndNamedParam("readFeedPopulated", "rssId", id));
	}

	public void removeChannel(Channel channel) {
		this.getHibernateTemplate().delete(channel);
		
	}

	public void removeChannels(final String[] channelIds) {
		getHibernateTemplate().execute(new HibernateCallback(){
			public Object doInHibernate(Session session) throws HibernateException, SQLException {
				session.createQuery("delete Channel  where channelId in (:channelIds)")
					   .setParameterList( "channelIds", channelIds )
					   .executeUpdate();
				return null;
			}
		});
	}


	public List getAllFeeds() {
		return getHibernateTemplate().findByNamedQuery("getAllFeeds");
	}

	public int getFeedCount() {
		class ReturnValue  {
			Integer value;
		}
		final ReturnValue rv = new ReturnValue();
		getHibernateTemplate().execute(new HibernateCallback(){
			public Object doInHibernate(Session session) throws HibernateException, SQLException {
				rv.value = 
					(Integer) session.createQuery("select count(*) from RssFeed").uniqueResult();
				return null;
			}
		});
		return rv.value.intValue();
    }

	private List getAllFeeds(final String queryName, final int startRecord, final int endRecord) {
		return getHibernateTemplate().executeFind(new HibernateCallback(){
			public Object doInHibernate(Session session) throws HibernateException, SQLException {
				Query query = session.getNamedQuery(queryName);
				query.setFirstResult(startRecord);
				query.setMaxResults(endRecord);
				List list = query.list();
				return list;
			}
		});
	}

	public List getAllFeeds(final int startRecord, final int endRecord) {
		return getAllFeeds("getAllFeeds", startRecord, endRecord);
	}

	public List getAllFeedsAsc(int startRecord, int endRecord) {
		return getAllFeeds("getAllFeedsAsc", startRecord, endRecord);
	}

	public List getAllFeedsDec(int startRecord, int endRecord) {
		return getAllFeeds("getAllFeedsDec", startRecord, endRecord);
	}
}
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值