mybatis源码解析

mybatis源码解析

准备工作:

mybatis官网,学习基本的用法。

http://www.mybatis.org/mybatis-3/zh/statement-builders.html

eclipse中搭建环境,新建demo项目。

代码

 package cn.howso.mybatis;


import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import cn.howso.mybatis.mapper.UserMapper;
import cn.howso.mybatis.model.User;


public class Index {


    public static void main(String[] args) throws IOException {
        String resource = "configuration.xml";
        Reader reader = Resources.getResourceAsReader(resource);
        // XMLConfigBuilder xMLConfigBuilder = new XMLConfigBuilder(reader);
        // Configuration configuration = xMLConfigBuilder.parse();
        // SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        // 根据configuartion.xml创建SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        // 打开session
        SqlSession session = sqlSessionFactory.openSession();
        // 基于statementId的接口
        User user = (User) session.selectOne("cn.howso.mybatis.mapper.UserMapper.selectByPrimaryKey", 1L);
        System.out.println(user.getName());
        // 基于Mapper的接口
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user2 = userMapper.selectByPrimaryKey(1L);
        System.out.println(user2.getName());
    }
}

可以看到客户端主要的接口类

SqlSessionFactoryBuilder=》用于创建SqlSessionFactory

SqlSessionFactory=》用于创建SqlSession

SqlSession=》代表和数据库的会话

UserMapper=》访问数据库的接口

以及主要流程:

1、加载并解析配置

2、根据配置生成sessonFactory

3、sessionFactory打开session

4、获取mapper

5、执行增删查改

 

第一部分:框架初始化

接下来

SqlSessionFactoryBuilder

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

XMLConfigBuilder有个parse方法,根据configuration.xml和环境变量以及properties得到一个Configuration

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 {
      Properties settings = settingsAsPropertiess(root.evalNode("settings"));
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectionFactoryElement(root.evalNode("reflectionFactory"));
      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方法实际上就是根据configuration.xml的内容设置configuration属性的内容。

我们只关注mapperElement方法

 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) {
            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) {
            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<?> 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.");
          }
        }
      }
    }
  }

configuration.xml部分内容

 <mappers>

<mapper resource="cn/howso/mybatis.../UserMapper.xml"/>

</mappers>

可以看出,方法内部读取resource属性配置的文件到流中,构建一个XMLMapperBuilder,然后解析。

XMLMapperBuilder

 public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }


    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

 private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || 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);
    }
  }

可以看到,它在处理UserMapper.xml中的各种节点。

我们重点关注它是如何生成statement

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

XMLStatementBuilder.parseStatementNode()

 public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");


    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }


    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);


    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);


    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);


    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());


    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    }


    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

重点关注SqlSource,它是最后生成sql语句的关键。

注意参数content的类型是XNode

builderAssistant.addMappedStatement

public MappedStatement addMappedStatement(...

...

   MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
  }

最后生成了MappedStatement,它代表映射的语句。

并且添加到了configuration中。

前面还有个内容要分析,那就是SqlSource的生成。

它由langDriver创建,langDriverLanguageDriver类型。

 在eclipse中,按ctrl+t

可以看到LanguageDriver是接口。

XMLLanguageDriver用于从XML创建SqlSource,所以支持动态sql。(动态sql可以在sql中写ifforEachwhenwhere等标签,也可以写${},#{}

RawLanguageDriver用于支持#{}sql

XMLLanguageDriver

 @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }


  @Override
  public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
    // issue #3
    if (script.startsWith("<script>")) {
      XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
      return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
    } else {
      // issue #127
      script = PropertyParser.parse(script, configuration.getVariables());
      TextSqlNode textSqlNode = new TextSqlNode(script);
      if (textSqlNode.isDynamic()) {
        return new DynamicSqlSource(configuration, textSqlNode);
      } else {
        return new RawSqlSource(configuration, script, parameterType);
      }
    }
  }

从第二个方法可以看出,可以根据字符串创建动态sql,要求是字符串包含在<script>标签中。

SqlSource有四种

DynamicSqlSource代表动态sqlRawSqlSource封装的sql可以有占位符#{}StaticSqlSource封装静态sqlProviderSqlSoruce用于注解的provider方式配置的sql

ProviderSqlSource

 private SqlSource createSqlSource(Object parameterObject) {
    try {
      String sql;
      if (providerTakesParameterObject) {
        sql = (String) providerMethod.invoke(providerType.newInstance(), parameterObject);
      } else {
        sql = (String) providerMethod.invoke(providerType.newInstance());
      }
      Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
      return sqlSourceParser.parse(sql, parameterType, new HashMap<String, Object>());
    } catch (Exception e) {
      throw new BuilderException("Error invoking SqlProvider method ("
          + providerType.getName() + "." + providerMethod.getName()
          + ").  Cause: " + e, e);
    }
  }

