Spring MyBatis integration example

1. Why Mybatis?

Short answer: simple, lightweight, open source, dynamic sql and sql control, previous iBATIS knowledge. Now let me elaborate a little bit on the subject. Back in the old days of Podcastmania.ro, see Story of Podcastpedia.org, I used my own MVC like framwork based on servlets to develop the web application and plain old JDBC to access the database. After “upgrading” to Spring MVC, I started using Spring’s JdbcTemplate for database access, which removed some of the boilerplate code. Later I got involved in projects where database access occured via iBATIS – Hibernate was there for a long time, but because of legacy reasons and no database normalization whatsoever, iBATIS was the optimal choice. By about the same time MyBatis had been just launched, so I read the documentation, did a pilot, liked it and switched from Spring’s JdbcTemplate to MyBatis. In the mean time I’ve been working on projects with Hibernate and JPA 2.0 with Hibernate used for persistence, so I’d say I have a pretty good overview on the most popular Java Persistence Frameworks. You have currently four major options:

  • JPA/Hibernate
  • myBatis (former iBatis)
  • Spring JDBC
  • JDBC

Octocat Source code for this post is available on Github - podcastpedia.org is an open source project.

Every approach has its pros and cons, but if I had to choose all over again a technology for Podcastpedia.org, I would choose MyBatis. You can find lots of resources on the web, lots of reviews, tutorials, pros and cons for using one technology over the other and so on – do your research and find out what is best persistence solution for your context. I also advise watching the following video, unfortunately the sound is pretty bad, but the content is really interesting :

1.1. What is MyBatis?

So now that I have made my choice, let’s see first what is MyBatis. Well according to the official website, “MyBatis is a first class persistence framework with support for custom SQL, stored procedures and advanced mappings. MyBatis eliminates almost all of the JDBC code and manual setting of parameters and retrieval of results. MyBatis can use simple XML or Annotations for configuration and map primitives, Map interfaces and Java POJOs (Plain Old Java Objects) to database records.”

Enough with talking, let’s focus now on the main topic of this post, which is to find out how Spring and MyBatis can interact.

2. Spring MyBatis interaction

For the sake of simplicity, in this post I will present a simple example, which explains what needs to be implemented and configured to retrieve the newest(recently updated) podcasts from the database via MyBatis with Spring. You can experience the end result “live” by visiting the homepage of Podcastpedia.org:

last updated podcasts

Print screen home page Podcastpedia.org

If you are familiar with iBATIS (predecessor of MyBatis), you might know that until version 3, the Spring Framework provided direct integration with iBATIS SQL Maps in terms of resource management, DAO implementation support, and transaction strategies. But by the time iBATIS became MyBatis, Spring 3 development was already over, and the Spring team did not want to release with code based on a non-released version of MyBatis, official Spring support would have to wait. Given the interest in Spring support for MyBatis, the MyBatis community decided it was time to reunite the interested contributors and add Spring integration as a community sub-project of MyBatis instead. This is how MyBatis-Spring project was born, which is also used throughout Podcastpedia.org

2.1. What is MyBatis-Spring?

MyBatis-Spring integrates MyBatis seamlessly with Spring. This library allows MyBatis to participate in Spring transactions, takes care of building MyBatis mappers and SqlSessions and inject them into other beans, translates MyBatis exceptions into Spring DataAccessExceptions, and finally, it lets you build your application code free of dependencies on MyBatis, Spring or MyBatis-Spring.

2.2. Installation

To use the MyBatis-Spring module, you just need to include the mybatis-spring-x.x.x.jar file and its dependencies in the classpath.

If you are using Maven just add the following dependency to your pom.xml:

<!-- MyBatis integration -->
<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis-spring</artifactId>
	<version>1.2.1</version>
</dependency>
<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis</artifactId>
	<version>3.2.3</version>
</dependency>

2.3. Spring application context setup

To use MyBatis with Spring you need at least two things defined in the Spring application context: an SqlSessionFactory and at least one mapper interface.

