工具除了会用,还应该多做点。我觉得使用一个软件工具(开源类),一般会经历几个步骤:
1. 通过wiki了解大致作用,然后开始码代码;
2. 系统性地学习其特性,找出可能需要的点,用上去;
3. 通过阅读其源码,清楚其来龙去脉;
4. 有能力你就去超越别人;
mybatis作为orm框架给我们带来了很多方便,其定制功能也让我们惊喜!还是来看看别人怎么做到的吧!
1. 下载git仓库, https://github.com/mybatis/mybatis-3
2. 打开IDE, 找到 test 包
3. 进入 org.apache.ibatis.autoconstructor.AutoConstructorTest, 有一个完整的sql 样例
public class AutoConstructorTest {
private static SqlSessionFactory sqlSessionFactory;
@BeforeClass
public static void setUp() throws Exception {
// create a SqlSessionFactory
try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml")) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
// populate in-memory database
BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
"org/apache/ibatis/autoconstructor/CreateDB.sql");
}
@Test
public void fullyPopulatedSubject() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
final Object subject = mapper.getSubject(1);
assertNotNull(subject);
}
}
@Test(expected = PersistenceException.class)
public void primitiveSubjects() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
mapper.getSubjects();
}
}
@Test
public void annotatedSubject() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
verifySubjects(mapper.getAnnotatedSubjects());
}
}
@Test(expected = PersistenceException.class)
public void badSubject() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
mapper.getBadSubjects();
}
}
@Test
public void extensiveSubject() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
verifySubjects(mapper.getExtensiveSubject());
}
}
private void verifySubjects(final List<?> subjects) {
assertNotNull(subjects);
Assertions.assertThat(subjects.size()).isEqualTo(3);
}
}
目标方法:fullyPopulatedSubject(),最终进行一条 select 查询:
SELECT * FROM subject WHERE id = #{id}
首先执行的肯定 setup()方法了,setup()做了三件事:
1. 加载mybatis-config文件读取到reader中;
2. 将配置文件传递工厂builder中创建会话工厂;
3. 执行hsqldb内存数据库的初始化工作;(非本文重点)
接下来,咱们从源码的角度,主要看会话工厂的创建过程!
创建 sqlSessionFactory
try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml")) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
// SqlSessionFactoryBuilder.build(reader) -> XMLConfigBuilder.parse()
// org.apache.ibatis.session.SqlSessionFactoryBuilder
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
// 建立factory
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
// 加载完成后,上下文管理器重置
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
标签解析:
/**
* Offline entity resolver for the MyBatis DTDs
*
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class XMLMapperEntityResolver implements EntityResolver {
private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";
private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";
/**
* Converts a public DTD into a local one
*
* @param publicId The public id that is what comes after "PUBLIC"
* @param systemId The system id that is what comes after the public id.
* @return The InputSource for the DTD
*
* @throws org.xml.sax.SAXException If anything goes wrong
*/
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
try {
if (systemId != null) {
String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
} else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) {
return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId);
}
}
return null;
} catch (Exception e) {
throw new SAXException(e.toString());
}
}
private InputSource getInputSource(String path, String publicId, String systemId) {
InputSource source = null;
if (path != null) {
try {
InputStream in = Resources.getResourceAsStream(path);
source = new InputSource(in);
source.setPublicId(publicId);
source.setSystemId(systemId);
} catch (IOException e) {
// ignore, null is ok
}
}
return source;
}
}
parser
// org.apache.ibatis.parsing.XPathParser.XPathParser(), 设备一些必要参数
public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(reader));
}
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
}
});
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
// 在返回fatory的时候调用 return build(parser.parse()); 将 config.xml 的配置全部解析到 configuration 实例中
// 在返回fatory的时候调用 return build(parser.parse()); 将 config.xml 的配置全部解析到 configuration 实例中
// org.apache.ibatis.builder.xml.XMLConfigBuilder.parse()
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 根元素为 /configuration, 依次向下解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
// org.apache.ibatis.parsing.XPathParser.evalNode() 映射 xml 属性到 bean
public XNode evalNode(Object root, String expression) {
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
return new XNode(this, node, variables);
}
private Object evaluate(String expression, Object root, QName returnType) {
try {
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
具体解析项如下,从这里也一目了然,配置项支持什么:
// 解析各配置参数到实例中
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 这个配置没有用过吧
loadCustomVfs(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"));
// 重要节点,解析 mapper
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
下面依次来看一下都怎么解析各配置项的吧~
properties 解析
// propertiesElement(root.evalNode("properties")); url, resource 解析
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
// 解析完成后,放到 parser 和 configuration 实例中
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
settings 配置项解析,返回内容待处理
// Properties settings = settingsAsProperties(root.evalNode("settings"));
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
return props;
}
// loadCustomVfs(settings); 这个...
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
String value = props.getProperty("vfsImpl");
if (value != null) {
String[] clazzes = value.split(",");
for (String clazz : clazzes) {
if (!clazz.isEmpty()) {
@SuppressWarnings("unchecked")
Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
configuration.setVfsImpl(vfsImpl);
}
}
}
}
typeAliases 别名设置解析,主要做类型检查,及别名的注册工作
// typeAliasesElement(root.evalNode("typeAliases"));
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 针对package配置,需注册一系列别名,以 simpleName 代替
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
// org.apache.ibatis.type.TypeAliasRegistry.registerAlias(), 具体数据结构为 map 封装
public void registerAlias(Class<?> type) {
String alias = type.getSimpleName();
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
registerAlias(a