Over a million developers have joined DZone.

Caching Over MyBatis : OSCache Performance Tuning Over MyBatis

Read this eGuide to discover the fundamental differences between iPaaS and dPaaS and how the innovative approach of dPaaS gets to the heart of today’s most pressing integration problems, brought to you in partnership with Liaison.

This article represents the fourth Proof of Concept from series described in the previous  article 4 Hands-On Approaches to Improve Your Data Access Layer Implementation and it presents how to implement OSCache over MyBatis, how to achieve an optim configuration for it and personal opinions of the author about the chosen approach for the Data Access Layer.

OSCache  is a Java framework  that eases caching content in web applications that can be downloaded free of charge from https://java.net/projects/oscache; the project belonged was started in 2011 as a subproject of openSymphony and it was maintained until openSymphony dissapearance. Throughout this article, the following aspects will be addressed:

1. How will an application benefit from caching using OSCache? OSCache's features will be detailed in this section.

2. Hands-on implementation of the OSCachePOC project - in this section the key concepts of OSCache will be explored through a hands on implementation.

3. Summary - How has the application performance been improved after this implementation?

Code of all the projects that will be implemented can be found at https://github.com/ammbra/CacherPoc   or if you are interested only in the current implementation, you can access it here : https://github.com/ammbra/CacherPoc/tree/master/OSCachePOC 

How will an application benefit from caching using OSCache?

