阿里P7淺析從 0 開始手寫一個(gè) Mybatis 框架,三步搞定析苫!

本文完成的Mybatis功能比較簡單兜叨,代碼還有許多需要改進(jìn)的地方,大家可以結(jié)合Mybatis源碼去動(dòng)手完善衩侥。

一国旷、Mybatis框架流程簡介

image.png

在手寫自己的Mybatis框架之前,我們先來了解一下Mybatis茫死,它的源碼中使用了大量的設(shè)計(jì)模式跪但,閱讀源碼并觀察設(shè)計(jì)模式在其中的應(yīng)用,才能夠更深入的理解源碼(ref:Mybatis源碼解讀-設(shè)計(jì)模式總結(jié))峦萎。

我們對上圖進(jìn)行分析總結(jié):

1屡久、mybatis的配置文件有2類
  • mybatisconfig.xml,配置文件的名稱不是固定的爱榔,配置了全局的參數(shù)的配置被环,全局只能有一個(gè)配置文件。

  • Mapper.xml 配置多個(gè)statemement详幽,也就是多個(gè)sql筛欢,整個(gè)mybatis框架中可以有多個(gè)Mappe.xml配置文件。

2唇聘、通過mybatis配置文件得到SqlSessionFactory
3版姑、通過SqlSessionFactory得到SqlSession,用SqlSession就可以操作數(shù)據(jù)了迟郎。
4漠酿、SqlSession通過底層的Executor(執(zhí)行器),執(zhí)行器有2類實(shí)現(xiàn):
  • 基本實(shí)現(xiàn)

  • 帶有緩存功能的實(shí)現(xiàn)

5谎亩、MappedStatement是通過Mapper.xml中定義statement生成的對象炒嘲。
6宇姚、參數(shù)輸入執(zhí)行并輸出結(jié)果集,無需手動(dòng)判斷參數(shù)類型和參數(shù)下標(biāo)位置夫凸,且自動(dòng)將結(jié)果集映射為Java對象
  • HashMap浑劳,KV格式的數(shù)據(jù)類型
  • Java的基本數(shù)據(jù)類型
  • POJO,java的對象

二夭拌、梳理自己的Mybatis的設(shè)計(jì)思路

根據(jù)上文Mybatis流程魔熏,我簡化了下,分為以下步驟:

1.讀取xml文件鸽扁,建立連接

從圖中可以看出蒜绽,MyConfiguration負(fù)責(zé)與人交互。待讀取xml后桶现,將屬性和連接數(shù)據(jù)庫的操作封裝在MyConfiguration對象中供后面的組件調(diào)用躲雅。本文將使用dom4j來讀取xml文件,它具有性能優(yōu)異和非常方便使用的特點(diǎn)骡和。推薦閱讀:Spring Boot 集成 Mybatis 實(shí)現(xiàn)雙數(shù)據(jù)源相赁。

2.創(chuàng)建SqlSession,搭建Configuration和Executor之間的橋梁

我們經(jīng)常在使用框架時(shí)看到Session慰于,Session到底是什么呢钮科?一個(gè)Session僅擁有一個(gè)對應(yīng)的數(shù)據(jù)庫連接。類似于一個(gè)前段請求Request婆赠,它可以直接調(diào)用exec(SQL)來執(zhí)行SQL語句绵脯。

從流程圖中的箭頭可以看出,MySqlSession的成員變量中必須得有MyExecutor和MyConfiguration去集中做調(diào)配休里,箭頭就像是一種關(guān)聯(lián)關(guān)系桨嫁。我們自己的MySqlSession將有一個(gè)getMapper方法,然后使用動(dòng)態(tài)代理生成對象后份帐,就可以做數(shù)據(jù)庫的操作了。推薦閱讀:Mybatis傳遞多個(gè)參數(shù)的4種方式楣导。

3.創(chuàng)建Executor废境,封裝JDBC操作數(shù)據(jù)庫

Executor是一個(gè)執(zhí)行器,負(fù)責(zé)SQL語句的生成和查詢緩存(緩存還沒完成)的維護(hù)筒繁,也就是jdbc的代碼將在這里完成噩凹,不過本文只實(shí)現(xiàn)了單表,有興趣的同學(xué)可以嘗試完成多表毡咏。

