Java

java

A Simple Caching Example on MyBatis using EhCache

Today i will show a simple example on how to combine ehcache caching framework with MyBatis ORM. I use Maven as my build tool, and Netbeans as my IDE.

Okay, here is my pom.xml,

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.edw.ehcache</groupId>
    <artifactId>MyBatisEhCache</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>MyBatisEhCache</name>
    <url>http://maven.apache.org</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.0.2</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>      
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.5</version>
        </dependency>     
    </dependencies>
</project>

And i create a simple mysql table,

CREATE TABLE `testing` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) NOT NULL DEFAULT '',
  `address` varchar(255) NOT NULL DEFAULT '',
  PRIMARY KEY (`Id`),
  UNIQUE KEY `ix` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1;

and a simple bean and xml representation from my table,

package com.edw.mybatisehcache.bean;

import java.io.Serializable;

public class Testing implements Serializable  {

    private Integer id;
    private String name;
    private String address;

    // setter and getter

    @Override
    public String toString() {
        return "testing{" + "id=" + id + ", name=" + name + ", address=" + address + '}';
    }            
}
<?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.edw.mybatisehcache.mapper.TestingMapper" >
  
     <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
    
    <resultMap id="Testings" type="com.edw.mybatisehcache.bean.Testing" >
        <id column="id" property="id" jdbcType="BIGINT" />
        <result column="name" property="name" jdbcType="VARCHAR" />
        <result column="address" property="address" jdbcType="VARCHAR" />
    </resultMap>  

    <select id="select" resultMap="Testings">
        select 
        *
        from testing    
    </select>   
</mapper>

An xml configuration to load my database connection,

<?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="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost/test"/>
                <property name="username" value="root"/>
                <property name="password" value=""/>
            </dataSource>
        </environment>          
    </environments>
     <mappers>        		
        <mapper resource="com/edw/mybatisehcache/xml/TestingMapper.xml" />  
    </mappers>              		
</configuration>

A java code to load my xml configuration,

package com.edw.mybatisehcache.config;

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

public class MyBatisSqlSessionFactory {

    private static final SqlSessionFactory FACTORY;