SqlSourceBuilder

  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }

可以看出provider注解方式支持带占位符#{}sql,但不支持动态sql

此外注解SelectInsertDeleteUpdate的相关源码,它们使用默认的LanguageDriverXMLLanguageDriver

MapperAnnotationBuilder

 public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
    String resource = type.getName().replace('.', '/') + ".java (best guess)";
    this.assistant = new MapperBuilderAssistant(configuration, resource);
    this.configuration = configuration;
    this.type = type;


    sqlAnnotationTypes.add(Select.class);
    sqlAnnotationTypes.add(Insert.class);
    sqlAnnotationTypes.add(Update.class);
    sqlAnnotationTypes.add(Delete.class);


    sqlProviderAnnotationTypes.add(SelectProvider.class);
    sqlProviderAnnotationTypes.add(InsertProvider.class);
    sqlProviderAnnotationTypes.add(UpdateProvider.class);
    sqlProviderAnnotationTypes.add(DeleteProvider.class);
  }

private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
    try {
      Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
      Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
      if (sqlAnnotationType != null) {
        if (sqlProviderAnnotationType != null) {
          throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
        }
        Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
        final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
        return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
      } else if (sqlProviderAnnotationType != null) {
        Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
      //TODO modified by zhoujiaping,at 2017-05-14 修改源码,支持SqlProvider写带标签的动态sql。
        if(ProviderHelper.isScriptSqlProvider(sqlProviderAnnotation, sqlProviderAnnotationType)){
            String string = ProviderHelper.create(assistant,method, sqlProviderAnnotation, sqlProviderAnnotationType).getSql();
            return buildSqlSourceFromStrings(new String[]{string},parameterType,languageDriver);
        }else{
            return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
        }
      }
      return null;
    } catch (Exception e) {
      throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
    }
  }

Configuration

 typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);


    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);


    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);


    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    languageRegistry.register(RawLanguageDriver.class);

第二部分:用户接口

因为这部分是给mybatis的用户(也就是我们普通开发程序员)用的,所以书上都会有。

第三部分:接口内部实现

获取SqlSession

 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

注意有个Executor,它是后面的sql执行器。

获取Mapper

  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

获取到的是对应接口的代理类。

 public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

从源码中可以看出用的是jdk动态代理。(jdk动态代理只能代理接口,不能代理类。)

UserMapper的方法如何执行

 @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }


可以看出mapper方法的执行,实际上是生成代理对象的InvocationHandlerinvoke方法执行。invoke又委托给了MapperMethodexecute方法。

 public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else if (SqlCommandType.FLUSH == command.getType()) {
        result = sqlSession.flushStatements();
    } else {
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

可以看到,它根据不同的操作,执行不同的方法。

insertupdatedeleteexecuteForManyexecuteForMapselectOneexecuteWithResultHandlerflushStatements

DefaultSessionFactory

 @Override
  public int insert(String statement) {
    return insert(statement, null);
  }


  @Override
  public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }


  @Override
  public int update(String statement) {
    return update(statement, null);
  }


  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

可以看到,insertdelete,实际上执行的都是update。而且将具体的执行操作又委托给了executor

Executor是个接口,它有四个公有的非抽象类。

BatchExecutor用于批量操作,它用jdbc的批量操作,SimpleExecutor每次操作都会发送一条sql

CachingExecutor

 public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }

从源码中可以看出,它把操作委托给了delegate,也就是剩下的三个Executor实现类。

SimpleExecutorupdate为例

SimpleExecutor

 @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

SimpleExecutor将操作委托给了StatementHandlerStatementHandler接收的参数stmt,是jdbc的接口。

StatementHandler是个接口,它有四个实现类。

RoutingStatementHandler

 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {


    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }


  }

可以看出,用于路由到其他三个实现类。

PreparedStatementHandler为例

 @Override
  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }

到这里,调用了jdbcPreparedStatementexecute方法。

第四部分mybatis插件原理

同学们自己去分析

第五部分 经验总结

一是mybatis的加载启动流程、执行流程、插件原理。

二是mybatis框架的设计。

前者略,因为阅读源码时就是按照这个目标去的。

后者,还没有分析

源码结构

可以看出mybatis分为很多模块(包)。

注解、绑定、构建、缓存、数据源、异常、执行器、输入输出、jdbc、日志、映射、解析器、插件、反射、脚本、会话、事务、类型。

5.1 annotations

注解是也是一种配置。所以它的核心作用就是替代配置文件。

比如SelectInsertResultMapSelectProvider注解,都是可选的,可以只用xml配置。