4.創(chuàng)建MapperProxy驮宴,使用動(dòng)態(tài)代理生成Mapper對象

我們只是希望對指定的接口生成一個(gè)對象,使得執(zhí)行它的時(shí)候能運(yùn)行一句sql罷了呕缭,而接口無法直接調(diào)用方法堵泽,所以這里使用動(dòng)態(tài)代理生成對象修己,在執(zhí)行時(shí)還是回到MySqlSession中調(diào)用查詢,最終由MyExecutor做JDBC查詢迎罗。這樣設(shè)計(jì)是為了單一職責(zé)睬愤,可擴(kuò)展性更強(qiáng)。

三纹安、實(shí)現(xiàn)自己的Mybatis

工程文件及目錄:


image.png

首先尤辱,新建一個(gè)maven項(xiàng)目,在pom.xml中導(dǎo)入以下依賴:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.liugh</groupId>
 <artifactId>liugh-mybatis</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>jar</packaging>
 
 <properties>
   <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   <maven.compiler.source>1.8</maven.compiler.source>
   <maven.compiler.target>1.8</maven.compiler.target>
   <java.version>1.8</java.version>
 </properties>
 
 <dependencies>
      <!-- 讀取xml文件 -->
   <dependency>
     <groupId>dom4j</groupId>
     <artifactId>dom4j</artifactId>
     <version>1.6.1</version>
   </dependency>
   
   <!-- MySQL -->
   <dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <version>5.1.29</version>
   </dependency>
   </dependencies>
</project>

創(chuàng)建我們的數(shù)據(jù)庫xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<database>
 <property name="driverClassName">com.mysql.jdbc.Driver</property>
 <property name="url">jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf8</property>
 <property name="username">root</property>
 <property name="password">123456</property>
</database>

然后在數(shù)據(jù)庫創(chuàng)建test庫厢岂,執(zhí)行如下SQL語句:

