spring-mybatis-2 (mapper的xml解析和注解解析)

mybatis框架设计图

在这里插入图片描述

mybatis的主要构件

  • SqlSession
    作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
  • Executor
    MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
  • StatementHandler
    封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
  • ParameterHandler
    负责对用户传递的参数转换成JDBC Statement 所需要的参数,
  • ResultSetHandler
    负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
  • TypeHandler
    负责java数据类型和jdbc数据类型之间的映射和转换
  • MappedStatement
    MappedStatement维护了一条<select|update|delete|insert>节点的封装,
  • SqlSource
    负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
  • BoundSql
    表示动态生成的SQL语句以及相应的参数信息
  • Configuration
    MyBatis所有的配置信息都维持在Configuration对象之中。

SqlSession过程流程

  1. 开启一个数据库访问会话—创建SqlSession对象
    在这里插入图片描述
  2. 为SqlSession传递一个配置的Sql语句 的Statement Id和参数,然后返回结果:
    List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);
    3.MyBatis执行器Executor根据SqlSession传递的参数执行query()方法
    上述的Executor.query()方法几经转折,最后会创建一个StatementHandler对象,
    然后将必要的参数传递给StatementHandler,使用StatementHandler来完成对数据库的查询,最终返回List结果集。

从上面的代码中我们可以看出,Executor的功能和作用是:

  1. 根据传递的参数,完成SQL语句的动态解析,生成BoundSql对象,供StatementHandler使用;
  2. 为查询创建缓存,以提高性能(具体它的缓存机制不是本文的重点,我会单独拿出来跟大家探讨,
    感兴趣的读者可以关注我的其他博文);
  3. 创建JDBC的Statement连接对象,传递给StatementHandler对象,返回List查询结果。
  4. StatementHandler对象负责设置Statement对象中的查询参数、处理JDBC返回的resultSet,
    将resultSet加工为List 集合返回:

StatementHandler对象主要完成两个工作:

  1. 对于JDBC的PreparedStatement类型的对象,创建的过程中,我们使用的是SQL语句字符串会包含 若干个? 占位符,我们其后再对占位符进行设值。
    StatementHandler通过parameterize(statement)方法对Statement进行设值;
  2. StatementHandler通过List<E> query(Statement statement, ResultHandler resultHandler)
    方法来完成执行Statement,并将Statement对象返回的resultSet封装成List;

StatementHandler 的parameterize(statement) 方法的实现:

/** 
*   StatementHandler 类的parameterize(statement) 方法实现  
*/  
public void parameterize(Statement statement) throws SQLException {  
    //使用ParameterHandler对象来完成对Statement的设值    
    parameterHandler.setParameters((PreparedStatement) statement);  
  } 

StatementHandler 的parameterize(Statement) 方法调用了 ParameterHandler的setParameters(statement) 方法,
ParameterHandler 的setParameters(Statement)方法负责根据我们输入的参数,对statement对象的 ? 占位符处进行赋值。

StatementHandler 的List<E> query(Statement statement, ResultHandler resultHandler)方法的实现:

源码
org.apache.ibatis.executor.statement.PreparedStatementHandler#query

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  
// 1.调用PreparedStatement#execute()方法,然后将resultSet交给ResultSetHandler处理    
   PreparedStatement ps = (PreparedStatement) statement;  
   ps.execute();  
   //2. 使用ResultHandler来处理ResultSet  
   return resultSetHandler.<E> handleResultSets(ps);  
 }  

StatementHandler 的List<E> query(Statement statement, ResultHandler resultHandler)方法的实现,是调用了ResultSetHandler的handleResultSets(Statement) 方法。

ResultSetHandler的handleResultSets(Statement) 方法会将Statement语句执行后生成的 resultSet 结果集转换成List 结果集:

mybatis层次结构
在这里插入图片描述

mybatis mapper xml的解析

mybatis 加载入口
org.mybatis.spring.SqlSessionFactoryBean#afterPropertiesSet

  @Override
  public void afterPropertiesSet() throws Exception {
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory

//在默认的构造器中,会注册很多别名处理器
 configuration = new Configuration();

  // mybatis 插件注册
   if (!isEmpty(this.plugins)) {
      for (Interceptor plugin : this.plugins) {
        configuration.addInterceptor(plugin);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered plugin: '" + plugin + "'");
        }
      }
    }

