Saturday, November 13, 2010

Using MyBatis 3 with Spring 3 Using Mapper XML Files

Recently I started to work on design an application as a part of a new project assigned to me at my work place. Since this is totally a new application to be developed from scratch, I got liberty/flexibility to choose the tools/frameworks which will form the foundation of the application. With lots of frameworks to choose from and many choices available for different tiers, I started evaluating each framework against the requirement of the application vs where it fits in which tier. When I came to data access tier (resource tier as some say), I realized that MyBatis (new name of iBatis after it moved to Google code from Apache) was best fit as ORM framework. I also decided to use Spring 3.x as DI framework and we all know how well MyBatis/iBatis and Spring (beans, data access, transaction etc) gel with each other. Using Spring's DI, declarative transaction management and MyBatis' query externalization, we as a developer can concentrate on implementing application/business logic and keep rest of the stuff like transaction management, JDBC resource management etc to be "headache" of container/frameworks.

So as I decided to go with MyBatis, I came to know that Spring 3.0 development finished first then MyBatis 3 and hence there is no official support in Spring 3.0 for MyBatis 3.0. Its expected to be there in Spring 3.1 release (Source: Spring-MyBatis integration RC2 reference doc). But thanks to MyBatis community, there is already a project for myabtis-spring integrations. At start I struggled a bit on how to wire Spring beans, dao with MyBatis mapper configuration files. As I was googling around, I stumbled upon a very good post by Giovanni Cuccu on this. It was very enlightening and provided me a good start. I created a maven/spring/mybatis project in my eclipse and started writing down some quick and crude code to test out. As soon as I tried to run my sample code I encountered...

java.lang.IllegalArgumentException: Property 'sqlSessionTemplate' is required
After debugging the mybatis-spring integration source code, I found that since I was using Spring DAO using mybatis-spring integration, org.mybatis.spring.support.SqlSessionDaoSupport class' checkDaoConfig explicitly checks for the sqlSessionTemplate property to be defined. After trying for some time and to no success, I changed my DAO implementation to be a mapper interface using MapperFactoryBean. This eliminated of having a class implementing SqlSessionDaoSupport just to invoke Spring's SqlMapClientTemplate's helper methods. Instead now its just an interface holding method signatures mirroring the java representation of mapped sql statements in MyBatis mapper configuration files. With this changed implementation, everything worked like a charm.

Here is the spring wiring I did to make mybatis-spring work with mapper files. Usually I keep my spring configuration logically separated by keep configuration related to data access in one file and application DAOs go to another. So with this I end up creating four configuration files and one java interface for this project.
  1. data-access-spring-context.xml: Holding spring configuration defining my data access
  2. mybatis-config.xml: Standard MyBatis configuration file
  3. user-dao.xml: MyBatis mapper file. One mapper file for one DAO defined in dao-spring-context.xml
  4. dao-spring-context.xml: Holding all DAOs used by application
  5. UserDAO.java

Version of various plugin and framework I used for this little POC...
  1. Spring 3.0.5
  2. MyBatis 3.0.2
  3. MyBatis-Spring 1.0.0-RC2

Let's start visiting each of these files to understand the required configuration...
  1. data-access-spring-context.xml:
    
    
        
            
                classpath:jdbc.properties
            
        
    
        
            
                ${DataSource.JndiName.DS}
            
            
                false
            
        
    
       
            
            
        
        
        
            
        
        
    
    
    
    For simplicity, I've not included the transaction manager, advice point cut etc in this. So here, main point of interest is how the sql session factory and sql session template is configured which actually wires MyBatis configuration to Spring data access
  2. mybatis-config.xml:
    
        
            
        
        
            
        
    
    
    
    This is a standard MyBatis configuration file, holding all the mappers of the applications.
  3. user-dao.xml
    
        
            
            
        
    
        
    
    
    
    Here I've defined a SQL queries required my my User DAO. User DAO is wired to spring data access and mybatis in my DAO spring context i.e. dao-spring-context.xml
  4. dao-spring-context.xml:
    
    
        
            
            
        
    
    
    Notice that the fully qualified interface name defined as mapperInterface serves as the namespace for the mapper in user-dao.xml.
  5. UserDAO.java:
    package com.jak.sandbox.biz.dao;
    
    import java.util.List;
    
    import com.jak.sandbox.shared.dto.model.User;
    
    public interface UserDAO
    {
        List<User> selectUsers();
    }
    
    

Well this is pretty much you need to do in order to use MyBatis with Spring. Now just write a test program which loads both spring context as defined above, inject userDAO bean to any of your Java class (typically a business service) and invoke selectUsers method.

MyBatis & Spring made data access so simple and using interface as your data access and having implementation as MyBatis mappers, makes data access more cleaner. This is because ideally your data access should only be taking request from business tier to perform some operations like select/insert/update/delete and return the result of such operation back. There should not be any logic. Having your data access as interfaces, takes the any chances of developer mistakenly putting any logic.

Cheers !!!
- Jay

