1. 程式人生 > >天哪!手動編寫mybatis雛形竟然這麼簡單

天哪!手動編寫mybatis雛形竟然這麼簡單

# 前言 mybaits 在ORM 框架中,可算是半壁江山了,由於它是輕量級,半自動載入,靈活性和易拓展性。深受廣大公司的喜愛,所以我們程式開發也離不開mybatis 。但是我們有對mabtis 原始碼進行研究嗎?或者想看但是不知道怎麼看的苦惱嗎? ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/1808952/202006/1808952-20200606155149392-1312204805.png) 歸根結底,我們還是需要知道為什麼會有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 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 也是這麼解決的。 ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/1808952/202006/1808952-20200606155150196-488359235.png) # 設計思路 既然如此,我們就來寫一個自定義吃持久層框架,來解決上述問題,當然是參照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 中就配置了資料庫的連線資訊,以及mapper 一個索引。mybatis中的SqlMapConfig.xml 中還包含其他的標籤,只是豐富了功能而已,所以我們只用最主要的。 **mapper.xml** 是每個類的sql 都會生成一個對應的mapper.xml 。我們這裡就用User 類來說吧,所以我們就建立一個UserMapper.xml ``` ``` 可以看到有點mybatis 裡面檔案的味道,有namespace表示名稱空間,id 唯一標識,resultType 返回結果集的型別,paramType 引數的型別。 我們使用端先建立到這,主要是兩個配置檔案,我們接下來看看框架端是怎麼實現的。 加油哈哈。 ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/1808952/202006/1808952-20200606155151127-1593042955.png) # 框架端實現 框架端,我們按照上面的設計思路一步一步來。 ## 獲取配置 怎麼樣獲取配置檔案呢?我們可以使用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 mapperMap=new HashMap<>(); //getter()和setter方法 } ``` ### XmlMapperBuilder 做好了上面的準備工作,我們先來解析mapper 吧。我們建立一個XmlMapperBuilder 類來解析。通過dom4j 的工具類來解析XML 檔案。我這裡用的dom4j 依賴為: ``` org.dom4j
dom4j 2.1.3
``` 思路: 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 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 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 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 * @return */ public List 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 List selectList(String statementid, Object... parm) throws Exception { Mapper mapper=configuration.getMapperMap().get(statementid); List query = executer.query(configuration, mapper, parm); return query; } } ``` 我們可以看到DefaultSqlSeeion 獲取到了configuration,並通過statementid 從configuration 中獲取mapper。 然後具體實現交給了Executer 類來實現。我們這裡先不管Executer 是怎麼實現的,就假裝已經實現了。那麼整個框架端就完成了。通過呼叫Sqlsession.selectList() 方法,來獲取結果。 ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/1808952/202006/1808952-20200606155151581-1303834004.png) 感覺我們都還沒有處理,就框架搭建好了?騙鬼呢,確實前面我們從獲取檔案解析檔案,然後建立工廠。都是做好準備工作。下面開始我們JDBC的實現。 # SqlSession 具體實現 我們前面說SqlSeesion 的具體實現有下面5步 1、獲取資料庫連線 2、獲取sql,並對sql 進行解析 3、通過內省,將引數注入到preparedStatement 中 4、執行sql 5、通過反射將結果集封裝成物件 但是我們在DefaultSqlSeeion 中將實現交給了Executer來執行。所以我們就要在Executer中來實現這些操作。 我們首先來建立一個Executer 介面,並寫一個DefaultSqlSeeion中呼叫的query 方法。 ``` public interface Executer { List 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 parameterMappings = new ArrayList(); // 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 getParameterMappings() { return parameterMappings; } public void setParameterMappings(List 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 parameterMappingList=new ArrayList<>(); public BoundSql(String sqlText, List 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 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 * @return * @throws Exception */ private List resultHandle(Mapper mapper,ResultSet resultSet) throws Exception{ ArrayList 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 的操作我們在我後面提供的原始碼中會有實現,這裡只是將整體的設計思路和流程。 ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/1808952/202006/1808952-20200606155151832-1005291429.png) # 測試 終於到了測試的環節啦。我們前面寫了自定義的持久層,我們現在來測試一下能不能正常的使用吧。 見證奇蹟的時刻到啦 ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/1808952/202006/1808952-20200606155152988-412209719.png) 我們先引入我們自定義的框架依賴。以及資料庫和單元測試 ``` mysql
mysql-connector-java 8.0.11
cn.quellanan myself-mybatis 1.0.0 junit junit 4.10 ``` 然後我們寫一個測試類 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 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 list1 = sqlSession.selectList("cn.quellanan.dao.UserDao.selectByName", user); for (User user1 : list1) { System.out.println(user1); } } ``` ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/1808952/202006/1808952-20200606155154923-720435254.png) 可以看到已經可以了,看來我們自定義的持久層框架生效啦。 ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/1808952/202006/1808952-20200606155155349-520459974.png) # 優化 但是不要高興的太早哈哈,我們看上面的測試方法,是不是感覺和平時用的不一樣,每次都都寫死statementId ,這樣不太友好,所以我們接下來來點騷操作,通用mapper 配置。 我們在SqlSession中增加一個getMapper方法,接收的引數是一個類。我們通過這個類就可以知道statementId . ``` /** * 使用代理模式來建立介面的代理物件 * @param mapperClass * @param * @return */ public T getMapper(Class mapperClass); ``` 具體實現就是利用JDK 的動態代理機制。 1、通過Proxy.newProxyInstance() 獲取一個代理物件 2、返回代理物件 那代理物件執行了哪些操作呢? 建立代理物件的時候,會實現一個InvocationHandler介面,重寫invoke() 方法,讓所有走這個代理的方法都會執行這個invoke() 方法。那這個方法做了什麼操作? 這個方法就是通過傳入的類物件,獲取到物件的類名和方法名。用來生成statementid 。所以我們在mapper.xml 配置檔案中的namespace 就需要制定為類路徑,以及id 為方法名。 實現方法: ``` @Override public T getMapper(Class 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 selectAll(); List selectByName(User user); } ``` 這個是不是我們熟悉的味道哈哈,就是mapper層的介面。 然後我們在mapper.xml 中指定namespace 和id ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/1808952/202006/1808952-20200606155156921-1664401357.png) 接下來我們在寫一個測試方法 ``` @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 users = mapper.selectAll(); for (User user1 : users) { System.out.println(user1); } User user=new User(); user.setUsername("張三"); List users1 = mapper.selectByName(user); for (User user1 : users1) { System.out.println(user1); } } ``` ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/1808952/202006/1808952-20200606155200346-1789786776.png) # 番外 自定義的持久層框架,我們就寫完了。這個實際上就是mybatis 的雛形,我們通過自己手動寫一個持久層框架,然後在來看mybatis 的原始碼,就會清晰很多。下面這些類名在mybatis 中都有體現。 ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/1808952/202006/1808952-20200606155202413-938393468.png) 這裡拋磚引玉,祝君閱讀原始碼愉快。 覺得有用的兄弟們記得收藏啊。 **厚顏無恥的求波點贊!!!** ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/1808952/202006/1808952-20200606155203034-116126381.png) > 本文由部落格一文多發平臺 [OpenWrite](https://openwrite.cn?from=article_bottom)