Wednesday, October 24, 2012

MyBatis Tutorial : Part4 - Spring Integration



MyBatis Tutorial: Part1 - CRUD Operations
MyBatis Tutorial: Part-2: CRUD operations Using Annotations
MyBatis Tutorial: Part 3 - Mapping Relationships
MyBatis Tutorial : Part4 - Spring Integration

MyBatis-Spring is a subproject of MyBatis and provides Spring integration support which drastically simplifies the MyBatis usage. For those who are familiar with Spring's way of Dependency Injection process, using MyBatis-Spring is a very simple.

First let us see the process of using MyBatis without Spring.

1. Create SqlSessionFactory using SqlSessionFactoryBuilder by passing mybatis-config.xml which contains DataSource properties, List of Mapper XMLs and TypeAliases etc.

2. Create SqlSession object from SqlSessionFactory

3. Get Mapper instance from SqlSession and execute queries.

4. Commit or rollback the transaction using SqlSession object.

With MyBatis-Spring, most of the above steps can be configured in Spring ApplicationContext and SqlSession or Mapper instances can be injected into Spring Beans. Then we can use Spring's TransactionManagement features without writing transaction commit/rollback code all over the code.


Now let us see how we can configure MyBatis+Spring integration stuff.

Step#1: Configure MyBatis-Spring dependencies in pom.xml
  
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.10</version>
   <scope>test</scope>
  </dependency>
  
  <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.1.1</version>
  </dependency>
  <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.1.1</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context-support</artifactId>
   <version>3.1.1.RELEASE</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-test</artifactId>
   <version>3.1.1.RELEASE</version>
   <scope>test</scope>
  </dependency>
  <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
             <version>5.1.21</version>
             <scope>runtime</scope>
         </dependency>
  <dependency>
   <groupId>cglib</groupId>
   <artifactId>cglib-nodep</artifactId>
   <version>2.2.2</version>
  </dependency>


Step#2: You don't need to configure Database properties in mybatis-config.xml.

We can configure DataSource in Spring Container and use it to build MyBatis SqlSessionFactory.

Instead of SqlSessionFactoryBuilder, MyBatis-Spring uses org.mybatis.spring.SqlSessionFactoryBean to build SqlSessionFactory.

We can pass dataSource, Mapper XML files locations, typeAliases etc to SqlSessionFactoryBean.


 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
  <property name="driverClassName" value="${jdbc.driverClassName}"/>
  <property name="url" value="${jdbc.url}"/>
  <property name="username" value="${jdbc.username}"/>
  <property name="password" value="${jdbc.password}"/>
 </bean>
 
 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="typeAliasesPackage" value="com.sivalabs.mybatisdemo.domain"/>
    <property name="mapperLocations" value="classpath*:com/sivalabs/mybatisdemo/mappers/**/*.xml" />
 </bean>

Step#3: Configure SqlSessionTemplate which provides ThreadSafe SqlSession object.

 <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
   <constructor-arg index="0" ref="sqlSessionFactory" />
 </bean>

Step#4: To be able to inject Mappers directly we should register org.mybatis.spring.mapper.MapperScannerConfigurer and configure the package name where to find Mapper Interfaces.

 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
   <property name="basePackage" value="com.sivalabs.mybatisdemo.mappers" />
 </bean>

Step#5: Configure TransactionManager to support Annotation based Transaction support.
 
 <tx:annotation-driven transaction-manager="transactionManager"/>
 
 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
 </bean>

Step#6: Update the Service classes and register them in Spring container.

package com.sivalabs.mybatisdemo.service;

import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.sivalabs.mybatisdemo.domain.User;
import com.sivalabs.mybatisdemo.mappers.UserMapper;

@Service
@Transactional
public class UserService
{
 @Autowired
 private SqlSession sqlSession; //This is to demonstrate injecting SqlSession object
 
 public void insertUser(User user) 
 {
  UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  userMapper.insertUser(user);
 }

 public User getUserById(Integer userId) 
 {
  UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  return userMapper.getUserById(userId);
 }
 
}
package com.sivalabs.mybatisdemo.service;

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.sivalabs.mybatisdemo.domain.Blog;
import com.sivalabs.mybatisdemo.mappers.BlogMapper;

@Service
@Transactional
public class BlogService
{
 @Autowired
 private BlogMapper blogMapper; // This is to demonstratee how to inject Mappers directly
 
 public void insertBlog(Blog blog) {
  blogMapper.insertBlog(blog);
 }
 
 public Blog getBlogById(Integer blogId) {
  return blogMapper.getBlogById(blogId);
 }
 
 public List<Blog> getAllBlogs() {
  return blogMapper.getAllBlogs();
 }
}

Note: When we can directly inject Mappers then why do we need to inject SqlSession objects? Because SqlSession object contains more fine grained method which comes handy at times.

For Example: If we want to get count of how many records got updated by an Update query we can use SqlSession as follows:
int updatedRowCount = sqlSession.update("com.sivalabs.mybatisdemo.mappers.UserMapper.updateUser", user);
So far I didn't find a way to get the row update count without using SqlSession object.