While some OpenSymphony projects continue elsewhere because they have been picked up  by external parties, OSCache has not been so fortunate. I managed to gather some informations about the library from google and it seems that:

  • OSCache is a caching solution that includes set of classes to perform fine grained dynamic caching of JSP content, servlet responses or arbitrary objects.
  • OSCache provides in memory and persistent on disk caches.
  • OSCache can allow your site to have error tolerance (for example,  if an error occurs like, let's say  your database becomes unavailable, you can serve the cached content so people can still surf the site almost without knowing).

Given the small amount of informations available about it, let's test OSCache capabilities on our Proof of Concept project.

Hands-on implementation of the OSCachePOC project

Our implementation of OSCachePOC will look as described in the diagram below:


In order to test OSCache performance the following project setup is performed:

1. Create a new Maven EJB Project from your IDE (this kind of project is platform provided by NetBeans but for those that use eclipse, here is an usefull tutorial http://theopentutorials.com/examples/java-ee/ejb3/how-to-create-a-ejb-3-x-project-using-maven-in-eclipse-part-2/ ). In the article this project is named OSCachePOC.

2. Edit your project's pom by adding required jars :

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.2.6</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-oscache</artifactId>
            <version>1.0.1</version>
        </dependency>
        <dependency>
            <groupId>opensymphony</groupId>
            <artifactId>oscache</artifactId>
            <version>2.4.1</version>
            <exclusions>
                <exclusion>
                    <artifactId>jms</artifactId>
                    <groupId>javax.jms</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>     

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.5</version>
        </dependency> 

3. Add your database connection driver, in this case apache derby:

        <dependency>
            <groupId>org.apache.derby</groupId>
            <artifactId>derbyclient</artifactId>
            <version>10.11.1.1</version>
        </dependency>

4. Run mvn clean and mvn install commands on your project.

If you have your project in place, let's go ahead with our implementation of MyBatis :

1. Configure under resources/com/tutorial/OSCachePOC/xml folder the Configuration.xml file with :

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="UNPOOLED">
                <property name="driver" value="org.apache.derby.jdbc.ClientDriver"/>
                <property name="url" value="jdbc:derby://localhost:1527/CRUD"/>
                <property name="username" value="cruddy"/>
                <property name="password" value="cruddy"/>
            </dataSource>
        </environment>         
    </environments>
    <mappers>               
        <mapper resource="com/tutorial/oscachepoc/xml/EmployeeMapper.xml" /> 
    </mappers>                   
</configuration>

2. Create in java your own SQLSessionFactory implementation. For example, create  com.tutorial.OSCachepoc.config. SQLSessionFactory

public class SQLSessionFactory {

 
    private static final SqlSessionFactory FACTORY;
 
    static {
        try {
            Reader reader = Resources.getResourceAsReader("com/tutorial/oscachepoc/xml/Configuration.xml");
            FACTORY = new SqlSessionFactoryBuilder().build(reader);
        } catch (Exception e){
            throw new RuntimeException("Fatal Error.  Cause: " + e, e);
        }
    }
 
    public static SqlSessionFactory getSqlSessionFactory() {
        return FACTORY;
    }
}

3. Create your necessary bean classes, the ones that will map to your sql results, like Employee:

public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;
    private Integer id;
    private String firstName;
    private String lastName;
    private String adress;
    private Date hiringDate;
    private String sex;
    private String phone;
    private int positionId;
    private int deptId;

    public Employee() {
    }

    public Employee(Integer id) {
        this.id = id;
    }


    @Override
    public String toString() {
        return "com.tutorial.oscachepoc.bean.Employee[ id=" + id + " ]";
    }
}

4. Create the IEmployeeDAO interface that will expose the ejb implementation when injected:

public interface IEmployeeDAO {
        public List<Employee> getEmployees();   
}

5. Implement the above inteface:

@Stateless(name="oscacheDAO")
@TransactionManagement(TransactionManagementType.CONTAINER)
public class EmployeeDAO implements IEmployeeDAO {

    private static Logger logger = Logger.getLogger(EmployeeDAO.class);
    private SqlSessionFactory sqlSessionFactory;

   @PostConstruct
     public void init() {
        sqlSessionFactory = SQLSessionFactory.getSqlSessionFactory();
    }


    @Override
    public List<Employee> getEmployees() {
        logger.info("Getting employees with oscache.....");
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<Employee> results = sqlSession.selectList("retrieveEmployees");
        sqlSession.close();
        return results;
    }
}

6. Create the EmployeeMapper that contains the query named "retrieveEmployees"

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.tutorial.oscachepoc.mapper.EmployeeMapper" >
     
    <resultMap id="results" type="com.tutorial.oscachepoc.bean.Employee" >
        <id column="id" property="id" javaType="integer" jdbcType="BIGINT" />
        <result column="first_name" property="firstName" javaType="string" jdbcType="VARCHAR"/>
        <result column="last_name" property="lastName" javaType="string" jdbcType="VARCHAR"/>
        <result column="adress" property="adress" javaType="string" jdbcType="VARCHAR" />
        <result column="hiring_date" property="hiringDate" javaType="date" jdbcType="DATE" />
        <result column="sex" property="sex" javaType="string" jdbcType="VARCHAR" />
        <result column="dept_id" property="deptId" javaType="integer" jdbcType="BIGINT" />
    </resultMap>     
<select id="retrieveEmployees" resultMap="results" >
        select id, first_name, last_name, hiring_date, sex, dept_id
        from employee   
    </select>  	
</mapper>																													

If you remember our CacherPOC setup from the previously article, then you can test your implementation if you add OSCachePOC project as dependency and inject the IEmployeeDAO inside the OSCacheServlet. Your CacherPOC pom.xml file should contain :

      <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>OSCachePoc</artifactId>
            <version>${project.version}</version>
        </dependency>																															

and your servlet should look like:

@WebServlet("/OsCacheServlet")
public class OsCacheServlet extends HttpServlet {
   private static Logger logger = Logger.getLogger(OsCacheServlet.class);

    @EJB(beanName="oscacheDAO")
    IEmployeeDAO employeeDAO;
    private static final String LIST_USER = "/listEmployee.jsp";

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String forward= LIST_USER;
        List<Employee> results = new ArrayList<Employee>();
         for (int i = 0; i < 10; i++) {
            for (Employee emp : employeeDAO.getEmployees()) {
                logger.debug(emp);
                results.add(emp);
            }
            
            try {
                Thread.sleep(3000);
            } catch (Exception e) {
                logger.error(e, e);
            }
        }
        req.setAttribute("employees", results);
        RequestDispatcher view = req.getRequestDispatcher(forward);
        view.forward(req, resp);
    }
    
}

