Spring
From Guides
Contents |
Hibernate
Sample applicationContext.xml
I'm posting a sample applicationContext.xml here which combines spring2 and hibernate3. The database I use is in this example is an HSQL DB.
I run the database using this command (windows, but linux is pretty much the same):
start java
-cp "c:\path-to-your-hsqldb-jar\hsqldb-1.8.0.7.jar" org.hsqldb.Server
-database.0 mydb
-dbname.0 xdb
Example with hibernate mapping files
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- Create a local session factory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<!-- Map all *.hbm.xml files in pojo dir to the session factory -->
<property name="mappingDirectoryLocations">
<list>
<value>classpath:/com/myapp/pojo</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.cglib.use_reflection_optimizer">true</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
<prop key="hibernate.c3p0.min_size">5</prop>
<prop key="hibernate.c3p0.max_size">20</prop>
<prop key="hibernate.c3p0.timeout">1800</prop>
<prop key="hibernate.c3p0.max_statements">50</prop>
</props>
</property>
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
<!-- Create the DataSource we will be using -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>org.hsqldb.jdbcDriver</value>
</property>
<!-- xdb is defined by running the hsqldb as xdb (see above) -->
<property name="url">
<value>jdbc:hsqldb:hsql://localhost/xdb</value>
</property>
<property name="username">
<value>sa</value>
</property>
<property name="password">
<value></value>
</property>
</bean>
<!-- Specify the Hibernate Transaction Manager -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref local="sessionFactory" />
</property>
</bean>
<!-- One example dao -->
<bean id="userDao"
class="com.myapp.dao.impl.UserDaoImpl">
<property name="sessionFactory">
<ref local="sessionFactory" />
</property>
</bean>
</beans>
Example with annotations
Please note that the following example has placeholders which I use in combination with maven2. Please substitute with your own values.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- Create a local session factory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="annotatedPackages">
<list>
<!-- Map all files in classpath to the session factory -->
<value>com.famvdploeg.forum.pojo</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.query.substitutions">true 1, false 0</prop>
<prop key="hibernate.dialect">${maven.hibernate.dialect}</prop>
<!-- <prop key="hibernate.hbm2ddl.auto">create</prop> -->
<prop key="hibernate.show_sql">${maven.hibernate.show_sql}</prop>
<prop key="hibernate.bytecode.use_reflection_optimizer">true</prop>
<prop key="hibernate.c3p0.min_size">5</prop>
<prop key="hibernate.c3p0.max_size">20</prop>
<prop key="hibernate.c3p0.timeout">1800</prop>
<prop key="hibernate.c3p0.max_statements">50</prop>
</props>
</property>
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>${maven.hibernate.jdbcdriver}</value>
</property>
<property name="url">
<value>${maven.hibernate.jdbcurl}</value>
</property>
<property name="username">
<value>${maven.hibernate.jdbcusername}</value>
</property>
<property name="password">
<value>${maven.hibernate.jdbcpassword}</value>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref local="sessionFactory" />
</property>
</bean>
<!-- Interceptor for hibernate calls to be able to create and close sessions -->
<bean id="hibernateInterceptor" class="org.springframework.orm.hibernate3.HibernateInterceptor">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
</beans>
Unit Testing
To be able to test the DAO Classes with transaction rollbacks I created a base class which the other classes can extend. It utilizes the org.springframework.test.AbstractTransactionalDataSourceSpringContextTests class which will do the basic setup. You could also define getters for you services or dao's here as convienence methods if you like.
Here is the base code, the only thing I do is specify the config locations. The rest is done by the superclass.
import org.springframework.context.ApplicationContext;
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
public class BaseTestSpringHibernate extends AbstractTransactionalDataSourceSpringContextTests{
protected String[] getConfigLocations() {
return new String[]{ "applicationContext.xml" };
}
}
Here is an example of a basic JUnit test case:
import org.springframework.context.ApplicationContext;
import com.myapp.dao.UserDao;
import com.myapp.pojo.User;
import junit.framework.TestCase;
public class UserDaoTest extends BaseTestSpringHibernate{
public void testContext(){
UserDao dao = (UserDao) getApplicationContext().getBean("userDao");
User user = new User();
user.setFirstname("Firstname");
user.setLastname("Lastname");
dao.saveUser(user);
user = dao.getUserById(new Long(user.getId()));
assertNotNull("This User object should not be null", user);
}
}
OpenAMF
I updated this entry because in my personal opinion you should try to avoid OpenAMF at this point. Since Adobe released BlazeDS as a framework for Java/ActionScript serialization I recommend using it for Serializing objects.
I will post the steps to integrate OpenAMF in your web application here.
- Download the OpenAMF zip file from the OpenAMF website.
- Unpack the openamf.jar into your /WEB-INF/lib folder.
- Unpack the dependencies of openamf.jar into your /WEB-INF/lib folder.
Although you won't need them all it is best to just place them there to avoid any errors. - Configure your /WEB-INF/web.xml, this is one way. In the next part I will discuss loading the servlet from spring. (I prefer the last method)
Basic Config - Spring and OpenAMF loaded seperately
Note: Using the DefaultGateway will allow you to call any java class! To limit this use the servlet-class: org.openamf.AdvancedGateway!!!
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="2.4"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http:/java.sun.com/dtd/web-app_2_4.dtd">
<!-- Which config files does Spring need to load -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/config/spring.context.xml
/WEB-INF/config/spring.dao.xml
/WEB-INF/config/spring.service.xml
</param-value>
</context-param>
<!-- Configure OpenAMF Gateway servlet -->
<servlet>
<servlet-name>DefaultGateway</servlet-name>
<display-name>DefaultGateway</display-name>
<description>DefaultGateway</description>
<servlet-class>org.openamf.DefaultGateway</servlet-class>
<init-param>
<param-name>OPENAMF_CONFIG</param-name>
<param-value>/WEB-INF/openamf-config.xml</param-value>
<description>Location of the OpenAMF config file.</description>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- URL mapping for the OpenAMF Gateway -->
<servlet-mapping>
<servlet-name>DefaultGateway</servlet-name>
<url-pattern>/gateway</url-pattern>
</servlet-mapping>
<!-- Listener for Spring -->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
</web-app>
Embed the OpenAMF Gateway in Spring
Since I am using spring together with hibernate I noticed I got some lazy initalization exceptions when passing actionscript objects back to my flex 2 application. So I wrapped the OpenAMF advanced gateway with Spring. I did so in the following manner:
- Made some changes to the web.xml by adding the SpringDispatcher and removing the OpenAMF servlet. It now looks like this:
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="2.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http:/java.sun.com/dtd/web-app_2_4.dtd"> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/classes/config/spring.context.xml /WEB-INF/classes/config/spring.dao.xml /WEB-INF/classes/config/spring.service.xml </param-value> </context-param> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/gateway</url-pattern> </servlet-mapping> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> </web-app>
- Created a spring-servlet.xml which I placed in the /WEB-INF directory. This is loaded because the servlet name is 'spring' which is used when loading the xml file. If your servlet was called 'somethingelse' spring would try to look for the file /WEB-INF/somethingelse-servlet.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <!-- Open Session In View Interceptor --> <bean id="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor"> <property name="sessionFactory"> <ref bean="sessionFactory" /> </property> </bean> <!-- Servlets and Mappings --> <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="interceptors"> <list> <ref local="openSessionInViewInterceptor" /> </list> </property> <property name="mappings"> <props> <prop key="/gateway">openAMFController</prop> </props> </property> </bean> <!-- Load the OpenAMF Gateway Controller --> <bean id="openAMFController" class="org.springframework.web.servlet.mvc.ServletWrappingController"> <property name="servletClass"> <value>org.openamf.AdvancedGateway</value> </property> <property name="servletName"> <value>gateway</value> </property> <property name="initParameters"> <props> <prop key="OPENAMF_CONFIG"> /WEB-INF/openamf-config.xml </prop> </props> </property> </bean> </beans>
You might need to modify your openamf-config.xml in the /WEB-INF directory. Look at the examples in there and define the services and methods which can be called from the gateway.
I use the OpenAMF SpringBeanInvoker for this purpose. Here is a short sample which will allow all calls on the media service. The name will need to match the bean's id in the spring config file.
<!-- Media Service --> <service> <name>mediaService</name> <service-location>mediaService</service-location> <invoker-ref>Spring</invoker-ref> <method> <name>*</name> <parameter> <type>*</type> </parameter> </method> </service>
Modifying OpenAMF for Spring Hibernate setup
If you want to use Spring and Hibernate under a OpenAMF setup I recommend you have a look at Granite Data Services. You can view the project on this page GraniteDS. I think I will migrate to this since Hibernate under OpenAMF is starting to give me a real headache.
Because I already wrote quite a lot of code on top of OpenAMF I thought it would be nicer to make some adaptions so OpenAMF was more Spring/Hibernate ready. I've extended the AdvancedGateway and AMFSerializer classes and replaced them with my own.
If you want to use them you should replace your class entry in the spring-servlet.xml with this CustomGateway class. ( <value>org.openamf.AdvancedGateway</value> should be replaced with <value>your.package.openamf.gateway.CustomGateway</value> )
Here are the classes:
CustomGateway class
package your.package.openamf.gateway;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.openamf.AMFMessage;
import org.openamf.AdvancedGateway;
import your.package.openamf.io.CustomAMFSerializer;
public class CustomGateway extends AdvancedGateway{
private static final long serialVersionUID = -3149933184426361462L;
/* (non-Javadoc)
* @see org.openamf.DefaultGateway#serializeAMFMessage(javax.servlet.http.HttpServletResponse, org.openamf.AMFMessage)
*/
protected void serializeAMFMessage(HttpServletResponse resp, AMFMessage message) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
CustomAMFSerializer serializer = new CustomAMFSerializer(dos);
serializer.serializeMessage(message);
resp.setContentType("application/x-amf");
resp.setContentLength(baos.size());
ServletOutputStream sos = resp.getOutputStream();
baos.writeTo(sos);
sos.flush();
}
}
The problem with the default serializer was that it was using reflection on the incoming object rather than seeing which properties ( and thus corresponding setters/getters ) were available in the original class. This proved to be a problem when Hibernate returned objects which were created by CGLib.
CustomAMFSerializer class
package your.package.openamf.io;
import java.beans.PropertyDescriptor;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import org.apache.commons.beanutils.PropertyUtils;
import org.openamf.AMFBody;
import org.openamf.config.OpenAMFConfig;
import org.openamf.io.AMFSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CustomAMFSerializer extends AMFSerializer {
private final static Logger logger = LoggerFactory.getLogger(CustomAMFSerializer.class);
private static HashMap classProperties = new HashMap();
public CustomAMFSerializer(DataOutputStream arg0) {
super(arg0);
}
/* (non-Javadoc)
* @see org.openamf.io.AMFSerializer#writeObject(java.lang.Object)
*/
protected void writeObject(Object object) throws IOException {
String classname = object.getClass().getName();
PropertyDescriptor[] properties;
if( logger.isDebugEnabled() ){
logger.debug("Serializing object of class: " + classname);
}
int index = classname.toLowerCase().indexOf("$$enhancerbycglib");
//Enhanced by CGLib. Don't call property setters/getters of the enhanced class so perform a smart call
if( index != -1 ){
String propertyClassname = classname.substring(0, index);
//already in map?
if( classProperties.containsKey(propertyClassname) ){
//then load the properties from memory off course :)
properties = (PropertyDescriptor[]) classProperties.get(propertyClassname);
}
else{
//else load the class get the properties and store them in the map
try {
//load the class
Class clazz = object.getClass().getClassLoader().loadClass(propertyClassname);
//get properties of new object
properties = PropertyUtils.getPropertyDescriptors(clazz);
//store the properties in the map
classProperties.put(propertyClassname, properties);
} catch (ClassNotFoundException e) {
logger.error(e.getMessage(), e);
properties = PropertyUtils.getPropertyDescriptors(object);
}
}
}
else{
if( !classProperties.containsKey(classname) ){
//get properties of object.
properties = PropertyUtils.getPropertyDescriptors(object);
//and store them
classProperties.put(classname, properties);
}
else{
properties = (PropertyDescriptor[]) classProperties.get(classname);
}
}
outputStream.writeByte(AMFBody.DATA_TYPE_OBJECT);
try {
for (int i = 0; i < properties.length; i++) {
if (!properties[i].getName().equals("class")) {
String propertyName = properties[i].getName();
Method readMethod = properties[i].getReadMethod();
Object propertyValue = null;
if (readMethod == null) {
logger.error(
"unable to find readMethod for : "
+ propertyName
+ " writing null!");
} else {
logger.debug("invoking readMethod " + readMethod);
propertyValue =
readMethod.invoke(object, new Object[0]);
}
logger.debug(propertyName + " = " + propertyValue);
outputStream.writeUTF(propertyName);
writeData(propertyValue);
}
}
outputStream.writeShort(0);
outputStream.writeByte(AMFBody.DATA_TYPE_OBJECT_END);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new IOException(e.getMessage());
}
}
}
