While working with a recent customer, a set of database tables had been namespaced differently in the QA and production environments. Ideally, the namespaces should mirror one another. Unfortunately the DB replication topology was constrained to one slave host, and there would have been a namespace collision. Shucks! In this post, I’ll explain how we managed namespaces for different environments using the MyBatis mapping layer with Spring configuration.
By decomposing the XML configuration, it’s possible to select desired namespaces via Spring profiles. For reference, Spring profiles effectively group bean definitions, which are instantiated when the profiles are invoked.
We start by examining sample MyBatis mapper files. Let’s say we want to handle multiple namespaces for the “object” table:
common-methods.xml
1234567891011121314
<?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"><mappernamespace="com.mkhsueh.example.mapper"><sqlid="excludeFailed"> LEFT JOIN pp.object o
ON (failureCandidates.customer_id = o.source_id)
LEFT JOIN pp.object_type ot
ON ot.object_type_id = o.object_type_id
WHERE o.source_id IS NULL OR
(ot.type = 'Purchase' AND o.failure_count = 0)
</sql></mapper>
A tag denotes SQL fragments that can be reused within the same file, or any other file loaded by the same session factory. The first thing we do is abstract the namespace into a SQL fragment, such that we can refer to it via the refid.
Represent the namespace fragments in their own configuration files:
<?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"><mappernamespace="com.mkhsueh.example.mapper.namespace"><sqlid="purchase"> pp.
</sql></mapper>
Now we substitute the hardcoded namespace with references:
common-methods.xml
1234567891011121314
<?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"><mappernamespace="com.mkhsueh.example.mapper"><sqlid="excludeFailed"> LEFT JOIN <includerefid="com.mkhsueh.example.mapper.namespace.purchase"/>object o
ON (failureCandidates.customer_id = o.source_id)
LEFT JOIN <includerefid="com.mkhsueh.example.mapper.namespace.purchase"/>object_type ot
ON ot.object_type_id = o.object_type_id
WHERE o.source_id IS NULL OR
(ot.type = 'Purchase' AND o.failure_count = 0)
</sql></mapper>
At this step, the key observation is that our mapping files are encapsulated in MyBatis session factories, and those factories can be tied to profiles.
<?xml version="1.0" encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!-- imports --><importresource="classpath:/spring/spring-datasource.xml"/><!-- dao; by default, use prod namespace --><beanid="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean"><propertyname="dataSource"ref="dataSource"/><propertyname="mapperLocations"><list><value>classpath:/mapper/service-methods.xml</value><value>classpath:/mapper/common-methods.xml</value><value>classpath:/mapper/namespace-prod.xml</value></list></property><propertyname="typeHandlers"><list><beanclass="com.mkhsueh.example.DateTimeTypeHandler"/></list></property></bean><!-- profile to use prod namespace --><beansprofile="prod"><beanid="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean"><propertyname="dataSource"ref="dataSource"/><propertyname="mapperLocations"><list><value>classpath:/mapper/service-methods.xml</value><value>classpath:/mapper/common-methods.xml</value><value>classpath:/mapper/namespace-prod.xml</value></list></property><propertyname="typeHandlers"><list><beanclass="com.mkhsueh.example.DateTimeTypeHandler"/></list></property></bean></beans><!-- profile to use qa namespace --><beansprofile="qa"><beanid="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean"><propertyname="dataSource"ref="dataSource"/><propertyname="mapperLocations"><list><value>classpath:/mapper/service-methods.xml</value><value>classpath:/mapper/common-methods.xml</value><value>classpath:/mapper/namespace-qa.xml</value></list></property><propertyname="typeHandlers"><list><beanclass="com.mkhsueh.example.DateTimeTypeHandler"/></list></property></bean></beans></beans>