Spring MyBatis integration example

(P) Codever is an open source bookmarks and snippets manager for developers & co. See our How To guides to help you get started. Public bookmarks repos on Github ⭐🙏
Contents
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
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:
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 SqlSession
s and inject them into other beans, translates MyBatis exceptions into Spring DataAccessException
s, 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" "https://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):
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" "https://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 ResultSet
s, 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 ResultMap
s 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 toresultSet.getString(columnName)
.
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.
Source code for this post is available on Github - podcastpedia.org is an open source project.