阶段一模块一

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

  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 多个数据源的实现方式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值