MyBatis 多数据源配置问题

MyBatis multi-datasource configuration issue

使用mybatis 3.2.8,Spring 3.2.9 release,mybatis-spring 1.2.2配置我项目的DAO和项目需要方便的都mysql 和 oracle 所以我使用 databaseIdProvider 来适应多数据源这是我的配置:

  1. 数据源

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

  1. 供应商属性

<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>

因为mysql的分页和oracle的分页不同所以我写了两种分页方式根据运行环境选择使用which.here是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>

并像这样使用它:

<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>

以上就是我的全部配置。 当我在 运行 查询操作或任何其他 DML 时,控制台将打印:

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

我发现这个异常是由于 org.apache.ibatis.session.Configuration.sqlFragments 没有键值 'PagePrefix' 引起的。 所以我按照打击步骤找出问题所在:

  1. 我的本地环境是oracle,所以我修改了mapper.xml,删除了mysql配置,只剩下oracle配置:

<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>

还是不行,异常依旧

  1. 然后我想可能是mybatis没有识别我的运行环境,所以我查看了源代码,看看是mybatis设置了信息还是not.i找到了启动 class org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory() 方法有:

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

我在这里切换断点,发现值是正确的。

  1. 然后我开始查找sql​​Fragments被分配的位置和时间value.i发现在解析mapper.xml时分配了sqlFragments: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);
        }
      }

和方法sqlElement对sqlFragments进行实际赋值并根据databaseId找到标签''

    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);
        }
      }

但那时还没有设置 databaseId,因为解析阶段在我之前提到的 setDatabaseId 操作之前。 现在我不知道如何解决这个问题problem.does有没有人遇到过和我一样的情况?

我设法创建了一个工作示例。诀窍是为属性获取正确的地图。您必须能够指定

DataSource().getConnection().getMetaData().getDatabaseProductName()

returns 并将其用作 属性 文件中的密钥 我的 postgres 映射是

<bean id="vendorProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="properties">
        <props>
            <prop key="PostgreSQL">postgres</prop>
            <prop key="Oracle">oracle</prop>
        </props>
    </property>
</bean>

PostgresSQL 是

的结果
DataSource().getConnection().getMetaData().getDatabaseProductName()

在我的映射文件中

<sql databaseId="postgres" id="PagePrefix">
    select sum(a) from (
</sql>

并且已正确加载

我发现问题是导致我在 mybatis-config.xml 中编写了映射器标签,这样 mybatis 将进行自己的配置,尽管 Spring 的 configure.so 如果你想通过 Spring 使用多数据源,你不能写 mybatis 自己的配置 xml.