Run your CacherPoc implementation to check if your Data Access Layer with MyBatis is working or download the code provided at https://github.com/ammbra/CacherPoc 

But if a great amount of employees is stored in database, or perhaps the retrieval of a  number of 10xemployeesNo represents a lot of workload for the database. Also, can be noticed that the query from the EmployeeMapper.xml retrieves data that almost never changes (id, first_name, last_name, hiring_date, sex cannot change; the only value that might change in time is dept_id); so a caching mechanism can be used.

Below is described how this can be achieved using OSCache:

Caching Step 1.  Add oscache.properties  file under resources folder and try a similar configuration for it (if this file is not defined,  if not found, the client will use the default settings):

cache.capacity=1000
cache.memory=true
cache.algorithm=com.opensymphony.oscache.base.algorithm.LRUCache

the above configuration states that cache is set to store maximum 1000 values in memory using Least Recently Used (LRU) algorithm for eviction.

Caching Step 2. Update your  EmployeeMapper.xml to use the previous implemented caching strategy:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.tutorial.oscachepoc.mapper.EmployeeMapper" >
   
    <cache type="org.mybatis.caches.oscache.OSCache"/>
     
    <resultMap id="results" type="com.tutorial.oscachepoc.bean.Employee" >
        <id column="id" property="id" javaType="integer" jdbcType="BIGINT" />
        <result column="first_name" property="firstName" javaType="string" jdbcType="VARCHAR"/>
        <result column="last_name" property="lastName" javaType="string" jdbcType="VARCHAR"/>
        <result column="adress" property="adress" javaType="string" jdbcType="VARCHAR" />
        <result column="hiring_date" property="hiringDate" javaType="date" jdbcType="DATE" />
        <result column="sex" property="sex" javaType="string" jdbcType="VARCHAR" />
        <result column="dept_id" property="deptId" javaType="integer" jdbcType="BIGINT" />
    </resultMap> 
 
    <select id="retrieveEmployees" resultMap="results" useCache="true">
        select id, first_name, last_name, hiring_date, sex, dept_id
        from employee   
    </select>  
</mapper>

By adding the line  <cache type=" org.mybatis.caches.oscache.OSCache "/> and specifying on the query useCache="true" you are activating OSCache cache with logging capabilities for the DataAccessLayer implementation.

Clean, build and redeploy both OSCachePOC and CacherPoc projects; now retrieve your employees for  two times in order to allow the in-memory cache to store the values. When you run your query for the first time, your application will execute the query on the database and retrieve the results. Second time you access the employee list, your application will access the in-memory storage.

Summary - How has the application performance been improved after this implementation?

An application's performances depend on a multitude of factors

  • how many times a cached piece of data can and is reduced by the application
  • the proportion of the response time that is alleviated by caching

Amdhal's law can be used to estimate the system's speed up :

where P is proportion speed up and S is speed up.

Let's take our implementation as example and calculate the speed up.

When the application ran the query without caching,a JDBC transaction is performed and  in your log will be something similar to :

INFO:   2014-12-22 18:01:30,020 [EmployeeDAO] INFO  com.tutorial.oscachepoc.dao.EmployeeDAO:38 - Getting employees.....
INFO:   2014-12-22 18:01:39,148 [JdbcTransaction] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:98 - Setting autocommit to false on JDBC Connection [org.apache.derby.client.net.NetConnection40@1c374fd]
INFO:   2014-12-22 18:01:39,159 [retrieveEmployees] DEBUG com.tutorial.oscachepoc.mapper.EmployeeMapper.retrieveEmployees:139 - ==>  Preparing: select id, first_name, last_name, hiring_date, sex, dept_id from employee
INFO:   2014-12-22 18:01:39,220 [retrieveEmployees] DEBUG com.tutorial.oscachepoc.mapper.EmployeeMapper.retrieveEmployees:139 - ==> Parameters:
INFO:   2014-12-22 18:01:39,316 [retrieveEmployees] DEBUG com.tutorial.oscachepoc.mapper.EmployeeMapper.retrieveEmployees:139 - <==      Total: 13

