主頁 >  其他 > 手寫 Mybatis,“整整” Mybatis原始碼

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

2020-11-01 04:10:37 其他

前言

前兩天寫了一個 手寫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/qita/197657.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)

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

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的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