通过纯代码构建 MyBatis 的 Configuration 时,如何实现类似 mapper locations 的配置?

本文基于 MyBatis 3.5.5 进行

问题描述

使用纯代码创建 Configuration 时,如何配置 XML Mapper 文件?

Mapper Interface 的 SQL 语句主要的配置方式常用的方式主要有 2 种:

  1. 通过 @Select 等注解配置
  2. 通过 Mapper XML 文件配置

本文内容与方式 2 有关。

平时使用 SpringBoot + MyBatis (Plus) 指定 XML Mapper 的位置一般利用配置文件指定,如下:

# MyBatis
mybatis.mapperLocations = classpath*:/mapper/**/*.xml

# MyBatis Plus
mybatis-plus.mapperLocations = classpath*:/mapper/**/*.xml

如果使用 MyBatis Configuration XML 指定 XML Mapper 的位置,一般配置如下:

<!-- Using classpath relative resources -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

<!-- Using url fully qualified paths -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>

如果使用纯代码配置 Configuration,又该如何指定 XML Mapper 配置?
本人未能在官方文档上找到相关配置方法,可能是我遗漏了或者文档中没有说明。

有个 Mapper Interface 如下:

public interface OrderMapper {
    @Select("select * from t_order")
    List<Order> findAll();
    Order findById(Long id);
}

有个 XML Mapper 如下:

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="vip.wuweijie.hello.mybatis.mapper.OrderMapper">
    <select id="findById" resultType="vip.wuweijie.hello.mybatis.entity.Order">
        select * from t_order where id = #{id}
    </select>
</mapper>

通过代码构建 Configuration 配置 MyBatis 的 SqlSessionFactory

        DataSource dataSource = new PooledDataSource("org.postgresql.Driver", "jdbc:postgresql://pg.sia.lo:5432/hello_quarkus", "postgres", "postgres");
        Environment environment = new Environment("dev", new JdbcTransactionFactory(), dataSource);
        Configuration configuration = new Configuration(environment);
        configuration.addMappers("vip.wuweijie.hello.mybatis.mapper");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

问题来了:configuration 中有 addMapper, addMappers 等方法,但是参数只能传入 package 或者 Class

如果此时执行以下代码,只能执行 mapper.findAll(),执行 mapper.findById(1L) 将会找不到 SQL:

        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
            OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
            System.out.println(mapper.findAll());
            System.out.println(mapper.findById(1L));
        }

执行结果:

[Order{id=1, orderTime=null, personName='null', orderType='null', remark='null'}, Order{id=2, orderTime=null, personName='null', orderType='null', remark='Double'}, Order{id=3, orderTime=null, personName='null', orderType='null', remark='From Post'}]

Exception in thread "main" org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): vip.wuweijie.hello.mybatis.mapper.OrderMapper.findById
	at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:235)
	at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:53)
	at org.apache.ibatis.binding.MapperProxy.lambda$cachedInvoker$0(MapperProxy.java:115)
	at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1705)
	at org.apache.ibatis.binding.MapperProxy.cachedInvoker(MapperProxy.java:102)
	at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:85)
	at com.sun.proxy.$Proxy18.findById(Unknown Source)
	at vip.wuweijie.hello.mybatis.MybatisApplication.main(MybatisApplication.java:45)

这个报错很简单,明显就是 XML Mapper 文件没有跟 Mapper 绑定,使用 Configuration XML 或者其他框架的封装解决仅仅是 加一行配置 的事情。

但现在要使用代码构建 Configuration,具体应该怎么做?
configuration 没有配置类似于 mapperLocations 的方法,也没有指定 XML Mapper 的方法,难道一定要通过 XMLConfig 配置 MyBatis 才能使用 XML Mapper 吗?


分析过程

先去看看 XMLConfigBuilder 是怎么做的。

org.apache.ibatis.builder.xml.XMLConfigBuilder代码节选:

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

parseConfiguration 方法最底下的 mapperElement 是解析 mappers 标签下内容的方法,即 XML Configuration 的这部分内容:

<mappers>
  <mapper resource="mapper/OrderMapper.xml"/>
  <mapper url="file:///var/mapper/OrderMapper.xml"/>
  <mapper class="vip.wuweijie.hello.mybatis.mapper.OrderMapper"/>
</mappers>

继续跟进:

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
          // 解析属性为 resource 的 mapper
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
          // 解析属性为 url 的 mapper
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
          // 解析属性为 class 的 mapper
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

了解了 XMLConfigBuilder 是怎么做的之后,我们构建 Configuration 时可以使用同样的方法:

最后将配置补充完整并执行:

        DataSource dataSource = new PooledDataSource("org.postgresql.Driver", "jdbc:postgresql://pg.sia.lo:5432/hello_quarkus", "postgres", "postgres");
        Environment environment = new Environment("dev", new JdbcTransactionFactory(), dataSource);
        Configuration configuration = new Configuration(environment);
        configuration.addMappers("vip.wuweijie.hello.mybatis.mapper");

		// 解析 XML Mapper 文件
        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(MybatisApplication.class.getResourceAsStream("/mapper/OrderMapper.xml"), configuration, "mapper/OrderMapper.xml", configuration.getSqlFragments());
        xmlMapperBuilder.parse();

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
        OrderMapper mapper;
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
            mapper = sqlSession.getMapper(OrderMapper.class);
            System.out.println(mapper.findAll());
            System.out.println(mapper.findById(1L));
        }

执行结果:

[Order{id=1, orderTime=null, personName='null', orderType='null', remark='null'}, Order{id=2, orderTime=null, personName='null', orderType='null', remark='Double'}, Order{id=3, orderTime=null, personName='null', orderType='null', remark='From Post'}]
Order{id=1, orderTime=null, personName='null', orderType='null', remark='null'}

总结

使用 XML Configuration 配置 XML Mapper 的方式:

<mappers>
  <mapper resource="mapper/OrderMapper.xml"/>
</mappers>

以下代码等价于在 XML Configuration 中配置 XML Mapper 的位置:

XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(MybatisApplication.class.getResourceAsStream("/mapper/OrderMapper.xml"), configuration, "mapper/OrderMapper.xml", configuration.getSqlFragments());
xmlMapperBuilder.parse();
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wuweijie@apache.org

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值