PS: You can have your interface insert/update/delete methods returning int, then MyBatis returns the number of records updated as an integer.

Step#7 Write JUnit Tests to test UserService and BlogService.


package com.sivalabs.mybatisdemo;

import java.util.List;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.sivalabs.mybatisdemo.domain.User;
import com.sivalabs.mybatisdemo.service.UserService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class SpringUserServiceTest 
{
 @Autowired
 private UserService userService;
 
    @Test
 public void testGetUserById() 
 {
  User user = userService.getUserById(1);
  Assert.assertNotNull(user);
  System.out.println(user);
  System.out.println(user.getBlog());
 }
        
    @Test
    public void testUpdateUser() 
    {
     long timestamp = System.currentTimeMillis();
  User user = userService.getUserById(2);
  user.setFirstName("TestFirstName"+timestamp);
     user.setLastName("TestLastName"+timestamp);
     userService.updateUser(user);
  User updatedUser = userService.getUserById(2);
  Assert.assertEquals(user.getFirstName(), updatedUser.getFirstName());
  Assert.assertEquals(user.getLastName(), updatedUser.getLastName());
 }
    
}
package com.sivalabs.mybatisdemo;

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

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.sivalabs.mybatisdemo.domain.Blog;
import com.sivalabs.mybatisdemo.domain.Post;
import com.sivalabs.mybatisdemo.service.BlogService;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class SpringBlogServiceTest 
{
 @Autowired
 private BlogService blogService;
 
 @Test
 public void testGetBlogById() 
 {
  Blog blog = blogService.getBlogById(1);
  Assert.assertNotNull(blog);
  System.out.println(blog);
  List<Post> posts = blog.getPosts();
  for (Post post : posts) {
   System.out.println(post);
  }
 }
    
    @Test
    public void testInsertBlog() 
    {
     Blog blog = new Blog();
     blog.setBlogName("test_blog_"+System.currentTimeMillis());
     blog.setCreatedOn(new Date());
     
     blogService.insertBlog(blog);
  Assert.assertTrue(blog.getBlogId() != 0);
  Blog createdBlog = blogService.getBlogById(blog.getBlogId());
  Assert.assertNotNull(createdBlog);
  Assert.assertEquals(blog.getBlogName(), createdBlog.getBlogName());
  
 }
    
}

9 comments:

  1. Your tutorials, as most of them online, are impossible to follow and useless. Like always, everything is right until you get to the "userMapper" part with the import of the mapper in java, which obviously doesn't exist and about whom you havent talked before. It could have been very nice though. Time to look for a working example.

    ReplyDelete
    Replies

    1. This reminds me of an old incident happend to me long back. For our new project we got to use JBPM and one of my ex-colleage bought JBPM book and reading it.
      After one weak he came to me and said I moved to another project, anyhow I read first 2 chapters so you can continue from 3rd chapter.

      And coming to your problem, I have posted 4 parts of MyBatis articles. If you are a complete beginner to MyBatis you could start with Part-1 and progress through next parts. May be you need to integrate Spring and MyBatis and without going through previous parts you just jumped on 4th part and complaining UserMapper is not there and my articles are useless. Funny !!!.

      To simply answer your question, please see Step#4 in Part-1: http://www.sivalabs.in/2012/10/mybatis-tutorial-part1-crud-operations.html where I explained how to create UserMapper.

      Delete
  2. Siva,

    I did the Step#3: Configure SqlSessionTemplate which provides ThreadSafe SqlSession object but when I see the JUnit logs I read:

    DEBUG 03-22 14:28:11 Creating a new SqlSession
    DEBUG 03-22 14:28:11 SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@94af2f] was not registered for synchronization because synchronization is not active

    Why it created DefaultSqlSession instead of using SqlSessionTemplate?

    ReplyDelete
  3. check this out

    http://stackoverflow.com/questions/16626743/spring3-mybatis-3-mapper-no-such-bean-found-exception

    ReplyDelete
  4. try this it works for me

    http://stackoverflow.com/questions/16626743/spring3-mybatis-3-mapper-no-such-bean-found-exception

    ReplyDelete
  5. will need the following lines in the application context file, (define the location of properties file, turn on autowire, and register the bean classes to be autowired.




    also, need this dependency in the pom file

    commons-dbcp
    commons-dbcp
    1.4

    ReplyDelete
  6. help! Please
    Error: java.lang.NullPointerException (me know: userService is null)

    @ContextConfiguration(locations = "classpath:applicationContext.xml")
    public class ExecuteController {
    @Autowired
    private static UserService userService;


    If members build success, can share public source code with me!
    mail: thuypn9@gmail.com
    thanks!

    ReplyDelete
  7. How to update two databases (Oracle & MySQL) in same transaction using mybatis. Could you please provide an article on this.

    ReplyDelete
  8. http://www.javaproficiency.com/2015/03/spring-tutorial.html

    ReplyDelete