主頁 >  其他 > 淺析MyBatis(二):手寫一個自己的MyBatis簡單框架

淺析MyBatis(二):手寫一個自己的MyBatis簡單框架

2021-04-01 19:01:56 其他

在??上一篇文章中,我們由一個快速案例剖析了 MyBatis 的整體架構與整體運行流程,在本篇文章中筆者會根據 MyBatis 的運行流程手寫一個自定義 MyBatis 簡單框架,在實踐中加深對 MyBatis 框架運行流程的理解,本文涉及到的專案代碼可以在 GitHub 上下載: ??my-mybatis ,

話不多說,現在開始!??????

1. MyBatis 運行流程回顧

首先通過下面的流程結構圖回顧 MyBatis 的運行流程,在 MyBatis 框架中涉及到的幾個重要的環節包括組態檔的決議、 SqlSessionFactory 和 SqlSession 的創建、 Mapper 介面代理物件的創建以及具體方法的執行,

通過回顧 MyBatis 的運行流程,我們可以看到涉及到的 MyBatis 的核心類包括 Resources、Configuration、 XMLConfigBuilder 、 SqlSessionFactory 、 SqlSession 、 MapperProxy 以及 Executor 等等,因此為了手寫自己的 MyBatis 框架,需要去實作這些運行流程中的核心類,

2. 手寫一個MyBatis 框架

本節中仍然是以學生表單為例,會手寫一個 MyBatis 框架,并利用該框架實作在 xml 以及注解兩種不同配置方式下查詢學生表單中所有學生資訊的操作,學生表的 sql 陳述句如下所示:

