主頁 > 軟體設計 > 手寫 Mybatis,“整整” Mybatis原始碼

手寫 Mybatis,“整整” Mybatis原始碼

2020-10-31 21:31:26 軟體設計

前言

前兩天寫了一個 手寫Spring ioc 框架,“擼擼”Spring 原始碼我們今天來整整Mybatis,

mybaits 在 ORM 框架中,可算是半壁江山了,由于它是輕量級,半自動加載,靈活性和易拓展性,深受廣大公司的喜愛,所以我們程式開發也離不開 mybatis ,

很多朋友對 mybatis 原始碼沒什么了解,或者想看但是不知道怎么看的苦惱嗎?

歸根結底,我們還是需要知道為什么會有 mybatis ,mybatis 解決了什么問題? 想要知道 mybatis 解決了什么問題,就要知道傳統的 JDBC 操作存在哪些痛點才促使 mybatis 的誕生, 我們帶著這些疑問,再來一步步學習吧,

內容稍微有點長!耐心閱讀!

另外本人整理收藏了20年多家公司面試知識點整理 ,以及各種Java核心知識點免費分享給大家,下方只是部分截圖
想要資料的話也可以點擊直接進入:暗號:csdn,免費獲取,

在這里插入圖片描述

傳統JDBC的弊端

所以我們先來來看下原始 JDBC 的操作

我們知道最原始的資料庫操作,分為以下幾步

  1. 獲取 connection 連接
  2. 獲取 preparedStatement
  3. 引數替代占位符
  4. 獲取執行結果 resultSet
  5. 決議封裝 resultSet 到物件中回傳,

如下是原始 JDBC 的查詢代碼,存在哪些問題?

