Posts Tagged ‘Spring’

Grafting Spring Transactions on Legacy Transaction Models

In another part of the continuing series on getting Spring to work properly with our legacy components, I recently had to revisit our the way were were handling transactions. In the previous post on this topic, I demonstrated how you can use a HandlerInterceptor to control your transactions. In this post, I demonstrate how you could use spring transactions, even if you can’t use the Spring way of configuring your Hibernate SessionFactory and need to support legacy code. The end result is much like putting a Ferrari body on a Pontiac Fiero, but it will accomplish the job.

The earlier solution has a significant drawback. The transaction will commit after the controller finished processing the request. Since Hibernate will not flush it’s session until a commit, all database errors will occur in a place where you can’t respond to them easily. Pushing the transaction boundary to the level below the controller (where it belongs anyway) will allow for correct error handling at the UI level. This can be accomplished with Spring Transactions.

Spring needs two things for @Transactional annotated code to do what you expect: the session factory and the transaction manager. Our legacy code used a statically initialized class to create a session factory. My first step was to manually create the HibernateTransactionManager with the correct session factory.

	sessionHolder = new HibernateUtilSessionHolder();
	SessionFactory sessionFactory = configuration.buildSessionFactory();
	sessionHolder.setSessionFactory(sessionFactory);
 
	HibernateTransactionManager htm = new HibernateTransactionManager(sessionFactory);
	sessionHolder.setTxManager(htm);

The sessionHolder is just a class I use to hold the current SessionFactory and TransactionManager. The sessionHolder is also the interface between the legacy code TransactionManager. Here is an example method from HibernateUtilSessionHolder:

    public Session getSession() {
        SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
        if (sessionHolder == null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("opening session");
            }
            Session s = sessionFactory.openSession();
            sessionHolder = new SessionHolder(s);
            TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
        }
        if (LOG.isDebugEnabled()) {
            LOG.debug("GETTING session");
        }
        return sessionHolder.getSession();
    }

Since I hide all the functionality of the TransactionManager behind the previous API, none of the legacy code is aware the underlying transaction model has changed and manual commits will work as they have done before.

The next trick is to get annotations working within the context of new code. Exposing the SessionFactory and TransactionManager to Spring is pretty easy. First, create two static methods to access what was created earlier:

    public PlatformTransactionManager getTransactionManagerInstance() {
        return sessionHolder.getTxManager();
    }
 
    public SessionFactory getSessionFactoryInstance() {
        return sessionHolder.getSessionFactory();
    }

Now expose them to the context by using Spring’s factory bean creation mechanism:

<bean id="hibernateUtil" class="net.vijedi.hibernate.util.HibernateUtil" />
 
<bean id="transactionManager" factory-bean="hibernateUtil" factory-method="getTransactionManagerInstance"/>
<bean id="sessionFactory" factory-bean="hibernateUtil" factory-method="getSessionFactoryInstance"/>

HIbernateUtil is the static class that created the SessionFactory and TransactionManager. The other two lines show how to use it as a factory.

Finally, all that’s left to do is to add

<tx :annotation-driven />

to the spring.xml file and mark up a bunch of code with @Transactional. It might be a Fiero underneath, but it still feels like a Ferrari.

  • Share/Bookmark

Integration testing Spring MVC Annotated Controllers

Annotations and POJO controllers make dead simple to unit test the web layer and ensure that the logic within it is correct. What’s not as clear is how to quickly (and automatically) test the configurations of your controllers and ensure the correct controller method is called with the correct parameters on a request.

After looking through the Spring MVC tests, it becomes apparent that you want to create a DispatcherServlet and send it requests. If the DispatcherServlet is initialized with the correct context, it will then behave just as it does in your web container. It will look at the request, find the correct handler, and make the appropriate controller call. I created three classes to help set up the environment.

public class MockWebContextLoader extends AbstractContextLoader {
 
    public static final ServletContext SERVLET_CONTEXT = new MockServletContext("/WebContent", new FileSystemResourceLoader());
 
    private final static GenericWebApplicationContext webContext = new GenericWebApplicationContext();
 
    protected BeanDefinitionReader createBeanDefinitionReader(final GenericApplicationContext context) {
        return new XmlBeanDefinitionReader(context);
    }
 
    public final ConfigurableApplicationContext loadContext(final String... locations) throws Exception {
 
        SERVLET_CONTEXT.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, webContext);
        webContext.setServletContext(SERVLET_CONTEXT);
        createBeanDefinitionReader(webContext).loadBeanDefinitions(locations);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(webContext);
        webContext.refresh();
        webContext.registerShutdownHook();
        return webContext;
    }
 
    public static WebApplicationContext getInstance() {
        return webContext;
    }
 
    protected String getResourceSuffix() {
        return "-context.xml";
    }
 
}

The MockWebContextLoader loads in the spring config locations and creates a WebContext . In order for this environment to mimic the one in your web container, you will need to pass in the same configs. I will show you how to do that later.

To help validate the success of a test, I’ve created another ViewResolver that just echoes the viewname into the response. You could have the ViewResolver return the correct view, but parsing that to gauge success seemed like too much of a headache.

public class TestViewResolver implements ViewResolver {
 
    public View resolveViewName(final String viewName, Locale locale) throws Exception {
        return new View() {
            public String getContentType() {
                return null;
            }
            @SuppressWarnings({"unchecked"})
            public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
                response.getWriter().write(viewName);
            }
        };
    }
 
}

Finally, I created an abstract class that would handle the creation of the DispatcherServlet for all tests that extend it. I’ve put my spring configuration files on the classpath that my test run it. If your configs are elsewhere, you will need to modify MockWebContextLoader to look in a different path.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=MockWebContextLoader.class, locations={"/classes/spring.xml", "/springmvc-servlet.xml"})
public abstract class AbstractControllerTestSupport {
 
    private static DispatcherServlet dispatcherServlet;
 
 
    @SuppressWarnings("serial")
    public static DispatcherServlet getServletInstance() {
        try {
            if(null == dispatcherServlet) {
                dispatcherServlet = new DispatcherServlet() {
                    protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
                        GenericWebApplicationContext wac = new GenericWebApplicationContext();
                        wac.setParent(MockWebContextLoader.getInstance());
                        wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class));
                        wac.refresh();
                        return wac;
                    }
                };
 
                dispatcherServlet.init(new MockServletConfig());
            }
        } catch(Throwable t) {
            Assert.fail("Unable to create a dispatcher servlet: " + t.getMessage());
        }
        return dispatcherServlet;
    }
 
    protected MockHttpServletRequest mockRequest(String method, String uri, Map<string ,String> params) {
        MockHttpServletRequest req = new MockHttpServletRequest(method, uri);
        for(String key : params.keySet()) {
            req.addParameter(key, params.get(key));
        }
        return req;
    }
 
    protected MockHttpServletResponse mockResponse() {
        return new MockHttpServletResponse();
    }
}
</string>

With this harness, it’s trivial to automatically check the configuration of the web controllers. Since the functionality is tested separately from the configuration, the problems can be isolated quicker, leaving more time to fix them.

  • Share/Bookmark
Return top

About

This is my blog about programming. For random stuff, checkout my Twitter or Tumblr