In MyBatis-Spring, an SqlSessionFactoryBean is used to create an SqlSessionFactory. Every MyBatis application centers around an instance of SqlSessionFactory. You use the SqlSessionFactory to create an SqlSession. Once you have a session, you use it to execute your mapped statements, commit or rollback connections and finally, when it is no longer needed, you close the session. With MyBatis-Spring you don’t need to use SqlSessionFactory directly because your beans can be injected with a thread safe SqlSession that automatically commits, rollbacks and closes the session based on Spring’s transaction configuration.

To configure the factory bean, put the following in the Spring XML configuration file:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<property name="dataSource" ref="dataSource" />
	<property name="configLocation" value="classpath:config/mybatisV3-config.xml"/>
</bean>

Notice that the SqlSessionFactory requires a DataSource. This can be any DataSource and should be configured just like any other Spring database connection. For Podcastpedia.org the DataSource is configured via JNDI:

<!-- ========================= DATASOURCE DEFINITION via JNDI ========================= -->
<!-- When resourceRef is true, the value of jndiName will be prepended with
server’s JNDI directory. Consequently, the actual name used will be
java:comp/env/jdbc/pcmDB.  -->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean" scope="singleton">
	<property name="jndiName" value="java:comp/env/jdbc/pcmDB" />
	<property name="resourceRef" value="true" />
</bean>

See my post Tomcat JDBC Connection Pool configuration for production and development, to find out how the database resource is configured for production and development/testing environments.

The location parameter specifies the location of the MyBatis config file. You will see in a coming section, MyBatis configuration XML, how this file looks like.

2.4. Injecting Mappers

Rather than code data access objects (DAOs) manually using SqlSessionDaoSupport or SqlSessionTemplate, Mybatis-Spring can create a thread safe mapper that you can inject directly into other bean. I like to call mappers also DAOs.

2.4.1. Service Layer

I will start at the Service Layer, which lies before the DAO layer. The service layer implementation class – StartPageServiceImpl, which loads the newest podcasts in the model to be displayed on the homepage, will be injected the PodcastDao mapper:

<!-- =================== Service beans configuration ================== -->
<bean id="startPageService" class="org.podcastpedia.service.impl.StartPageServiceImpl">
	<property name="podcastDao" ref="podcastDao"/>
</bean>

Once the mapper is injected it can then be used in the business logic:

public class StartPageServiceImpl implements StartPageService {

   private static final Integer NUMBER_OF_PODCASTS_IN_CHART = 5;
   private static Logger LOG = Logger.getLogger(StartPageServiceImpl.class);
   private PodcastDao podcastDao;

   public void setPodcastDao(PodcastDao podcastDao) {
	   this.podcastDao = podcastDao;
   }

   @Cacheable(value="newestAndRecommendedPodcasts", key="#root.method.name")
   public  List<Podcast> getNewestPodcasts() {
	   List<Podcast> newestPodcasts = podcastDao.getNewestPodcasts(NUMBER_OF_PODCASTS_IN_CHART);
	   for(Podcast p : newestPodcasts) {
		   Episode lastEpisode = episodeDao.getLastEpisodeForPodcast(p.getPodcastId());
		   p.setLastEpisode(lastEpisode);
	   }

	   return newestPodcasts;
   }
  ..............
}

Notice that there are no SqlSession or MyBatis references at the service layer. Nor is there any need to create, open or close the session, MyBatis-Spring will take care of that. We’ll see in the next section how I implemented and configured the PodcastDao.

Note: If you want to find out how the @Cacheable annotation is configured, visit my post Spring caching with Ehcache.

2.4.2. DAO layer

2.4.2.1. Register mapper

You can register the mapper either using a classical XML configuration or the new 3.0+ Java Config (a.k.a. @Configuration). I prefer the classical way with XML configuration, as I like to hold the configuration as separate as possible from the code:

2.4.2.1.1. With XML Config

The mapper/dao is registered to Spring by including a MapperFactoryBean in the application context XML config:

<!-- =============== MyBATIS beans configuration ================== -->
<bean id="podcastDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
   <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
   <property name="mapperInterface" value="org.podcastpedia.dao.PodcastDao" />