CREATE TABLE `user` (
 `id` varchar(64) NOT NULL,
 `password` varchar(255) DEFAULT NULL,
 `username` varchar(255) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `test`.`user` (`id`, `password`, `username`) VALUES ('1', '123456', 'liugh');

創(chuàng)建User實(shí)體類光督,和UserMapper接口和對應(yīng)的xml文件:

package com.liugh.bean;

public class User {
   private String id;
   private String username;
   private String password;
   //省略get set toString方法...
}
package com.liugh.mapper;

import com.liugh.bean.User;

public interface UserMapper {
 
 public User getUserById(String id);  
}
<?xml version="1.0" encoding="UTF-8"?>
<mapper nameSpace="com.liugh.mapper.UserMapper">
   <select id="getUserById" resultType ="com.liugh.bean.User">
       select * from user where id = ?
   </select>
</mapper>

基本操作配置完成,接下來我們開始實(shí)現(xiàn)MyConfiguration:

package com.liugh.sqlSession;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import com.liugh.config.Function;
import com.liugh.config.MapperBean;


/**
* 讀取與解析配置信息塔粒,并返回處理后的Environment
*/
public class MyConfiguration {
 private static ClassLoader loader = ClassLoader.getSystemClassLoader();

 /**
  * 讀取xml信息并處理
  */
 public  Connection build(String resource){
     try {
         InputStream stream = loader.getResourceAsStream(resource);
     SAXReader reader = new SAXReader();
     Document document = reader.read(stream);
     Element root = document.getRootElement();
     return evalDataSource(root);
   } catch (Exception e) {
     throw new RuntimeException("error occured while evaling xml " + resource);
   }
 }
 
 private  Connection evalDataSource(Element node) throws ClassNotFoundException {
       if (!node.getName().equals("database")) {
         throw new RuntimeException("root should be <database>");
       }
   String driverClassName = null;
   String url = null;
   String username = null;
   String password = null;
   //獲取屬性節(jié)點(diǎn)
   for (Object item : node.elements("property")) {
     Element i = (Element) item;      
     String value = getValue(i);
     String name = i.attributeValue("name");
     if (name == null || value == null) {
       throw new RuntimeException("[database]: <property> should contain name and value");
     }
     //賦值
     switch (name) {
       case "url" : url = value; break;
       case "username" : username = value; break;
       case "password" : password = value; break;
       case "driverClassName" : driverClassName = value; break; 
       default : throw new RuntimeException("[database]: <property> unknown name"); 
     }
   }
   
    Class.forName(driverClassName); 
    Connection connection = null;
   try {
     //建立數(shù)據(jù)庫鏈接
     connection = DriverManager.getConnection(url, username, password);
   } catch (SQLException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
   }
   return connection;
 }
 
 //獲取property屬性的值,如果有value值,則讀取 沒有設(shè)置value,則讀取內(nèi)容
 private  String getValue(Element node) {
   return node.hasContent() ? node.getText() : node.attributeValue("value");
 }
 
 
 
   @SuppressWarnings("rawtypes")
   public  MapperBean readMapper(String path){
       MapperBean mapper = new MapperBean();
       try{
         InputStream stream = loader.getResourceAsStream(path);
      SAXReader reader = new SAXReader();
      Document document = reader.read(stream);
      Element root = document.getRootElement();
           mapper.setInterfaceName(root.attributeValue("nameSpace").trim()); //把mapper節(jié)點(diǎn)的nameSpace值存為接口名
           List<Function> list = new ArrayList<Function>(); //用來存儲(chǔ)方法的List
           for(Iterator rootIter = root.elementIterator();rootIter.hasNext();) {//遍歷根節(jié)點(diǎn)下所有子節(jié)點(diǎn)
               Function fun = new Function();    //用來存儲(chǔ)一條方法的信息
               Element e = (Element) rootIter.next(); 
               String sqltype = e.getName().trim();
               String funcName = e.attributeValue("id").trim();
               String sql = e.getText().trim();
               String resultType = e.attributeValue("resultType").trim();
               fun.setSqltype(sqltype);
               fun.setFuncName(funcName);
               Object newInstance=null;
       try {
         newInstance = Class.forName(resultType).newInstance();
       } catch (InstantiationException e1) {
         e1.printStackTrace();
       } catch (IllegalAccessException e1) {
         e1.printStackTrace();
       } catch (ClassNotFoundException e1) {
         e1.printStackTrace();
       }
               fun.setResultType(newInstance);
               fun.setSql(sql);
               list.add(fun);
           }
           mapper.setList(list);
           
       } catch (DocumentException e) {
           e.printStackTrace();
       }
       return mapper;
   }
}

用面向?qū)ο蟮乃枷朐O(shè)計(jì)讀取xml配置后:

package com.liugh.config;

import java.util.List;
public class MapperBean {

   private String interfaceName; //接口名
   private List<Function> list; //接口下所有方法
   //省略 get  set方法...

}

Function對象包括sql的類型结借、方法名、sql語句窗怒、返回類型和參數(shù)類型映跟。

package com.liugh.config;

public class Function {
   private String sqltype;  
   private String funcName;  
   private String sql;       
   private Object resultType;  
   private String parameterType; 
 //省略 get set方法
}

接下來實(shí)現(xiàn)我們的MySqlSession,首先的成員變量里得有Excutor和MyConfiguration,代碼的精髓就在getMapper的方法里扬虚。

package com.liugh.sqlSession;

import java.lang.reflect.Proxy;

public class MySqlsession {
 
 private Excutor excutor= new MyExcutor();  
 
 private MyConfiguration myConfiguration = new MyConfiguration();
 
   public <T> T selectOne(String statement,Object parameter){  
       return excutor.query(statement, parameter);  
   }  
       
   @SuppressWarnings("unchecked")
   public <T> T getMapper(Class<T> clas){ 
     //動(dòng)態(tài)代理調(diào)用
       return (T)Proxy.newProxyInstance(clas.getClassLoader(),new Class[]{clas},
           new MyMapperProxy(myConfiguration,this));  
   }  

}

緊接著創(chuàng)建Excutor和實(shí)現(xiàn)類:

package com.liugh.sqlSession;

