mybatis 源代码

mybatis 的使用非常的简单,简单的加载一下配置文件即可实现对数据库的 curd 操作,如下所示

  1. public class App {
  2. public static void main(String[] args) throws Exception {
  3. String resource = "config.xml";
  4. InputStream inputStream = Resources.getResourceAsStream(resource);
  5. SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
  6. SqlSessionFactory factory = sqlSessionFactoryBuilder.build(inputStream, null, null);
  7. try (SqlSession sqlSession = factory.openSession(true)) {
  8. int statement = sqlSession.insert("statement", "12");
  9. }
  10. }
  11. }

在这个简单的示例代码的背后,mybatis 为我们做了些什么,怎么做的。

配置文件说明

  • 加载并解析配置文件

配置文件分为2部分,分别是 configuration.xmlmapper.xml 部分,核心代码

  1. SqlSessionFactory factory = sqlSessionFactoryBuilder.build(inputStream, null, null);

简单一行即可实现 xml 的解析。内部解析顺序如下

  • 获取 configuration.xml (不一定是这个名字)
  • 解析 configuration.xml 获取 Configuration 对象
  • 分别解析 configuration.xml 里边的 properties , settings typeAliases plugins objectFactory objectWrapperFactory reflectorFactory environments databaseIdProvider typeHandlers
  • 解析 mapper.xml 集合

第三步的一般只会使用 typeAliases 一项,其他的基本上使用默认设置或者Spring整和之后的设置

解析配置文件

解析代码 —> SqlSessionFactoryBuilder#build

  1. public SqlSessionFactory build(Reader reader, @Nullable String environment, @Nullable Properties properties) {
  2. XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
  3. Configuration configuration = parser.parse(); //核心
  4. return build(configuration);
  5. }

核心代码 ——> XMLConfigBuilder#parse

  1. public Configuration parse() {
  2. //获取root节点 configuration并解析
  3. XNode xNode = parser.evalNode("/configuration");
  4. parseConfiguration(xNode);
  5. return configuration;
  6. }

解析节点配置信息,XMLConfigBuilder#parseConfiguration , 解析之前需要说明—mybatis 的Configuration对象是全局唯一的,解析配置文件本质是为了获取 Configuration 实例对象,即 使用xml中的节点信息去填充 Configuration 实例中对应的字段 ,例如别名注册,其实就是一个Map<别名,class>。

  1. private void parseConfiguration(XNode root) {
  2. try {
  3. //先解析properties文件,和Spring整合之后我还没有看到使用的,我司主要使用xbatis+mysql
  4. //基本不用,maven的穿透更加好用
  5. propertiesElement(root.evalNode("properties"));
  6. //解析settings配置,我是一般没有用
  7. Properties settings = settingsAsProperties(root.evalNode("settings"));
  8. loadCustomVfs(settings);
  9. //别名,这个是需要的,简化后续的statement,简单
  10. typeAliasesElement(root.evalNode("typeAliases"));
  11. //插件,执行拦截使用,特殊需求可能会用到
  12. pluginElement(root.evalNode("plugins"));
  13. //一下节点,一般没有使用过
  14. objectFactoryElement(root.evalNode("objectFactory"));
  15. objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  16. reflectorFactoryElement(root.evalNode("reflectorFactory"));
  17. settingsElement(settings);
  18. environmentsElement(root.evalNode("environments"));
  19. databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  20. typeHandlerElement(root.evalNode("typeHandlers"));
  21. //解析mappers节点,mybatis核心
  22. mapperElement(root.evalNode("mappers"));
  23. } catch (Exception e) {
  24. throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  25. }
  26. }

解析别名节点

  1. private void typeAliasesElement(XNode parent) {
  2. if (parent != null) {
  3. for (XNode child : parent.getChildren()) {
  4. //package 扫描包下的bean,进行注册
  5. if ("package".equals(child.getName())) {
  6. String typeAliasPackage = child.getStringAttribute("name");
  7. configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
  8. } else {
  9. //单独注册
  10. String alias = child.getStringAttribute("alias");
  11. String type = child.getStringAttribute("type");
  12. Class<?> clazz = Resources.classForName(type);
  13. if (alias == null) {
  14. // this.typeAliasRegistry = this.configuration.getTypeAliasRegistry(); , 填充 Configuration 的 typeAliasRegistry 字段
  15. typeAliasRegistry.registerAlias(clazz);
  16. } else {
  17. typeAliasRegistry.registerAlias(alias, clazz);
  18. }
  19. }
  20. }
  21. }
  22. }

解析 mapper.xml 文件