5.2binding

这个包主要用于将绑定Mapper代理对象,Mapper方法。

MapperRegistry:用于保存Mapper的实例,MapperRegistry又被Configuration持有。

Configuration

  protected Properties variables = new Properties();
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
  protected MapperRegistry mapperRegistry = new MapperRegistry(this);

可以看到这里出现了循环依赖,Configuration依赖了MapperRegistryMapperRegistry的构造函数依赖了Configuration

MapperMethod:用于执行Mapper的方法。它在MapperProxy的方法中被创建并调用。

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

MapperProxyMapper接口的代理对象的InvocationHandler,它用来执行Mapper接口的方法。

mybatis的增删改查接口,一种方式是通过字符串,例如User user = (User)session.selectOne("cn.howso.mybatis.mapper.UserMapper.selectByPrimaryKey", 1L);

另一种是接口方式 UserMapper userMapper = session.getMapper(UserMapper.class);

List<User> list = userMapper.selectAll(u);

吐槽:它推荐面向接口方式,不支持实现类方式,但是又提供SqlProvider注解,让我们提供自己的实现。用SqlProvider,又没有了静态检查机制,通过字符串匹配的方式确定方法。方法不支持重载,不能有同名方法,即使参数列表不同。要实现通用dao非常麻烦。

5.3builder

用于构建ConfigurationMapperSqlSourceStatement,以及包括一些用于帮助构建的Resolver

里面有两个子包,xmlannotation。其中的XMLMapperBuilderMapperAnnotationBuilder分别基于xmlannotation构建Mapper,但是名字取得却不统一。后者很明显可以改为AnnotationMapperBuilder,保持一致。

两个类的作者都是Clinton Begin,可以看到,就算是著名框架的作者,写的代码也有自我不一致的地方。

由于mybatis不是一个ioc框架,所以它的代码实现上,很多地方new实现类。导致代码不方便拓展。比如SqlProvider支持RawSqlSource但不支持DynamicSqlSource,如果想让它支持,就必须修改源代码。

虽然从性能上来说StaticSqlSource性能最好,DynamicSqlSource性能低,但是我们自己可以用缓存解决问题啊。

问题是它不提供这样的拓展点。

5.4 cache

缓存

我把子包impl删除后,annotationsbuilderexecutor等包都编译报错,说明很多包依赖了这个模块的具体实现,而不是只依赖接口。

其中有个decorators包,这个包里面的类,都是实现了装饰器模式的类,用于装饰Cache,对Cache进行增强,有的增加日志功能,有的实现先进先出功能,有的实现将缓存序列化和反序列化的功能。

这个设计模式很值得学习,先设计一个简单功能的类,然后需要增强功能就考虑装饰器模式,用新的类装饰它增强它。

5.5 datasource

数据源,其中包括jndi数据源,连接池数据源,非连接池数据源。

5.6 exceptions

mybatis定义的异常

5.7 io

用于读取配置文件的工具类。

5.8 jdbc

这个包主要用于提供java接口创建sql语句。以及用于执行sql文件的工具。

5.9 logging

日志模块,支持log4jlog4j2slf4j等。有连接日志、语句日志、结果集日志。

5.10 mapping

映射,包括结果集映射、参数映射、sql映射。

5.11 parsing

解析器,用于解析xml生成内部对象表示形式,解析${}#{}

5.12 plugin

插件,用于拦截ExecutorStatementHandlerParameterHandlerResultSetHandler

怎么知道可以拦截这四大对象呢?

Configuration类的源代码。

这里我要吐槽一下这个设计,插件实现用的jdk动态代理,invoke方法参数类型丢失。能够拦截哪些方法通过注解配置。这里完全可以参考servlet过滤器或者springmvc拦截器的实现啊。

5.13 reflection

反射工具。其中最常用的类是MetaObject,可以修改对象的私有属性。

5.14 scripting

解析xml配置的sql

5.15 session

会话

5.16 transaction

事务

5.17 type

类型处理,包括设置sql语句参数时的类型处理和获取sql语句执行结果的类型处理。

总结:

从整体来说,mybatis框架相对简单,它作了比较细的模块划分,内容也很全(缓存、日志、事务、数据源等)。由于不是ioc容器,在配置一些功能时,难免使用new方式,导致框架灵活性降低。有些设计并不好,例如插件的设计。

在简单性与灵活性之间,mybatis的取舍做的还是挺不错的。

附录:

mybatis插件相关的项目,可以参考https://github.com/zhoujiaping/mybatis-mapper

里面有一个分页插件,一个通用dao插件。

猜你喜欢

转载自blog.csdn.net/zhoujiaping123/article/details/73898255