</bean>

The Mapper BeanFactory can be set up with a SqlSessionFactory, as here, or a pre-configured SqlSessionTemplate. You saw how the SqlSessionFactory was configured in the section Installation/Spring application context setup. You can also download the complete Spring configuration file for the DAO layer.

The second parameter – mapperInterface – sets the mapper interface of the MyBatis mapper. Note that the mapper class specified must be an interface, NOT an actual implementation class:

package org.podcastpedia.dao;

import java.util.Date;
import java.util.List;
import java.util.Map;

import org.podcastpedia.domain.Comment;
import org.podcastpedia.domain.Podcast;
import org.podcastpedia.domain.Tag;

/**
 * Interface for database access
 *
 * @author ama
 *
 */
public interface PodcastDao {

	  /**
	   * Returns the newest podcasts (ORDER BY last_updated DESC)
	   *
	   * @param numberOfPodcasts (number of podcasts to be returned)
	   * @return
	   */
	  public List<Podcast> getNewestPodcasts(Integer numberOfPodcasts);

      ....................
}

If the PodcastDao had a corresponding MyBatis XML mapper file in the same classpath location as the mapper interface, it would have been parsed automatically by the MapperFactoryBean. But because they are different places in the classpath locations, the parameter had to be specified.

Let’s see now how the sql statement is configured and implemented in MyBatis.

2.5. MyBatis configuration XML

As mentioned in the beginning of the post the SqlSessionFactoryBean has the configLocation parameter that defines where the MyBatis configuration resides. Here is an extract from the configuration file that is relevant for the example presented here:

<?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>
	<typeAliases>
		<typeAlias type="org.podcastpedia.domain.Podcast" alias="Podcast"/>
         .......
	</typeAliases>
    <mappers>
        <mapper resource="maps/PodcastMapper.xml" />
		........
    </mappers>
</configuration>

2.5.1. typeAliases

A type alias is simply a shorter name for a Java type. It’s only relevant to the XML configuration and simply exists to reduce redundant typing of fully qualified classnames which include package names. As you’ll see in the following section, when referencing a org.podcastpedia.domain.Podcast object, I will just use “Podcast”.

2.5.2. mappers

