June 30, 2010

Initializing MyBatis on Application Startup using a ServletContextListener

In order to initialize MyBatis 3.0.1 and make it available to your java web application your can use a ServletContextListener to set the sqlSessionFactory as an application context attribute.

This way all your servlets will have access to this object once the web application is up. So here's how to do it.

1) Configuring the custom ServletContextListener

Add the listener definition to the web deployment descriptor (web.xml)

<listener>
  <listener-class>
com.mypackage.listeners.CustomServletContextListener
  </listener-class>
</listener>

Implement the listener

 
package com.mypackage.listeners;

import java.io.Reader;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class CustomServletContextListener implements ServletContextListener
{
 public void contextInitialized(ServletContextEvent event) 
 {   
  ServletContext ctx = event.getServletContext();  
       
     String resource = "mybatis.config.xml";
     try{
      //load mybatis configuration 
      Reader reader = Resources.getResourceAsReader(resource);      
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
      ctx.setAttribute("sqlSessionFactory", sqlSessionFactory);
     }
     catch(Exception e){
      System.out.println("FATAL ERROR: myBatis could not be initialized");
      System.exit(1);
     }     
 }

 @Override
 public void contextDestroyed(ServletContextEvent event){
  
 }
}

2) Retrieving the sqlSessionFactory from a Servlet

Now whenever you need a myBatis sqlSessionFactory, you can use the following code from any servlet

 

public class TestServlet extends HttpServlet{
      
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
  {
   SqlSessionFactory sf = (SqlSessionFactory)getServletContext().getAttribute("sqlSessionFactory");
   MyCrazyDAO dao = new MyCarzyDAO(sf); //you can use the sqlSessionFactory to initialize your dao layer
   
   try{
    dao.getSomeDataFromDB();
    request.setAttribute("abc", abc);         
   }
   catch(PersistenceException p){
    p.printStackTrace();  
   }
          
   RequestDispatcher view = request.getRequestDispatcher("page.jsp");
   view.forward(request, response); 
  }
}


Initializing multiple MyBatis environments/databases on application startup

If you have multiple databases (eg. defined multiple <environment> elements) in your mybatis.config.xml file (as shown below), you will need a minor change.

<environments default="development">
  
  <environment id="development">
   <transactionManager type="JDBC"/>
   <dataSource type="JNDI">
    <property name="initial_context" value="java:comp/env"/>
    <property name="data_source" value="/jdbc/mydb"/> 
   </dataSource>
  </environment>
  
  <environment id="testing">
   <transactionManager type="JDBC"/>
   <dataSource type="POOLED">
    <property name="driver" value="${db.driver}"/>
    <property name="url" value="${db.url}"/>
    <property name="username" value="${db.user}"/>
    <property name="password" value="${db.pass}"/>
   </dataSource>
  </environment>
  
 </environments>

The MyBatis reference says that you should use only one SqlSessionFactory instance per database.

Therefore the code ServletContextListener from before should be modified to create two independent sqlSessionFactory variables in application scope.

try{
      //load mybatis configuration 
      Reader reader = Resources.getResourceAsReader(resource);      
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); //will associate the session factory with the 'default' environment
      ctx.setAttribute("sqlSessionFactory", sqlSessionFactory);
      SqlSessionFactory sqlSessionFactory2 = new SqlSessionFactoryBuilder().build(reader,"testing");
      ctx.setAttribute("sqlSessionFactory2", sqlSessionFactory2);
     }
     catch(Exception e){
      System.out.println("FATAL ERROR: myBatis could not be initialized");
      System.exit(1);
     }  

You can now use two sqlSessionFactories to manipulate data from two databases. And you can reference them in any servlet using

SqlSessionFactory sf = (SqlSessionFactory)getServletContext().getAttribute("sqlSessionFactory");
SqlSessionFactory sf2 = (SqlSessionFactory)getServletContext().getAttribute("sqlSessionFactory2");