CREATE TABLE `student` (
  `id` int(10) NOT NULL AUTO_INCREMENT COMMENT '學生ID',
  `name` varchar(20) DEFAULT NULL COMMENT '姓名',
  `sex` varchar(20) DEFAULT NULL COMMENT '性別',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

insert  into `student`(`id`,`name`,`sex`) values 
(1,'張三','男'),
(2,'托尼·李四','男'),
(3,'王五','女'),
(4,'趙六','男');

學生表對應的 Student 物體類以及 StudentMapper 類可在專案的 entity 包和 mapper 包中查看,我們在 StudentMapper 只定義了 findAll() 方法用于查找學生表中的所有學生資訊,

下面準備自定義 MyBatis 框架的組態檔,在 mapper 配置時我們先將配置方式設定為指定 xml 組態檔的方式,整個組態檔如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <!-- 配置環境-->
  <environments default="development">
    <!-- 配置MySQL的環境-->
    <environment id="development">
      <!--  配置事務型別-->
      <transactionManager type="JDBC"/>
      <!--  配置資料源-->
      <dataSource type="POOLED">
        <!-- 配置連接資料庫的四個基本資訊-->
        <property name="driver" value="https://www.cnblogs.com/chiaki/p/com.mysql.jdbc.Driver"/>
        <property name="url" value="https://www.cnblogs.com/chiaki/p/jdbc:mysql://localhost:3306/mybatis_demo"/>
        <property name="username" value="https://www.cnblogs.com/chiaki/p/root"/>
        <property name="password" value="https://www.cnblogs.com/chiaki/p/admin"/>
      </dataSource>
    </environment>
  </environments>

  <!-- 指定映射組態檔的位置,映射組態檔的時每個dao獨立的組態檔-->
  <mappers>
    <!-- 使用xml組態檔的方式:resource標簽 -->
    <mapper resource="mapper/StudentMapper.xml"/>
    <!-- 使用注解方式:class標簽 -->
    <!--<mapper />-->
  </mappers>
</configuration>

本文在撰寫組態檔時仍按照真正 MyBatis 框架的配置方式進行,這里無需加入組態檔的頭資訊,同時將資料庫的相關資訊直接寫在組態檔中以簡化我們的決議流程,

2.1 讀取和決議組態檔并設定Configuration物件

2.1.1 自定義Resources類讀取MyBatis組態檔

在真正的 MyBatis 框架中對 Java 的原生反射機制進行了相應的封裝得到了 ClassLoaderWrapper 這樣一個封裝類,以此實作更簡潔的呼叫,本文在自定義時就直接采用原生的 Java 反射機制來獲取組態檔并轉換為輸入流,自定義的 Resources 類如下所示:

// 自定義Resources獲取配置轉換為輸入流
public class Resources {

  /**
   * 獲取組態檔并轉換為輸入流
   * @param filePath 組態檔路徑
   * @return 組態檔輸入流
   */
  public static InputStream getResourcesAsStream(String filePath) {
    return Resources.class.getClassLoader().getResourceAsStream(filePath);
  }
}

2.1.2 自定義MappedStatement類

在真正的 MyBatis 框架中, MappedStatement 是一個封裝了包括 SQL陳述句、輸入引數、輸出結果型別等在內的操作資料庫配置資訊的類,因此本小節中也需要自定義這樣一個類,在本文的案例中只需要定義與 SQL 陳述句和輸出結果型別相關的變數即可,代碼如下:

// 自定義MappedStatement類
@Data
public class MappedStatement {
  /**  SQL陳述句  **/
  private String queryString;
  /**  結果型別  **/
  private String resultType;
}

2.1.3 自定義Configuration類

上一篇文章中已經介紹過,在 MyBatis 框架中對于組態檔的決議都會設定到 Configuration 物件中,然后根據該物件去構建 SqlSessionFactory 以及 SqlSession 等物件,因此 Configuration 是一個關鍵的類,在本節開頭中自定義的組態檔中,真正重要的配置物件就是與資料庫連接的標簽以及 mapper 配置對應標簽下的內容,因此在 Configuration 物件中必須包含與這些內容相關的變數,如下所示:

// 自定義Configuration配置類
@Data
public class Configuration {
  /**  資料庫驅動  **/
  private String driver;
  /**  資料庫url  **/
  private String url;
  /**  用戶名  **/
  private String username;
  /**  密碼  **/
  private String password;
  /**  mappers集合  **/
  private Map<String, MappedStatement> mappers = new HashMap<>();
}

2.1.4 自定義DataSourceUtil工具類獲取資料庫連接

這里定義一個工具類用于根據 Configuration 物件中與資料庫連接有關的屬性獲取資料庫連接的類,撰寫 getConnection() 方法,如下所示:

// 獲取資料庫連接的工具類
public class DataSourceUtil {
  public static Connection getConnection(Configuration configuration) {
    try {
      Class.forName(configuration.getDriver());
      return DriverManager.getConnection(configuration.getUrl(), configuration.getUsername(), configuration.getPassword());
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}

2.1.5 自定義XMLConfigBuilder類決議框架組態檔

進一步自定義決議組態檔的 XMLConfigBuilder 類,根據真正 MyBatis 框架決議組態檔的流程,這個自定義的 XMLConfigBuilder 類應該具備決議 mybatis-config.xml 組態檔的標簽資訊并設定到 Configuration 物件中的功能,對于 xml 檔案的決議,本文采用 dom4j + jaxen 來實作,首先需要在專案的 pom.xml 檔案中引入相關依賴,如下所示:

<dependency>
  <groupId>dom4j</groupId>
  <artifactId>dom4j</artifactId>
  <version>1.6.1</version>
</dependency>
<dependency>
  <groupId>jaxen</groupId>
  <artifactId>jaxen</artifactId>
  <version>1.2.0</version>
</dependency>

引入依賴后,我們在 XMLConfigBuilder 類中定義 parse() 方法來決議組態檔并回傳 Configuration 物件,如下所示:

public static Configuration parse(InputStream in) {
  try {
    Configuration configuration = new Configuration();
    // 獲取SAXReader物件
    SAXReader reader = new SAXReader();
    // 根據輸入流獲取Document物件
    Document document = reader.read(in);
    // 獲取根節點
    Element root = document.getRootElement();
    // 獲取所有property節點
    List<Element> propertyElements = root.selectNodes("//property");
    // 遍歷節點進行決議并設定到Configuration物件
    for(Element propertyElement : propertyElements){
      String name = propertyElement.attributeValue("name");
      if("driver".equals(name)){
        String driver = propertyElement.attributeValue("value");
        configuration.setDriver(driver);
      }
      if("url".equals(name)){
        String url = propertyElement.attributeValue("value");
        configuration.setUrl(url);
      }
      if("username".equals(name)){
        String username = propertyElement.attributeValue("value");
        configuration.setUsername(username);
      }
      if("password".equals(name)){
        String password = propertyElement.attributeValue("value");
        configuration.setPassword(password);
      }
    }
    // 取出所有mapper標簽判斷其配置方式
    // 這里只簡單配置resource與class兩種,分別表示xml配置以及注解配置
    List<Element> mapperElements = root.selectNodes("//mappers/mapper");
    // 遍歷集合
    for (Element mapperElement : mapperElements) {
      // 獲得resource標簽下的內容
      Attribute resourceAttribute = mapperElement.attribute("resource");
      // 如果resource標簽下內容不為空則決議xml檔案
      if (resourceAttribute != null) {
        String mapperXMLPath = resourceAttribute.getValue();
        // 獲取xml路徑決議SQL并封裝成mappers
        Map<String, MappedStatement> mappers = parseMapperConfiguration(mapperXMLPath);
        // 設定Configuration
        configuration.setMappers(mappers);
      }
      // 獲得class標簽下的內容
      Attribute classAttribute = mapperElement.attribute("class");
      // 如果class標簽下內容不為空則決議注解
      if (classAttribute != null) {
        String mapperClassPath = classAttribute.getValue();
        // 決議注解對應的SQL封裝成mappers
        Map<String, MappedStatement> mappers = parseMapperAnnotation(mapperClassPath);
        // 設定Configuration
        configuration.setMappers(mappers);
      }
    }
    //回傳Configuration
    return configuration;
  } catch (Exception e) {
    throw new RuntimeException(e);
  } finally {
    try {
      in.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

可以看到在 XMLConfigBuilder#parse() 方法中對 xml 組態檔中與資料庫連接相關的屬性進行了決議并設定到 Configuration 物件,同時最重要的是對 mapper 標簽下的配置方式也進行了決議,并且針對指定 xml 組態檔以及注解的兩種情況分別呼叫了 parseMapperConfiguration() 方法和 parseMapperAnnotation() 兩個不同的方法,

2.1.5.1 實作parseMapperConfiguration()方法決議xml配置

針對 xml 組態檔,實作 XMLConfigBuilder#parseMapperConfiguration() 方法來進行決議,如下所示:

/**
 * 根據指定的xml檔案路徑決議對應的SQL陳述句并封裝成mappers集合
 * @param mapperXMLPath xml組態檔的路徑
 * @return 封裝完成的mappers集合
 * @throws IOException IO例外
 */
private static Map<String, MappedStatement> parseMapperConfiguration(String mapperXMLPath) throws IOException {
  InputStream in = null;
  try {
    // key值由mapper介面的全限定類名與方法名組成
    // value值是要執行的SQL陳述句以及物體類的全限定類名
    Map<String, MappedStatement> mappers = new HashMap<>();
    // 獲取輸入流并根據輸入流獲取Document節點
    in = Resources.getResourcesAsStream(mapperXMLPath);
    SAXReader saxReader = new SAXReader();
    Document document = saxReader.read(in);
    // 獲取根節點以及namespace屬性取值
    Element root = document.getRootElement();
    String namespace = root.attributeValue("namespace");
    // 這里只針對SELECT做處理(其它SQL型別同理)
    // 獲取所有的select節點
    List<Element> selectElements = root.selectNodes("//select");
    // 遍歷select節點集合決議內容并填充mappers集合
    for (Element selectElement : selectElements){
      String id = selectElement.attributeValue("id");
      String resultType = selectElement.attributeValue("resultType");
      String queryString = selectElement.getText();
      String key = namespace + "." + id;
      MappedStatement mappedStatement = new MappedStatement();
      mappedStatement.setQueryString(queryString);
      mappedStatement.setResultType(resultType);
      mappers.put(key, mappedStatement);
    }
    return mappers;
  } catch (Exception e){
    throw new RuntimeException(e);
  } finally {
    // 釋放資源
    if (in != null) {
      in.close();
    }
  }
}

在實作 parseMapperConfiguration() 方法時,仍然是利用 dom4j + jaxen 對 Mapper 介面的 xml 組態檔進行決議,遍歷 selectElements 集合,獲取 namespace 標簽以及 id 標簽下的內容進行拼接組成 mappers 集合的 key 值,獲取 SQL 陳述句的型別標簽(select)以及具體的 SQL 陳述句封裝成 MappedStatement 物件作為 mappers 集合的 value 值,最后回傳 mappers 物件,

2.1.5.2 實作parseMapperAnnotation()方法決議注解配置

要實作對注解的決議,首先必須要定義注解,這里針對本案例的查詢陳述句,實作一個 Select 注解,如下所示,

// 自定義Select注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
    String value();
}

然后就是實作 parseMapperAnnotation() 對 Select 注解的決議,實作代碼如下,

/**
 * 決議mapper介面上的注解并封裝成mappers集合
 * @param mapperClassPath mapper介面全限定類名
 * @return 封裝完成的mappers集合
 * @throws IOException IO例外
 */
private static Map<String, MappedStatement> parseMapperAnnotation(String mapperClassPath) throws Exception{
  Map<String, MappedStatement> mappers = new HashMap<>();
  // 獲取mapper介面對應的Class物件
  Class<?> mapperClass = Class.forName(mapperClassPath);
  // 獲取mapper介面中的方法
  Method[] methods = mapperClass.getMethods();
  // 遍歷方法陣列對SELECT注解進行決議
  for (Method method : methods) {
    boolean isAnnotated = method.isAnnotationPresent(Select.class);
    if (isAnnotated) {
      // 創建Mapper物件
      MappedStatement mappedStatement = new MappedStatement();
      // 取出注解的value屬性值
      Select selectAnnotation = method.getAnnotation(Select.class);
      String queryString = selectAnnotation.value();
      mappedStatement.setQueryString(queryString);
      // 獲取當前方法的回傳值及泛型
      Type type = method.getGenericReturnType();
      // 校驗泛型
      if (type instanceof ParameterizedType) {
        ParameterizedType parameterizedType = (ParameterizedType) type;
        Type[] types = parameterizedType.getActualTypeArguments();
        Class<?> clazz = (Class<?>) types[0];
        String resultType = clazz.getName();
        // 給Mapper賦值
        mappedStatement.setResultType(resultType);
      }
      // 給key賦值
      String methodName = method.getName();
      String className = method.getDeclaringClass().getName();
      String key = className + "." + methodName;
      // 填充mappers
      mappers.put(key, mappedStatement);
    }
  }
  return mappers;
}

在實作 parseMapperAnnotation() 方法時,根據 Mapper 介面的全限定類名利用反射機制獲取 Mapper 介面的 Class 物件以及 Method[] 方法陣列,然后遍歷方法陣列其中的注解相關方法并對注解進行決議,最后完成對 mappers 集合的填充并回傳,

2.2 實作創建會話工廠SqlSessionFactory

2.2.1 自定義SqlSessionFactoryBuilder會話工廠構建者類

在前期準備中,我們圍繞 Configuration 類的配置自定義了 Resource 類、 MappedStatement 類以及 XMLConfiguration 類,接下來根據 MyBatis 的執行流程,需要創建一個 SqlSessionFactory 會話工廠類用于創建 SqlSession , 所謂工欲善其事,必先利其器,因此首先要自定義一個會話工廠的構建者類 SqlSessionFactoryBuilder ,并在類中定義一個 build() 方法,通過呼叫 build() 方法來創建 SqlSessionFactory 類,如下所示,

// 會話工廠構建者類
public class SqlSessionFactoryBuilder {
  /**
   * 根據引數的位元組輸入流構建一個SqlSessionFactory工廠
   * @param in 組態檔的輸入流
   * @return SqlSessionFactory
   */
  public SqlSessionFactory build(InputStream in) {
    // 決議組態檔并設定Configuration物件
    Configuration configuration = XMLConfigBuilder.parse(in);
    // 根據Configuration物件構建會話工廠
    return new DefaultSqlSessionFactory(configuration);
  }
}

在這個類中我們定義了 build() 方法,入參是 MyBatis 組態檔的輸入流,首先會呼叫 XMLConfigBuilder#parse() 方法對組態檔輸入流進行決議并設定 Configuration 物件,然后會根據 Configuration 物件構建一個 DefaultSqlSessionFactory 物件并回傳,上篇文章中已經介紹了在 MyBatis 中 SqlSessionFactory 介面有 DefaultSqlSessionFactory 這樣一個默認實作類,因此本文也定義 DefaultSqlSessionFactory 這樣一個默認實作類,

2.2.2 自定義SqlSessionFactory介面與其默認實作類

會話工廠類 SqlSessionFactory 是一個介面,其中定義了一個 openSession() 方法用于創建 SqlSession 會話,如下所示:

// 自定義SqlSessionFactory介面
public interface SqlSessionFactory {
  /**
   * 用于打開一個新的SqlSession物件
   * @return SqlSession
   */
  SqlSession openSession();
}

該介面有一個 DefaultSqlSessionFactory 默認實作類,其中實作了 openSession() 方法,如下所示:

// 自定義DefaultSqlSessionFactory默認實作類
public class DefaultSqlSessionFactory implements SqlSessionFactory {
  // Configuration物件
  private final Configuration configuration;
  // 構造方法
  public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }
  /**
   * 用于創建一個新的操作資料庫物件
   * @return SqlSession
   */
  @Override
  public SqlSession openSession() {
    return new DefaultSqlSession(configuration);
  }
}

可以看到在實作 openSession() 方法中涉及到了 SqlSession 介面以及 SqlSession 介面的 DefaultSqlSession 默認實作類,

2.3 實作創建會話SqlSession

2.3.1 自定義SqlSession介面與其默認實作類

在自定義 SqlSession 介面時,先思考該介面中需要定義哪些方法,在 MyBatis 執行流程中,需要使用 SqlSession 來創建一個 Mapper 介面的代理實體,因此一定需要有 getMapper() 方法來創建 MapperProxy 代理實體,同時,還會涉及到 SqlSession 的釋放資源的操作,因此 close() 方法也是必不可少的,因此自定義 SqlSession 的代碼如下:

// 自定義SqlSession介面
public interface SqlSession {
  
  /**
   * 根據引數創建一個代理物件
   * @param mapperInterfaceClass mapper介面的Class物件
   * @param <T> 泛型
   * @return mapper介面的代理實體
   */
  <T> T getMapper(Class<T> mapperInterfaceClass);
  
  /**
   * 釋放資源
   */
  void close();
}

進一步創建 SqlSession 介面的 DefaultSqlSession 默認實作類,并實作介面中的 getMapper() 和 close() 方法,

public class DefaultSqlSession implements SqlSession {
  
  // 定義成員變數
  private final Configuration configuration;
  private final Connection connection;
  
  // 構造方法
  public DefaultSqlSession(Configuration configuration) {
    this.configuration = configuration;
    // 呼叫工具類獲取資料庫連接
    connection = DataSourceUtil.getConnection(configuration);
  }
  
  /**
   * 用于創建代理物件
   * @param mapperInterfaceClass mapper介面的Class物件
   * @param <T> 泛型
   * @return mapper介面的代理物件
   */
  @Override
  public <T> T getMapper(Class<T> mapperInterfaceClass) {
    // 動態代理
    return (T) Proxy.newProxyInstance(mapperInterfaceClass.getClassLoader(), 
                                      new Class[]{mapperInterfaceClass}, 
                                      new MapperProxyFactory(configuration.getMappers(), connection));
  }

  /**
   * 用于釋放資源
   */
  @Override
  public void close() {
    if (connection != null) {
      try {
        connection.close();
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}

與真正的 MyBatis 實作流程一樣,本文在 getMapper() 方法的實作程序中也采用動態代理的方式回傳 Mapper 介面的代理實體,其中包括了構建 MapperProxyFactory 類,在呼叫 Proxy#newProxyInstance() 方法時,包括的入參以及含義如下:

  • ClassLoader :和被代理物件使用相同的類加載器,這里就是 mapperInterfaceClass 的 ClassLoader ;
  • Class[] :代理物件和被代理物件要有相同的行為(方法);
  • InvocationHandler : 事情處理,執行目標物件的方法時會觸發事情處理器方法,把當前執行的目標物件方作為引數傳入,

然后 DefaultSqlSession#close() 方法的實作主要就是呼叫資料庫連接的 close() 方法,

2.3.2 自定義MapperProxyFactory類

為了實作動態代理,需要自定義 MapperProxyFactory 類用于創建 Mapper 介面的代理實體,其代碼如下:

// 自定義MapperProxyFactory類
public class MapperProxyFactory implements InvocationHandler {
  // mappers集合
  private final Map<String, MappedStatement> mappers;
  private final Connection connection;
  
  public MapperProxyFactory(Map<String, MappedStatement> mappers, Connection connection) {
    this.mappers = mappers;
    this.connection = connection;
  }

  // 實作InvocationHandler介面的invoke()方法
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 獲取方法名
    String methodName = method.getName();
    // 獲取方法所在類的名稱
    String className = method.getDeclaringClass().getName();
    // 組合key
    String key = className + "." + methodName;
    // 獲取mappers中的Mapper物件
    MappedStatement mappedStatement = mappers.get(key);
    // 判斷是否有mapper
    if (mappedStatement != null) {
      // 呼叫Executor()工具類的query()方法
      return new Executor().query(mappedStatement, connection);
    } else {
      throw new IllegalArgumentException("傳入引數有誤");
    }
  }
}

2.4 執行代理物件的相關方法

創建 Mapper 介面的代理物件后,下一步就是執行代理物件的相關方法,這里需要實作 Executor 類用于執行 MapperedStatement 物件中的封裝的 SQL 陳述句并回傳其中指定輸出型別的結果, 在 Executor 類中定義查詢所有相關的 selectList() 方法,如下所示:

// 自定義Executor類
public class Executor {
  
  // query()方法將selectList()的回傳結果轉換為Object型別
  public Object query(MappedStatement mappedStatement, Connection connection) {
    return selectList(mappedStatement, connection);
  }

  /**
   * selectList()方法
   * @param mappedStatement mapper介面
   * @param connection 資料庫連接
   * @param <T> 泛型
   * @return 結果
   */
  public <T> List<T> selectList(MappedStatement mappedStatement, Connection connection) {
    
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;
    
    try {
      // 取出SQL陳述句
      String queryString = mappedStatement.getQueryString();
      // 取出結果型別
      String resultType = mappedStatement.getResultType();
      Class<?> clazz = Class.forName(resultType);
      // 獲取PreparedStatement物件并執行
      preparedStatement = connection.prepareStatement(queryString);
      resultSet = preparedStatement.executeQuery();
      // 從結果集物件封裝結果
      List<T> list = new ArrayList<>();
      while(resultSet.next()) {
        //實體化要封裝的物體類物件
        T obj = (T) clazz.getDeclaredConstructor().newInstance();
        // 取出結果集的元資訊
        ResultSetMetaData resultSetMetaData = https://www.cnblogs.com/chiaki/p/resultSet.getMetaData();
        // 取出總列數
        int columnCount = resultSetMetaData.getColumnCount();
        // 遍歷總列數給物件賦值
        for (int i = 1; i <= columnCount; i++) {
          String columnName = resultSetMetaData.getColumnName(i);
          Object columnValue = resultSet.getObject(columnName);
          PropertyDescriptor descriptor = new PropertyDescriptor(columnName, clazz);
          Method writeMethod = descriptor.getWriteMethod();
          writeMethod.invoke(obj, columnValue);
        }
        // 把賦好值的物件加入到集合中
        list.add(obj);
      }
      return list;
    } catch (Exception e) {
      throw new RuntimeException(e);
    } finally {
      // 呼叫release()方法釋放資源
      release(preparedStatement, resultSet);
    }
  }

  /**
   * 釋放資源
   * @param preparedStatement preparedStatement物件
   * @param resultSet resultSet物件
   */
  private void release(PreparedStatement preparedStatement, ResultSet resultSet) {
    if (resultSet != null) {
      try {
        resultSet.close();
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    if (preparedStatement != null) {
      try {
        preparedStatement.close();
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}

在 Executor 類中最為核心的就是 selectList() 方法,該方法的實作邏輯在于從 MappedStatement 物件中取出 SQL 陳述句以及結果集型別,然后根據 SQL 陳述句資訊構建 PreparedStatement 物件并執行回傳 ResultSet 物件,然后將 ResultSet 中的資料轉換為 MappedStatement 中指定的結果集型別 ResultType 的資料并回傳,

2.5 小結

至此,一個手寫 MyBatis 簡單框架就搭建完成了,其搭建程序完全遵循原生 MyBatis 框架對 SQL 陳述句的執行流程,現對上述程序做下小結:

  • ?撰寫必要的物體類,包括 Configuration 、 MapperStatement 類等?;
  • ?撰寫必要的工具類,包括獲取資料庫連接的 DataSourceUtil 類、讀取組態檔的 Resources 類以及決議配置的 XMLConfigBuilder 類?;
  • ?撰寫 XMLConfigBuilder 類時,基于 dom4j + jaxen 對 xml 組態檔進行加載和決議,基于反射機制對自定義注解配置進行加載和決議,加載決議完成后填充 mappers 集合并設定到 Configuration 物件中?;
  • ?撰寫 SqlSessionFactoryBuilder 構建者類用于構建 SqlSessionFactory 類?;
  • ?撰寫 SqlSessionFactory 和 SqlSession 介面及其默認實作類?;
  • ?撰寫 MapperProxyFactory 類實作基于動態代理創建 Mapper 介面的代理實體?;
  • ?撰寫 Executor 類用于根據 mappers 集合執行相應 SQL 陳述句并回傳結果?,

3. 自定義MyBatis框架的測驗

為了測驗前文中手寫的 MyBatis 簡單框架,定義如下的測驗方法:

// MyBatisTest測驗類
public class MybatisTest {
  
  private InputStream in;
  private SqlSession sqlSession;
  
  @Before
  public void init() {
    // 讀取MyBatis的組態檔
    in = Resources.getResourcesAsStream("mybatis-config.xml");
    // 創建SqlSessionFactory的構建者物件
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    // 使用builder創建SqlSessionFactory物件
    SqlSessionFactory factory = builder.build(in);
    // 使用factory創建sqlSession物件
    sqlSession = factory.openSession();
  }

  @Test
  public void testMyMybatis() {
    // 使用SqlSession創建Mapper介面的代理物件
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    // 使用代理物件執行方法
    List<Student> students = studentMapper.findAll();
    System.out.println(students);
  }

  @After
  public void close() throws IOException {
    // 關閉資源
    sqlSession.close();
    in.close();
  }
}

首先在組態檔中將 mapper 的配置方式設定為指定 xml 檔案,其中 StudentMapper 介面的 xml 檔案如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="cn.chiaki.mapper.StudentMapper">
  <select id="findAll" resultType="cn.chiaki.entity.Student">
    SELECT * FROM student
  </select>
</mapper>

運行測驗方法得到的結果如下所示,驗證了手寫框架的正確性,

image-20210313011156925

此外,我們修改 mybatis-config.xml 組態檔的 mapper 配置方式為注解配置,同時在 StudentMapper 介面上加入注解,如下所示,

<mappers>
  <!-- 使用xml組態檔的方式:resource標簽 -->
  <!--<mapper resource="mapper/StudentMapper.xml"/>-->
  <!-- 使用注解方式:class標簽 -->
  <mapper />
</mappers>
@Select("SELECT * FROM STUDENT")
List<Student> findAll();

再次運行測驗方法可以得到相同的運行結果,如下圖所示,

image-20210313011648055

通過運行測驗方法驗證了本文手寫的 MyBatis 簡單框架的正確性,

4. 全文總結

本文根據原生 MyBatis 框架的運行流程,主要借助 dom4j 以及 jaxen 工具,逐步實作了一個自定義的 MyBatis 簡易框架,實作案例中查詢所有學生資訊的功能,本文的實作程序相對簡單,僅僅只是涉及到了 select 型別的 SQL 陳述句的決議,不涉及其它查詢型別,也不涉及到 SQL 陳述句帶引數的情況,同時也無法做到對組態檔中與資料庫相關的快取、事務等相關標簽的決議,總而言之只是一個玩具級別的框架,然而,本文實作這樣一個簡單的自定義 MyBatis 框架的目的是加深對 MyBatis 框架運行流程的理解,所謂萬丈高樓平地起,只有先打牢底層基礎,才能進一步去實作更高級的功能,讀者可以自行嘗試,

參考資料

淺析MyBatis(一):由一個快速案例剖析MyBatis的整體架構與運行流程

dom4j 官方檔案:https://dom4j.github.io/

jaxen 代碼倉庫:https://github.com/jaxen-xpath/jaxen

《互聯網輕量級 SSM 框架解密:Spring 、 Spring MVC 、 MyBatis 原始碼深度剖析》

覺得有用的話,就點個推薦吧~
??????

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/270732.html

標籤:其他

上一篇:Redmine部署中遇到的問題

下一篇:關于回歸測驗的那些面試題,都幫你整理好了!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more