解析 mapper.xml 这里以 mapper resource = “xxxxx” 为例

  1. private void mapperElement(XNode parent) throws Exception {
  2. if (parent != null) {
  3. for (XNode child : parent.getChildren()) {
  4. //package
  5. if ("package".equals(child.getName())) {
  6. String mapperPackage = child.getStringAttribute("name");
  7. configuration.addMappers(mapperPackage); //扫描 mapper
  8. } else {
  9. //resource,url class 只能同时存在一个
  10. String resource = child.getStringAttribute("resource");
  11. String url = child.getStringAttribute("url");
  12. String mapperClass = child.getStringAttribute("class");
  13. if (resource != null && url == null && mapperClass == null) {
  14. ErrorContext.instance().resource(resource);
  15. //读取xx_mapper.xml
  16. InputStream inputStream = Resources.getResourceAsStream(resource);
  17. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
  18. //解析xxx/mapper.xml
  19. mapperParser.parse();
  20. } else if (resource == null && url != null && mapperClass == null) {
  21. ErrorContext.instance().resource(url);
  22. InputStream inputStream = Resources.getUrlAsStream(url);
  23. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
  24. mapperParser.parse();
  25. } else if (resource == null && url == null && mapperClass != null) {
  26. Class<?> mapperInterface = Resources.classForName(mapperClass);
  27. //使用接口的模式,xxxMapper
  28. configuration.addMapper(mapperInterface);
  29. } else {
  30. throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
  31. }
  32. }
  33. }
  34. }
  35. }

开始解析

  1. public void parse() {
  2. //查看是不是已经被加载过了
  3. if (!configuration.isResourceLoaded(resource)) {
  4. //解析mapper root节点,以/开头,解析基本的
  5. configurationElement(parser.evalNode("/mapper"));
  6. //添加到loadedResources中,set集合
  7. configuration.addLoadedResource(resource);
  8. //搭建命名空间 namespace 需要唯一
  9. bindMapperForNamespace();
  10. }
  11. parsePendingResultMaps();
  12. parsePendingCacheRefs();
  13. parsePendingStatements();
  14. }

解析 mapper.xml 的属性

  1. /**
  2. * 解析过程还是和configuration差不多,一个节点一个节点解析
  3. */
  4. private void configurationElement(XNode context) {
  5. try {
  6. //获取namespace属性 , namespace 不能为null的,也不能为empty的
  7. String namespace = context.getStringAttribute("namespace");
  8. if (namespace == null || namespace.equals("")) {
  9. throw new BuilderException("Mapper's namespace cannot be empty");
  10. }
  11. // 节点 cache-ref | cache | resultMap* | parameterMap* | sql* | insert* | update* | delete* | select*,+*表示可多个
  12. //当前mapper设置namespace
  13. builderAssistant.setCurrentNamespace(namespace);
  14. //缓存引用,和cache两个节点使用的都不多,可以说基本没用过
  15. cacheRefElement(context.evalNode("cache-ref"));
  16. //缓存
  17. cacheElement(context.evalNode("cache"));
  18. //主要使用的不多,目前见到的是少部分使用场景,1、获取parameterMap节点群,>=0个节点,2、解析节点
  19. parameterMapElement(context.evalNodes("/mapper/parameterMap"));
  20. //用的最多,也是mybatis的核心
  21. resultMapElements(context.evalNodes("/mapper/resultMap"));
  22. //sql,必使用,动态sql依赖
  23. sqlElement(context.evalNodes("/mapper/sql"));
  24. //curd 4个节点,绑定使用
  25. buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  26. } catch (Exception e) {
  27. throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  28. }
  29. }

解析resultMap

  1. private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
  2. //正在解析那个mapper文件,如果发生异常,将抛出这个ErrorContext中的内容,这种思路可以学习
  3. ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
  4. //resultMap 没有id会自动产生一个,强烈建议设置一个,dtd文件的验证一般都是有id属性的,确保万无一失,xbatis团队还是加了这个
  5. String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
  6. //type类型是必须的,应该是dtd改了,为了兼容以前的情况加上了这套逻辑
  7. String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType"))));
  8. //是否继承了哪个resultMap,复用
  9. String extend = resultMapNode.getStringAttribute("extends");
  10. //是否自动映射
  11. Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
  12. Class<?> typeClass = resolveClass(type);
  13. Discriminator discriminator = null;
  14. List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
  15. //所有字节点
  16. List<XNode> resultChildren = resultMapNode.getChildren();
  17. //constructor?,id*,result*,association*,collection*, discriminator?
  18. for (XNode resultChild : resultChildren) {
  19. //构造器
  20. if ("constructor".equals(resultChild.getName())) {
  21. processConstructorElement(resultChild, typeClass, resultMappings);
  22. }
  23. //discriminator
  24. else if ("discriminator".equals(resultChild.getName())) {
  25. discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
  26. } else {
  27. List<ResultFlag> flags = new ArrayList<>();
  28. //id
  29. if ("id".equals(resultChild.getName())) {
  30. flags.add(ResultFlag.ID);
  31. }
  32. //核心还是搭建ResultMapping,一个resultMapping的节点 对应了 一个ResultMapping的实例
  33. ResultMapping resultMapping = buildResultMappingFromContext(resultChild, typeClass, flags);
  34. resultMappings.add(resultMapping);
  35. }
  36. }
  37. //一个完整的 resultMapping 就搭建完成了
  38. ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
  39. try {
  40. return resultMapResolver.resolve();
  41. } catch (IncompleteElementException e) {
  42. configuration.addIncompleteResultMap(resultMapResolver);
  43. throw e;
  44. }
  45. }

