Using MyBatis Annotations with Spring 3.0 and Maven

For my current project, the decision was made by our tech lead to use MyBatis 3.  Since I am the primary developer for this project but new to MyBatis, I needed to figure out how MyBatis works.  Having worked with Spring for a few years and Spring Annotations a little less, I decided to use MyBatis with annotations instead of XML files.  That decision made sense until I realized there is not a lot of documentation surrounding how MyBatis annotations work.

The intent of this post is to share what I have learned in the couple of weeks I have been using MyBatis with annotations using code examples. I focused primarily on the selects because the other CRUD operations work close enough to the selects that you should be able to figure them out based on the Select examples.

Where to Start

MyBatis annotations start with a Mapper Class.  This mapper class is nothing more than an interface with method signatures.  The method signatures are then annotated to create and execute the desired SQL and mapping.

To use the annotations with Spring and maven you need to add the MyBatis and MyBatis-Spring dependencies to your pom.xml.


<dependencies/>
....
    <dependency>
     	    <groupId>org.mybatis</groupId>
	    <artifactId>mybatis</artifactId>
	    <version>3.0.4</version>
    </dependency>

    <dependency>
	    <groupId>org.mybatis</groupId>
	    <artifactId>mybatis-spring</artifactId>
	    <version>1.0.0</version>
    </dependency>

.....
</dependencies>

You then need to add these beans to your Spring Application Context. I am assuming you already have a transactionManager and dataSource defined in your existing applicationContext.


<!-- MyBatis Data Mapper scanning.  Mappers need to be in this package or a subpackage of this. -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<property name="basePackage" value="com.objectpartners.dao" />
</bean>

<!-- This line enables component scanning for all spring beans, we don't need to configure each bean in the app context-->
<context:component-scan base-package="com.objectpartners" />

&lt;!-- MyBatis needs the sqlSessionFactory Bean defined. --&lt;
&lt;bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"&lt;
	&lt;property name="dataSource" ref="dataSource" /&lt;
&lt;/bean&lt;

For the examples, I will add code snippets to the Mapper Interface, Service, and Domain Objects. Here is the starting code for each of them.


//Teacher Object
...
public class Teacher {

    private String title;
    private String name;
    private String grade;
    private List<Student> students;
 ...

// Student Object
...
public class Student {

    private String name;
    private Teacher teacher;
    private String grade;
...

// Teacher Mapper
package com.objectpartners.daos;

import java.util.List;

import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Component;

import com.objectpartners.Teacher;

@Component
public interface TeacherDao {

}


//StudentDao
package com.objectpartners.daos;

import org.springframework.stereotype.Component;

@Component
public interface StudentDao {

}

//  Service using the mapper
package com.objectpartners;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class SchoolService {

    @Autowired
    protected TeacherDao TeacherDao;

    @Autowired
    protected StudentDao StudentDao;



}

Simplest example

In the most basic example the instance variables of your object are named the same as the columns in the database. When the instance variables and column names are the same you do not need to map the columns to the instance variables. Given we have a Teacher object with instance variable named the same as the database columns, we can select all of the records from the Teacher table with just this little bit of code.


//TeacherDao
...

        @Select("Select * from teacher")
             List<Teacher> selectAllTeachers();

...


//School Service
...
    public List<Teacher> selectAllTeachers() {
        return teacherDao.selectAllTeachers();
    }
...

Simple Query with parameter

The following example queries the Teacher table and return the one Teacher with the given name. I have been using named parameters in my queries. If you don’t want to use named parameters, you can use the position in the parameter list. Also, if you use the #name the query statement is created as a prepared statement. If you use ‘${name}’ the parameters are inlined.


//TeacherDao
...

        // This example creates a prepared statement, something like select * from teacher where name = ?;
        @Select("Select * from teacher where name = #name")
             Teacher selectTeachForGivenName(@Param("name") String name);
        

