Creating Mybatis Mapper xmls and usage of MapperFactoryBean, MapperScannerConfigurer to retrieve threadsafe SqlSessionTemplate

Posted: May 15, 2012 in Techilla
Tags: , , , , , ,

Goal of this session

  • Mybatis Mapper xmls and interfaces creation
  • Mybatis MapperFactoryBean to retrieve Mybatis SqlSessions which are threadsafe.
  • Mybatis MapperScannerConfigurer to automatically wire the mapper interfaces
  • Mybatis SqlSessionDaoSupport and SqlSessionTemplate
  • Spring Annotated Controllers
  • Spring example of get, post methods to retrieve and update an object.

Ok, so we have done our configurational changes here.

We have created our datasource, sqlsessionfactorybean, mybatis configurational file and referred to our mybatis mapper xmls.But we haven’t yet created the model object or the mapper xmls.

Step 1:  Model Object Creation : We will create the Employee object first.

public class Employee {
	protected long empId;
	protected String firstname;
	protected String lastname;
	protected String email;
	protected String telephone;
	protected String birthday;

          // we will see these properties later
	private CommonsMultipartFile fileData;
	private byte[] fileDataBytes;
	private String fileName;
	private String fileContentType;
          //getters and setters for all attributes.
	}

Step 2: Creating the Mybatis interfaces and mapper xmls.

Lets start with the interfaces. We had 2 interfaces BaseMapperInterface and EmployeeMapperInterface. Of these, BaseMapperInterface is a marker interface.

We will go straight to the EmployeeMapperInterface. There are multiple ways to do it, so I will show the legacy way first.

package com.mybatis.dao;
public interface EmployeeMapperInterface extends BaseMapperInterface{
public List getEmployeeWithId(Long id);
public int insertEmployee(Employee e);
public int updateEmployee(Employee e);
public void deleteEmployee(Long id);
//other data access methods.
}

Now, we will define the EmployeeMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mybatis.dao.EmployeeMapperInterface">
<select id="getEmployeeWithId" parameterType="Long" resultType="emp">
		select empId,firstname,lastname,emailId as email,birthday,fileName from
		employee where
		1=1
		<if test="_parameter != null">
			AND empId=#{empId}
		</if>
		order by empId
	</select>