    static {
        try {
            Reader reader = Resources.getResourceAsReader("com/edw/mybatisehcache/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;
    }
}

A java interface to do handle queries,

package com.edw.mybatisehcache.mapper;

import com.edw.mybatisehcache.bean.Testing;
import java.util.List;

public interface TestingMapper {
    public List<Testing> select();    
}

And this is my ehcache.xml configuration,

<?xml version="1.0" encoding="UTF-8"?>
<!--
    caching configuration
-->
<ehcache>
    
    <diskStore path="F:\\cache" />
    
    <defaultCache eternal="true" maxElementsInMemory="1000"
                   overflowToDisk="true" diskPersistent="true" timeToIdleSeconds="0"
                   timeToLiveSeconds="0" memoryStoreEvictionPolicy="LRU" statistics="true" />
</ehcache>

This is my main java class, as you can see i try to do a repeated simple select queries,

package com.edw.mybatisehcache.main;

import com.edw.mybatisehcache.bean.Testing;
import com.edw.mybatisehcache.config.MyBatisSqlSessionFactory;
import com.edw.mybatisehcache.mapper.TestingMapper;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.log4j.Logger;

public class Main {

    private static Logger logger = Logger.getLogger(Main.class);

    public static void main(String[] args) {

        for (int i = 0; i < 3; i++) {
            SqlSessionFactory sqlSessionFactory = MyBatisSqlSessionFactory.getSqlSessionFactory();
            SqlSession sqlSession = sqlSessionFactory.openSession();
            TestingMapper testingMapper = sqlSession.getMapper(TestingMapper.class);

            List<Testing> testings = testingMapper.select();
            for (Testing testing : testings) {
                logger.debug(testing);
            }
            sqlSession.close();

            try {
                Thread.sleep(3000);
            } catch (Exception e) {
                logger.error(e, e);
            }
        }
    }
}

This is what is written on my netbeans’ console,

2013-07-25 15:30:10,648 [Segment] DEBUG net.sf.ehcache.store.disk.Segment:779 - fault removed 0 from heap
2013-07-25 15:30:10,648 [Segment] DEBUG net.sf.ehcache.store.disk.Segment:796 - fault added 0 on disk
2013-07-25 15:30:13,722 [Cache] DEBUG net.sf.ehcache.Cache:1970 - Cache: com.edw.mybatisehcache.mapper.TestingMapper store hit for 2026218237:1652924294:com.edw.mybatisehcache.mapper.TestingMapper.select:0:2147483647:select 
        *
        from testing
2013-07-25 15:30:13,722 [Main] DEBUG com.edw.mybatisehcache.main.Main:24 - testing{id=1, name=edw, address=Ciledug}
2013-07-25 15:30:13,722 [Main] DEBUG com.edw.mybatisehcache.main.Main:24 - testing{id=2, name=kamplenk, address=Cikokol}
2013-07-25 15:30:13,722 [Main] DEBUG com.edw.mybatisehcache.main.Main:24 - testing{id=3, name=nugie, address=Pamulang}
2013-07-25 15:30:13,722 [Main] DEBUG com.edw.mybatisehcache.main.Main:24 - testing{id=4, name=tebek, address=Karawaci}

Here is my Netbeans project structure

Have fun (K)

Set Isolation Level on MyBatis

On this example, im trying to set mybatis default isolation level, but before i go further let me explain a little why we need to setup an isolation level.

In a typical application, multiple transactions run concurrently, often working with the same data to get their job done. Concurrency, while necessary, can lead to the following problems:

Dirty read—Dirty reads occur when one transaction reads data that has been written but not yet committed by another transaction. If the changes are later rolled back, the data obtained by the first transaction will be invalid.

Nonrepeatable read—Nonrepeatable reads happen when a transaction performs the same query two or more times and each time the data is different. This is usually due to another concurrent transaction updating the data between the queries.

Phantom reads—Phantom reads are similar to nonrepeatable reads. These occur when a transaction (T1) reads several rows, and then a concurrent transaction (T2) inserts rows. Upon subsequent queries, the first transaction (T1) finds additional rows that were not there before.

That’s why on some mission critical application, i always set Isolation Level on Serializable. Why i use Serializable because this fully ACID-compliant isolation level ensures that dirty reads, nonrepeatable reads, and phantom reads are all prevented. This is the slowest of all isolation levels because it is typically accomplished by doing full table locks on the tables involved in the transaction.

Okay, here is how to achieve Serializable Isolation Level using MyBatis,

import com.edw.mybatis.bean.Testing;
import com.edw.mybatis.config.MyBatisSqlSessionFactory;
import com.edw.mybatis.mapper.TestingMapper;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.log4j.Logger;

public class Main {

    private static Logger logger = Logger.getLogger(Main.class);

    public static void main(String[] args) {

        SqlSessionFactory sqlSessionFactory = MyBatisSqlSessionFactory.getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession(TransactionIsolationLevel.SERIALIZABLE);
        TestingMapper testingMapper = sqlSession.getMapper(TestingMapper.class);

        try {
            Testing testing = new Testing();
            testing.setName("name 1");
            testing.setAddress("address 1");

            int success = testingMapper.insert(testing);
            logger.debug(success);
            
            sqlSession.commit();
        } catch (Exception e) {
            logger.error(e, e);
            sqlSession.rollback();
        } finally {
            sqlSession.close();
        }
    }
}

Hope this piece of code could help others, have fun with other TransactionIsolationLevel parameters, such as READ_COMMITTED or REPEATABLE_READ. 😉

How to Add Expiry Date on Your Static Files using Java’s Servlet Filter

Perhaps there would be some people wondering, why should i use expiry date on my static files? Well for a first-time visitor to your page may have to make several HTTP requests to load all your web page’s content, but by using the Expires header you make those components cacheable. This avoids unnecessary HTTP requests on subsequent page views. Expires headers are most often used with images, but they should be used on all components including scripts, stylesheets, and Flash components.

But dont forget, using a far future Expires header affects page views only after a user has already visited your site. It has no effect on the number of HTTP requests when a user visits your site for the first time and the browser’s cache is empty. Therefore the impact of this performance improvement depends on how often users hit your pages with a primed cache.

Okay, so basically i use a simple ServletFilter to add Expiry header on each static contents, on this example would be .css, .png and .gif files. So here is my code,

package com.edw.fw.server.filter;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

public class ExpiryFilter implements Filter {

	// add a five years expiry
	private Integer years = 5;

	@Override
	public void destroy() {
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		if (years > -1) {
			Calendar c = Calendar.getInstance();
			c.setTime(new Date());
			c.add(Calendar.YEAR, years);

			// HTTP header date format: Thu, 01 Dec 1994 16:00:00 GMT
			String o = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss zzz")
					.format(c.getTime());
			((HttpServletResponse) response).setHeader("Expires", o);
		}

		chain.doFilter(request, response);
	}

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {		
	}
}

And dont forget to register your Filter to your web.xml file

		<!--  
			expiration date filter
		-->
	  	<filter>
			<description>Set cache expiry for static content</description>
			<filter-name>ExpiryFilter</filter-name>
			<filter-class>com.edw.fw.server.filter.ExpiryFilter</filter-class>
		</filter>
		<filter-mapping>
			<filter-name>ExpiryFilter</filter-name>
			<url-pattern>*.css</url-pattern>
			<dispatcher>REQUEST</dispatcher>
		</filter-mapping>
		<filter-mapping>
			<filter-name>ExpiryFilter</filter-name>
			<url-pattern>*.png</url-pattern>
			<dispatcher>REQUEST</dispatcher>
		</filter-mapping>
		<filter-mapping>
			<filter-name>ExpiryFilter</filter-name>
			<url-pattern>*.gif</url-pattern>
			<dispatcher>REQUEST</dispatcher>
		</filter-mapping>

This is what it looks like on my browser’s http log,

as you can see, my http request get 304 not modified header, due to accessing my browser’s cache instead of the targetted web page.

Okay, i hope this helped other. Have Fun 🙂

A java.lang.IllegalArgumentException when Using SpringMVC @PathVariable

Another exception happened to me today while im deploying my application to Tomcat 7, somehow the error (again) never happen on my IDE. The error is related to Spring MVC’s @PathVariable. Below is the complete stacktrace for the error.

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalArgumentException: Name for argument type [java.lang.String] not available, and parameter name information not found in class file either.
	org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:894)
	org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
	com.opensymphony.module.sitemesh.filter.PageFilter.parsePage(PageFilter.java:119)
	com.opensymphony.module.sitemesh.filter.PageFilter.doFilter(PageFilter.java:55)
	org.springframework.security.web.FilterChainProxy$VirtualFilterChain .doFilter(FilterChainProxy.java:330)
	org.springframework.security.web.access.intercept.FilterSecurityInterceptor .invoke(FilterSecurityInterceptor.java:118)
	org.springframework.security.web.access.intercept.FilterSecurityInterceptor .doFilter(FilterSecurityInterceptor.java:84)

And this is the suspected method which raise exception,