23 comments:

  1. can u write small injection sample for this post?
    tutorial is realy good and simple

    ReplyDelete
  2. Injection sample? can you elaborate?

    ReplyDelete
  3. With Spring moving to Annotations, how could this same sample be written using Annotations?

    ReplyDelete
  4. You can refer to this link for annotation examples

    http://www.jpadbf.org/2010/09/mybatis3-spring3-integration-using-mybatis3-new-features/

    ReplyDelete
  5. David McManamonDec 2, 2010 11:33 AM

    Does this example really work? applicationContext appears incorrect:
    class="org.mybatis.spring.mapper.MapperFactoryBean"

    ReplyDelete
  6. David, yes it does work. am using Spring 3.0.5, mybatis 3 and mybatis-spring 1.0 RC3. Hope this helps.

    ReplyDelete
  7. David this sample uses RC2 where the MapperFactoryBean was in org.mybatis.spring package but now it is in org.mybatis.spring.mapper. It good be a good idea to update this article.

    Apart from that it seems that there is a xml syntax error here:







    it should be:

    ReplyDelete
  8. David McManamonDec 8, 2010 12:03 PM

    I see, I'm looking at the current mybatis-spring and the package name has changed.

    ReplyDelete
  9. Guys, Sorry for the typo. Yes its RC2 and not RC3. I'll update the article to include the version which have been used for the sample code mentioned.

    ReplyDelete
  10. great tutorial, i was going through the MyBatis docs but couldn't find a simple example of how to use XML files in stead of annotations.

    This worked great, Thanks!

    ReplyDelete
  11. you have made it more complicated,,

    ReplyDelete
  12. For something whos reading this guide.
    Just finished converting spring2ibatis2->spring3mybatis3 myself. The "org.mybatis.spring.support.SqlSessionDaoSupport " class's checkDaoConfig assert is very valid, all you need is add "" in all your Dao beans. The other post omitted that, so you getting that error.

    There's no need for using Dao xmls. Sometime you do need to do something extra on the Dao before returning results back to your say.."UserManager"

    ReplyDelete
  13. hey jay can you please provide the class containing main method..???

    ReplyDelete
  14. Anuj, I'm not sure if I still have that project with me, as I just did it as POC. But what exactly you want from that main class. It would have only the spring container initialization.

    ReplyDelete
  15. jay the thing is when i try to run this snippets inside my code they are givin error for bean creation.

    error is that no init() method found for sqlSessionFactory bean.

    and the problem is i am totally new to SPRING + MYBATIS and i need to migrate my simple JAVA based client server program to it..
    so if u can help ...

    ReplyDelete
  16. Anuj, if you can send the exception stacktrace then debugging can be easier...

    ReplyDelete
  17. Hello Jay,

    I am facing problem in writing to db via myBatis in my Spring3 application... I get a null exception error as soon as the DAO object is accessed. Can I share the codebase with you, it is still small, w/o much functionality?

    ReplyDelete
  18. Vandana, sure you can, not sure how much I'd be of help, but will try. Paste the troubling code over here and will have a look

    ReplyDelete
  19. Thanks a ton Jay for the willingness to help.

    I am pasting some relevant parts of some files below, please let me know if more details are required, or if I can send you the complete codebase zip:

    ==================================================
    /WEB-INF/web.xml
    ....


    org.springframework.web.context.ContextLoaderListener


    ......

    contextConfigLocation
    /WEB-INF/applicationContext.xml

    ....
    ==================================================
    /WEB-INF/applicationContext.xml
    ....




    ......
    ==================================================
    /WEB-INF/mybatis-config.xml
    ----








    .....
    NOTE: I have tried with moving the mybatis.xml file in the src folder and also here in the WEB-INF folder. I have also tried with adding Envirnment tag in the mybatis file.
    ==================================================
    /src/com/abc/xyz/service/UserService.java

    Problem is when UserMapper is accessed in below code, it gives a null exception error. Tried the following variations:
    =========================================
    Variation1
    @Autowired
    private UserMapper userMapper;

    public void insertUser(User user) {
    userMapper.insertUser(user);
    }
    =================================
    Variation 2
    @Autowired
    private UserMapper userMapper;
    private SqlSessionFactory sqlSessionFactory;

    //constructor
    public UserService(){
    sqlSessionFactory = MyBatisConnectionFactory.getSqlSessionFactory();
    }


    public void insertUser(User user) {
    SqlSession session = sqlSessionFactory.openSession();
    try{
    session.insert("User.insertUser",user);

    /*Also tried this: userMapper = session.getMapper(UserMapper.class);
    userMapper.insertUser(user); */
    }
    ==============================================

    All I get is a NullException Error when userMApper is accessed.

    ReplyDelete
  20. Vandana, this seems to be more of spring wiring problem then myBatis. Have you checked if your spring application context fires up properly, like all your beans are getting instantiated properly and wiring is happening? Either your application contexts is not getting initialized or there is some issue with your userMapper bean wiring.

    ReplyDelete
  21. Can I have copy zip file or war link for these code?

    Thanks

    ReplyDelete
  22. Sorry I can not find the project, it was almost a year back and I just did as to test something.

    ReplyDelete
  23. use the below link.I tried and it works correctly

    http://www.jpadbf.org/2010/09/mybatis3-spring3-integration-using-mybatis3-new-features/

    ReplyDelete

 
Disclaimer: This is a personal blog and all content represent what I think and it does not advocate/support/advertise any other person/company. I do not earn money or intended to do so with this blog or any of the contents the blog hosts. If I post something here that you find helpful, that's wonderful. Just in case, if I say something stupid, the stupidity is mine, and mine alone and I can not be held for anything if you fall for such stupidity :-). I cannot be held responsible for any kind of damage that may be caused by downloading or viewing the files or information provided herewith. Anybody and everybody can use/refer the contents of this blog at their own will and of course at own risk. There is no need for any kind of approval of the author. Although it would be great if feedback is left for any such usage to the author