目錄 一、前言
二、目標
三、設計
四、實現(xiàn)
五、測試
六、總結
一、前言 你是怎么面對功能迭代的?
其實很多程序員在剛開始做編程或者新加入一家公司時,都沒有多少機會可以做一個新項目,大部分時候都是在老項目上不斷的迭代更新。在這個過程你可能要學習N個前人留下的各式各樣的風格迥異的代碼片段,在這些縱橫交錯的流程中,找到一席之地,把自己的ifelse
加進去。
雖然這樣胡亂的加ifelse
,剛上手就“擺爛”的心態(tài),讓人很難受。但要想在已經(jīng)被壓縮的工期下,還能交付出高質量的代碼其實也很難完成,所以一部分研發(fā)被逼到能用就行,能跑就可以。
但說回來,其實不能逐步清理一片屎山,讓代碼在你的手上逐步清晰、整潔、干凈,很多時候也是作為碼農自身經(jīng)驗的不足,不懂得系統(tǒng)重構、不了解設計原則、不熟悉業(yè)務背景、不清楚產品走向等等原因造成的。所以最好的辦法是提升自身的能力,每接到一次需求都有一些技術上的改變,既然它是屎山,那就當做打怪升級了,修一點、改一塊、補一片,總會在你手上越來越易于維護和擴展的。
二、目標 在我們漸進式的逐步實現(xiàn) Mybatis 框架過程中,首先我們要有一個目標導向的思路,也就是說 Mybatis 的核心邏輯怎么實現(xiàn)。
其實我們可以把這樣一個 ORM 框架的目標,簡單的描述成是為了給一個接口提供代理類,類中包括了對 Mapper 也就是 xml 文件中的 SQL 信息(類型
、入?yún)?/code>、出參
、條件
)進行解析和處理,這個處理過程就是對數(shù)據(jù)庫的操作以及返回對應的結果給到接口。如圖 4-1
圖 4-1 ORM 框架核心流程 那么按照 ORM 核心流程的執(zhí)行過程,我們本章節(jié)就需要在上一章節(jié)的基礎上,繼續(xù)擴展對 Mapper 文件的解析以及提取出對應的 SQL 文件。并在當前這個階段,可以滿足我們調用 DAO 接口方法的時候,可以返回 Mapper 中對應的待執(zhí)行 SQL 語句。為了不至于把整個工程撐大,小傅哥會帶著大家逐步完成這些內容,所以本章節(jié)暫時不會對數(shù)據(jù)庫進行操作,待后續(xù)逐步實現(xiàn)
三、設計 結合上一章節(jié)我們使用了 MapperRegistry
對包路徑進行掃描注冊映射器,并在 DefaultSqlSession
中進行使用。那么在我們可以把這些命名空間、SQL描述、映射信息統(tǒng)一維護到每一個 DAO 對應的 Mapper XML 的文件以后,其實 XML 就是我們的源頭了。通過對 XML 文件的解析和處理就可以完成 Mapper 映射器的注冊和 SQL 管理。這樣也就更加我們操作和使用了。如圖 4-2
圖 4-2 XML 文件解析注冊處理 首先需要定義 SqlSessionFactoryBuilder
工廠建造者模式類,通過入口 IO 的方式對 XML 文件進行解析。當前我們主要以解析 SQL 部分為主,并注冊映射器,串聯(lián)出整個核心流程的脈絡。 文件解析以后會存放到 Configuration 配置類中,接下來你會看到這個配置類會被串聯(lián)到整個 Mybatis 流程中,所有內容存放和讀取都離不開這個類。如我們在 DefaultSqlSession 中獲取 Mapper 和執(zhí)行 selectOne 也同樣是需要在 Configuration 配置類中進行讀取操作。 四、實現(xiàn) 1. 工程結構mybatis-step-03 └── src ├── main │ └── java │ └── cn.bugstack.mybatis │ ├── binding │ │ ├── MapperMethod.java │ │ ├── MapperProxy.java │ │ ├── MapperProxyFactory.java │ │ └── MapperRegistry.java │ ├── builder │ │ ├── xml │ │ │ └── XMLConfigBuilder.java │ │ └── BaseBuilder.java │ ├── io │ │ └── Resources.java │ ├── mapping │ │ ├── MappedStatement.java │ │ └── SqlCommandType.java │ └── session │ ├── defaults │ │ ├── DefaultSqlSession.java │ │ └── DefaultSqlSessionFactory.java │ ├── Configuration.java │ ├── SqlSession.java │ ├── SqlSessionFactory.java │ └── SqlSessionFactoryBuilder.java └── test ├── java │ └── cn.bugstack.mybatis.test.dao │ ├── dao │ │ └── IUserDao.java │ ├── po │ │ └── User.java │ └── ApiTest.java └── resources ├── mapper │ └──User_Mapper.xml └── mybatis-config-datasource.xml
工程源碼:https://t./bmqNFQ7
XML 解析和注冊類實現(xiàn)關系,如圖 4-2
圖 4-2 XML 解析和注冊類實現(xiàn)關系 SqlSessionFactoryBuilder 作為整個 Mybatis 的入口,提供建造者工廠,包裝 XML 解析處理,并返回對應 SqlSessionFactory 處理類。 通過解析把 XML 信息注冊到 Configuration 配置類中,再通過傳遞 Configuration 配置類到各個邏輯處理類里,包括 DefaultSqlSession 中,這樣就可以在獲取映射器和執(zhí)行SQL的時候,從配置類中拿到對應的內容了。 2. 構建SqlSessionFactory建造者工廠源碼詳見 :cn.bugstack.mybatis.session.SqlSessionFactoryBuilder
public class SqlSessionFactoryBuilder { public SqlSessionFactory build (Reader reader) { XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(reader); return build(xmlConfigBuilder.parse()); } public SqlSessionFactory build (Configuration config) { return new DefaultSqlSessionFactory(config); } }
SqlSessionFactoryBuilder 是作為整個 Mybatis 的入口類,通過指定解析XML的IO,引導整個流程的啟動。 從這個類開始新增加了 XMLConfigBuilder、Configuration 兩個處理類,分別用于解析 XML 和串聯(lián)整個流程的對象保存操作。接下來我們會分別介紹這些新引入的對象。 3. XML 解析處理源碼詳見 :cn.bugstack.mybatis.builder.xml.XMLConfigBuilder
public class XMLConfigBuilder extends BaseBuilder { private Element root; public XMLConfigBuilder (Reader reader) { // 1. 調用父類初始化Configuration super (new Configuration()); // 2. dom4j 處理 xml SAXReader saxReader = new SAXReader(); try { Document document = saxReader.read(new InputSource(reader)); root = document.getRootElement(); } catch (DocumentException e) { e.printStackTrace(); } } public Configuration parse () { try { // 解析映射器 mapperElement(root.element("mappers" )); } catch (Exception e) { throw new RuntimeException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } return configuration; } private void mapperElement (Element mappers) throws Exception { List<Element> mapperList = mappers.elements("mapper" ); for (Element e : mapperList) { // 解析處理,具體參照源碼 // 添加解析 SQL configuration.addMappedStatement(mappedStatement); } // 注冊Mapper映射器 configuration.addMapper(Resources.classForName(namespace)); } } }
XMLConfigBuilder 核心操作在于初始化 Configuration,因為 Configuration 的使用離解析 XML 和存放是最近的操作,所以放在這里比較適合。 之后就是具體的 parse() 解析操作,并把解析后的信息,通過 Configuration 配置類進行存放,包括:添加解析 SQL、注冊Mapper映射器。 解析配置整體包括:類型別名、插件、對象工廠、對象包裝工廠、設置、環(huán)境、類型轉換、映射器,但目前我們還不需要那么多,所以只做一些必要的 SQL 解析處理。 4. 通過配置類包裝注冊機和SQL語句**源碼詳見(配置項)**:cn.bugstack.mybatis.session.Configuration
public class Configuration { /** * 映射注冊機 */ protected MapperRegistry mapperRegistry = new MapperRegistry(this ); /** * 映射的語句,存在Map里 */ protected final Map<String, MappedStatement> mappedStatements = new HashMap<>(); public <T> void addMapper (Class<T> type) { mapperRegistry.addMapper(type); } public void addMappedStatement (MappedStatement ms) { mappedStatements.put(ms.getId(), ms); } }
在配置類中添加映射器注冊機和映射語句的存放;
映射器注冊機是我們上一章節(jié)實現(xiàn)的內容,用于注冊 Mapper 映射器鎖提供的操作類。 另外一個 MappedStatement 是本章節(jié)新添加的 SQL 信息記錄對象,包括記錄:SQL類型、SQL語句、入?yún)㈩愋汀⒊鰠㈩愋偷取?em style="color: rgb(14, 136, 235);letter-spacing: 0.3em;">詳細可參照源碼 5. DefaultSqlSession結合配置項獲取信息源碼詳見 :cn.bugstack.mybatis.session.defaults.DefaultSqlSession
public class DefaultSqlSession implements SqlSession { private Configuration configuration; @Override public <T> T selectOne (String statement, Object parameter) { MappedStatement mappedStatement = configuration.getMappedStatement(statement); return (T) ("你被代理了!" + "\n方法:" + statement + "\n入?yún)ⅲ? + parameter + "\n待執(zhí)行SQL:" + mappedStatement.getSql()); } @Override public <T> T getMapper (Class<T> type) { return configuration.getMapper(type, this ); } }
DefaultSqlSession 相對于上一章節(jié),小傅哥這里把 MapperRegistry mapperRegistry
替換為 Configuration configuration
,這樣才能傳遞更豐富的信息內容,而不只是注冊器操作。 之后在 DefaultSqlSession#selectOne、DefaultSqlSession#getMapper 兩個方法中都使用 configuration 來獲取對應的信息。 目前 selectOne 方法中只是把獲取的信息進行打印,后續(xù)將引入 SQL 執(zhí)行器進行結果查詢并返回。 五、測試 1. 事先準備提供 DAO 接口和對應的 Mapper xml 配置
public interface IUserDao { String queryUserInfoById (String uId) ; }
<mapper namespace ="cn.bugstack.mybatis.test.dao.IUserDao" > <select id ="queryUserInfoById" parameterType ="java.lang.Long" resultType ="cn.bugstack.mybatis.test.po.User" > SELECT id, userId, userHead, createTime FROM user where id = #{id} </select > </mapper >
2. 單元測試@Test public void test_SqlSessionFactory () throws IOException { // 1. 從SqlSessionFactory中獲取SqlSession Reader reader = Resources.getResourceAsReader("mybatis-config-datasource.xml" ); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); SqlSession sqlSession = sqlSessionFactory.openSession(); // 2. 獲取映射器對象 IUserDao userDao = sqlSession.getMapper(IUserDao.class ) ; // 3. 測試驗證 String res = userDao.queryUserInfoById("10001" ); logger.info("測試結果:{}" , res); }
目前的使用方式就和 Mybatis 非常像了,通過加載 xml 配置文件,交給 SqlSessionFactoryBuilder 進行構建解析,并獲取 SqlSessionFactory 工廠。這樣就可以順利的開啟 Session 以及完成后續(xù)的操作。 測試結果
07 :07 :40.519 [main] INFO cn.bugstack.mybatis.test.ApiTest - 測試結果:你被代理了! 方法:cn.bugstack.mybatis.test.dao.IUserDao.queryUserInfoById 入?yún)ⅲ篬Ljava.lang.Object;@23223 dd8 待執(zhí)行SQL: SELECT id, userId, userHead, createTime FROM user where id = ? Process finished with exit code 0
從測試結果我們可以看到,目前的代理操作已經(jīng)可以把我們從 XML 中解析的 SQL 信息進行打印了,后續(xù)我們將結合這部分的處理繼續(xù)完成數(shù)據(jù)庫的操作。 六、總結 了解 ORM 處理的核心流程,知曉目前我們所處在的步驟和要完成的內容,只有非常清楚的知道這個代理、封裝、解析和返回結果的過程才能更好的完成整個框架的實現(xiàn)。 SqlSessionFactoryBuilder 的引入包裝了整個執(zhí)行過程,包括:XML 文件的解析、Configuration 配置類的處理,讓 DefaultSqlSession 可以更加靈活的拿到對應的信息,獲取 Mapper 和 SQL 語句。 另外從整個工程搭建的過程中,可以看到有很多工廠模式、建造者模式、代理模式的使用,也有很多設計原則的運用,這些技巧都可以讓整個工程變得易于維護和易于迭代。這也是研發(fā)人員在學習源碼的過程中,非常值得重點關注的地方。