public static void main(String[] args) {
        String dirver="com.mysql.jdbc.Driver";
        String url="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8";
        String userName="root";
        String password="123456";

        Connection connection=null;
        List<User> userList=new ArrayList<>();
        try {
            Class.forName(dirver);
            connection= DriverManager.getConnection(url,userName,password);

            String sql="select * from user where username=?";
            PreparedStatement preparedStatement=connection.prepareStatement(sql);
            preparedStatement.setString(1,"張三");
            System.out.println(sql);
            ResultSet resultSet=preparedStatement.executeQuery();

            User user=null;
            while(resultSet.next()){
                user=new User();
                user.setId(resultSet.getInt("id"));
                user.setUsername(resultSet.getString("username"));
                user.setPassword(resultSet.getString("password"));
                userList.add(user);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (!userList.isEmpty()) {
            for (User user : userList) {
                System.out.println(user.toString());
            }
        }

    }

大家是不是發現了上面有哪些不友好的地方?

我這里總結了以下幾點:

  1. 資料庫的連接資訊存在硬編碼,即是寫死在代碼中的,
  2. 每次操作都會建立和釋放 connection 連接,操作資源的不必要的浪費,
  3. sql 和引數存在硬編碼,
  4. 將回傳結果集封裝成物體類麻煩,要創建不同的物體類,并通過 set 方法一個個的注入,

存在上面的問題,所以 mybatis 就對上述問題進行了改進, 對于硬編碼,我們很容易就想到組態檔來解決,mybatis 也是這么解決的, 對于資源浪費,我們想到使用連接池,mybatis 也是這個解決的, 對于封裝結果集麻煩,我們想到是用 JDK 的反射機制,好巧,mybatis 也是這么解決的,

設計思路

既然如此,我們就來寫一個自定義持久層框架,來解決上述問題,當然是參照 mybatis 的設計思路,這樣我們在寫完之后,再來看 mybatis 的原始碼就恍然大悟,這個地方這樣配置原來是因為這樣啊,

我們分為使用端和框架端兩部分,

學海無涯,我們一起勉力前行!

Ps:有需要的小伙伴可以點擊直接進入:暗號:csdn,免費獲取,

在這里插入圖片描述

使用端

我們在使用 mybatis 的時候是不是需要使用 SqlMapConfig.xml 組態檔,用來存放資料庫的連接資訊,以及 mapper.xml 的指向資訊,mapper.xml 組態檔用來存放 sql 資訊,

所以我們在使用端來創建兩個檔案 SqlMapConfig.xml 和 mapper.xml,

框架端

框架端要做哪些事情呢?如下:

  1. 獲取組態檔,也就是獲取到使用端的 SqlMapConfig.xml 以及 mapper.xml 的檔案
  2. 決議組態檔,對獲取到的檔案進行決議,獲取到連接資訊,sql,引數,回傳型別等等,這些資訊都會保存在 configuration 這個物件中,
  3. 創建 SqlSessionFactory,目的是創建 SqlSession 的一個實體,
  4. 創建 SqlSession ,用來完成上面原始 JDBC 的那些操作,

那在 SqlSession 中 進行了哪些操作呢?

  1. 獲取資料庫連接
  2. 獲取 sql ,并對 sql 進行決議
  3. 通過內省,將引數注入到 preparedStatement 中
  4. 執行 sql
  5. 通過反射將結果集封裝成物件

使用端實作

好了,上面說了一下,大概的設計思路,主要也是仿照 mybatis 主要的類實作的,保證類名一致,方便我們后面閱讀原始碼,我們先來配置好使用端吧,我們創建一個 maven 專案,

在專案中,我們創建一個 User 物體類

public class User {
    private Integer id;
    private String username;
    private String password;
    private String birthday;
    //getter()和 setter()方法
}

創建 SqlMapConfig.xml 和 Mapper.xml SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&amp;characterEncoding=utf8&amp;useUnicode=true&amp;useSSL=false"></property>
    <property name="userName" value="root"></property>
    <property name="password" value="123456"></property>

    <mapper resource="UserMapper.xml">
    </mapper>
</configuration>

可以看到我們 xml 中就配置了資料庫的連接資訊,以及 mapper 一個索引,mybatis 中的 SqlMapConfig.xml 中還包含其他的標簽,只是豐富了功能而已,所以我們只用最主要的,

mapper.xml 是每個類的 sql 都會生成一個對應的 mapper.xml ,我們這里就用 User 類來說吧,所以我們就創建一個 UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="cn.quellanan.dao.UserDao">
    <select id="selectAll" resultType="cn.quellanan.pojo.User">
        select * from user
    </select>
    <select id="selectByName" resultType="cn.quellanan.pojo.User" paramType="cn.quellanan.pojo.User">
        select * from user where username=#{username}
    </select>
</mapper>

可以看到有點 mybatis 里面檔案的味道,有 namespace 表示命名空間,id 唯一標識,resultType 回傳結果集的型別,paramType 引數的型別, 我們使用端先創建到這,主要是兩個組態檔,

我們接下來看看框架端是怎么實作的,

框架端實作

架端,我們按照上面的設計思路一步一步來,

獲取配置

怎么樣獲取組態檔呢?我們可以使用 JDK 自帶自帶的類 Resources 加載器來獲取檔案,我們創建一個自定義 Resource 類來封裝一下:

import java.io.InputStream;
public class Resources {
    public  static InputStream getResources(String path){
        //使用系統自帶的類 Resources 加載器來獲取檔案,
        return Resources.class.getClassLoader().getResourceAsStream(path);
    }
}

這樣通過傳入路徑,就可以獲取到對應的檔案流啦,

決議組態檔

上面獲取到了 SqlMapConfig.xml 組態檔,我們現在來決議它, 不過在此之前,我們需要做一點準備作業,就是決議的記憶體放到什么地方?

所以我們來創建兩個物體類 Mapper 和 Configuration ,

Mapper Mapper 物體類用來存放使用端寫的 mapper.xml 檔案的內容,我們前面說了里面有 id、sql、resultType 和 paramType .所以我們創建的 Mapper 物體如下:

public class Mapper {
    private String id;
    private Class<?> resultType;
    private Class<?> parmType;
    private String sql;
    //getter()和 setter()方法
}

這里我們為什么不添加 namespace 的值呢? 聰明的你肯定發現了,因為 mapper 里面這些屬性表明每個 sql 都對應一個 mapper , 而 namespace 是一個命名空間,算是 sql 的上一層,所以在 mapper 中暫時使用不到,就沒有添加了,

Configuration Configuration 物體用來保存 SqlMapConfig 中的資訊,所以需要保存資料庫連接,我們這里直接用 JDK 提供的 DataSource ,還有一個就是 mapper 的資訊,每個 mapper 有自己的標識,所以這里采用 hashMap 來存盤,如下:

public class Configuration {

    private DataSource dataSource;
    HashMap <String,Mapper> mapperMap=new HashMap<>();
    //getter()和 setter 方法
    }

XmlMapperBuilder

做好了上面的準備作業,我們先來決議 mapper 吧,我們創建一個 XmlMapperBuilder 類來決議,通過 dom4j 的工具類來決議 XML 檔案,我這里用的 dom4j 依賴為:

         <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>

思路:

  1. 獲取檔案流,轉成 document,
  2. 獲取根節點,也就是 mapper,獲取根節點的 namespace 屬性值
  3. 獲取 select 節點,獲取其 id,sql , resultType ,paramType
  4. 將 select 節點的屬性封裝到 Mapper 物體類中,
  5. 同理獲取 update/insert/delete 節點的屬性值封裝到 Mapper 中
  6. 通過 namespace.id 生成 key 值將 mapper 物件保存到 Configuration 物體中的 HashMap 中,
  7. 回傳 Configuration 物體
    代碼如下:
public class XmlMapperBuilder {
    private Configuration configuration;
    public XmlMapperBuilder(Configuration configuration){
        this.configuration=configuration;
    }

    public Configuration loadXmlMapper(InputStream in) throws DocumentException, ClassNotFoundException {
        Document document=new SAXReader().read(in);

        Element rootElement=document.getRootElement();
        String namespace=rootElement.attributeValue("namespace");

        List<Node> list=rootElement.selectNodes("//select");

        for (int i = 0; i < list.size(); i++) {
            Mapper mapper=new Mapper();
            Element element= (Element) list.get(i);
            String id=element.attributeValue("id");
            mapper.setId(id);
            String paramType = element.attributeValue("paramType");
            if(paramType!=null && !paramType.isEmpty()){
                mapper.setParmType(Class.forName(paramType));
            }
            String resultType = element.attributeValue("resultType");
            if (resultType != null && !resultType.isEmpty()) {
                mapper.setResultType(Class.forName(resultType));
            }
            mapper.setSql(element.getTextTrim());
            String key=namespace+"."+id;
            configuration.getMapperMap().put(key,mapper);
        }
        return configuration;
    }

}

上面我只決議了 select 標簽,大家可以決議對應 insert/delete/uupdate 標簽,操作都是一樣的,

XmlConfigBuilder

我們再來決議一下 SqlMapConfig.xml 配置資訊思路是一樣的,

1、獲取檔案流,轉成 document,
2、獲取根節點,也就是 configuration,
3、獲取根節點中所有的 property 節點,并獲取值,也就是獲取資料庫連接資訊
4、創建一個 dataSource 連接池
5、將連接池資訊保存到 Configuration 物體中
6、獲取根節點的所有 mapper 節點
7、呼叫 XmlMapperBuilder 類決議對應 mapper 并封裝到 Configuration 物體中
8、完
代碼如下:

public class XmlConfigBuilder {
    private Configuration configuration;
    public XmlConfigBuilder(Configuration configuration){
        this.configuration=configuration;
    }

    public Configuration loadXmlConfig(InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {

        Document document=new SAXReader().read(in);

        Element rootElement=document.getRootElement();

        //獲取連接資訊
        List<Node> propertyList=rootElement.selectNodes("//property");
        Properties properties=new Properties();

        for (int i = 0; i < propertyList.size(); i++) {
            Element element = (Element) propertyList.get(i);
            properties.setProperty(element.attributeValue("name"),element.attributeValue("value"));
        }
        //是用連接池
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(properties.getProperty("driverClass"));
        dataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        dataSource.setUser(properties.getProperty("userName"));
        dataSource.setPassword(properties.getProperty("password"));
        configuration.setDataSource(dataSource);

        //獲取 mapper 資訊
        List<Node> mapperList=rootElement.selectNodes("//mapper");
        for (int i = 0; i < mapperList.size(); i++) {
            Element element= (Element) mapperList.get(i);
            String mapperPath=element.attributeValue("resource");
            XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(configuration);
            configuration=xmlMapperBuilder.loadXmlMapper(Resources.getResources(mapperPath));
        }
        return configuration;
    }
}

創建 SqlSessionFactory

完成決議后我們創建 SqlSessionFactory 用來創建 Sqlseesion 的物體,這里為了盡量還原 mybatis 設計思路,也也采用的工廠設計模式, SqlSessionFactory 是一個介面,里面就一個用來創建 SqlSessionf 的方法, 如下:

public interface SqlSessionFactory {
    public SqlSession openSqlSession();
}

單單這個介面是不夠的,我們還得寫一個介面的實作類,所以我們創建一個 DefaultSqlSessionFactory, 如下:

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }
    public SqlSession openSqlSession() {
        return new DefaultSqlSeeion(configuration);
    }
}

可以看到就是創建一個 DefaultSqlSeeion 并將包含配置資訊的 configuration 傳遞下去,DefaultSqlSeeion 就是 SqlSession 的一個實作類,

創建 SqlSession
在 SqlSession 中我們就要來處理各種操作了,比如 selectList,selectOne,insert, update , delete 等等, 如下:

public interface SqlSession {

    /**
     * 條件查找
     * @param statementid  唯一標識,namespace.selectid
     * @param parm  傳參,可以不傳也可以一個,也可以多個
     * @param <E>
     * @return
     */
    public <E> List<E> selectList(String statementid,Object...parm) throws Exception;

    public <T> T selectOne(String statementid, Object...parm) throws Exception;

    public int insert(String statementid, Object...parm) throws Exception;
    public int update(String statementid, Object...parm) throws Exception;
    public int delete(String statementid, Object...parm) throws Exception;
    public void commit() throws Exception;


    /**
     * 使用代理模式來創建介面的代理物件
     * @param mapperClass
     * @param <T>
     * @return
     */
    public <T> T getMapper(Class<T> mapperClass);

然后我們創建 DefaultSqlSeeion 來實作 SqlSeesion ,

public class DefaultSqlSeeion implements SqlSession {

    private Configuration configuration;

    private Executer executer=new SimpleExecuter();

    public DefaultSqlSeeion(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <E> List<E> selectList(String statementid, Object... parm) throws Exception {
        Mapper mapper=configuration.getMapperMap().get(statementid);
        List<E> query = executer.query(configuration, mapper, parm);
        return query;
    }

    @Override
    public <T> T selectOne(String statementid, Object... parm) throws Exception {
        List<Object> list =selectList(statementid, parm);
        if(list.size()==1){
            return (T) list.get(0);
        }else{
            throw new RuntimeException("回傳結果過多");
        }
    }

    @Override
    public int insert(String statementid, Object... parm) throws Exception {
        return update(statementid,parm);
    }

    @Override
    public int update(String statementid, Object... parm) throws Exception {
        Mapper mapper=configuration.getMapperMap().get(statementid);
        int update = executer.update(configuration, mapper, parm);
        return update;
    }

    @Override
    public int delete(String statementid, Object... parm) throws Exception {
        return update(statementid,parm);
    }

    @Override
    public void commit() throws Exception {
        executer.commit();
    }



    @Override
    public <T> T getMapper(Class<T> mapperClass) {

        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSeeion.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                //獲取到方法名
                String name = method.getName();
                //型別
                String className = method.getDeclaringClass().getName();
                String statementid=className+"."+name;

                Mapper mapper = configuration.getMapperMap().get(statementid);
                SqlCommandType sqlType = mapper.getSqlCommandType();
                Type genericReturnType = method.getGenericReturnType();


                switch (sqlType){
                    case SELECT:
                        //判斷是否實作泛型型別引數化
                        if(genericReturnType instanceof ParameterizedType){
                            return selectList(statementid,args);
                        }else {
                            return selectOne(statementid,args);
                        }
                    case INSERT:return insert(statementid,args);
                    case DELETE:return delete(statementid,args);
                    case UPDATE:return update(statementid,args);
                    default:break;
                }
                return null;
            }
        });


        return (T) proxyInstance;
    }

}