<insert id="insertEmployee" parameterType="com.spring.model.Employee">
		<!--
			the interface returns int, not long otherwise typecast errors were
			thrown
		-->
		<selectKey keyProperty="empId" resultType="Long" order="BEFORE">
			select Hibernate_Sequence.nextval as empId from dual
  		</selectKey>

		insert into employee(empId,firstname,lastname,emailId,filename,fileContentType,fileData )
		values(#{empId},#{firstname},#{lastname},#{email},#{fileData.originalFilename,jdbcType=VARCHAR},
			#{fileData.contentType,jdbcType=VARCHAR},#{fileData.bytes} )

	</insert>

   <update id="updateEmployee" parameterType="com.spring.model.Employee">
		update employee
		<set>
			<if test="firstname != null">firstname= #{firstname,jdbcType=VARCHAR},</if>
			<if test="lastname != null">lastname =#{lastname,jdbcType=VARCHAR},</if>
			<if test="email != null">emailid = #{email,jdbcType=VARCHAR},</if>
			<if test="fileData.originalFilename != null">filename = #{fileData.originalFilename,jdbcType=VARCHAR},</if>
			<if test="fileData.ContentType != null">fileContentType =	#{fileData.contentType,jdbcType=VARCHAR},</if>
			<if test="fileData.bytes != null">fileData = #{fileData.bytes},</if>
		</set>
		where
		empId = #{empId}
	</update>

	<delete id="deleteEmployee" parameterType="Long">
		delete from employee
		where empId = #{empId}
	</delete>

Now, if you see the above xml, you would see a select, insert , update and delete tags. Don’t worry, we would go through each of them later in this tutorial. For now, we shall see the implementation of this interface.

Step 3 : Legacy way of defining the implementation of the interface.
Remember MapperFactoryBean on the 1st post? Its not required if we follow this legacy approach and the changed mapper-config.xml will look like below

<bean id="employeeMapper" class ="com.mybatis.dao.EmployeeMapperImpl">
   <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

Obviously we should still follow the inheritance strategy as defined earlier and this referring of sqlSessionFactory directly by employeeMapper is just for demonstration purposes.

I say this legacy because with MapperFactoryBean this is no longer required. We can work only with the interfaces. However, life is not always so beautiful and we have to look at old code in order to debug , maintain or improve.


public class EmployeeMapperImpl extends SqlSessionDaoSupport implements EmployeeMapperInterface{

	@Override
	public void deleteEmployee(Long id) {
		getSqlSession().delete("com.mybatis.dao.EmployeeMapperInterface.deleteEmployee", id);
	}

	@Override
	public List<Employee> getEmployeeWithId(Long id) {
		return getSqlSession().selectList("com.mybatis.dao.EmployeeMapperInterface.getEmployeeWithId", id);

	}

	@Override
	public int insertEmployee(Employee e) {
		return getSqlSession().insert("com.mybatis.dao.EmployeeMapperInterface.insertEmployee", e);	}

	@Override
	public int updateEmployee(Employee e) {
		SqlSessionTemplate tm= (SqlSessionTemplate) getSqlSession();
		int id = tm.update("com.mybatis.dao.EmployeeMapperInterface.updateEmployee", e);
		//getSqlSession().rollback();
		return id;
	}
}

Now, before we start explaining, we would slightly veer off from the MVC pattern and we shall look at how a standalone Mybatis application would work . For a detailed explanation, please visit here.

At the core of all Mybatis stuff, is an SqlSessionFactory which opens and closes SqlSession. SqlSession is not thread safe and needs to be aligned with the HttpRequest cycle in case of a web application.

 For Spring Mybatis, there are 3 ways to retrieve the sessions :

i. extend SqlSessionDaoSupport as we are doing above and retrieve the session using getSqlSession(). This method returns a thread-safe SqlSession which we can use in our Spring transactions. Then, this session can be used to perform insert/update/delete/selectOne/selectList operations.

ii. We can also use SqlSessionTemplate. We can instantiate it in the database-config.xml

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

As can be seen above, it can also be instantiated by using the sqlSessionFactory.

SqlSession sqlSsession = new SqlSessionTemplate(sqlSessionFactory);

To summarize, SqlSessionTemplate should always be used instead of SqlSession because the base MyBatis SqlSession cannot participate in Spring transactions and is not thread safe. Switching between the two classes in the same application can cause data integrity issues.
Also, in step i. when we do getSqlSession, we actually retrieve SqlSessionTemplate, so the below statement would work fine.

SqlSessionTemplate tm= (SqlSessionTemplate) getSqlSession();

iii.  Finally the MapperFactoryBean
Instead of using SqlSessionDaoSupport or SqlSessionTemplate directly from the DAOs, we can use MapperFactoryBean to inject interface DAOs into our service classes.


<bean id="baseMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
	<property name="mapperInterface" 									value="com.mybatis.dao.BaseMapperInterface" />
	<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

<bean id="employeeMapper" parent="baseMapper">
<property name="mapperInterface" value="com.mybatis.dao.EmployeeMapperInterface" />
</bean>

The MapperFactoryBean handles creating an SqlSession as well as closing it. If there is a Spring transaction in progress, the session will also be committed or rolled back when the transaction completes. Finally, any exceptions will be translated into Spring DataAccessExceptions.

MapperScannerConfigurer and automatically marking the interfaces

Finally, those of you who don’t want to spend lots of time in wiring down each and every interface bean, can use the MapperScannerConfigurer which allows you to automatically scan the directories and mark the mapper interfaces.
1. Define the MapperScannerConfigurer and mark the basePackage.


<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
     <property name="basePackage" value="org.mybatis.spring.sample.mapper" />
</bean>

2. The mapper interfaces which are referred in the service classes need to be autowired, since their references will no longer be present in the mapper-config.xml

3. Finally, sqlSessionFactory also needs to be configured(contrary to the Mybatis manual), else the mappers will not work and will throw this exception :

org.springframework.beans.factory.BeanCreationException: Error creating bean with name ’employeeMapperInterface’ defined in file [C:\anirban\Work_Ani\SpringIntegration\WebRoot\WEB-INF\classes\com\mybatis\dao\EmployeeMapperInterface.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Property ‘sqlSessionFactory’ or ‘sqlSessionTemplate’ are required

Note: There was an issue with PropertyPlaceHolderConfigurer not working when used in conjunction with mybatis-spring-3.1.0. Its resolved by replacing with mybatis-spring-3.1.1 bundle.

Thats it folks for the Mybatis stuff for this session. Now, lets jump to the Spring related changes . Recall that ,                                                                                             

 Request –> Spring MVC controller –> accesses Service facade –> accesses the DAO xml/interface.

We are done with the last leg(DAO xml/interface) and we will now construct the Service facade.
As always to encourage good coding practices, we will create an interface for the EmployeeService.

public interface EmployeeBaseService {
	public Employee getEmployeeById(long empId);
	public long saveEmployee(Employee employee) throws Exception;
	public void deleteEmployee(long empId);

}

Now, the actual implementation of the service:

@Service
public class EmployeeService implements EmployeeBaseService{

	@Autowired
	EmployeeMapperInterface employeeMapper;
         //getters, setters
         @Override
	public Employee getEmployeeById(long empId){
		//retrieve from database
		List empList = employeeMapper.getEmployeeWithId(empId);
		if(empList != null && empList.size()>0){
			return (Employee) empList.get(0);
		}
		return null;

	}

	@Override
	public long saveEmployee(Employee employee){
		long empId = 0l;
		if(employee.getEmpId()==0){
			empId  = new Long( employeeMapper.insertEmployee(employee));
		}else{
			 employeeMapper.updateEmployee(employee);
			 empId  =  employee.getEmpId();
		}
		return empId;
	}

	@Override
	public void deleteEmployee(long empId) {
		employeeMapper.deleteEmployee(empId);

	}

Annotated Employee Controller

@Controller
public class EmployeeController {
	@Autowired
	private EmployeeBaseService employeeService;

          @RequestMapping(value = "/employeeHome",method=RequestMethod.GET)
	public String displayHomePage(@RequestParam("empId") long empId,ModelMap m){
		System.out.println("employeeHome>> "+empId);
		//retrieve the employee with this id.
		Employee employee = null;
		Employee empMap = employeeService.getEmployeeByIdMap(empId);
		if(empId ==0){
			employee = new Employee();
		}else{
			employee = employeeService.getEmployeeById(empId);

		}
		m.addAttribute("employee",employee);
		System.out.println(employee);
		return ProjectConstants.SECURE_FOLDER+"employeeHome";
	}
         @RequestMapping(value="/employeeEdit",method=RequestMethod.POST)
	public String saveEmployee(@ModelAttribute("employee")
                Employee emp,  BindingResult br) throws Exception{
		if(br.hasErrors()){
			return "employeeHome";
		}
		System.out.println(emp);

		employeeService.saveEmployee(emp);
		return "redirect:employeeHome.html?empId="+emp.getEmpId();
	}

Ok, lets go over it. The controller contains a reference to the EmployeeBaseService and this is autowired.
First the displayHomePage() which is accessed via a GET.

 

Points to note :

1. @RequestParam(“”) is used to bind a request parameter to a method parameter.  If a RequestParam is specified, it becomes mandatory for the URI to contain it.Else, can  be used as @RequestParam(value=””,required=false)

2.  This method has a signature :                                                                                              String * (@RequestParam(“<param>”) datatype method-arg,ModelMap model)

3.  ModelMap.addAttribute(“paramName”,<param>). This paramName is important. As it has to be set in the view as the modelattribute, else the jsp will not be rendered and the following exception will come:
Neither BindingResult nor plain target object for bean name ‘command’ available as request attribute
4.  Returns the viewname , so essentially there would be a employeeHome.jsp lying around under ProjectConstants.SECURE_FOLDER.

5. @RequestMapping(value = “/employeeHome”,method=RequestMethod.GET) means this method would be invoked when we have a call like :
http://localhost:8080/springProj/employeeHome.html?empId=92178033
Note : the request parameter being passed.

Next, the saveEmployee() which is accessed via the POST.

  1. Annotation @ModelAttribute as the method parameter. This is used to retrieve the command object, after filling out the form.
  2. Returns a redirect to the url which is going to be picked up the get method, the redirect prevents an accidental re-update upon refreshing the screen.
  3.  @RequestMapping(value=”/employeeEdit”,method=RequestMethod.POST) will invoke this method, meaning there must be a form whose action would point to employeeEdit.html

Finally, lets complete this tutorial by showing the view (*. jsp) which will render the model.

Spring ModelAttribute JSP

Features to be noted in this jsp:

  1. modelAttribute “emp” is necessary as mentioned earlier.
  2. <form:form method=”post” action=”employeeEdit.html”  , so basically this will look like an employee personal details update form where the employee is redirected to his/her view page after the update.

 

Right that’s it.

Advertisements
Comments
  1. […] Anirban's Empire! About work, play and life in between! Skip to content HomeAbout Me ← Creating Mybatis Mapper xmls and usage of MapperFactoryBean, MapperScannerConfigurer to retri… […]

  2. Anand Roongta says:

    Can you please tell me how would I configure MapperScannerConfigurer to read from an XML rather than a interface.. ?

  3. Xunzi says:

    Thanks this tutorial was more useful than the myBatis-Spring “tutorial”.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s