// mybatis 全局配置文件解析
  if (xmlConfigBuilder != null) {
      try {
        // 全局配置文件解析
        xmlConfigBuilder.parse();
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
        }
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }

    // mapper的xml解析 重点
    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
           // 查看该方法   
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }

        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    }

mapper xml 文件下的 resultMap 标签的解析
ResultMap:将查询结果集中的列一一映射到java对象的各个属性上

org.apache.ibatis.builder.xml.XMLMapperBuilder#parse

// parser 就是XPathParser对象,使用该对象查找 mapper标签节点
 configurationElement(parser.evalNode("/mapper"));

org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement

// context 就是mapper xml文件中的内容
// 解析 resultMap 标签下的所有标签
 resultMapElements(context.evalNodes("/mapper/resultMap"));

返回到
org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement

mapper xml 文件下的 sql 标签的解析
sql片段:Sql中可将重复的sql提取出来,使用时用include引用即可,最终达到sql重用的目的

sqlElement(context.evalNodes("/mapper/sql"));

mapper xml 文件下的 crud 的标签解析

 buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

查看
org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode

SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)

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

org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode

 // 将 context 内容即sql内容解析后,分为 动态的SqlNode和静态的SqlNode
 List<SqlNode> contents = parseDynamicTags(context);

SqlNode
在这里插入图片描述

org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseDynamicTags

List<SqlNode> parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<SqlNode>();
    //sql的所有节点,即 ParentNode#getChildNodes,使用 ParentNode的实现类是 DeferredElementImpl
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || 
                                 child.getNode().getNodeType() == Node.TEXT_NODE) {
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          //解析到 sql 内容有静态sql
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
        //是动态标签时,会走到这里,例如: <where>
        String nodeName = child.getNode().getNodeName();
        //根据标签名获取对应的处理器,这里的 nodeName 就是动态sql的标签
        NodeHandler handler = nodeHandlers(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        //解析动态标签,例如 <if>
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    // 看下面动态sql生成的结果 <if>
    return contents;
  }

  // 注册了标签对就的处理类
   NodeHandler nodeHandlers(String nodeName) {
    Map<String, NodeHandler> map = new HashMap<String, NodeHandler>();
    map.put("trim", new TrimHandler());
    map.put("where", new WhereHandler());
    map.put("set", new SetHandler());
    map.put("foreach", new ForEachHandler());
    map.put("if", new IfHandler());
    map.put("choose", new ChooseHandler());
    map.put("when", new IfHandler());
    map.put("otherwise", new OtherwiseHandler());
    map.put("bind", new BindHandler());
    return map.get(nodeName);
  }

==NodeHandler ==
在这里插入图片描述
if 标签解析
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.IfHandler#handleNode

   @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      //这里迭代调用 parseDynamicTags,解析动态标签里的 sql
      List<SqlNode> contents = parseDynamicTags(nodeToHandle);
      MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
      //获取 test 元素上的值,即表达式
      String test = nodeToHandle.getStringAttribute("test");
      IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      targetContents.add(ifSqlNode);
    }
<!--sql片段-->
    <sql id="query_user_where_sql">
        <if test="id!=null and id!=''">
             id=#{id}
        </if>
    </sql>
    <select id="selectUserById" resultType="com.dn.spring.mybatis.bean.UserDo">
        SELECT * from t_user
        <where>
            <include refid="query_user_where_sql"/>
        </where>
    </select>

上面的动态sql解析后,如下图所示
在这里插入图片描述

回到
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode

public SqlSource parseScriptNode() {
    List<SqlNode> contents = parseDynamicTags(context);
    // MixedSqlNode 里有很多元素,这些元素是分层次结构的,每个元素下面可能还有很多元素,如上图所示的层次
    MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
    SqlSource sqlSource = null;
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

回到
org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode

//将前面获取到的 xml属性,封装成 MappedStatement 
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

MappedStatement 类

  • id
    mapper的命名空间 + sql语句的id
  • SqlSource
    sql已拼装好了, 但是还没有参数值
  • resultMaps
    sql 结果的映射关系也封装在这个在类中
  • sqlCommandType
    当前标签是sql的哪个类型

sqlSource内容
将 sql中的 #{ } 替换为 是在 org.apache.ibatis.builder.SqlSourceBuilder#parse 中的 String sql = parser.parse(originalSql); 中做的
在这里插入图片描述
最终 会封装到 org.apache.ibatis.session.Configuration#mappedStatements 属性中,key就是mapper的命名空间 + sql语句的id ,value 就是 MappedStatement 对象.每个定义的sql都会有一个MappedStatement 对象

mybatis mapper 注解的解析

org.apache.ibatis.builder.xml.XMLMapperBuilder#parse

 if (!configuration.isResourceLoaded(resource)) {
      //mapper xml的解析
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      // mapper 接口方法上 annotation的支持
      bindMapperForNamespace();
    }

org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace

  private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
       // mapper 接口类
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        //查看该方法,根据key(mapper的接口类) 在org.apache.ibatis.binding.MapperRegistry#knownMappers容器中获取值,
       // 值是 MapperProxyFactory
        if (!configuration.hasMapper(boundType)) {
          configuration.addLoadedResource("namespace:" + namespace);
          //初始化 org.apache.ibatis.binding.MapperRegistry#knownMappers 容器
          configuration.addMapper(boundType);
        }
      }
    }
  }

org.apache.ibatis.session.Configuration#addMapper

 public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }

org.apache.ibatis.binding.MapperRegistry#addMapper

//容器存入信息
knownMappers.put(type, new MapperProxyFactory<T>(type));

//mapper接口类中的方法上的注解析
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();

查看构造器和parse方法
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse

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);
  }
  
public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      //加载 mapper xml的配置文件
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
     // 获取接口的方法
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            //解析方法
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }  

org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parseStatement

// 解析方法上的注解后,将信息封装成 SqlSource 
 SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);

org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#getSqlSourceFromAnnotations

      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);
         // 查看 buildSqlSourceFromStrings 方法
        return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
      } else if (sqlProviderAnnotationType != null) {
        //例如 @SelectProvider 注解的支持日解析
        Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
        return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
      }

org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#buildSqlSourceFromStrings

 return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);

org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, java.lang.String, java.lang.Class<?>)

if (script.startsWith("<script>")) {
//方法上的注解,例如 @Select 的值是 <script>开头的
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
      // 使用XMLLanguageDriver#createSqlSource解析 ognl表达式
      return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
  }else {
      //走到这里,则注解里的值是纯文本的
      script = PropertyParser.parse(script, configuration.getVariables());
      TextSqlNode textSqlNode = new TextSqlNode(script);
      if (textSqlNode.isDynamic()) {
        // 封装成 DynamicSqlSource
        return new DynamicSqlSource(configuration, textSqlNode);
      } else {
        return new RawSqlSource(configuration, script, parameterType);
      }
    }     

注解上也是支持 ognl表达式的,并且使用的是 org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource的方式解析的

参考

Mybatis笔记 - SQL标签方法

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring-MyBatis整合是一种常见的开发方式,其中spring-mybatis.xml文件是用来配置SpringMyBatis框架集成的配置文件。在这个文件中,我们会定义数据源、事务管理器、扫描Mapper接口等配置信息。同时,我们还需要将MyBatis的SqlSessionFactory注入到Spring容器中,以便其他组件可以使用它来执行数据库操作。以下是一个简单的spring-mybatis.xml文件示例: ```xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 配置数据源 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverClass}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <!-- 配置SqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="typeAliasesPackage" value="com.example.entity" /> <property name="mapperLocations" value="classpath*:com/example/mapper/*.xml" /> </bean> <!-- 配置Mapper扫描 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.mapper" /> </bean> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 开启Spring注解功能 --> <context:annotation-config /> <!-- 开启Spring的AOP功能 --> <aop:aspectj-autoproxy /> <!-- 开启Spring的事务管理功能 --> <tx:annotation-driven transaction-manager="transactionManager" /> </beans> ``` 在这个配置文件中,我们使用Spring注解功能、AOP功能和事务管理功能,以及MyBatisMapper扫描和SqlSessionFactory配置。其中,数据源、事务管理器和Mapper扫描的配置信息需要根据实际情况进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值