Configuration elements which tell MyBatis where to find the mappers. Java doesn’t really provide any good means of auto-discovery in this regard, so the best way to do it is to simply tell MyBatis where to find the mapping files. You can use class path relative resource references, or literal, fully qualified url references (including file:/// URLs):

MyBatis config files

MyBatis configuration file and mappers location

Note: There are many other parameters you can set up in the configuration file for MyBatis. See the Configuration documentation page for more details.

2.6. Mapper XML Files

The true power of MyBatis is in the Mapped Statements. This is where the magic happens. For all of their power, the Mapper XML files are relatively simple. Certainly if you were to compare them to the equivalent JDBC code, you would immediately see a savings of 95% of the code. MyBatis was built to focus on the SQL, and does its best to stay out of your way.

Here I will present a snippet from the PodcastMapper.xml that shows how the PodcastDao interface method is mapped:

<?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="org.podcastpedia.dao.PodcastDao">
	<!--    result maps     -->
	<resultMap id="podcastsMap" type="Podcast" >
		<id column="podcast_id" property="podcastId"  />
		<result column="url" property="url" />
		<result column="rating" property="rating" />
		<result column="numberRatings" property="number_ratings" />
		<result column="number_visitors" property="numberOfVisitors" />
		<result column="DESCRIPTION" property="description" />
		<result column="PODCAST_IMAGE_URL" property="urlOfImageToDisplay" />
		<result column="TITLE" property="title" />
		<result column="last_episode_url" property="lastEpisodeMediaUrl" />
		<result column="title_in_url" property="titleInUrl" />
		<result column="publication_date" property="publicationDate"/>
	</resultMap>

	<select id="getNewestPodcasts" resultMap="podcastsMap" parameterType="Integer">
		SELECT
			PODCAST_ID,
			URL,
			NUMBER_VISITORS,
			DESCRIPTION,
			PODCAST_IMAGE_URL,
			TITLE,
			last_episode_url,
			title_in_url,
			publication_date
		FROM
			podcasts
		WHERE
			availability=200
		ORDER BY publication_date DESC
			limit 0, #{value};
	</select>
	.....
</mapper>

Notice the mapper’s namespace value at line 5 – org.podcastpedia.dao.PodcastDao and the id of the <select> element at line 21. If you combine, it results org.podcastpedia.dao.PodcastDao.getNewestPodcasts which is exactly the method of the interface defined at the DAO layer.

2.6.1. select

The select statement is one of the most popular elements that you’ll use in MyBatis. Putting data in a database isn’t terribly valuable until you get it back out, so most applications query far more than they modify the data. For every insert, update or delete, there is probably many selects. This is one of the founding principles of MyBatis, and is the reason so much focus and effort was placed on querying and result mapping.

The statement exposed here is called getNewestPodcasts (same as corresponding method in the PodcastDao interface), takes a parameter of type Integer (same as input of the corresponding method in PodcastDao interface) and has a resultMap parameter of value "podcastsMap", which is a named reference to an external resultMap.

2.6.2. Result Map

The resultMap element is the most important and powerful element in MyBatis. It’s what allows you to do away with 90% of the code that JDBC requires to retrieve data from ResultSets, and in some cases allows you to do things that JDBC does not even support. In fact, to write the equivalent code for something like a join mapping for a complex statement could probably span thousands of lines of code. The design of the ResultMaps is such that simple statements don’t require explicit result mappings at all, and more complex statements require no more than is absolutely necessary to describe the relationships.

Let’s talk a little about the structure of the resultMap. The top element has an id(=”podcastMap”) which uniquely identifies the select in the mapper (namespace), and is referenced in the select, and a type(=”Podcast”), which is a fully qualified Java class name, or a type alias (this is the case here, "Podcast" being the type alias defined in the MyBatis configuration file)

2.6.2.1. id & result
<id column="podcast_id" property="podcastId"  />
<result column="url" property="url" />

These are the most basic of result mappings. Both id, and column map a single column value to a single property or field of a simple data type (String, int, double, Date, etc.).

The only difference between the two is that id will flag the result as an identifier property to be used when comparing object instances. This helps to improve general performance, but especially performance of caching and nested result mapping (i.e. join mapping).

What happens in this map is that some of the columns of the PODCASTS table are mapped to the properties of the Podcast bean:

  • the column attribute represents the column name from the database, or the aliased column label – this is the same string that would normally be passed to resultSet.getString(columnName).
  • the property attribute represents the field or property to map the column result to. If a matching JavaBeans property exists for the given name, then that will be used. Otherwise, MyBatis will look for a field of the given name. In both cases you can use complex property navigation using the usual dot notation. For example, you can map to something simple like: username, or to something more complicated like: address.street.number.
  • Believe it or not, that’s it. It might seem like a lot just do a simple SELECT, but once you learn the basics it becomes really simple and you get some powerful stuff, some of which I will present in a future post. And also is important to mention that the learning curve is pretty small.

    3. Summary

    Well, that’s it. You’ve learned about MyBatis, how it integrates with Spring via MyBatis-Spring, how to configure MyBatis in Spring’s application context and how to use it in a Spring application.

    Octocat Source code for this post is available on Github - podcastpedia.org is an open source project.

    4. Resources

    1. MyBatis
    2. MyBatis-Spring
    3. Spring Data Access with JDBC
    4. MyBatis-Blog
    Podcastpedia image

    Adrian Matei

    Creator of Podcastpedia.org and Codingpedia.org, computer science engineer, husband, father, curious and passionate about science, computers, software, education, economics, social equity, philosophy - but these are just outside labels and not that important, deep inside we are all just consciousness, right?

    Free Programming Books are now on Codingmarks.org

    We're happy to announce that we've reached and surpassed our goal of 1 Mb #codingmarks, by importing the books from the Free-Programming-Books project Continue reading