public interface Excutor {
 public <T> T query(String statement,Object parameter);  
}

MyExcutor中封裝了JDBC的操作:

package com.liugh.sqlSession;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.liugh.bean.User;


public class MyExcutor implements Excutor{
 
 private MyConfiguration xmlConfiguration = new MyConfiguration();
 
  @Override  
     public <T> T query(String sql, Object parameter) {  
         Connection connection=getConnection();  
         ResultSet set =null;
         PreparedStatement pre =null;
         try {  
             pre = connection.prepareStatement(sql); 
             //設(shè)置參數(shù)
             pre.setString(1, parameter.toString());
             set = pre.executeQuery();  
             User u=new User();  
             //遍歷結(jié)果集
             while(set.next()){  
                 u.setId(set.getString(1));
                 u.setUsername(set.getString(2)); 
                 u.setPassword(set.getString(3));
             }  
             return (T) u;  
         } catch (SQLException e) {  
             e.printStackTrace();  
         } finally{
                try{  
                    if(set!=null){  
                      set.close();  
                    }if(pre!=null){  
                      pre.close();  
                    }if(connection!=null){  
                      connection.close();  
                    }  
                }catch(Exception e2){  
                    e2.printStackTrace();  
                }  
            }   
         return null;  
     }  
   
     private Connection getConnection() {  
         try {  
             Connection connection =xmlConfiguration.build("config.xml");
             return connection;  
         } catch (Exception e) {  
             e.printStackTrace();  
         }  
         return null;  
     }  
}

MyMapperProxy代理類完成xml方法和真實(shí)方法對應(yīng)努隙,執(zhí)行查詢:

package com.liugh.sqlSession;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
import com.liugh.config.Function;
import com.liugh.config.MapperBean;

public class MyMapperProxy implements InvocationHandler{
 
 private  MySqlsession mySqlsession;  
 
 private MyConfiguration myConfiguration;
    
   public MyMapperProxy(MyConfiguration myConfiguration,MySqlsession mySqlsession) {  
       this.myConfiguration=myConfiguration;  
       this.mySqlsession=mySqlsession;  
   }  

 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   MapperBean readMapper = myConfiguration.readMapper("UserMapper.xml");
   //是否是xml文件對應(yīng)的接口
   if(!method.getDeclaringClass().getName().equals(readMapper.getInterfaceName())){
     return null;  
   }
   List<Function> list = readMapper.getList();
   if(null != list || 0 != list.size()){
     for (Function function : list) {
     //id是否和接口方法名一樣
      if(method.getName().equals(function.getFuncName())){  
               return mySqlsession.selectOne(function.getSql(), String.valueOf(args[0]));  
           }  
     }
   }
      return null;  
 }
}

到這里,就完成了自己的Mybatis框架辜昵,我們測試一下:

package com.liugh;

import com.liugh.bean.User;
import com.liugh.mapper.UserMapper;
import com.liugh.sqlSession.MySqlsession;

public class TestMybatis {
 
   public static void main(String[] args) {  
       MySqlsession sqlsession=new MySqlsession();  
       UserMapper mapper = sqlsession.getMapper(UserMapper.class);  
       User user = mapper.getUserById("1");  
       System.out.println(user);
   } 
}

執(zhí)行結(jié)果:


image.png

查詢一個(gè)不存在的用戶試試:


image.png

到這里我們就大功告成了荸镊!

歡迎大家加入粉絲群:963944895,群內(nèi)免費(fèi)分享Spring框架堪置、Mybatis框架SpringBoot框架躬存、SpringMVC框架、SpringCloud微服務(wù)舀锨、Dubbo框架岭洲、Redis緩存、RabbitMq消息坎匿、JVM調(diào)優(yōu)盾剩、Tomcat容器、MySQL數(shù)據(jù)庫教學(xué)視頻及架構(gòu)學(xué)習(xí)思維導(dǎo)圖

寫在最后:

禿頂程序員的不易替蔬,看到這里告私,點(diǎn)了關(guān)注吧!
點(diǎn)關(guān)注承桥,不迷路驻粟,持續(xù)更新!P滓臁蜀撑!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末挤巡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子屯掖,更是在濱河造成了極大的恐慌玄柏,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,686評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贴铜,死亡現(xiàn)場離奇詭異粪摘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)绍坝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門徘意,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人轩褐,你說我怎么就攤上這事椎咧。” “怎么了把介?”我有些...
    開封第一講書人閱讀 158,160評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵勤讽,是天一觀的道長。 經(jīng)常有香客問我拗踢,道長脚牍,這世上最難降的妖魔是什么琳骡? 我笑而不...
    開封第一講書人閱讀 56,736評(píng)論 1 284
  • 正文 為了忘掉前任豌注,我火速辦了婚禮洞焙,結(jié)果婚禮上侄柔,老公的妹妹穿的比我還像新娘。我一直安慰自己钉稍,他們只是感情好焙畔,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,847評(píng)論 6 386
  • 文/花漫 我一把揭開白布捎琐。 她就那樣靜靜地躺著蓄髓,像睡著了一般叉庐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上会喝,一...
    開封第一講書人閱讀 50,043評(píng)論 1 291
  • 那天陡叠,我揣著相機(jī)與錄音,去河邊找鬼好乐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瓦宜,可吹牛的內(nèi)容都是我干的蔚万。 我是一名探鬼主播,決...
    沈念sama閱讀 39,129評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼临庇,長吁一口氣:“原來是場噩夢啊……” “哼反璃!你這毒婦竟也來了昵慌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,872評(píng)論 0 268
  • 序言:老撾萬榮一對情侶失蹤淮蜈,失蹤者是張志新(化名)和其女友劉穎斋攀,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體梧田,經(jīng)...
    沈念sama閱讀 44,318評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡淳蔼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,645評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了裁眯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹉梨。...
    茶點(diǎn)故事閱讀 38,777評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖穿稳,靈堂內(nèi)的尸體忽然破棺而出存皂,到底是詐尸還是另有隱情,我是刑警寧澤逢艘,帶...
    沈念sama閱讀 34,470評(píng)論 4 333
  • 正文 年R本政府宣布旦袋,位于F島的核電站,受9級(jí)特大地震影響它改,放射性物質(zhì)發(fā)生泄漏疤孕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,126評(píng)論 3 317
  • 文/蒙蒙 一搔课、第九天 我趴在偏房一處隱蔽的房頂上張望胰柑。 院中可真熱鬧,春花似錦爬泥、人聲如沸柬讨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,861評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽踩官。三九已至,卻和暖如春境输,著一層夾襖步出監(jiān)牢的瞬間蔗牡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評(píng)論 1 267
  • 我被黑心中介騙來泰國打工嗅剖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辩越,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,589評(píng)論 2 362
  • 正文 我出身青樓信粮,卻偏偏與公主長得像黔攒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,687評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • 1 Mybatis入門 1.1 單獨(dú)使用jdbc編程問題總結(jié) 1.1.1 jdbc程序 上邊使...
    哇哈哈E閱讀 3,295評(píng)論 0 38
  • 1. 簡介 1.1 什么是 MyBatis 督惰? MyBatis 是支持定制化 SQL不傅、存儲(chǔ)過程以及高級(jí)映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,464評(píng)論 0 4
  • A今天學(xué)了什么 1.JavaScript簡介 2.變量 3.如何使用:聲明、賦值赏胚、取值 4.數(shù)據(jù)類型的分類 5.引...
    執(zhí)念念不直閱讀 230評(píng)論 0 0
  • 但凡職業(yè)高手觉阅,始學(xué)于知識(shí)崖疤,悟道于實(shí)踐,境界至高則臻于法無定法留拾,游刃于無形戳晌。底蘊(yùn)的厚度決定事業(yè)的高度,合理的知識(shí)結(jié)構(gòu)...
    隔壁老周丨johanny閱讀 2,609評(píng)論 0 1
  • 課后總結(jié)及收獲分享1這節(jié)課的關(guān)鍵內(nèi)容是什么痴柔?可以按照你自己的方式來做總結(jié)沦偎。 你為什么害怕拒絕和被拒絕。 害怕拒絕和...
    水邊的魚閱讀 176評(píng)論 0 0