buildResultMappingFromContext 方法

  1. /** resultMap 所有的节点属性都在这里了
  2. * @param context resultMapping 字节点,
  3. * @param resultType resultMapping 对应的类型,这个类型一般都是简化过的
  4. * @param flags flags
  5. */
  6. private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
  7. String property;
  8. //有没有构造器,目前从我工作角度来看,几乎没有使用到构造器的地方,但是肯定有某些场景是使用这个的
  9. if (flags.contains(ResultFlag.CONSTRUCTOR)) {
  10. property = context.getStringAttribute("name");
  11. } else {
  12. //property 节点
  13. property = context.getStringAttribute("property");
  14. }
  15. //改属性对应的类名,获取所有的属性
  16. String column = context.getStringAttribute("column");
  17. String javaType = context.getStringAttribute("javaType");
  18. String jdbcType = context.getStringAttribute("jdbcType");
  19. String nestedSelect = context.getStringAttribute("select");
  20. String nestedResultMap = context.getStringAttribute("resultMap",processNestedResultMappings(context, Collections.<ResultMapping>emptyList()));
  21. String notNullColumn = context.getStringAttribute("notNullColumn");
  22. String columnPrefix = context.getStringAttribute("columnPrefix");
  23. String typeHandler = context.getStringAttribute("typeHandler");
  24. String resultSet = context.getStringAttribute("resultSet");
  25. String foreignColumn = context.getStringAttribute("foreignColumn");
  26. boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
  27. Class<?> javaTypeClass = resolveClass(javaType);
  28. @SuppressWarnings("unchecked")
  29. Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
  30. JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
  31. //除非有什么特殊的不行的业务需求,才需要加入自定义的类型处理器,最终还是放到 configuration 实例当中
  32. return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  33. }

解析 4个 curd 节点

  1. //解析curd节点,属于解析当中最为复杂的部
  2. public void parseStatementNode() {
  3. //获取节点id
  4. String id = context.getStringAttribute("id");
  5. //数据库标示,99的情况是nul
  6. String databaseId = context.getStringAttribute("databaseId");
  7. if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
  8. return;
  9. }
  10. //-----------------节点属性解析
  11. Integer fetchSize = context.getIntAttribute("fetchSize");
  12. Integer timeout = context.getIntAttribute("timeout");
  13. //parameterMap parameterType 这两个我用起来一直不知道他们的区别是什么
  14. String parameterMap = context.getStringAttribute("parameterMap");
  15. //map 已经被移除了,但是type还是用的比较多的,例如常用的hashmap,map,list,array
  16. String parameterType = context.getStringAttribute("parameterType");
  17. Class<?> parameterTypeClass = resolveClass(parameterType);
  18. //同理还有这两个,区分度要大一些,但是新人还是会迷糊,虽然xbatis的文档不错
  19. String resultMap = context.getStringAttribute("resultMap");
  20. String resultType = context.getStringAttribute("resultType");
  21. String lang = context.getStringAttribute("lang");
  22. //
  23. LanguageDriver langDriver = getLanguageDriver(lang);
  24. Class<?> resultTypeClass = resolveClass(resultType);
  25. String resultSetType = context.getStringAttribute("resultSetType");
  26. StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  27. ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  28. //----------------节点属性解析
  29. //---------------节点数据
  30. String nodeName = context.getNode().getNodeName();
  31. SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  32. boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  33. //是否刷新缓存 是否使用缓存
  34. boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  35. boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  36. boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
  37. //----------------
  38. //----------------解析前先处理include子节点
  39. XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  40. //Node
  41. includeParser.applyIncludes(context.getNode());
  42. //解析 selectKey节点
  43. processSelectKeyNodes(id, parameterTypeClass, langDriver);
  44. //解析 xml/annotation 获取sql
  45. SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  46. //-----结果集
  47. String resultSets = context.getStringAttribute("resultSets");
  48. String keyProperty = context.getStringAttribute("keyProperty");
  49. String keyColumn = context.getStringAttribute("keyColumn");
  50. //-----结束
  51. KeyGenerator keyGenerator;
  52. String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  53. keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  54. if (configuration.hasKeyGenerator(keyStatementId)) {
  55. keyGenerator = configuration.getKeyGenerator(keyStatementId);
  56. } else {
  57. keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
  58. configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
  59. ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  60. }
  61. builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
  62. fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
  63. resultSetTypeEnum, flushCache, useCache, resultOrdered,
  64. keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  65. }

到达这一步,mybatis xml文件就解析完成了,基本上只大概的谈了一下一些基本的属性的解析,不过只要能够理解 mybatis 的解析过程,后续的执行还是蛮好理解的,而且 mybatis 的源代码相对来说还是较为简单的。sql 节点没有说明,因为这里还是和设置参数结合起来讲述会简单一下,如果直接过一篇,反而没有多大的效果。

2018-10-12