Dashboard > HOW-TOS > Home > Spring, Struts and OJB
HOW-TOS Log In   View a printable version of the current page.
Spring, Struts and OJB
Added by Jason McKerr, last edited by K Lars Lohn on Jul 26, 2005  (view change)
Labels: 
(None)

Getting Started with Spring and OJB

Part 1 Part 2 Part 3

I'm going to do a couple of short articles on integrating Spring with OJB (and Apache Struts, although that is somewhat incidental). I'm actually going to do three of these, all using the same application. The application that I am using is Matt Raible's AppFuse MyUsers application. My app can be found here. Matt's can be found here I have taken Matt's application, which already supports OJB and simplified it quite a bit for newbies. Here are some changes that I made:

  • I removed all the other DataAccess implementations. There's no extra code in here for Hibernate, EJB, iBatis or whatever.
  • Matt used two different Interfaces (UserManager and UserDAO) for his persistence and Data Access Objects (DAO) objects. That means that his access strategy object (UserDAOOjb) implemented UserDAO and his service objects (UserManagerImpl) implemented the UserManager Interface: This despite the fact that UserManager and UserDAO are essentially the same object. I felt it better if both objects implement the same interface for simplicity's sake.

To get the app to run in tomcat:

  • Download it from here: here
  • unzip into your webapps directory and REMOVE THE WAR FROM THE WEBAPPS DIRECTORY
  • If you are starting the application from the command line, move the myusers/db folder to the directory from which you are starting tomcat (likely <tomcat-home>/bin).
  • Start Tomcat by going to <tomcat-root>/bin and running catalin.sh run or catalina.bat run.
  • With a browser go to http://localhost:8080/myusers

I also have three different applications that I'm going to post. The first is the simplest form of using a data access strategy (OJB, Hibernate, EJB) with Spring declarative transactions. The code-methodology for calls to the database are implemented within the DAO object. That way you don't have to figure out hiow to connect a facade-style DAO to the OJB DAO to the implementing classes. In simpler terms, calls made to the UserDAO Interface are acting upon the UserDAOOjb (UserDAO --> UserDAOOjb) instead of UserDAO --> UserDAOImpl --> UserDAOOjb.

So the first application combines UserDAOImpl with UserDAOOjb while the second will be UserDAO --> UserDAOImpl --> UserDAOOjb. The first one is to show the simple way...The second is to show a flexible system that allows your DAO implementations to be independent of any data access strategy like OJB. Dig it. The third installment will just clean some other issues up, like using JNDI based datasources, different caching, all that smokey goodness.

OK, let's start. The first thing to do is to download the application from above. This application is a full WAR file with the source in the same directories as the class files. So no separate source directory (I was feeling lazy). The database is embedded HSQL, so you won't have to do anything else other than the instructions above to get this working.

Implementation Library Requirements:

  • Struts jars (1.1)
  • Spring Jars (1.2.1)
  • OJB Jars (1.0.3)

Implementation File Requirements:

Now to nitty...

  • OJB.properties: According to Robert Sfeir's blog (and he's right), you gotta make some changes to this file (I've changed them already, but for reference):
    1. Change ConnectionFactoryClass=org.apache.ojb.broker.accesslayer.ConnectionFactoryPooledImpl to ConnectionFactoryClass=org.springframework.orm.ojb.support.LocalDataSourceConnectionFactory
    2. Change the location of the repository file appropriately (repositoryFile property).
    3. Note: Robert actually has other changes, but those were for an earlier version of OJB. So don't sweat it.
  • Repository.xml: You need to do the usual stuff in repository for the most part. But key to this is:
    1. Notice that there is no database connection information in my repository's jdbc-connection-descriptor. However, It's now defined in your applicationContext.xml.
    2. Also notice, the Default Cache from OJB will not work with the current setup. In the third article in this set, I will show how to get back to normal JNDI and Caching setups. Use the PerBroker cache.
    ?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE descriptor-repository PUBLIC 
     "-//Apache Software Foundation//DTD OJB Repository//EN" "repository.dtd">
    
    <descriptor-repository version="1.0" isolation-level="read-uncommitted">
      <jdbc-connection-descriptor 
       jcd-alias="dataSource" default-connection="true" 
       platform="Hsqldb" jdbc-level="3.0" useAutoCommit="1">
    
        <object-cache class="org.apache.ojb.broker.cache.ObjectCachePerBrokerImpl">
        </object-cache>
    		
        <connection-pool maxActive="10" maxIdle="2" maxWait="3" validationQuery="" 
         logAbandoned="true" removeAbandoned="true" removeAbandonedTimeout="8"/>
    	
        <sequence-manager className="org.apache.ojb.broker.util.sequence.SequenceManagerNativeImpl">
        </sequence-manager>
      </jdbc-connection-descriptor>
    	
      <class-descriptor class="org.appfuse.model.User" table="app_user">
        <field-descriptor name="id" column="id" primarykey="true" autoincrement="true" access="readonly"/>
        <field-descriptor name="firstName" column="first_name"/>
        <field-descriptor name="lastName" column="last_name"/>
      </class-descriptor>
    </descriptor-repository>
  • applicationContext.xml: This is the place where you register a bunch of important stuff with Spring to tell it how to handle database connections, OJB persistence, beans, transactions, all that goodness. \\\\
    1. The first section, "dataSource" defines your database connection information. Make sure that your bean id="dataSource" matches jcd-alias="dataSource" that you have in your OJB-repository.xml file.
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName">
        <value>org.hsqldb.jdbcDriver</value>
      </property>
      <property name="url">
        <value>jdbc:hsqldb:db/appfuse</value>
      </property>
      <property name="username">
        <value>sa</value>
      </property>
      <property name="password">
        <value></value>
      </property>
    </bean>

    2. Next you have the OJBConfigurer line. You only use this if you're NOT using JNDI datasource lookups.
    <bean id="ojbConfigurer" class="org.springframework.orm.ojb.support.LocalOjbConfigurer"/>

    3. Next you setup the TransactionManager with your implementation. In this case:
    <bean id="transactionManager" class="org.springframework.orm.ojb.PersistenceBrokerTransactionManager"/>

    4. NOTE: I have only done limited work with Spring/OJB together. I have no idea how other OJB strategies, like JDO will work.\\\\
    5. Now we get to specifics: With the next line:
    <bean id="userManagerTarget" class="org.appfuse.dao.UserDAOOjbImpl"/>
    you are registering the target bean of a transaction. You haven't actually told Spring anything important yet. All you've done is told it that this bean now exists. This is the object that implements the Interface that you are going to program to. That way, when you do program to the interface, Spring understands what implementation to use. I will show this in action later. \\\\
    6. Now, you tie a transactional API to the object registered in the last step:
    <bean id="userManager" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
      <property name="transactionManager">
        <ref local="transactionManager">
      </property>
      <property name="target">
        <ref local="userManagerTarget">
      </property>
      <property name="transactionAttributes">
        <props>
          <prop key="save*">PROPAGATION_REQUIRED</prop>
          <prop key="remove*">PROPAGATION_REQUIRED</prop>
          <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
        </props>
      </property>
    </bean>

    So what you've now done is:
    • Told the userManager bean to use Spring's TransactionProxyFactoryBean for intercepting transactions.
    • Told the bean to use the proper transactionManager as registered above.
    • Told it what bean the transaction stuff is going to be operating upon... UserDAOOjbImpl
    • Told it what the transaction attributes should be for particular method signatures.
  • Now you've got the setup for OJB, and you can see the implementation code in the application file. What we haven't done is told the application layer how to invoke this beastie...So despite the length of this article, we are not done. Get back to work slacker...
  • So we've told spring how to register the beans, and given it all the information regarding transactional contexts, but we haven't yet tied Struts to Spring (If you're using Webwork, or Spring MVC please refer to docs).
    1. The first thing to do is to to plug Spring into Struts. There is a plugin config for that. The following code block goes in the plugin section of your struts-config.xml file (see included for full sample):
    <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
      <set-property property="contextConfigLocation"
       value="/WEB-INF/applicationContext.xml,/WEB-INF/action-servlet.xml"/>
    </plug-in>

    This basically tells Struts to work with spring, and oh by the way, the location of the Spring config files.
    2. Now delegate your Struts action to Spring in the mappings section of your struts-config.xml.
    <action path="/user" type="org.springframework.web.struts.DelegatingActionProxy" 
     name="userForm" scope="request" parameter="method" validate="false">
      <forward name="list" path="/userList.jsp"/>
      <forward name="edit" path="/userForm.jsp"/>
    </action>

    Note that in type, you'd normally give Struts your Action class, but here, you tell it to delegate to Spring. In the step before, you basically told Spring/Struts to look in /WEB-INF/action-servlet.xml for the information about the delegation steps to be done.
    3. Now, in action-servlet.xml you connect all of these dots.
    • You register an action and give it the Struts action class that you normally would have put into the type property in your Struts mapping.
      <bean name="/user" class="org.appfuse.web.UserAction" singleton="false">
        <property name="userManager"><ref bean="userManager"/></property>
      </bean>
    • With the <property name="userManager"> element, you tell it that within the action class, there is a property (setter method) that meets the criteria for setUserManager. In the action class, you can see that I have
      private UserDAO mgr = null;
      
      public void setUserManager(UserDAO userManager) {
        this.mgr = userManager;
      }
    • Lastly, with the <ref bean="userManager"/> as a value within the property element, you have told the Spring framework to take the userManager (which in turns uses the UserDAOOjbImpl object) that you defined in applicationContext.xml and set the setUserManager accessor in your UserAction class.

So the sequence goes something like this:

  • In your web page you hit a user.do action (a link or form action)
  • Struts looks up the action in struts-config.xml
  • Struts knows now to delegate the action to Spring, and use Spring's config files applicationContext.xml and action-servlet.xml
  • Now Spring is basically looking at the action-servlet.xml file for an action that looks like /user.
  • Once Spring finds that action, it associates the action with a Class to use, UserAction.
  • Spring notices that there is a property that it needs to set in the UserAction class, and that the value of the property is the userManager bean that was registered in applicationContext.xml
  • It then looks in applicationContext.xml
  • It finds the userManager object, and understands that:
    • The transactionManager is org.springframework.orm.ojb.PersistenceBrokerTransactionManager
    • The target is an implementation class that implements the Interface that you are programming (org.appfuse.dao.UserDAOOjbImpl) by referencing userManagerTarget.
    • It checks the transactionAttributres to find out how to manage transactions for certain methods.
  • Now you can do actions in your Struts Action class that operate on the UserDAO interface which is an instance of UserDAOOjbImpl.
    For example:
    public ActionForward save(ActionMapping mapping, ActionForm form,
    			HttpServletRequest request, HttpServletResponse response)
    			throws Exception {
      if (log.isDebugEnabled()) {
        log.debug("entering 'save' method...");
      }
    
      ActionMessages errors = form.validate(mapping, request);
      if (!errors.isEmpty()) {
        saveErrors(request, errors);
        return mapping.findForward("edit");
      }
    
      DynaActionForm userForm = (DynaActionForm) form;
      mgr.saveUser((User) userForm.get("user")); //DO SOMETHING WITH INTERFACE HERE
    
      ActionMessages messages = new ActionMessages();
      messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("user.saved"));
      saveMessages(request, messages);
      
      return list(mapping, form, request, response);
    }

OK, that's it. I'm tired. Second installment to come later. Post comments if you have problems.


Jason McKerr
The Open Source Lab
"Open Minds. Open Doors. Open Source"

Hi,

I followed the way you write this tutorial to my own example codes. It seems to be working i.e inserting data to the database. However, I get this worrying exception everytime the PB api is invoked even though I have used org.springframework.transaction.interceptor.TransactionProxyFactoryBean to demarcate the transaction.Can anyone help?

persistenceBrokerTemplate.store(product);

exception:
java.lang.Exception: ** Try to store object without active PersistenceBroker transaction **
at org.apache.ojb.broker.core.PersistenceBrokerImpl.store(PersistenceBrokerImpl.java:788)
at org.apache.ojb.broker.core.PersistenceBrokerImpl.store(PersistenceBrokerImpl.java:726)
at org.apache.ojb.broker.core.DelegatingPersistenceBroker.store(DelegatingPersistenceBroker.java:175)
at org.apache.ojb.broker.core.DelegatingPersistenceBroker.store(DelegatingPersistenceBroker.java:175)
at org.springframework.orm.ojb.PersistenceBrokerTemplate$9.doInPersistenceBroker(PersistenceBrokerTemplate.java:237)
at org.springframework.orm.ojb.PersistenceBrokerTemplate.execute(PersistenceBrokerTemplate.java:137)
at org.springframework.orm.ojb.PersistenceBrokerTemplate.store(PersistenceBrokerTemplate.java:235)
at com.fedex.cefam.dao.ProductDaoImpl2.add(ProductDaoImpl2.java:12)
at com.fedex.cefam.business.ProductMgr.addProduct(ProductMgr.java:31)
at com.fedex.cefam.business.ProductMgrTest.testAddProduct(ProductMgrTest.java:28)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at junit.framework.TestCase.runTest(TestCase.java:154)
at junit.framework.TestCase.runBare(TestCase.java:127)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:118)
at junit.framework.TestSuite.runTest(TestSuite.java:208)(cache.CacheDistributor 242 )
<====
Setup new object cache instance on CONNECTION LEVEL for
PersistenceBroker: org.apache.ojb.broker.core.PersistenceBrokerImpl@676437
descriptorBasedCache: false
Connection jcdAlias: dataSource
Calling class: class com.fedex.cefam.entity.ProductObjectCache: org.apache.ojb.broker.metadata.ObjectCacheDescriptor@1f297e7[ObjectCache=class org.apache.ojb.broker.cache.ObjectCachePerBrokerImpl,Properties={}]
====>
(cache.CacheDistributor 281 ) Specified cache class org.apache.ojb.broker.cache.ObjectCachePerBrokerImpl does not implement interface org.apache.ojb.broker.cache.ObjectCacheInternal and will be wrapped by a helper class

at junit.framework.TestSuite.run(TestSuite.java:203)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:478)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:344)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
(END EXCEPTION)

Posted by Anonymous at Oct 13, 2005 20:16 | Permalink
Site powered by a free Open Source Project / Non-profit License (more) of Confluence - the Enterprise wiki.
Learn more or evaluate Confluence for your organisation.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.2.7 Build:#524 Jul 28, 2006) - Bug/feature request - Contact Administrators