阶段一模块一

Mybatis(三):架构及解析器模块详解 Mybatis(三):架构及解析器模块详解 文章目录Mybatis(三):架构及解析器模块详解1 架构2 解析器模块2.1 XPathParser2.1.1 构造方法2.1.2 解析方法2.2 占位符处理2.3 XNode3 总结 在前面的文章中我们了解了Mybatis的基本使用及其执行流程。相信你已经对Mybatis有了简单的了解。 今天我们正式进去源码学习阶段,在这篇文章中,我们首先介绍下Mybatis的结构,之后再学习下解析器模块的内容。 首先自行去github上下载其源码,地址为:https:// 阅读详情

首先,说明一些自己学习过程中产生的问题:

  1. XML 文档解析问题
  2. 怎样处理参数以及返回结果

最初,看完自定义持久层框架部分后,我发现了课程里面的简单的框架雏形与 MyBatis 源码之中有很多出入部分,于是我在思考,为什么 MyBatis 要这样做?于是我决定不只是简单地写一个自定义持久层框架,而是参照着 MyBatis 源代码实现一个简版的 MyBatis 框架。

  1. XML 配置文件解析问题:

    翻阅 MyBatis 源码,我们可以发现,MyBatis 没有使用课程中的 Dom4J 技术,而是使用 DOM 搭配 XPath 来完成对 XML 配置文件的解析,那么为什么要这样做?

    在 Java 中,一共有这样几种解析方式:

    • DOM

      优点:

      • 形成了树形结构,直观好理解,代码更易编写
      • 解析过程中树结构保存在内存中,方便修改

      缺点:

      • 当 XML 文件较大时,对内存耗费比较大,容易影响解析性能并造成内存溢出

      当然,在 MyBatis 中直接将解析的内容封装成了实体类 Configuration 等等。

    • SAX

      优点:

      • 采用事件驱动模式,对内存耗费比较小
      • 适用于只需要处理 XML 中数据

      缺点:

      • 不易编码
      • 很难同时访问同一个 XML 中多出不同数据
    • JDOM

      仅使用具体类而不使用接口,并且 API 大量使用了 Collections 类

    • Dom4J

      JDOM 的一种智能分支,合并了许多超出基本 XML 文档表示的功能。内部使用接口和抽象基本类方法,是一个优秀的 Java XML API。具有性能优异、灵活性好、功能强大和极端易用的特点,并且是一款开源的插件。

      因为这款插件的本质就是对 JDOM 的封装,而且因为其功能过分强大,无可避免的会有一些冗余,而 MyBatis 本身是一款轻量级的框架,自然不能使用这种方式。仅代表个人看法。

    那么 MyBatis 中是如何解析 XML 的呢?下面参照着我的代码来给大家进行讲解。

    1. XNode 标签节点类,对每一个标签内容进行解析,并封装成 XNode 对象。

      public class XNode {
      
          // 当前解析的标签 Node 对象
          private final Node node;
          // 当前解析标签名称
          private final String name;
          // 当前解析标签中的内容. <mapper>...</mapper>
          private final String body;
          // 当前标签对应的属性集合. 例如: <mapper namespace = "..."></mapper> 中的 namespace = "..."
          private final Properties attributes;
          // XPath 标签解析器
          private final XPathParser xpathParser;
      
          // 类中的所有成员变量皆为常量, 不可以进行修改, 只能通过构造方法进行初始化
          public XNode(Node node, XPathParser xpathParser) {
              this.node = node;
              this.name = node.getNodeName();
              this.body = this.parseBody(node);
              this.attributes = this.parseAttributes(node);
              this.xpathParser = xpathParser;
          }
      
          public Node getNode() {
              return node;
          }
      
          // 解析标签中的内容
          private String parseBody(Node node) {
              String data = this.getBodyData(node);
              if (data == null) {
                  NodeList children = node.getChildNodes();
                  for (int i = 0; i < children.getLength(); ++i) {
                      Node child = children.item(i);
                      data = this.getBodyData(child);
                      if (data != null) {
                          break;
                      }
                  }
              }
              return data;
          }
      
          private String getBodyData(Node child) {
              // 判断标签类型, 只有 Text node 以及 CDATASection 类型才需要进行解析
              if (child.getNodeType() != 4 && child.getNodeType() != 3) {
                  return null;
              } else {
                  return ((CharacterData) child).getData();
              }
          }
      
          // 解析标签的属性, 并存放到 Properties 对象中
          private Properties parseAttributes(Node node) {
              Properties attributes = new Properties();
              NamedNodeMap attributeNodes = node.getAttributes();
              if (attributeNodes != null) {
                  for (int i = 0; i < attributeNodes.getLength(); ++i) {
                      Node attribute = attributeNodes.item(i);
                      String value = attribute.getNodeValue();
                      attributes.put(attribute.getNodeName(), value);
                  }
              }
              return attributes;
          }
      
          // 根据 XPath 解析标签, 将解析结果封装成 XNode 对象
          public XNode evalNode(String expression) {
              return this.xpathParser.evalNode(this.node, expression);
          }
      
          // 解析的标签存在多个, 返回 XNode 对象集合
          public List<XNode> evalNodes(String expression) {
              return this.xpathParser.evalNodes(this.node, expression);
          }
      
          public String getStringBody() {
              return this.body;
          }
      
          // 根据属性名称获取属性值
          public String getStringAttribute(String name) {
              return this.attributes.getProperty(name);
          }
      
          // 获取当前标签下的所有子标签的集合
          public List<XNode> getChildren() {
              NodeList nodeList = this.node.getChildNodes();
              if (nodeList != null) {
                  int length = nodeList.getLength();
                  List<XNode> children = new ArrayList<>(length);
                  for (int i = 0; i < length; i++) {
                      Node node = nodeList.item(i);
                      if (node.getNodeType() == 1) {
                          children.add(new XNode(node, this.xpathParser));
                      }
                  }
                  return children;
              }
              return null;
          }
      
          // 获取当前标签下的所有子标签的属性, 例如解析 dataSource 标签时采取这种方式
          public Properties getChildrenAsProperties() {
              Properties properties = new Properties();
              List<XNode> children = this.getChildren();
              if (children != null) {
                  for (XNode node : children) {
                      String name = node.getStringAttribute("name");
                      String value = node.getStringAttribute("value");
                      if (name != null && value != null) {
                          properties.setProperty(name, value);
                      }
                  }
              }
              return properties;
          }
      }
      
    2. XPathParser XPath 解析器,负责解析 XML 文档中的节点,并将解析结果封装成 XNode 对象。

      package cn.worstone.bean;
      
      import org.w3c.dom.Document;
      import org.w3c.dom.Node;
      import org.w3c.dom.NodeList;
      import org.xml.sax.ErrorHandler;
      import org.xml.sax.SAXException;
      import org.xml.sax.SAXParseException;
      
      import javax.xml.namespace.QName;
      import javax.xml.parsers.DocumentBuilder;
      import javax.xml.parsers.DocumentBuilderFactory;
      import javax.xml.xpath.XPath;
      import javax.xml.xpath.XPathConstants;
      import javax.xml.xpath.XPathExpressionException;
      import javax.xml.xpath.XPathFactory;
      import java.io.InputStream;
      import java.util.ArrayList;
      import java.util.List;
      
      public class XPathParser {
      
          // 需要解析的 XML 文档对应的 Document 对象
          private final Document document;
          // XPath 对象
          private XPath xPath;
      
          // 根据 XML 文件流构建 XPath 解析器
          public XPathParser(InputStream inputStream) {
              this.document = this.createDocument(inputStream);
              XPathFactory factory = XPathFactory.newInstance();
              this.xPath = factory.newXPath();
          }
      
          public Object evaluate(String expression, QName returnType) {
              return evaluate(expression, this.document, returnType);
          }
      
          // 根据 XPath 表达式、解析的对象以及返回结果类型解析标签
          // 默认不指定解析对象即为当前的 document 对象
          // 根据不同的返回结果分别进行了封装
          public Object evaluate(String expression, Object item, QName returnType) {
              try {
                  return this.xPath.evaluate(expression, item, returnType);
              } catch (XPathExpressionException e) {
                  throw new RuntimeException("Error evaluating XPath.  Cause: " + e);
              }
          }
      
          public XNode evalNode(String expression) {
              return this.evalNode(this.document, expression);
          }
      
          // XPathConstants.NODE
          public XNode evalNode(Object item, String expression) {
              Node node = (Node) this.evaluate(expression, item, XPathConstants.NODE);
              return node == null ? null : new XNode(node, this);
          }
      
          public List<XNode> evalNodes(String expression) {
              return this.evalNodes(this.document, expression);
          }
      
          // XPathConstants.NODESET
          public List<XNode> evalNodes(Object item, String expression) {
              NodeList nodeList = (NodeList) this.evaluate(expression, item, XPathConstants.NODESET);
              List<XNode> nodes = new ArrayList<>();
              if (nodeList != null) {
                  for (int i = 0; i < nodeList.getLength(); i++) {
                      Node node = nodeList.item(i);
                      if (node != null) {
                          nodes.add(new XNode(node, this));
                      }
                  }
              }
              return nodes;
          }
      
          // 根据不同设置以及输入字节流创建 Document 文档对象
          private Document createDocument(InputStream inputStream) {
              try {
                  DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                  factory.setValidating(false);
                  factory.setNamespaceAware(false);
                  factory.setIgnoringComments(true);
                  factory.setIgnoringElementContentWhitespace(false);
                  factory.setCoalescing(false);
                  factory.setExpandEntityReferences(true);
      
                  DocumentBuilder builder = factory.newDocumentBuilder();
                  builder.setErrorHandler(new ErrorHandler() {
                      public void error(SAXParseException exception) throws SAXException {
                          throw exception;
                      }
      
                      public void fatalError(SAXParseException exception) throws SAXException {
                          throw exception;
                      }
      
                      public void warning(SAXParseException exception) throws SAXException {
                      }
                  });
                  return builder.parse(inputStream);
      
              } catch (Exception e) {
                  throw new RuntimeException("Error creating document instance.  Cause: " + e);
              }
          }
      
      }
      

      当然 MyBatis 中内部实现不可能这样简单,例如 XNode 解析标签内容的时候,MyBatis 其实是使用了标记处理器对内容中的 ${} 占位符进行处理。

      当源码翻阅到这个位置的时候,我再一次产生了一个问题:SQL 语句中的 #{} 占位符是什么时候解析的呢?这个问题后面会找到答案。

    3. XMLConfigBuilder 这个文件才是 MyBatis 核心配置文件的解析入口。

      package cn.worstone.builder.xml;
      
      import cn.worstone.bean.Configuration;
      import cn.worstone.bean.XNode;
      import cn.worstone.bean.XPathParser;
      import cn.worstone.io.Resources;
      import com.alibaba.druid.pool.DruidDataSource;
      
      import javax.sql.DataSource;
      import java.io.InputStream;
      import java.util.List;
      import java.util.Properties;
      
      public class XMLConfigBuilder {
      
          // 标记当前配置文件是否解析
          private boolean parsed;
          // XPath 解析器
          private final XPathParser parser;
          // 封装配置信息的实体类
          private Configuration configuration;
      
          public XMLConfigBuilder(InputStream inputStream) {
              this.parsed = false;
              this.parser = new XPathParser(inputStream);
              this.configuration = new Configuration();
          }
      
          public Configuration parse() {
              if (this.parsed) {
                  throw new RuntimeException("Each XMLConfigBuilder can only be used once.");
              } else {
                  this.parsed = true;
                  // 解析配置文件的根标签 configuration
                  this.parseConfiguration(this.parser.evalNode("/configuration"));
              }
              return this.configuration;
          }
      
          public void parseConfiguration(XNode root) {
             	// 因为只是 MyBatis 的简单实现, 这里仅需要实现数据源以及映射器的解析
              DataSource dataSource = dataSourceElement(root.evalNode("dataSource"));
              this.configuration.setDataSource(dataSource);
              mapperElement(root.evalNode("mappers"));
          }
      
          // 对数据源 dataSource 标签进行解析
          private DataSource dataSourceElement(XNode context) {
              if (context != null) {
                  // 这里就是上面提到过的, 直接获取所有 property 标签的属性
                  Properties properties = context.getChildrenAsProperties();
                  // 这里使用的是 Druid 数据库连接池, 而在 MyBatis 中, 有一个基于"池"概念简单实现的数据库连接池 POOLED, 其内部是对用户关闭数据源操作的一个拦截
                  // 在MyBatis 中此处是使用连接池工厂创建连接池, 并根据解析后的数据源属性返回一个数据源信息
                  DruidDataSource dataSource = new DruidDataSource();
                  dataSource.setDriverClassName(properties.getProperty("driver"));
                  dataSource.setUrl(properties.getProperty("url"));
                  dataSource.setUsername(properties.getProperty("username"));
                  dataSource.setPassword(properties.getProperty("password"));
                  return dataSource;
              } else {
                  throw new RuntimeException("Configuration declaration requires a DataSource.");
              }
          }
      
          // 对映射器 mappers 标签进行解析
          private void mapperElement(XNode context) {
              if (context != null) {
                  List<XNode> children = context.getChildren();
                  for (XNode node : children) {
                      String resource = node.getStringAttribute("resource");
                      InputStream inputStream = Resources.getResourceAsStream(resource);
                      // 构建 XMLMapperBuilder 对象对每个映射器文件进行解析
                      XMLMapperBuilder mapperBuilder = new XMLMapperBuilder(inputStream, this.configuration, resource);
                      mapperBuilder.parse();
                  }
              }
          }
      }
      
    4. XMLMapperBuilder 映射配置文件解析

      package cn.worstone.builder.xml;
      
      import cn.worstone.bean.Configuration;
      import cn.worstone.bean.XNode;
      import cn.worstone.bean.XPathParser;
      
      import java.io.InputStream;
      import java.util.Iterator;
      import java.util.List;
      
      public class XMLMapperBuilder {
          // 映射配置文件的命名空间
          private String namespace;
          // 当前解析映射配置文件的资源路径
          private final String resource;
          // XPath 解析器
          private final XPathParser parser;
          // 封装配置信息实体类
          private final Configuration configuration;
      
          public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource) {
              this.parser = new XPathParser(inputStream);
              this.resource = resource;
              this.configuration = configuration;
          }
      
          public void parse() {
              // 解析映射配置文件的根标签 mapper
              this.configurationElement(this.parser.evalNode("/mapper"));
              // 绑定映射配置文件 Mapper 接口, 将其添加到 Configuration 对象的 mapperRegistry 中
              this.bindMapperForNamespace();
          }
      
          private void configurationElement(XNode context) {
              // 获取当前映射配置文件的 namespace, MyBatis 中该类没有这个成员变量, 而是将其封装在 MapperBuilderAssistant 中, 用于后续使用
              this.namespace = context.getStringAttribute("namespace");
              // 解析 SQL 语句对应标签
              this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
          }
      
          private void buildStatementFromContext(List<XNode> nodes) {
              Iterator iterator = nodes.iterator();
              while (iterator.hasNext()) {
                  XNode node = (XNode) iterator.next();
                  // 创建 XMLStatementBuilder 对象, 对 SQL 语句标签进行解析, 并封装成 MappedStatement 存放到 Configuration 对象中
                  XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, node, this.namespace);
                  statementParser.parseStatementNode();
              }
          }
      
          // 通过 namespace 获取对应 Mapper 接口, 然后创建其代理对象存放在 mapperRegistry
          private void bindMapperForNamespace() {
              if (this.namespace != null) {
                  Class boundType = null;
                  try {
                      boundType = this.getClass().getClassLoader().loadClass(this.namespace);
                  } catch (ClassNotFoundException e) {
                  }
                  
                  if (boundType != null && !this.configuration.hasMapper(boundType)) {
                      this.configuration.addMapper(boundType);
                  }
              }
          }
      }
      
    5. XMLStatementBuilder 解析映射配置文件中的 SQL 语句

      package cn.worstone.builder.xml;
      
      import cn.worstone.bean.*;
      import cn.worstone.scripting.LanguageDriver;
      import cn.worstone.scripting.xml.XMLLanguageDriver;
      import cn.worstone.type.SqlCommandType;
      
      import java.util.Locale;
      
      public class XMLStatementBuilder {
      
          // 封装配置信息的实体类
          private final Configuration configuration;
          // 当前解析标签的 XNode
          private final XNode node;
          // 当前标签对应的命名空间
          private final String namespace;
      
          public XMLStatementBuilder(Configuration configuration, XNode node, String namespace) {
              this.configuration = configuration;
              this.node = node;
              this.namespace = namespace;
          }
      
          // 解析 SQL 语句, 将其封装成 MappedStatement 对象
          public void parseStatementNode() {
              // 当前的 SQL 标签名称, select insert update delete
              String nodeName = this.node.getNode().getNodeName();
              // 通过标签名称得到 SqlCommandType. 这是一个枚举类型, 封装 SQL 语句类型, 用于 Mapper 接口开发方式中调用方法的判断
              SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
              // 获取当前标签的 id 属性
              String id = this.node.getStringAttribute("id");
              // 通过 id 属性以及 namespace 属性, 通过该方法生成 namespace.id 的唯一标识
              String statement = this.applyCurrentNamespace(id);
              // 获取 resultType 属性
              String resultType = this.node.getStringAttribute("resultType");
              // 获取 parameterType 属性
              String parameterType = this.node.getStringAttribute("parameterType");
              // 通过类全限定名获取其 Class
              Class<?> resultTypeClass = this.resolveClass(resultType);
              Class<?> parameterTypeClass = this.resolveClass(parameterType);
              // 封装 ResultMap
              ResultMap resultMap = new ResultMap(id, resultTypeClass);
              // 语言驱动, 在 MyBatis 中包含多种语言驱动, 应对不同场景
              LanguageDriver driver = new XMLLanguageDriver();
              // 解析 SQL 语句, 将解析内容封装成 SQLSource
              SqlSource sqlSource = driver.createSqlSource(this.node);
              // 将参数信息封装成 ParameterMap 对象
              ParameterMap parameterMap = new ParameterMap(id, parameterTypeClass, sqlSource.getParameterMappings());
              parameterMap.build();
              // 将封装好的 MappedStatement 对象存放到 Configuration 对象中
              this.configuration.getMappedStatements().put(statement, new MappedStatement(id, resultMap, parameterMap, sqlSource, this.configuration, sqlCommandType));
          }
      
          // 通过 id 属性以及 namespace 属性, 通过该方法生成 namespace.id 的唯一标识
          private String applyCurrentNamespace(String base) {
              if (base == null) {
                  return null;
              } else {
                  if (base.startsWith(this.namespace + ".")) {
                      return base;
                  }
                  if (base.contains(".")) {
                      throw new RuntimeException("Dots are not allowed in element names, please remove it from " + base);
                  }
              }
              return this.namespace + "." + base;
          }
      
          // 通过类全路径获取其 Class
          private <T> Class<? extends T> resolveClass(String alias) {
              if (alias == null) {
                  return null;
              } else {
                  try {
                      return (Class<? extends T>) Class.forName(alias);
                  } catch (Exception e) {
                      throw new RuntimeException("Error resolving class. Cause: " + e);
                  }
              }
          }
      }
      
    6. XMLLanguageDriver SQL 语句处理驱动

      package cn.worstone.scripting.xml;
      
      import cn.worstone.bean.BoundSql;
      import cn.worstone.bean.MappedStatement;
      import cn.worstone.bean.SqlSource;
      import cn.worstone.bean.XNode;
      import cn.worstone.handler.DefaultParameterHandler;
      import cn.worstone.handler.ParameterHandler;
      import cn.worstone.scripting.LanguageDriver;
      
      public class XMLLanguageDriver implements LanguageDriver {
      
          public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
              return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
          }
      
          // 解析 SQL 语句
          @Override
          public SqlSource createSqlSource(XNode node) {
              // 创建 XMLScriptBuilder 对象, 解析 SQL 语句
              XMLScriptBuilder builder = new XMLScriptBuilder(node);
              return builder.parseScriptNode();
          }
      }
      

      在这里有些同学可能会问,为什么要这么麻烦,不就是解析 SQL 语句嘛?但是我们要知道,在 MyBatis 中有许多种方式编写 SQL 语句,直接在 XML 中编写或者使用注解方式开发,其次在 MyBatis 中包含许多动态 SQL 语句的实现方式,方便我们使用,这些种种被 MyBatis 拆分然后实现,因为要符合 “高内聚、低耦合”的设计原则,所以 MyBatis 框架中拆分得非常细致,并且异常处理机制十分周全。

    7. XMLScriptBuilder 解析 SQL 语句,封装内容成 SqlSource,这里与课程里面的不太一样,课程里面直接封装成了 BoundSql,但是在 MyBatis 中是封装成了 SqlSource,BoundSql 只是负责将 SQL 信息在这个地方传递使用。

      package cn.worstone.scripting.xml;
      
      import cn.worstone.bean.SqlSource;
      import cn.worstone.bean.XNode;
      import cn.worstone.builder.SqlSourceBuilder;
      
      public class XMLScriptBuilder {
          private final XNode context;
      
          public XMLScriptBuilder(XNode context) {
              this.context = context;
          }
      
          // 通过SqlSourceBuilder 对象解析 SQL 语句, 封装到 SqlSource 中并返回
          public SqlSource parseScriptNode() {
              SqlSourceBuilder builder = new SqlSourceBuilder();
              return builder.parse(this.context.getStringBody());
          }
      }
      
    8. SqlSourceBuilder 通过通用标识符解析器对 SQL 语句进行解析

      package cn.worstone.builder;
      
      import cn.worstone.bean.SqlSource;
      import cn.worstone.parsing.GenericTokenParser;
      import cn.worstone.parsing.ParameterMappingTokenHandler;
      
      public class SqlSourceBuilder {
      
          public SqlSource parse(String originalSql) {
              ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler();
              GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
              String sql = parser.parse(originalSql);
              return new SqlSource(sql, handler.getParameterMappings());
          }
      }
      

      MyBatis 就是在这个地方进行 #{} 占位符进行解析,将占位符替换成 ? 并将其中类型名称存放到 ParameterMappding 集合中,然后封装到 SqlSource 中。

    9. SqlSource 封装了解析后的 SQL 语句,其中的占位符已经替换成了 ?,以及解析出来的属性名称集合

      package cn.worstone.bean;
      
      import java.util.List;
      
      public class SqlSource {
      
          // 解析后 SQL 语句
          private final String sql;
          // 解析出来的属性名称集合, 占位符中的参数名称
          private final List<ParameterMapping> parameterMappings;
      
          public SqlSource(String sql, List<ParameterMapping> parameterMappings) {
              this.sql = sql;
              this.parameterMappings = parameterMappings;
          }
      
          // 通过传入参数获取 BoundSql
          public BoundSql getBoundSql(Object parameterObject) {
              return new BoundSql(this.sql, parameterObject, this.parameterMappings);
          }
      
          public List<ParameterMapping> getParameterMappings() {
              return parameterMappings;
          }
      }
      

    至此,解析 XML 配置文件讲解完毕。

    我相信大伙一定对 MyBatis 中使用 Map 传递参数并不陌生,这种方式堪称万能,真的是非常好用,但为什么可以这样用呢?在教学视频中,子慕老师是使用两个查询作为例子,那么在增删改操作中,我们是否可以同样支持 Map 以及 String 类型参数直接传入呢?接下来,我们先带着这个问题,探究一下数据库操作的执行流程。

    这里先说明一下群里小伙伴的一个问题,关于 MyBatis 自动提交这部分源码。

    1. DefaultSqlSessionFactory 是 SqlSessionFactory 的默认实现,就是用来创建 SqlSession 对象的工厂

      package cn.worstone.session.defaults;
      
      import cn.worstone.bean.Configuration;
      import cn.worstone.executor.Executor;
      import cn.worstone.session.SqlSession;
      import cn.worstone.session.SqlSessionFactory;
      import cn.worstone.transaction.JDBCTransaction;
      import cn.worstone.transaction.Transaction;
      
      public class DefaultSqlSessionFactory implements SqlSessionFactory {
          // 封装配置信息的实体类
          private final Configuration configuration;
      
          public DefaultSqlSessionFactory(Configuration configuration) {
              this.configuration = configuration;
          }
      
          // 创建一个不自动提交的 SqlSession 对象
          @Override
          public SqlSession openSession() {
              return openSessionFromDataSource(false);
          }
      
          // 通过设置是否自动提交属性创建一个 SqlSession 对象
          @Override
          public SqlSession openSession(boolean autoCommit) {
              return openSessionFromDataSource(autoCommit);
          }
      
          // 从数据源打开一个会话连接
          private SqlSession openSessionFromDataSource(boolean autoCommit) {
              Transaction transaction = null;
              try {
                  // 创建一个事务对象
                  transaction = new JDBCTransaction(this.configuration.getDataSource(), autoCommit);
                  // 根据事务创建一个执行器
                  Executor executor = this.configuration.newExecutor(transaction);
                  // 创建一个 SqlSession 对象
                  return new DefaultSqlSession(this.configuration, executor);
              } catch (Exception e) {
                  // 出现异常关闭事务
                  this.closeTransaction(transaction);
                  throw new RuntimeException("Error opening session.  Cause: " + e);
              }
          }
      
          private void closeTransaction(Transaction transaction) {
              try {
                  if (transaction != null) {
                      transaction.close();
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      }
      
    2. JDBCTransaction JDBC 事务

      package cn.worstone.transaction;
      
      import javax.sql.DataSource;
      import java.sql.Connection;
      import java.sql.SQLException;
      
      public class JDBCTransaction implements Transaction {
      
          // 配置信息
          private Connection connection;
          // 数据源
          private DataSource dataSource;
          // 是否自动提交
          private boolean autoCommit;
      
          public JDBCTransaction(DataSource dataSource, boolean desiredAutoCommit) {
              this.dataSource = dataSource;
              this.autoCommit = desiredAutoCommit;
          }
      
          // 获取数据库连接
          @Override
          public Connection getConnection() throws SQLException {
              if (this.connection == null) {
                  this.openConnection();
              }
              return this.connection;
          }
      
          // 提交事务
          @Override
          public void commit() throws SQLException {
              this.connection.commit();
          }
      
          // 回滚事务
          @Override
          public void rollback() throws SQLException {
              this.connection.rollback();
          }
      
          // 关闭事务
          @Override
          public void close() throws SQLException {
              this.connection.close();
          }
      
          // 从数据源中获取数据库连接
          public void openConnection() throws SQLException {
              this.connection = this.dataSource.getConnection();
              // 设置自动提交属性
              this.setDesiredAutoCommit(this.autoCommit);
          }
      
          // 设置数据库连接自动提交属性
          private void setDesiredAutoCommit(boolean desiredAutoCommit) {
              // 由于这个方法是在 openConnection 中调用, 所以无须判断当前 connection 是否为 null
              try {
                  // 如果当前连接中的自动提交设置与用户设置的值不一致, 重新设置当前连接的自动提交属性
                  if (this.connection.getAutoCommit() != autoCommit) {
                      this.connection.setAutoCommit(autoCommit);
                  }
              } catch (Exception e) {
                  throw new RuntimeException("Error configuring AutoCommit.  Your driver may not support getAutoCommit() or setAutoCommit(). Requested setting: " + desiredAutoCommit + ".  Cause: " + e);
              }
          }
      }
      

      在 MyBatis 中有三种事务的实现方式,JDBC 只是其中一种,而自动提交部分的源码就在这里。

    通过学习,我们都应该知道, MyBatis 中的 SqlSession 中封装了许多对数据库操作的方法,所以想要知道 SQL 语句的执行流程,我们则需要通过查阅这部分源码进行探究。

    1. DefaultSqlSession SqlSession 的默认实现类,封装了对数据库的操作,以及事务相关的操作

      package cn.worstone.session.defaults;
      
      import cn.worstone.bean.BoundSql;
      import cn.worstone.bean.Configuration;
      import cn.worstone.bean.MappedStatement;
      import cn.worstone.executor.Executor;
      import cn.worstone.session.SqlSession;
      
      import java.sql.SQLException;
      import java.util.List;
      
      public class DefaultSqlSession implements SqlSession {
      
          // 配置文件
          private final Configuration configuration;
          // 执行器
          private Executor executor;
      
          public DefaultSqlSession(Configuration configuration, Executor executor) {
              this.configuration = configuration;
              this.executor = executor;
          }
      
          // 查询集合, 无参
          @Override
          public <T> List<T> selectList(String statement) {
              return this.selectList(statement, null);
          }
      
          // 查询集合, 有参
          @Override
          public <T> List<T> selectList(String statement, Object parameter) {
              List<T> list;
              // 根据 MappedStatement 唯一标识从配置文件中获取 MappedStatement
              MappedStatement mappedStatement = this.configuration.getMappedStatements().get(statement);
              try {
                  // 调用执行器中的 query 方法
                  list = this.executor.query(mappedStatement, parameter, mappedStatement.getSqlSource().getBoundSql(parameter));
              } catch (Exception e) {
                  throw new RuntimeException("Error querying database.  Cause: " + e);
              }
              return list;
          }
      
          // 查询单个
          @Override
          public <T> T selectOne(String statement, Object parameter) {
              List<T> list = this.selectList(statement, parameter);
              if (list.size() > 1) {
                  throw new RuntimeException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
              } else if (list.size() == 1) {
                  return list.get(0);
              }
              return null;
          }
      
          // 添加方法
          @Override
          public int insert(String statement, Object parameter) {
              return this.update(statement, parameter);
          }
      
          // 更新方法
          @Override
          public int update(String statement, Object parameter) {
              MappedStatement mappedStatement = this.configuration.getMappedStatements().get(statement);
              BoundSql boundSql = mappedStatement.getBoundSql(parameter);
              try {
                  return this.executor.update(mappedStatement, parameter, boundSql);
              } catch (Exception e) {
                  throw new RuntimeException("Error update database.  Cause: " + e);
              }
          }
      
          // 删除方法
          @Override
          public int delete(String statement, Object parameter) {
              return this.update(statement, parameter);
          }
      
          // 事务提交
          @Override
          public void commit() throws SQLException {
              this.executor.commit();
          }
      
          // 事务回滚
          @Override
          public void rollback() throws SQLException {
              this.executor.rollback();
          }
      
          // 关闭连接
          @Override
          public void close() {
              try {
                  this.executor.close();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
      
          // 获取配置文件
          @Override
          public Configuration getConfiguration() {
              return this.configuration;
          }
      
          // 获取 Mapper 
          @Override
          public <T> T getMapper(Class<T> mapperClass) {
              return this.configuration.getMapper(mapperClass, this);
          }
      }
      

      因为会有需要手动控制事务的场景,所以在 SqlSession 中添加了事务操作。由于传统 Dao 的开发方式的弊端,所以 MyBatis 在 SqlSession 中添加了 Mapper 接口方法。

      在 MyBatis 中,SqlSession 调用的是 Executor 方法去执行数据库操作。

    2. Executor 执行器,在 MyBatis 中,一二级缓存相关实现就是在这实现的。这里用到了装饰器模式,Executor 是接口,BaseExecutor 是一个抽象类,SimpleExecutor 是默认的实现类,当开启了二级缓存,就会创建一个 CachingExecutor,其中包含一个 Executor 对象,并且额外实现了二级缓存的相关功能。由于时间关系,缓存相关功能没有实现。

      package cn.worstone.executor;
      
      import cn.worstone.bean.BoundSql;
      import cn.worstone.bean.Configuration;
      import cn.worstone.bean.MappedStatement;
      import cn.worstone.transaction.Transaction;
      
      import java.sql.Connection;
      import java.sql.SQLException;
      import java.util.List;
      
      public abstract class BaseExecutor implements Executor {
      
          // 配置文件
          protected final Configuration configuration;
          // 事务
          protected final Transaction transaction;
      
          protected BaseExecutor(Configuration configuration, Transaction transaction) {
              this.configuration = configuration;
              this.transaction = transaction;
          }
      
          // 查询方法
          @Override
          public <T> List<T> query(MappedStatement ms, Object parameter, BoundSql boundSql) {
              return this.queryFromDatabase(ms, parameter, boundSql);
          }
      
          // 更新方法
          @Override
          public int update(MappedStatement ms, Object parameter, BoundSql boundSql) throws SQLException, NoSuchFieldException, IllegalAccessException {
              return this.doUpdate(ms, parameter);
          }
      
          // 因为查询方法有缓存的存在, 所有有两种情况: 1. 当前查询信息存在缓存中, 此时无需从数据库中查询 2. 当前查询信息在缓存中没有, 则需要从数据库中查询
          private <E> List<E> queryFromDatabase(MappedStatement mappedStatement, Object parameter, BoundSql boundSql) {
              List list = null;
              try {
                  list = this.doQuery(mappedStatement, parameter, boundSql);
              } catch (Exception e) {
                  e.printStackTrace();
              }
              return list;
          }
      
          // 获取数据库连接
          protected Connection getConnection() throws SQLException {
              return this.transaction.getConnection();
          }
      
      	// 真正的更新操作
          protected abstract int doUpdate(MappedStatement mappedStatement, Object parameters) throws SQLException;
      
          // 真正的查询操作
          protected abstract <E> List<E> doQuery(MappedStatement mappedStatement, Object parameters, BoundSql boundSql) throws SQLException;
      
          // 事务相关操作
          public void commit() throws SQLException {
              this.transaction.commit();
          }
      
          public void rollback() throws SQLException {
              this.transaction.rollback();
          }
      
          public void close() throws SQLException {
              this.transaction.close();
          }
      }
      

      其实,事务相关操作都是在 BaseExecutor 中实现的,SqlSession 中调用的 BaseExecutor 相关方法,因为 MyBatis 中有缓存相关操作,所以抽象出一个 queryFromDatabase 方法。

      package cn.worstone.executor;
      
      import cn.worstone.bean.BoundSql;
      import cn.worstone.bean.Configuration;
      import cn.worstone.bean.MappedStatement;
      import cn.worstone.handler.StatementHandler;
      import cn.worstone.transaction.Transaction;
      
      import java.sql.Connection;
      import java.sql.SQLException;
      import java.sql.Statement;
      import java.util.List;
      
      public class SimpleExecutor extends BaseExecutor {
      
          public SimpleExecutor(Configuration configuration, Transaction transaction) {
              super(configuration, transaction);
          }
      
          // 数据库更新操作
          @Override
          protected int doUpdate(MappedStatement mappedStatement, Object parameter) throws SQLException {
              Statement statement = null;
              try {
                  BoundSql boundSql = mappedStatement.getBoundSql(parameter);
                  // 获取 Statement 处理器
                  StatementHandler handler = configuration.newStatementHandler(this, mappedStatement, parameter, boundSql);
                  statement = this.prepareStatement(handler);
                  return handler.update(statement);
              } finally {
                  if (statement != null) {
                      statement.close();
                  }
              }
          }
      
          // 数据库查询操作
          @Override
          protected <E> List<E> doQuery(MappedStatement mappedStatement, Object parameter, BoundSql boundSql) throws SQLException {
              Statement statement = null;
              try {
                  StatementHandler handler = configuration.newStatementHandler(this, mappedStatement, parameter, boundSql);
                  statement = this.prepareStatement(handler);
                  return handler.query(statement);
              } finally {
                  if (statement != null) {
                      statement.close();
                  }
              }
          }
      
          // 获取 Statement 实例
          private Statement prepareStatement(StatementHandler handler) throws SQLException {
              Connection connection = this.getConnection();
              Statement statement = handler.prepare(connection);
              return statement;
          }
      }
      
    3. StatementHandler SQL 处理器,所有的数据库操作在这实现的。

      package cn.worstone.handler;
      
      import cn.worstone.bean.BoundSql;
      import cn.worstone.bean.Configuration;
      import cn.worstone.bean.MappedStatement;
      import cn.worstone.executor.Executor;
      
      import java.sql.Connection;
      import java.sql.SQLException;
      import java.sql.Statement;
      
      public abstract class BaseStatementHandler implements StatementHandler {
          // 配置信息
          protected final Configuration configuration;
          // 执行器
          protected final Executor executor;
          // 结果处理器
          protected final ResultSetHandler resultSetHandler;
          // 参数处理器
          protected final ParameterHandler parameterHandler;
          // SQL 映射配置信息
          protected final MappedStatement mappedStatement;
          // SQL 语句相关信息
          protected BoundSql boundSql;
      
          protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
              this.configuration = mappedStatement.getConfiguration();
              this.executor = executor;
              this.mappedStatement = mappedStatement;
              this.boundSql = boundSql;
              this.parameterHandler = this.configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
              this.resultSetHandler = this.configuration.newResultSetHandler(executor, mappedStatement, this.parameterHandler, boundSql);
          }
      
          // Statement 准备方法实现一致, 所以直接在抽象类中实现, 其中 instantiateStatement 方法实现有可能存在变化, 故没有在抽象类中实现
          // 将各部分代码独立出去, 形成一个个单独的功能方法, "高内聚, 低耦合"
          public Statement prepare(Connection connection) {
              Statement statement = null;
              try {
                  statement = this.instantiateStatement(connection);
                  return statement;
              } catch (Exception e) {
                  this.closeStatement(statement);
                  throw new RuntimeException("Error preparing statement.  Cause: " + e);
              }
          }
      
          // 实例化 Statement 对象
          protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
      
          // 关闭方法不存在什么不同, 所以直接在抽象类中实现
          protected void closeStatement(Statement statement) {
              try {
                  if (statement != null) {
                      statement.close();
                  }
              } catch (SQLException e) {
              }
          }
      }
      
      package cn.worstone.handler;
      
      import cn.worstone.bean.BoundSql;
      import cn.worstone.bean.MappedStatement;
      import cn.worstone.executor.Executor;
      
      import java.sql.Connection;
      import java.sql.PreparedStatement;
      import java.sql.SQLException;
      import java.sql.Statement;
      import java.util.List;
      
      public class PreparedStatementHandler extends BaseStatementHandler {
      
          public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, BoundSql boundSql) {
              super(executor, mappedStatement, parameter, boundSql);
          }
      
          @Override
          protected Statement instantiateStatement(Connection connection) throws SQLException {
              return connection.prepareStatement(this.boundSql.getSql());
          }
      
          // 更新方法
          @Override
          public int update(Statement statement) throws SQLException {
              PreparedStatement preparedStatement = (PreparedStatement) statement;
              // 参数处理器设置传入参数
              this.parameterHandler.setParameters(preparedStatement);
              preparedStatement.execute();
              // 因为更新操作返回结果只是 int 或者 void, 所以直接返回影响行数
              return preparedStatement.getUpdateCount();
          }
      
          // 查询方法
          @Override
          public <E> List<E> query(Statement statement) throws SQLException {
              PreparedStatement preparedStatement = (PreparedStatement) statement;
              this.parameterHandler.setParameters(preparedStatement);
              preparedStatement.execute();
              // 处理返回结果集
              return this.resultSetHandler.handleResultSets(preparedStatement);
          }
      }
      

      在 MyBatis 中默认是使用预处理方式处理 SQL 语句。关于 MyBatis 中的占位符 #{} 以及 ${},#{} 占位符底层是以 PreparedStatement 预处理的方式处理 SQL 语句,不会出现 SQL 注入问题;而 ${} 占位符是以字符串拼接的方式拼接参数,所以会出现 SQL 注入问题,所以使用的时候需要注意,但是可以动态设置表名。

    4. ParameterHandler 传入参数处理器

      package cn.worstone.handler;
      
      import cn.worstone.bean.*;
      
      import java.lang.reflect.Field;
      import java.sql.PreparedStatement;
      import java.sql.SQLException;
      import java.util.List;
      import java.util.Map;
      
      public class DefaultParameterHandler implements ParameterHandler {
          private final MappedStatement mappedStatement;
          private final Object parameterObject;
          private final BoundSql boundSql;
          private final Configuration configuration;
      
          public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
              this.mappedStatement = mappedStatement;
              this.parameterObject = parameterObject;
              this.boundSql = boundSql;
              this.configuration = mappedStatement.getConfiguration();
          }
      
          @Override
          public Object getParameterObject() {
              return this.parameterObject;
          }
      
          @Override
          public void setParameters(PreparedStatement preparedStatement) throws SQLException {
              try {
                  ParameterMap parameterMap = this.mappedStatement.getParameterMap();
                  Class parameterClass = parameterMap.getType();
                  List<ParameterMapping> parameterMappings = parameterMap.getParameterMappings();
                  for (int i = 0; i < parameterMappings.size(); i++) {
                      ParameterMapping parameterMapping = parameterMappings.get(i);
                      String property = parameterMapping.getProperty();
                      Object value;
                      // 当前只添加了自定义实体类、Map 以及 String 类型参数处理, 后续抽象出具体的类型处理器进行处理
                      if (parameterClass.isAssignableFrom(Map.class)) {
                          // Map 类型, 根据 #{} 中的属性名获取属性值
                          value = ((Map) this.parameterObject).get(property);
                      } else if (parameterClass.isAssignableFrom(String.class)) {
                          // String 类型, 参数对象即为设置参数
                          value = this.parameterObject;
                      } else {
                          // 自定义实体类, 通过 Java 反射技术获取对应的属性值
                          Field declaredField = parameterClass.getDeclaredField(property);
                          declaredField.setAccessible(true);
                          value = declaredField.get(this.parameterObject);
                      }
                      preparedStatement.setObject(i + 1, value);
                  }
              } catch (Exception e) {
                  throw new RuntimeException("The parameters cannot be set. Cause: " + e);
              }
          }
      }
      

      当我写这段代码的时候出现了一个非常有意思的情况,我当时想到了传入参数为 Map 的情况,但是我的删除方法是使用了传入单个 String 类型的方法,但是当时我是没有对 String 类型进行处理的,所以报了错,然后查找到了这个位置,看到这块代码,大家可以明显发现缺点,类型映射部分还是不健全,而且可以单独抽象出去。

    5. ResultSetHandler 返回结果集处理器

      package cn.worstone.handler;
      
      import cn.worstone.bean.BoundSql;
      import cn.worstone.bean.Configuration;
      import cn.worstone.bean.MappedStatement;
      import cn.worstone.executor.Executor;
      
      import java.beans.PropertyDescriptor;
      import java.lang.reflect.Method;
      import java.sql.ResultSet;
      import java.sql.ResultSetMetaData;
      import java.sql.SQLException;
      import java.sql.Statement;
      import java.util.ArrayList;
      import java.util.List;
      
      public class DefaultResultSetHandler implements ResultSetHandler {
      
          private final Executor executor;
          private final Configuration configuration;
          private final MappedStatement mappedStatement;
          private final ParameterHandler parameterHandler;
          private final BoundSql boundSql;
      
          public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, BoundSql boundSql) {
              this.executor = executor;
              this.configuration = mappedStatement.getConfiguration();
              this.mappedStatement = mappedStatement;
              this.parameterHandler = parameterHandler;
              this.boundSql = boundSql;
          }
      
          // MyBatis 将功能拆分得非常细致, 可以让开发人员迅速定位到报错的位置. 大致原理都是依靠 Java 中的反射技术实现的, 内省技术也是反射技术封装实现的.
          @Override
          public <E> List<E> handleResultSets(Statement statement) throws SQLException {
              Class resultClass = this.mappedStatement.getResultMap().getType();
              ResultSet resultSet = statement.getResultSet();
              try {
                  List<E> resultList = new ArrayList<>(resultSet.getRow());
                  while (resultSet.next()) {
                      ResultSetMetaData metaData = resultSet.getMetaData();
                      E result = (E) resultClass.getDeclaredConstructor().newInstance();
                      int columnCount = metaData.getColumnCount();
                      for (int i = 1; i <= columnCount; i++) {
                          String columnName = metaData.getColumnName(i);
                          Object value = resultSet.getObject(columnName);
                          PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultClass);
                          Method writer = propertyDescriptor.getWriteMethod();
                          writer.invoke(result, value);
                      }
                      resultList.add(result);
                  }
                  return resultList;
              } catch (Exception e) {
                  throw new RuntimeException("The returned result set cannot be encapsulated. Cause: " + e);
              } finally {
                  if (resultSet != null) {
                      resultSet.close();
                  }
              }
          }
      }
      

      这两部分代码其实只是课程中的简单实现,使用 Java 内省技术实现结果集的封装。在 MyBatis 中,这部分实现复杂的多,而不是这样简单的实现。至于我为什么将其拆分开,因为当我按照课程内容实现了更新方法的时候,我发现与查询方法十分相似,代码十分冗余,所以将其中内容按照功能进行拆分。

    6. Configuration 配置信息实体类

      其实这个类在 MyBatis 中可以说是最核心的一个实体类,不仅存放着配置信息,而且无处不在,许多地方都是依靠这个对象传递相关配置信息,而且这个方法里面封装了很多方法,功能十分强大。生成 StatementHandler 、ParameterHandler以及ResultSetHandler实例的方法全都是在 Configuration 中,而且实现插件的 this.interceptorChain.pluginAll() 就是在此处。

      package cn.worstone.bean;
      
      import cn.worstone.binding.MapperRegistry;
      import cn.worstone.executor.Executor;
      import cn.worstone.executor.SimpleExecutor;
      import cn.worstone.handler.*;
      import cn.worstone.scripting.LanguageDriver;
      import cn.worstone.scripting.xml.XMLLanguageDriver;
      import cn.worstone.session.SqlSession;
      import cn.worstone.transaction.Transaction;
      
      import javax.sql.DataSource;
      import java.util.HashMap;
      import java.util.Map;
      
      public class Configuration {
          private DataSource dataSource;
          private final MapperRegistry mapperRegistry;
          private final Map<String, MappedStatement> mappedStatements;
      
          public Configuration() {
              this.mapperRegistry = new MapperRegistry(this);
              this.mappedStatements = new HashMap<>();
          }
      
          public DataSource getDataSource() {
              return dataSource;
          }
      
          public void setDataSource(DataSource dataSource) {
              this.dataSource = dataSource;
          }
      
          public boolean hasMapper(Class<?> type) {
              return this.mapperRegistry.hasMapper(type);
          }
      
          public <T> void addMapper(Class<T> type) {
              this.mapperRegistry.addMapper(type);
          }
      
          public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
              return this.mapperRegistry.getMapper(type, sqlSession);
          }
      
          public Map<String, MappedStatement> getMappedStatements() {
              return mappedStatements;
          }
      	
          // 创建执行器
          public Executor newExecutor(Transaction transaction) {
              return new SimpleExecutor(this, transaction);
          }
      
          // 创建参数处理器
          public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
              return mappedStatement.getLanguageDriver().createParameterHandler(mappedStatement, parameterObject, boundSql);
          }
      
          // 创建结果集处理器
          public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, BoundSql boundSql) {
              return new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, boundSql);
          }
      
          // 创建 Statement 处理器
          public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
              return new PreparedStatementHandler(executor, mappedStatement, parameterObject, boundSql);
          }
      
          // 获取默认的语言驱动
          public LanguageDriver getDefaultScriptingLanguageInstance() {
              return new XMLLanguageDriver();
          }
      
          // 判断是否存在当前的 MappedStatement
          public boolean hasStatement(String statementName) {
              return this.mappedStatements.containsKey(statementName);
          }
      
          // 根据唯一标识获取 MappedStatement
          public MappedStatement getMappedStatement(String id) {
              return this.mappedStatements.get(id);
          }
      }
      
  2. 关于 Mapper 接口实现的方式

    在课程里面这种方式的实现是直接通过在 getMapper 方法中使用匿名内部类实现的,而这里采用与 MyBatis 框架中一致的方式实现。

    1. 解析 XML 映射器的时候,直接创建 Mapper 代理对象,存储在Configuration 的 mapperRegister 中

      private void bindMapperForNamespace() {
          if (this.namespace != null) {
              Class boundType = null;
              try {
                  boundType = this.getClass().getClassLoader().loadClass(this.namespace);
              } catch (ClassNotFoundException e) {
              }
      
              if (boundType != null && !this.configuration.hasMapper(boundType)) {
                  this.configuration.addMapper(boundType);
              }
          }
      }
      
    2. SqlSession 中的 getMapper 方法直接从 Configuration 中获取对应的 Mapper 代理对象

      @Override
      public <T> T getMapper(Class<T> mapperClass) {
          return this.configuration.getMapper(mapperClass, this);
      }
      
    3. MapperProxy Mapper 接口的代理对象

      package cn.worstone.binding;
      
      import cn.worstone.bean.Configuration;
      import cn.worstone.bean.MappedStatement;
      import cn.worstone.session.SqlSession;
      
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      import java.lang.reflect.ParameterizedType;
      import java.lang.reflect.Type;
      
      public class MapperProxy<T> implements InvocationHandler {
      
          private final SqlSession sqlSession;
          private final Class<T> mapperInterface;
      
          public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) {
              this.sqlSession = sqlSession;
              this.mapperInterface = mapperInterface;
          }
      
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) {
              Configuration configuration = this.sqlSession.getConfiguration();
              String methodName = method.getName();
              String className = this.mapperInterface.getName();
              String statement = className + "." + methodName;
              MappedStatement mappedStatement = configuration.getMappedStatement(statement);
              Object parameterObject = args == null ? null : args[0];
              switch (mappedStatement.getSqlCommandType()) {
                  case SELECT:
                      Type returnType = method.getGenericReturnType();
                      if (returnType instanceof ParameterizedType) {
                          return this.sqlSession.selectList(statement, parameterObject);
                      } else {
                          return this.sqlSession.selectOne(statement, parameterObject);
                      }
                  case INSERT:
                      return this.sqlSession.insert(statement, parameterObject);
                  case UPDATE:
                      return this.sqlSession.update(statement, parameterObject);
                  case DELETE:
                      return this.sqlSession.delete(statement, parameterObject);
                  default:
                      throw new RuntimeException("Unknown execution method for: " + method.getName());
              }
          }
      }
      

      因为 Mapper 对象中封装了对应的 SqlSession 实例,所以我们可以从 SqlSession 中取出 Configuration ,然后再获取我们想要的一切东西。因为前面已经对 SQL 语句的参数类型进行了存储,所以这里直接对其进行判断,然后调用不同的方法即可。当然在 MyBatis 中实现不是这样简单,因为这里的查询结果集只有两种,但事实上,结果集也存在许多种类型, MyBatis 皆有相关的处理。有兴趣的同学可以翻阅一下 MapperMethod 类的源码,是上述部分功能的实现。