我們可以看到 DefaultSqlSeeion 獲取到了 configuration,并通過 statementid 從 configuration 中獲取 mapper, 然后具體實作交給了 Executer 類來實作,我們這里先不管 Executer 是怎么實作的,就假裝已經實作了,那么整個框架端就完成了,通過呼叫 Sqlsession.selectList() 方法,來獲取結果,

感覺我們都還沒有處理,就框架搭建好了?騙鬼呢,確實前面我們從獲取檔案決議檔案,然后創建工廠,都是做好準備作業,下面開始我們 JDBC 的實作,

SqlSession 具體實作

我們前面說 SqlSeesion 的具體實作有下面 5 步

  1. 獲取資料庫連接
  2. 獲取 sql,并對 sql 進行決議
  3. 通過內省,將引數注入到 preparedStatement 中
  4. 執行 sql
  5. 通過反射將結果集封裝成物件

但是我們在 DefaultSqlSeeion 中將實作交給了 Executer 來執行,所以我們就要在 Executer 中來實作這些操作,

我們首先來創建一個 Executer 介面,并寫一個 DefaultSqlSeeion 中呼叫的 query 方法,

public interface Executer {

    <E> List<E> query(Configuration configuration,Mapper mapper,Object...parm) throws Exception;

}

接著我們寫一個 SimpleExecuter 類來實作 Executer , 然后 SimpleExecuter.query() 方法中,我們一步一步的實作,

