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-->


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

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 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 selectAllTeachers();

...


//School Service
...
    public List 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 selectAllPeople();

...


//School Service
...
    public List 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 selectAllPeople();

...


//School Service
...
    public List 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 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 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.

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

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*