我的作业大致是这样的,但是其中还有许多问题,很多地方功能也不是特别完善,还是要不断地补充,在这里简单地聊一下后续的内容:

  1. 一级缓存的实现

  2. 异常处理部分的补全

    @Test
    public void update() throws XPathExpressionException, IOException, ParserConfigurationException, SAXException, SQLException {
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml"));
        try(SqlSession sqlSession = sqlSessionFactory.openSession()) {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            Map<String, Object> parameterMap = new HashMap<>(4);
            parameterMap.put("id", "5f414d80-c413-42d4-8844-909d3e107ce8");
            parameterMap.put("name", "xxx");
            int row = userMapper.update(parameterMap);
            System.out.println(row);
            sqlSession.commit();
        }
    }
    

    细心的可能会发现这里抛出了大量的异常,其实在这部分,只需要保留 2 个异常即可,其他的异常都应该由框架处理,反馈给用户。

  3. 参数以及结果集处理

    当前参数支持自定义实体类型、Map 类型以及 String 类型,需要补充其他类型参数的支持。对返回结果集目前也仅支持两种,这肯定是不行的,后续需要补充。

扩充下关于 MyBatis 多个数据源的实现方式

【SWPUCTF 2021 新生赛】hardrce 对每个字符的ASCII值进行按位取反操作(这种要进行自增马进行绕过。 阅读详情

相关推荐

瑞芯微RK3568与RK3566芯片选型指南:从接口差异到应用场景深度解析

在嵌入式系统与边缘计算领域,芯片选型是决定产品成败的关键一步。其核心在于理解芯片的架构、接口扩展能力与系统可靠性之间的平衡。以瑞芯微(Rockchip)的RK3568和RK3566为例,这两款同为四核A55架构的芯片,在基础算力上相近,但其面向的应用赛道却因接口配置而截然不同。RK3568凭借其双千兆网口、PCIe 3.0高速接口以及全链路ECC内存支持,在技术价值上更侧重于构建高可靠、高扩展性的系统,如边缘计算网关、工业控制器和网络视频录像机(NVR)。相比之下,RK3566则以其精简的接口配置,精准服务

weixin_33314040的博客 480

MyBatis-3.4.2-源码分析14:XML解析之sqlElement(context.evalNodes("/mapper/sql"))

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34397291的博客 287

Win10局域网内文件共享+远程桌面控制保姆级教程(含常见问题解决)

本文提供Win10局域网内文件共享和远程桌面控制的详细教程,包括环境检查、共享文件夹设置、远程桌面连接步骤及常见问题解决方案。通过内置功能实现高效办公协作,提升多设备协同工作效率,特别适合需要频繁传输文件或远程控制场景的专业人士。

rose2的博客 779

mybatis通用mapper的方法解析

Mapper的内置方法 model层就是实体类,对应数据库的表。controller层是Servlet,主要是负责业务模块流程的控制,调用service接口的方法,在struts2就是Action。Service层主要做逻辑判断,Dao层是数据访问层,与数据库进行对接。至于Mapper是mybtis框架的映射用到,mapper映射文件在dao层用。 下面是介绍一下Mapper的内置方法: 1

qq_22978533的博客 2318

行车记录仪 - 碰撞检测

功能需求 行车记录仪的核心功能是记录,大部分时候,行车记录仪都是在循环录制视频和音频,而设备的存储空间是有限的,因此录制机制一般是循环覆盖录制,如此一些重要的记录(例如:发生碰撞时的记录)就可能会被覆盖,为了避免这种情况的发生,行车记录仪设备就需要感知这些情况并做特殊处理(例如:备份相应时刻的录像数据)。 实现原理 碰撞检测是通过物理芯片G-sensor来实现的,加速度发生变化时G-sensor驱动会将加速度上报给应用层,应用层做相应处理。 G-sensor G-sensor(Gravity se

qazw9600的专栏 5217

基于51单片机的多功能智能路灯控制系统(仿真+代码+原理图)

智能路灯控制系统主要由51单片机最小系统+1602显示模块+DS1302时钟模块+光强检测模块+按键输入模块+声光报警模块+LED照明模块+状态检测电路+人体车辆红外检测模块组成。能实现以下功能: 1.能够按键设定系统时间、工作时间、光照阈值 2.工作时间内路灯点亮,凌晨12点后路灯关闭,有人或车通过是,路灯点亮10s 3.非工作时间光照强度低于阈值,路灯点亮 4.能够检测系统工作状态,路灯应亮未亮,声光报警 5.具备手动控制,能够切换自动/手动控制功能

u012759563的博客 8180

MyBatis-3.4.2-源码分析1:解析XML之propertiesElement(root.evalNode("properties"))

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34293902的博客 655

STM32F103C8T6的PC13-15引脚配置避坑指南:为什么你的IO口不工作?

本文详细解析了STM32F103C8T6的PC13-15引脚作为普通IO口不工作的原因及解决方案。这三个引脚默认关联侵入检测和低速外部时钟等特殊功能,需先禁用TAMPER和LSE,并开启AFIO时钟,才能正确配置为普通GPIO。文章提供了标准外设库和HAL库的配置步骤、常见问题排查方法及硬件设计注意事项。

weixin_29169899的博客 523

MyBatis-3.4.2-源码分析15:XML解析之buildStatementFromContext(context.evalNodes(‘select|insert|update|delete’...

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34242658的博客 325

ISCE+Mintpy进行SBASInSAR处理(持续更新)

通过调用pairs_misregistration方法,利用safe_dict中的数据信息,对每对SLC数据进行配准误差的计算和分析,以确定数据之间的精确差异,为后续的时间序列处理提供基础。给定同一地区的两景SAR影像,经过配准之后,对应像元进行共轭相乘得到相位差,并形成干涉图,但是从干涉图中得到的相位差实际上是主值,即在(-π,π)呈周期性变化,为了还原其真实的相位差,必须在主值的基础上加或减整数倍的相位周期2π,把被卷叠的相位展开,才能顺利地反演出地面目标的高程和形变。

Cyy2511250589的博客 3736

mojoPortal 路径穿越漏洞复现(CVE-2025-28367)(附脚本)

 在 mojoPortal CMS 版本 <=2.9.0.1 中发现了一个导致 ViewState 反序列化的目录遍历漏洞。通过利用此问题,未经身份验证的攻击者可以泄露 Web 根目录中的敏感文件,包括应用程序的文件。此配置文件包含用于验证和解密 ViewState 数据的文件。知道此密钥后,网络攻击者可以构建恶意 ViewState 有效负载,从而导致在底层服务器上执行远程代码。(CVE-2025-28367)

iSee857的博客 501

第三阶段模块一

棋耀的菜园 324

摇骰子、抽奖转盘酒桌游戏 人生重启模拟器小程序源码分享-开通流量主躺着赚钱

摇骰子、抽奖转盘酒桌游戏 人生重启模拟器小程序源码分享-开通流量主躺着赚钱

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 1651

阶段一模块三

Java核心类库 目录Java核心类库二级目录三级目录 二级目录 三级目录

Ermiteratt的博客 158

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

阶段一模块二总结

阶段一模块二总结

qq_23187859的博客 311

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

阶段一模块三总结

阶段一模块三总结

qq_23187859的博客 274

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

阶段一模块二

Java阶段一 模块二

wangxu123445678的博客 4693

阶段三模块一 HTML

内容输出来源:拉钩教育Java就业训练营 1 HTML介绍 HTML 指的是超文本标记/标签语言 (Hyper Text Markup Language) 专门制作网页的计算机语言 普通的文本就是英文单词,英文字母一样的存在。 超文本的意思是有一些单词或字母,在网页浏览器的世界中被赋予了特殊的权利。 字母img只是普通的字母,没什么特殊的含义。而被加上尖括号后,在网页的世界中,就具备了 显示图片的作用。 <html> <head> <title>拉钩有我

wangxu123445678的博客 206

阶段三模块一作业

简答题 1、谈谈你对工程化的初步认识,结合你之前遇到过的问题说出三个以上工程化能够解决问题或者带来的价值。 2、你认为脚手架除了为我们创建项目结构,还有什么更深的意义? 编程题 1、概述脚手架实现的过程,并使用 NodeJS 完成一个自定义的小型脚手架工具 2、尝试使用 Gulp 完成项目的自动化构建 项目基础代码下载地址: 百度网盘:https://pan.baidu.com/s/1AyGApMTFEfCeGfQBdykOGg 提取码: bw3r 说明: 本次作业中的编程题要求大家完成相应代码后(二选一)

weixin_45345105的博客 337

阶段一模块四 反射

内容输出来源:拉钩教育Java就业训练营 1 反射概念 在某些特殊场合中编写代码时不确定要创建什么类型的对象,也不确定要调用什么样的方法,这些都希望通过运行时传递的参数来决定,该机制叫做动态编程技术,也就是反射机制。 反射机制就是用于动态创建对象并且动态调用方法的机制。 2 Class类 java.lang.Class类的实例可以用于描述Java应用程序中的类和接口,也就是一种数据类型。 该类没有公共构造方法,该类的实例由Java虚拟机和类加载器自动构造完成,本质上就是加载到内存中的运行时类。 2.1 获取

wangxu123445678的博客 129

阶段一模块三笔记

标题javaSe阶段一模块三笔记

weixin_53381980的博客 145

阶段一模块五 新特性

内容输出来源:拉钩教育Java就业训练营 1 Java8 新特性 1.1 函数式接口 函数式接口主要指只包含一个抽象方法的接口,如:java.lang.Runnable、java.util.Comparator接口等。 Java8提供@FunctionalInterface注解来定义函数式接口,若定义的接口不符合函数式的规范便会报错。 Java8中增加了java.util.function包,该包包含了常用的函数式接口,具体如下: 接口名称 方法声明 功能介绍 Consumer void a

wangxu123445678的博客 189

阶段三模块一 CSS

内容输出来源:拉钩教育Java就业训练营 1 CSS CSS是指层叠样式表 cascading style sheets 通过CSS可以让我们定义HTML元素如何显示。 CSS可以让我们原本HTML不能描述的效果,通过CSS描述出来。 通过CSS描述我们的html页面,可以让我们的页面更加漂亮,可以提高工作效率。 2 CSS与HTML结合方式 2.1 内联/行内样式 就是在我们的HTML标签上通过style属性来引用CSS代码。 优点:简单方便 ; 缺点:只能对一个标签进行修饰。 2.2 内部样式表 我们通

wangxu123445678的博客 216

第六阶段模块一作业

《Scala编程》课程作业 第一题:百元喝酒 作业要求:每瓶啤酒2元,3个空酒瓶或者5个瓶盖可换1瓶啤酒。100元最多可喝多少瓶啤酒?(不允许借啤酒)思路:利用递归算法,一次性买完,然后递归算出瓶盖和空瓶能换的啤酒数。 代码: object HomeWorkOne { /** * * @param emptyBottle 空瓶数量 * @param bottleCap 瓶盖数量 * @param sumBottle 可以换到的总瓶数 */ def changeBott

crckerfang的博客 608

阶段二模块一

作业要求 开发 Minicat V 4.0,在已有 Minicat 基础上进一步扩展,模拟出 webapps 部署效果。磁盘上放置一个 webapps 目录,webapps 中可以有多个项目,例如 demo1、demo2、demo3... 每个项目中含有 Servlet,可以根据请求 url 定位对应 Servlet 进一步处理。 作业具体要求参考以下链接文档: https://gitee.com/lagouedu/alltestfile/raw/master/tomcat/Tomcat%E4%BD%9C%

LiveForCode的博客 296

阶段一模块一 直播

使用 TypeScript 的 Vue.js 项目差异 1)基本操作 安装 @vue/cli 最新版本 使用 @vue/cli 创建一个项目(不选 TypeScript) 先选这俩个。 use npm 使用 @vue/cli 安装 TypeScript 插件 注意项目中千万别这么搞,插件会覆盖掉一下代码。 2)通过 Git Diff 对比介绍使用 TypeScript 的 Vue.js 项目差异 安装了 @vue/cli-plugin-typescript 等插件 shims

weixin_45345105的博客 132
上一篇: 自增 ID 与 UUID
下一篇: 阶段一模块二
LiveForCode
博客等级 码龄5年 2粉丝 16原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值