主頁 > 後端開發 > 天哪!手動撰寫mybatis雛形竟然這么簡單

天哪!手動撰寫mybatis雛形竟然這么簡單

2020-10-13 12:56:08 後端開發

前言

mybaits 在ORM 框架中,可算是半壁江山了,由于它是輕量級,半自動加載,靈活性和易拓展性,深受廣大公司的喜愛,所以我們程式開發也離不開mybatis ,但是我們有對mabtis 原始碼進行研究嗎?或者想看但是不知道怎么看的苦惱嗎?
在這里插入圖片描述
歸根結底,我們還是需要知道為什么會有mybatis ,mybatis 解決了什么問題?
想要知道mybatis 解決了什么問題,就要知道傳統的JDBC 操作存在哪些痛點才促使mybatis 的誕生,
我們帶著這些疑問,再來一步步學習吧,

原始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 的原始碼就恍然大悟,這個地方這樣配置原來是因為這樣啊,
我們分為使用端和框架端兩部分,

使用端

我們在使用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="https://www.cnblogs.com/quellanan/p/com.mysql.jdbc.Driver"></property>
    <property name="jdbcUrl" value="https://www.cnblogs.com/quellanan/p/jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false"></property>
    <property name="userName" value="https://www.cnblogs.com/quellanan/p/root"></property>
    <property name="password" value="https://www.cnblogs.com/quellanan/p/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 等等,
我們這里SqlSession 就先寫一個selectList 方法,
如下:

public interface SqlSession {

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

然后我們創建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;
    }

}

我們可以看到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}

{username} 這樣的sql 我們改怎么決議呢?

分兩步
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 = https://www.cnblogs.com/quellanan/p/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() 方法,讓所有走這個代理的方法都會執行這個invoke() 方法,那這個方法做了什么操作?
這個方法就是通過傳入的類物件,獲取到物件的類名和方法名,用來生成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 中都有體現,
在這里插入圖片描述
這里拋磚引玉,祝君閱讀原始碼愉快,
覺得有用的兄弟們記得收藏啊,

厚顏無恥的求波點贊!!!
在這里插入圖片描述

本文由博客一文多發平臺 OpenWrite 發布!

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

標籤:Java

上一篇:用一個測驗類簡化排序演算法時間復雜度的研究

下一篇:爬蟲之資料決議

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

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more