    @RequestMapping(value ="/baculsoft/news/view/{id}", method = RequestMethod.GET)
    public String newsView(ModelMap modelMap, 
                        @PathVariable String id) {
        modelMap.put("news", newsService.get(id);
        return "news/view";
    }

The workaround is actually simple, below is how to deal with it,

    @RequestMapping(value ="/baculsoft/news/view/{id}", method = RequestMethod.GET)
    public String newsView(ModelMap modelMap, 
                        @PathVariable("id") String id) {
        modelMap.put("news", newsService.get(id);
        return "news/view";
    }

Actually very simple workaround, but since i have hundreds of methods using @PathVariable, it’s not so simple anymore. And the weird thing is, the .war exported from Eclipse IDE run perfectly, only .war created from Netbeans that raise exception. I dont know why, but somehow Netbeans’ ant create a different war compared to Eclipse’s war file.

After sometimes googling, i found out that it happens due to javac’s debug parameter on Netbeans’ ant, changing “debug” default value into “on” on Netbeans’ build-impl.xml makes my war file run perfectly on Tomcat 7. Well i hope it hepled others, cheers (B)

A Weird Exception, java.lang.IllegalArgumentException: Filter mapping specifies an unknown filter name “FilterName”

Today i found an exception while deploying my .war application to Tomcat web server for production usage, very weird because i never had this exception on my IDE before. This is the complete exception stacktrace,

Caused by: java.lang.IllegalArgumentException: Filter mapping specifies an unknown filter name FilterName
	at org.apache.catalina.core.StandardContext.addFilterMap(StandardContext.java:2506)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)

below is my web.xml content,

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="crm" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_4.xsd">
    <display-name>edw</display-name>
    <context-param>
        <param-name>edw</param-name>
        <param-value>edw</param-value>
    </context-param>
    
    <filter-mapping>
        <filter-name>FilterName</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
	
    <filter>
        <filter-name>FilterName</filter-name>
        <filter-class>com.edw.filter.FilterName</filter-class>
    </filter>
</web-app>

How to fix it is actually very simple, i only need to swap between and tag. Below is the correct web.xml.

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="crm" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_4.xsd">
    <display-name>edw</display-name>
    <context-param>
        <param-name>edw</param-name>
        <param-value>edw</param-value>
    </context-param>
    
    <filter>
        <filter-name>FilterName</filter-name>
        <filter-class>com.edw.filter.FilterName</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>FilterName</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

Weird eh :p