本文基于
MyBatis 3.5.5
进行
使用纯代码创建
Configuration
时,如何配置XML Mapper
文件?
Mapper Interface
的 SQL 语句主要的配置方式常用的方式主要有 2 种:
@Select
等注解配置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
的位置,一般配置如下:
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
mappers>
<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
如下:
<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.(MapperMethod.java:235)
at org.apache.ibatis.binding.MapperMethod.(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();