獲取資料庫連接
因為資料庫連接資訊保存在 configuration,所以直接獲取就好了,

//獲取連接
        connection=configuration.getDataSource().getConnection();

獲取 sql,并對 sql 進行決議

我們這里想一下,我們在 Usermapper.xml 寫的 sql 是什么樣子?

select * from user where username=#{username}

分兩步

  1. 將 sql 找到 #{***} ,并將這部分替換成 ?號

  2. 對 #{***} 進行決議獲取到里面的引數對應的 paramType 中的值,

具體實作用到下面幾個類, GenericTokenParser 類,可以看到有三個引數,開始標記,就是我們的 “#{” ,結束標記就是 “}” , 標記處理器就是處理標記里面的內容也就是 username,

public class GenericTokenParser {

  private final String openToken; //開始標記
  private final String closeToken; //結束標記
  private final TokenHandler handler; //標記處理器

  public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
    this.openToken = openToken;
    this.closeToken = closeToken;
    this.handler = handler;
  }

  /**
   * 決議${}和#{}
   * @param text
   * @return
   * 該方法主要實作了組態檔、腳本等片段中占位符的決議、處理作業,并回傳最終需要的資料,
   * 其中,決議作業由該方法完成,處理作業是由處理器 handler 的 handleToken()方法來實作
   */
  public String parse(String text) {
      //具體實作
      }