while running the queries with OSCache caching the JDBC transaction is performed only once (to initialize the cache) and after that the log will look like

INFO:   2014-12-17 07:59:50,902 [EmployeeDAO] INFO  com.tutorial.oscachepoc.dao.EmployeeDAO:38 - Getting employees with oscache.....
INFO:   2014-12-17 07:59:50,903 [AbstractConcurrentReadCache] DEBUG com.opensymphony.oscache.base.algorithm.AbstractConcurrentReadCache:694 - get called (key=-2135929388:-430013521:com.tutorial.oscachepoc.mapper.EmployeeMapper.retrieveEmployees:0:2147483647:select id, first_name, last_name, hiring_date, sex, dept_id
        from employee)
INFO:   2014-12-17 07:59:50,903 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=1 ]
INFO:   2014-12-17 07:59:50,904 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=2 ]
INFO:   2014-12-17 07:59:50,904 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=3 ]
INFO:   2014-12-17 07:59:50,904 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=4 ]
INFO:   2014-12-17 07:59:50,904 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=5 ]
INFO:   2014-12-17 07:59:50,905 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=6 ]
INFO:   2014-12-17 07:59:50,905 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=7 ]
INFO:   2014-12-17 07:59:50,905 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=10 ]
INFO:   2014-12-17 07:59:50,905 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=11 ]
INFO:   2014-12-17 07:59:50,905 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=12 ]
INFO:   2014-12-17 07:59:50,905 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=14 ]
INFO:   2014-12-17 07:59:50,906 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=8 ]
INFO:   2014-12-17 07:59:50,906 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=9 ]
INFO:   2014-12-17 07:59:53,906 [EmployeeDAO] INFO  com.tutorial.oscachepoc.dao.EmployeeDAO:38 - Getting employees with oscache.....
INFO:   2014-12-17 07:59:53,906 [AbstractConcurrentReadCache] DEBUG com.opensymphony.oscache.base.algorithm.AbstractConcurrentReadCache:694 - get called (key=-2135929388:-430013521:com.tutorial.oscachepoc.mapper.EmployeeMapper.retrieveEmployees:0:2147483647:select id, first_name, last_name, hiring_date, sex, dept_id
        from employee)
INFO:   2014-12-17 07:59:53,907 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=1 ]
INFO:   2014-12-17 07:59:53,907 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=2 ]
INFO:   2014-12-17 07:59:53,908 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=3 ]
INFO:   2014-12-17 07:59:53,909 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=4 ]
INFO:   2014-12-17 07:59:53,910 [OsCacheServlet] DEBUG com.tutorial.cacherpoc.OsCacheServlet:41 - com.tutorial.oscachepoc.bean.Employee[ id=5 ]

Let's look at the time that each of our 10 times requests has scored: the first not cached  10 times requests took about  57seconds and 51 milliseconds , while the cached requests scored a time of 27seconds and 15 miliseconds.

In order to apply Amdhal's law for the system the following input is needed:

  • Un-cached page time:  60 seconds
  • Database time (S): 58 seconds
  • Cache retrieval time: 27.081 seconds
  • Proportion: 96.6% (58/60) (P)

The expected system speedup is thus:

 1 / (( 1 – 0.966) + 0.966 / (58/27.081)) = 1 / (0.034 + 0.966 /2.104) = 2.028 times system speedup

This result can be improved of course, but the purpose of this article was to proove that caching using OSCache over MyBatis offers a significant improvement to what used to be available before its implementation.

Learn more from:


Discover the unprecedented possibilities and challenges, created by today’s fast paced data climate and why your current integration solution is not enough, brought to you in partnership with Liaison

Topics:

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}