jeudi 19 mars 2015

MyBatis multi-datasource configuration issue

Using mybatis 3.2.8 ,Spring 3.2.9 release,mybatis-spring 1.2.2 to configure my project's DAO and the project need to convenient both mysql and oracle so i use the databaseIdProvider to adapt multi-datasource here is my configuration:



  1. datasource





<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>xxxxx</value>
</property>
</bean>




  1. vendorProperties





<bean id="vendorProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="properties">
<props>
<prop key="SQL Server">sqlserver</prop>
<prop key="DB2">db2</prop>
<prop key="Oracle">oracle</prop>
<prop key="MySQL">mysql</prop>
<prop key="H2">h2</prop>
</props>
</property>
</bean>




  1. databaseIdProvider





<bean id="databaseIdProvider" class="org.apache.ibatis.mapping.VendorDatabaseIdProvider">
<property name="properties" ref="vendorProperties" />
</bean>




  1. sqlSessionFactory





<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="databaseIdProvider" ref="databaseIdProvider" />
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
</bean>



because mysql's paging is different from oracle's so i wrote two ways of paging and depends on runtime environment to chose to use which.here is the mapper.xml:





<sql databaseId="oracle" id="PagePrefix">
<!--
WARNING - @mbggenerated
This element is automatically generated by MyBatis Generator, do not modify.
-->
<if test="page != null">
select * from ( select row_.*, rownum rownum_ from (
</if>
</sql>
<sql databaseId="mysql" id="PagePrefix">
<!--
WARNING - @mbggenerated
This element is automatically generated by MyBatis Generator, do not modify.
-->
<if test="page != null">
select * from (
</if>
</sql>



and use it like this:





<select id="selectByExample" parameterType="xxx.xxx.xxx" resultMap="BaseResultMap">
<!--
WARNING - @mbggenerated
This element is automatically generated by MyBatis Generator, do not modify.
-->
<include refid="PagePrefix" />select
<if test="distinct">
distinct
</if>
from xxx
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<include refid="PageSuffix" />
</select>



the above is all my configuration. when i am running the query operation or any other DML,the console will print:





org.apache.ibatis.builder.IncompleteElementException: Could not find SQL statement to include with refid 'xxx.xxx.xxx.PagePrefix'


i find this exception was caused by org.apache.ibatis.session.Configuration.sqlFragments did not had the value that the key is 'PagePrefix'. so i following the blow steps to find what is wrong:



  1. my local environment is oracle,so i modified the mapper.xml, removed the mysql config just left the oracle config:





<sql databaseId="oracle" id="PagePrefix">
<!--
WARNING - @mbggenerated
This element is automatically generated by MyBatis Generator, do not modify.
-->
<if test="page != null">
select * from ( select row_.*, rownum rownum_ from (
</if>
</sql>



but it's also not work,the exception still remained.



  1. then i thought maybe the mybatis did not recognized my runtime environment,so i looked into the source code to find out the whether the information was set into the mybatis or not.i find the startup class org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory() method has:





if (this.databaseIdProvider != null) {
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}



and i toggled breakpoint in here and find the value was right.



  1. Then i started to find where and when the sqlFragments was assigned value.i found that sqlFragments was assigned when parsing the mapper.xml: org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement()





private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}



and the method sqlElement do the actual assign value on sqlFragments and according the databaseId to find the tag ''





private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) sqlFragments.put(id, context);
}
}



but in that time the databaseId hasn't been set yet beacuse the parsing phase is before the setDatabaseId operation what i mentioned before. Now i have no idea how to solve this problem.does any people encountered the same situation like me?


Aucun commentaire:

Enregistrer un commentaire