主要的就是 parse() 方法,用來獲取操作 1 的 sql,獲取結果例如:

select * from user where username=?

那上面用到 TokenHandler 來處理引數, ParameterMappingTokenHandler 實作 TokenHandler 的類

public class ParameterMappingTokenHandler implements TokenHandler {
    private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();

    // context 是引數名稱 #{id} #{username}

    @Override
    public String handleToken(String content) {
        parameterMappings.add(buildParameterMapping(content));
        return "?";
    }

    private ParameterMapping buildParameterMapping(String content) {
        ParameterMapping parameterMapping = new ParameterMapping(content);
        return parameterMapping;
    }

    public List<ParameterMapping> getParameterMappings() {
        return parameterMappings;
    }

    public void setParameterMappings(List<ParameterMapping> parameterMappings) {
        this.parameterMappings = parameterMappings;
    }

}

可以看到將引數名稱存放 ParameterMapping 的集合中了, ParameterMapping 類就是一個物體,用來保存引數名稱的,

public class ParameterMapping {

    private String content;

    public ParameterMapping(String content) {
        this.content = content;
    }
    //getter()和 setter() 方法,
}

所以我們在我們通過 GenericTokenParser 類,就可以獲取到決議后的 sql,以及引數名稱,我們將這些資訊封裝到 BoundSql 物體類中,

public class BoundSql {

    private String sqlText;
    private List<ParameterMapping> parameterMappingList=new ArrayList<>();
    public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {
        this.sqlText = sqlText;
        this.parameterMappingList = parameterMappingList;
    }
    getter()和 setter() 方法,
  }