        // This example creates n inlined statement, something like select * from teacher where name = 'someName';
        @Select("Select * from teacher where name = '${name}')
        Teacher selectTeachForGivenName(@Param("name") String name);

...


//School Service
...
    public Teacher selectAllTeachers() {
        return teacherDao.selectTeachForGivenName("Smith");
    }
...

Mapping the columns to the instance variables

The following example queries the Student table and maps the columns to the Student instance variables. In this example, the Teachers name is in the DataBase but we want the Teacher object, so we select the Teacher object from the Teacher table. We get the Teacher object by calling TeacherDao.selectTeachForGivenName. Since the method is expecting a parameter, we are passing “name” along in the beginning of the of the result mapping – @Result(column = “{name=TEACHER_NAME}”. This is stating that the “name” parameter should get the value from the Student table’s column Teacher_Name.


//StudentDao
...

    @Results(value = { 
             @Result(column = "NAME", property = "name"),
             @Result(column = "{name=TEACHER_NAME}", property = "teacher", javaType=Teacher.class, one=@One(select="com.objectpartners.TeacherDao.selectTeachForGivenName")),
            //  IF the mapping to Teacher took multiple paramaters it would look like this...
            //  @Result(column = "{name=TEACHER_NAME,param2=COLUMN2}", property = "teacher", javaType=Teacher.class, one=@One(select="com.objectpartners.TeacherDao.selectTeachForGivenName")), 
          @Result(column = "GRADE", property = "grade")
           })
    @Select("select * from student ")
    List<Person> selectAllPeople();

...


//School Service
...
    public List<Student> selectAllStudents() {
        return studentDao.selectAllStudentsForTeacher("Smith");
    }
...

Using Type Discriminators

A type discriminator is very useful if you have multiple object types in one table. Lets say that Student, Teacher, Administrator, and Janitor all extend Person. Now, if Person had a personType attribute of “S” for Student, “T” for Teacher, and other personType attributes for other person types we can do the following.

The List of Person objects that are returned are a mixture of Person objects, Student Objects, and Teacher objects. The Type Discriminator casts each subclass of Person to the correct type when it is returned in the List. Since we never specified what the other Person types are, MyBatis returns the Administrators and Janitors a Person objects instead of casting them to the correct type.


//PersonDao
...

     @Results(value = { 
             @Result(column = "NAME", property = "name"),
           })
    @TypeDiscriminator(javaType = String.class, cases = { 
            @Case(type = Student.class, value = "S"),
            @Case(type = Teacher.class, value = "T") }, column = "PERSON_TYPE")
    @Select("select * from student ")
    List<Person> selectAllPeople();

...


//School Service
...
    public List<Student> selectAllStudents() {
        return personDao.selectAllPeople("Smith");
    }
...

Using the Select Provider

Instead of using a @Select for queries, you may use something called a @SelectProvider. This basically allows you to use the MyBatis SelectBuilder imports to build up your select statements in a different way that is more “safe” – maybe easier to read. We started out using the @SelectProvider and SelectBuilder very early on in our project. We eventually dropped the use and went to the @Select annotation instead. We felt, for us, it was more important to have the query right with the mapper and typeDiscriminator, a instead of in a separate file. I am including it here because it took me a while, longer than it should have, to figure out how to use the @SelectProvider annotation with selectBuilder.

See the MyBatis manual for more info on SelectBuilder. The difficulty for me was figuring out how to use the @SelectProvider with the selectBuilder functionality – especially when dealing with multiple parameters.


//  This is the method that builds the SQL String used by @SelectProvider.  
// StudentSelectBuilder Class
....
import static org.apache.ibatis.jdbc.SelectBuilder.BEGIN;
import static org.apache.ibatis.jdbc.SelectBuilder.FROM;
import static org.apache.ibatis.jdbc.SelectBuilder.SELECT;
import static org.apache.ibatis.jdbc.SelectBuilder.SQL;
import static org.apache.ibatis.jdbc.SelectBuilder.WHERE;

import java.util.Map;

public class StudentDaoSelectProvider {

    // If this method took only 1 parameter (name for example) we could have defined it as  selectStudentsForTeacher(String name)
    // but since it is more than 1 parameter it is a Map - this hung me up for a while.
    public static String selectStudentsForTeacher(Map<String, String> params) {
        
        BEGIN();
        SELECT("s.name, s.grade");
        FROM("Student s");
       //  The multiple WHERE methods make them an "AND"
       
        WHERE("s.teacher_name = #{name}");
        WHERE("s.grade = #{grade}");
        return SQL();
    }

//  Student Dao
    @Results(value = { 
            @Result(column = "NAME", property = "name", javaType = String.class),
            @Result(column = "GRADE", property = "grade", javaType = String.class)
          })
   @SelectProvider(type=StudentDaoSelectProvider.class, method="selectStudentsForTeacher")
   List<Student> selectAllStudents(@Param("name") String name, @Param("grade") String grade);


Conclusion

We are still using MyBatis annotations for the project I am working on. Overall we are very pleased with how the annotations are working and how all of our code – queries included – are right in the .class file with the mapping and methods names. That said, one drawback to using the annotations is that we did not find a way to map our columns to our instance variables once per class instead of for every select. We accepted this limitation and have not regretted our decision to not use mapping.xml files in this project.

About the Author

Object Partners profile.

One thought on “Using MyBatis Annotations with Spring 3.0 and Maven

  1. mtaki says:

    Thanks for very nice and helpful artical it has been of very helpful to me

    1. jana says:

      Thanks a lot its simple and easy to follow. very well written

  2. Jonathan Greenblatt says:

    I ran into the limitation you described where I wanted to have a reusable complex object mapping and use annotations. The solution to this is to use the xml file and annotations. You create a mapping xml file with the same name and classpath location as the java file mapper. You create mapping in the xml file and refer to it by id in the java interface with the following annotation: @ResultMap(“DevDataMap”)

  3. Mike G. says:

    Great paper, but I have a question. I personally do not like annotations. I know they are all the rage, but from my perspective, I prefer XML mapper files since they consolidate my SQL rather than having them sprinkled in my code. I feel the same about Spring and annotations as a form of configuration v. XML files. My question is this. Aside from being popular and en vogue, what makes annotations better than XML mapper files? I don’t see tools like IDEs providing that much more help if you use annotations. So what is it? I’d really like to get some feedback on this.

  4. Manuel E says:

    Muchas gracias por compartir información tan útil.
    Thank you very much for sharing information so useful.

  5. Narsimlu says:

    I have mapper xml file,how to generate the mapper interface.Mapper interfarec should be generated with annotation and sql queries before the method names.plz help me on this

  6. Matt says:

    How would this work if you have more than one database and wanted to use the same mappers on both?

Leave a Reply to jana Cancel reply

Your email address will not be published.

Related Blog Posts
Natively Compiled Java on Google App Engine
Google App Engine is a platform-as-a-service product that is marketed as a way to get your applications into the cloud without necessarily knowing all of the infrastructure bits and pieces to do so. Google App […]
Building Better Data Visualization Experiences: Part 2 of 2
If you don't have a Ph.D. in data science, the raw data might be difficult to comprehend. This is where data visualization comes in.
Unleashing Feature Flags onto Kafka Consumers
Feature flags are a tool to strategically enable or disable functionality at runtime. They are often used to drive different user experiences but can also be useful in real-time data systems. In this post, we’ll […]
A security model for developers
Software security is more important than ever, but developing secure applications is more confusing than ever. TLS, mTLS, RBAC, SAML, OAUTH, OWASP, GDPR, SASL, RSA, JWT, cookie, attack vector, DDoS, firewall, VPN, security groups, exploit, […]