好了,那么分兩步走,先獲取,后決議 獲取 獲取原始 sql 很簡單,sql 資訊就存在 mapper 物件中,直接獲取就好了,

String sql=mapper.getSql()

決議

1、創建一個 ParameterMappingTokenHandler 處理器
2、創建一個 GenericTokenParser 類,并初始化開始標記,結束標記,處理器
3、執行 genericTokenParser.parse(sql) ;獲取決議后的 sql‘’,以及在 parameterMappingTokenHandler 中存放了引數名稱的集合,
4、將決議后的 sql 和引數封裝到 BoundSql 物體類中,

/**
     * 決議自定義占位符
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql){
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{","}",parameterMappingTokenHandler);
        String parse = genericTokenParser.parse(sql);
        return new BoundSql(parse,parameterMappingTokenHandler.getParameterMappings());

    }

將引數注入到 preparedStatement 中

上面的就完成了 sql 的決議,但是我們知道上面得到的 sql 還是包含 JDBC 的 占位符,所以我們需要將引數注入到 preparedStatement 中,

  1. 通過 boundSql.getSqlText() 獲取帶有占位符的 sql .
  2. 接收引數名稱集合 parameterMappingList
  3. 通過 mapper.getParmType() 獲取到引數的類,
  4. 通過 getDeclaredField(content) 方法獲取到引數類的 Field,
  5. 通過 Field.get() 從引數類中獲取對應的值
  6. 注入到 preparedStatement 中
     BoundSql boundSql=getBoundSql(mapper.getSql());
        String sql=boundSql.getSqlText();
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();

        //獲取 preparedStatement,并傳遞引數值
        PreparedStatement preparedStatement=connection.prepareStatement(sql);
        Class<?> parmType = mapper.getParmType();

        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();
            Field declaredField = parmType.getDeclaredField(content);
            declaredField.setAccessible(true);
            Object o = declaredField.get(parm[0]);
            preparedStatement.setObject(i+1,o);
        }
        System.out.println(sql);
        return preparedStatement;

執行 sql

其實還是呼叫 JDBC 的 executeQuery() 方法或者 execute() 方法

//執行 sql
 ResultSet resultSet = preparedStatement.executeQuery();

通過反射將結果集封裝成物件

在獲取到 resultSet 后,我們進行封裝處理,和引數處理是類似的,

  1. 創建一個 ArrayList
  2. 獲取回傳型別的類
  3. 回圈從 resultSet 中取資料
  4. 獲取屬性名和屬性值
  5. 創建屬性生成器
  6. 為屬性生成寫方法,并將屬性值寫入到屬性中
  7. 將這條記錄添加到 list 中
  8. 回傳 list
/**
     * 封裝結果集
     * @param mapper
     * @param resultSet
     * @param <E>
     * @return
     * @throws Exception
     */
    private <E> List<E> resultHandle(Mapper mapper,ResultSet resultSet) throws Exception{
        ArrayList<E> list=new ArrayList<>();
        //封裝結果集
        Class<?> resultType = mapper.getResultType();
        while (resultSet.next()) {
            ResultSetMetaData metaData = resultSet.getMetaData();
            Object o = resultType.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,resultType);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o,value);
            }
            list.add((E) o);
        }
        return list;
    }

創建 SqlSessionFactoryBuilder

我們現在來創建一個 SqlSessionFactoryBuilder 類,來為使用端提供一個人口,

public class SqlSessionFactoryBuilder {

    private Configuration configuration;

    public SqlSessionFactoryBuilder(){
        configuration=new Configuration();
    }

    public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {
        XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(configuration);
        configuration=xmlConfigBuilder.loadXmlConfig(in);

        SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return sqlSessionFactory;
    }
}

可以看到就一個 build 方法,通過 SqlMapConfig 的檔案流將資訊決議到 configuration ,創建并回傳一個 sqlSessionFactory ,

到此,整個框架端已經搭建完成了,但是我們可以看到,只實作了 select 的操作, update 、inster 、delete 的操作我們在我后面提供的原始碼中會有實作,這里只是將整體的設計思路和流程,

測驗

終于到了測驗的環節啦,我們前面寫了自定義的持久層,我們現在來測驗一下能不能正常的使用吧, 見證奇跡的時刻到啦

我們先引入我們自定義的框架依賴,以及資料庫和單元測驗

 <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        <dependency>
            <groupId>cn.quellanan</groupId>
            <artifactId>myself-mybatis</artifactId>
            <version>1.0.0</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>

然后我們寫一個測驗類

  1. 獲取 SqlMapperConfig.xml 的檔案流
  2. 獲取 Sqlsession
  3. 執行查找操作
@org.junit.Test
    public void test() throws Exception{
        InputStream inputStream= Resources.getResources("SqlMapperConfig.xml");
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(inputStream).openSqlSession();
        List<User> list = sqlSession.selectList("cn.quellanan.dao.UserDao.selectAll");

        for (User parm : list) {
            System.out.println(parm.toString());
        }
        System.out.println();

        User user=new User();
        user.setUsername("張三");
        List<User> list1 = sqlSession.selectList("cn.quellanan.dao.UserDao.selectByName", user);
        for (User user1 : list1) {
            System.out.println(user1);
        }

    }

在這里插入圖片描述

可以看到已經可以了,看來我們自定義的持久層框架生效啦,

優化

我們看上面的測驗方法,是不是感覺和平時用的不一樣,每次都都寫死 statementId ,這樣不太友好,所以我們接下來來點騷操作,通用 mapper 配置, 我們在 SqlSession 中增加一個 getMapper 方法,接收的引數是一個類,我們通過這個類就可以知道 statementId ,

/**
     * 使用代理模式來創建介面的代理物件
     * @param mapperClass
     * @param <T>
     * @return
     */
    public <T> T getMapper(Class<T> mapperClass);

具體實作就是利用 JDK 的動態代理機制,

  1. 通過 Proxy.newProxyInstance() 獲取一個代理物件
  2. 回傳代理物件 那代理物件執行了哪些操作呢? 創建代理物件的時候,會實作一個 InvocationHandler 介面,重寫 invoke() 方法,讓所有走這個代理的方法都會執行這個 i nvoke() 方法,那這個方法做了什么操作? 這個方法就是通過傳入的類物件,獲取到物件的類名和方法名,用來生成 statementid ,

所以我們在 mapper.xml 組態檔中的 namespace 就需要制定為類路徑,以及 id 為方法名, 實作方法:

@Override
    public <T> T getMapper(Class<T> mapperClass) {

        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSeeion.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                //獲取到方法名
                String name = method.getName();
                //型別
                String className = method.getDeclaringClass().getName();
                String statementid=className+"."+name;

                return selectList(statementid,args);
            }
        });


        return (T) proxyInstance;
    }

我們寫一個 UserDao

public interface UserDao {
    List<User> selectAll();

    List<User> selectByName(User user);
}

這個是不是我們熟悉的味道哈哈,就是 mapper 層的介面, 然后我們在 mapper.xml 中指定 namespace 和 id
在這里插入圖片描述
接下來我們在寫一個測驗方法

@org.junit.Test
    public void test2() throws Exception{
        InputStream inputStream= Resources.getResources("SqlMapperConfig.xml");
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(inputStream).openSqlSession();

        UserDao mapper = sqlSession.getMapper(UserDao.class);
        List<User> users = mapper.selectAll();
        for (User user1 : users) {
            System.out.println(user1);
        }

        User user=new User();
        user.setUsername("張三");
        List<User> users1 = mapper.selectByName(user);
        for (User user1 : users1) {
            System.out.println(user1);
        }

    }

在這里插入圖片描述

瞎比比

自定義的持久層框架,我們就寫完了,這個實際上就是 mybatis 的雛形,我們通過自己手動寫一個持久層框架,然后在來看 mybatis 的原始碼,就會清晰很多,下面這些類名在 mybatis 中都有體現,
在這里插入圖片描述

好了,本文到此就結束了!閱讀原始碼愉快, 覺得有用的兄弟們三連啊,

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

標籤:其他

上一篇:Java大牛發布最新Java實戰筆記,從基礎知識到函式式編程

下一篇:阿里巴巴一個超級牛逼的混沌實驗實施工具【Chaosblade】

標籤雲
其他(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)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more